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 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...
随机推荐
- 用WebCollector制作一个爬取《知乎》并进行问题精准抽取的爬虫(JAVA)
简单介绍: WebCollector是一个无须配置.便于二次开发的JAVA爬虫框架(内核),它提供精简的的API.仅仅需少量代码就可以实现一个功能强大的爬虫. 怎样将WebCollector导入项目请 ...
- libevent简单介绍和使用
<pre class="html" name="code">libevent接口的使用是简单easy的.关键还是一些其他技术须要深入了解.如epol ...
- Swift - 产生不重复数字的随机数生成器
在Swift中,可以使用函数类型的参数,也可以使用函数类型的返回值.而作为返回值的函数,还能“捕获”外部的值,并多次使用它.这个特性,常可用来创建各种生成器. 下面通过创建一个“随机数生成器函数”作为 ...
- hdu 1760 一道搜索博弈题 挺新颖的题目
A New Tetris Game Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others ...
- Apple iOS MDM开发流程
一年前曾参与过中石油的一个移动平台项目,实现了通过MDM对iOS设备进行管理.由于苹果对于mdm这块的接口及开发流程只向几个合作伙伴进行了分享,并没有对具体实现的文档进行公开,所以这方面的资料非常少. ...
- 一个能够自己主动生成静态库,自己主动安装程序的Makefile
.PHONY:clean install CC=g++ CFLAGS=-Wall -g BIN=libecho.a INCLUDE=echo SRC=src OBJS=Socket.o Rio.o T ...
- ios-王云鹤 调用ios系统功能---------------打电话、发短信、发邮件
--------------------------------------菜鸟总结,欢迎读者雅正------------------------------------------------- 先 ...
- Boost Thread学习笔记
thread自然是boost::thread库的主 角,但thread类的实现总体上是比较简单的,前面已经说过,thread只是一个跨平台的线程封装库,其中按照所使用的编译选项的不同,分别决定使用 W ...
- poj 2184 Cow Exhibition(背包变形)
这道题目和抢银行那个题目有点儿像,同样涉及到包和物品的转换. 我们将奶牛的两种属性中的一种当作价值,另一种当作花费.把总的价值当作包.然后对于每一头奶牛进行一次01背包的筛选操作就行了. 需要特别注意 ...
- 使用commons-daemon启动、关闭java程序
系统环境: CentOS 7 X64 JDK1.8 一: 安装jsvc 下载 commons-daemon的源代码包 http://apache.fayea.com//commons/daemon/s ...