C语言数组
在C语言中,对于三维或三维以上数组的使用并没有很好的支持,而且使用率也非常的低,后面会对三维数组做一些简单的分析,这篇文章主要以二维数组来探讨一些C语言中数组使用的相关概念和技巧。
1 一个var[i][j]引用形式的可能声明
当你看见像var[i][j]这样的二维数组引用形式时,你能知道他是怎么被声明的吗?答案是否定的,指针和数组使用的部分通用性会让你无法判断这样的一种形式的声明原型,对于一个二维数组而言,它一般的声明方式是:
int var[10][12]; /* 标准的int类型二维数组 */
它可以通过var[i][j]形式来访问,那么这种形式的引用还有三种可能的声明:
(1) int *var[10]; /* int类型指针数组 */
(2) int **var; /* int类型的指针的指针 */
(3) int (*var)[12]; /* 类型为int数组的指针 */
很显然,这非常像函数内部无法分配传递给它的实参究竟是一个数组函数一个指针一样(上一篇文章数组与指针之间的较量已经详细说明),基于同样的理由:作为左值的数组名被编译前当做是指针。
2 数组参数如何被编译前修改
将一个数组作为参数传递给函数会被编译器做修改,在函数中实际上把传递过来的数组名当做了指针来使用,就像第一小节中的var[i][j]引用形式可能有四种声明方式,这四种方式的声明在作为参数传递给函数时,编译器是怎样对其进行修改的?是不是像var[i][j]这样的二维数组被修改成**var?
事实上,数组名被修改成一个指针参数的规则不是递归的定义,数组的数组被被改写为数组的指针而不是指针的指针,下面是四种声明方式的修改情况:
(1)数组的数组: char var[i][j];作为实参传递给函数时,所匹配的形式参数为数组的指针char (*)[j];
(2)指针数组: char *var[j];作为实参传递给函数时,所匹配的形式参数为指针的指针char **var;
(3)数组指针: char (*var)[j];作为实参传递给函数时,所匹配的形式参数还是数组的指针char (*var)[j];
(4)指针的指针: char **var;作为实参传递给函数时,所匹配的形式参数还是指针的指针char **var;
从上面的四种情况,你或许已经领略了这句话:被修改成指针的只是数组名而已,这相当于只修改最左面的维度为指针(不递归的精妙所在),如果声明已经不是数组名形式或者说已经成为纯指针(数组名的深刻含义),那么编译器不会对其做任何修改。
可以举个例子来说明:
通常我们在main()函数中可以看到char **argv(基于传参的入口函数)参数,它实际上保持的是用户传递过来的多字符串,这些字符串在被作为实参传递之前被存储为字符(串)指针数组char *temp[i](暂且叫做temp),temp被编译器修改位char **类型,因为temp实际上是一个数组名。
3 如何使用数组参数
(1) 一维数组
一维数组作为函数参数是数组传参最简单的形式,形参被改写为指针,所以需要一个约定来表示数组的长度,一般有两种方法:
A 增加一个额外的参数,表示元素的个数
B 赋予数组最后一个元素一个特殊的值,提示他是数组的尾部,但要保证这个特殊值不会出现在正常元素值中
(2) 二维数组
和一维数组的方法一样,对于B方法需要多加一行来填所有的元素为特殊值即可,二维数组传参的使用后面会详细说明。
4 使用指针对函数传递多维数组
第三小节描述的方法比较笨拙,可以标记数组范围这个问题,但是这种方法不能表达“这个数组的边界在不同的调用中可以变化”这个概念,如果一个二维数组的每个元素(一个一维数组)的大小不同,这将无法表示。
C语言中,我们需要知道每一维度的长度,这样才能为地址运算提供正确的单位长度,然而没有办法向函数传递一个普通的多维数组,即使数组名被编译器改写为指针(非递归改写,这只能解决一维数组),事实上,我们需要提供除了最左边一维以外的所有维度的长度。对于下面的函数声明,我们可以这样调用:
void my_func(int a[][5][10]);
调用方法:
int b[10][5][10];
int c[5][5][10];
my_func(b);
my_func(c);
但是不可以这样调用:
int d[10][10][10];
int e[20][10][5];
my_func(d);
my_func(e);
这样的调用肯定是过不了编译器这一关的。
那就是说:你可以像函数传递预先确定长度的特殊数组,但这个方法不能满足一般的情况,下面我们来一步一步说明这个问题。
1)方法一
my_func(int array[10][20]);
这样的方法虽然简单,但同时也是作用做小的声明方式,因为它只能处理10行20列的int类型的数组,如果要一个更为普通的多维数组形参的方法,使函数能操作任意长度数组。
2)方法二
my_func(int (*array)[20]);
这样可以确保他被编译器当做一个指向20个元素的int数组的指针,但对于二维数组的参数传递,它并不具有通用性,因为还有一个20感觉很糟糕。
3)方法三
本为的第二小节的最后有分析过:
main(int argc, char **argv);
当然也可能是:
main(int argc, char *argv[]);
前面一种是一个指针的指针,后面一种是一个指针数组,那这里我们就可以这样声明一个比较通用的可以传递二维数组的函数:
my_func(int **array);或者
my_func(int *array[]);
这样也可以通过最后一个指针元素设置成NULL来标识该二维数组的结束。实际上,我们真的可以通过一些技术来解决一维和二维数组的通用声明,但是对于三维和更多维的数组都无法实现的很好,这也是C语言的一个内在限制。
5 函数返回数组
严格的说来,无法完成直接从函数返回一个数组,这是C语言的一个限制,但是,可以让函数返回一个指向任何数据结构的指针,当然包括数组的指针。
对于返回数组的指针的办法我们必须知道一些事项:
1)在函数中动态分配数组
我们知道,如果这样声明一个函数:
int (*my_func())[5]; /* 返回的类型为一个指向保护5个int元素的数组的指针 */
可以在函数中声明这样的返回类型,然后通过动态分配内存的方式给它分配内存,经过处理后返回。这种动态分配内存的一个典型的应用场景是:我们并不知道要定义多大的内存,可能很小也可能很大,他的大小在运行期间可能会动态的变化。
要注意的是:在函数内部使用动态分配并在外部使用,很可能会忘记释放这段内存,从而造成内存泄露,所以一定要记得使用完之后释放内存。
2)千万不要在函数的内部声明局部数组,然后作为返回值从函数返回。
函数的局部变量在函数过期时,将被释放掉(系统回收),如果你这样做,幸运的话,可能在短时间内还可以取得你想要的数据(实际上函数的局部变量在进程的堆栈中,在函数过期时堆栈一定会变化),但天知道后面会发生什么事情。
6 总结
通过这一篇和前面一篇“C语言数组和指针之间的较量”的学习,相信对C语言的数组和指针已经有了比较深刻的了解,其中的特性可能需要你在实际编程中领悟和体会,其实个人觉得还是尽量少用C语言的一些比较晦涩的特性,能简单解决的话又何乐而不为呢?
关于讲解数组和指针的资料有很多,比如“明明白白C指针”等对指针和数组的讲解尤其独到之处,但这里做一些自己的总结也算是给过去学习东西一点交代,以后还可以经常拿过来回顾一下,呵呵,“温故而知新,可以为师矣...”
C语言数组的更多相关文章
- GO语言数组和切片实例详解
本文实例讲述了GO语言数组和切片的用法.分享给大家供大家参考.具体分析如下: 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式: ...
- C语言 数组 列优先 实现
C语言数组结构列优先顺序存储的实现 (GCC编译). 从行优先转换为列优先存储方式, 与行优先相比, 不同之处在于改变了数组维界基址的先后顺序, 从而改变了映像函数常量基址. /** * @brief ...
- C语言 数组 行优先 实现
C语言数组结构行优先顺序存储的实现 (GCC编译). /** * @brief C语言 数组 行优先 实现 * @author wid * @date 2013-11-02 * * @note 若代码 ...
- 不可或缺 Windows Native (5) - C 语言: 数组
[源码下载] 不可或缺 Windows Native (5) - C 语言: 数组 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 数组 示例cArray.h #ifn ...
- C语言数组:C语言数组定义、二维数组、动态数组、字符串数组
1.C语言数组的概念 在<更加优美的C语言输出>一节中我们举了一个例子,是输出一个 4×4 的整数矩阵,代码如下: #include <stdio.h> #include &l ...
- Go语言数组的使用
Go 语言数组 Go 语言提供了数组类型的数据结构. 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形.字符串或者自定义类型. 相对于去声明number0 ...
- Go 语言数组
Go 语言提供了数组类型的数据结构. 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形.字符串或者自定义类型. 相对于去声明number0, number ...
- C语言 > 数组和指针
C语言 数组和指针 const: 关于指针和const需要注意一些规则.首先,把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的. 然而,只能把非const数据的地 ...
- Go语言数组和切片的原理
目录 数组 创建 访问和赋值 切片 结构 初始化 访问 追加 拷贝 总结 数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...
随机推荐
- [ACM] n划分数m部分,它要求每一个部分,并采取了最大的产品(间隔DP)
A - 爱管闲事 春希很爱管闲事,他每天都会抽出时间帮助一些同学,因为春希很死板,出于公平性,春希不会先帮助后来找他的同学. 如今有n个同学须要他的帮助,尽管他非常想一天之类帮助全部人,但毕竟精力有限 ...
- MongoDB系列之三(副本集配置)
今天我测试了一下MongoDB的副本集的配置. 首先从概念上说一下MongoDB副本集和主从复制的区别.其实副本集(Replica Set)是主从复制的高级形式.高级在哪里呢?主动复制实现了数据备份+ ...
- Eclipse用法和技巧七:自动生成get和set方法2
上一篇文章中我们介绍了自动批量生成get和set函数的方法.这个方法一般在声明完类的数据域之后使用,比较方便快捷.这里再补充几个自动生成get和set函数的方法. 步骤一:在声明的数据域中按Ctrl+ ...
- Eclipse用法和技巧一:还原视图和编辑器
链接地址:http://blog.csdn.net/maybe_windleave/article/details/8763744 在实际使用eclipse过程中,由于经常关闭或者打开视图,某一刻你会 ...
- Storm流计算之项目篇(Storm+Kafka+HBase+Highcharts+JQuery,含3个完整实际项目)
1.1.课程的背景 Storm是什么? 为什么学习Storm? Storm是Twitter开源的分布式实时大数据处理框架,被业界称为实时版Hadoop. 随着越来越多的场景对Hadoop的MapRed ...
- UIGestureRecognizer在多层视图中的触发问题
在一个superview中,添加了一个subview.tap一下superview,将subview隐藏起来. 在视图superview添加一个UITapGestureRecognizer对象,在UI ...
- delphi不同版本字符串类型的演化(要支持基于firemonkey的app调用,字符串最好使用olevariant类型)
string,DELPHI2009以前的版本string=ansistring,一个字符占一个字节,DELPHI2009及以上版本string=unicodestring,一个字符占二个字节. cha ...
- 5.中文问题(自身,操作系统级别,应用软件的本身),mysql数据库备份
第一层因素: mysql的自身的设置 mysql有六处使用了字符集.分别为:client .connection.database.results.server .system. mysql&g ...
- C语言字符串操作函数
1.函数名: stpcpy 功 能: 拷贝一个字符串到另一个 用 法: char *stpcpy(char *destin, char *source); 程序例: #include < ...
- Swift - 开关按钮(UISwitch)的用法
下面演示如何创建开关,以及监听它值的改变,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class ViewController: UIV ...