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 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...
随机推荐
- 用xerces-c来进行xml schema校验
在xerces-c的官方站点上有文章指引说明是怎样进行xml schema校验. http://xerces.apache.org/xerces-c/schema-3.html 给出的样例代码: // ...
- Eclipse完美汉化教程
首先我们打开http://www.eclipse.org/babel/downloads.php下载语言包. 方法一:可以复制图片里的地址通过Eclipse下载,Help→Install New So ...
- Android中View绘制优化之三---- 优化View
本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning 译三: 优化视图 关于如何设计自定义View以及响应触摸时间等,请看Android developer : 地 ...
- 【MongoDB】在windows平台下搭建mongodb的分片集群(二)
在上一片博客中我们讲了Mongodb数据库中分片集群的主要原理. 在本篇博客中我们主要讲描写叙述分片集群的搭建过程.配置分片集群主要有两个步骤.第一启动全部须要的mongod和mongos进程. 第二 ...
- js验证日期
寻寻觅觅,Web开发里,对日期的验证太多了,网上好多是用正则表达式来验证,但是这种验证也只能验证格式,没办法验证有效性,比如平年(2月28天)和闰年(2月29天).平时用得多,以前经常用一次写一次,腻 ...
- Citrix 服务器虚拟化之三十二 XenConvert
Citrix 服务器虚拟化之三十二 XenConvert 简介: Citrix XenConvert 是用于实现物理到虚拟(P2V)转换的工具,可将工作负载从运行 Windows 的服务器或桌面计算 ...
- Swift - 本地数据的保存与加载(使用NSCoder将对象保存到.plist文件)
下面通过一个例子将联系人数据保存到沙盒的“documents”目录中.(联系人是一个数组集合,内部为自定义对象). 功能如下: 1,点击“保存”将联系人存入userList.plist文件中 2,点击 ...
- U3D——Unity3D的脚本-script入门
Unity3D的基本操作非常easy就能掌握了,接下来就是游戏系统的核心部分:脚本. 什么是Script(脚本)?简而言之,就是使用代码来运行一系列动作命令的特殊文本,它须要编译器来从新解读.U ...
- 【ASP.NET Web API教程】5.1 HTTP消息处理器
原文:[ASP.NET Web API教程]5.1 HTTP消息处理器 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本系列教程,请先看前面的内容. 5.1 HTTP ...
- 使用Delphi声明C++带函数的结构体实战 good
在小组开发中,应用程序部分采用Delphi7,一些组件使用C++做.在今天将一个动态库的C++接口声明头文件转换为D7的Unit单元时,一切都很顺利,直到遇到下面这样一个另类的东西: typedef ...