在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语言数组的更多相关文章

  1. GO语言数组和切片实例详解

    本文实例讲述了GO语言数组和切片的用法.分享给大家供大家参考.具体分析如下: 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. (1)数组的创建. 数组有3种创建方式: ...

  2. C语言 数组 列优先 实现

    C语言数组结构列优先顺序存储的实现 (GCC编译). 从行优先转换为列优先存储方式, 与行优先相比, 不同之处在于改变了数组维界基址的先后顺序, 从而改变了映像函数常量基址. /** * @brief ...

  3. C语言 数组 行优先 实现

    C语言数组结构行优先顺序存储的实现 (GCC编译). /** * @brief C语言 数组 行优先 实现 * @author wid * @date 2013-11-02 * * @note 若代码 ...

  4. 不可或缺 Windows Native (5) - C 语言: 数组

    [源码下载] 不可或缺 Windows Native (5) - C 语言: 数组 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 数组 示例cArray.h #ifn ...

  5. C语言数组:C语言数组定义、二维数组、动态数组、字符串数组

    1.C语言数组的概念 在<更加优美的C语言输出>一节中我们举了一个例子,是输出一个 4×4 的整数矩阵,代码如下: #include <stdio.h> #include &l ...

  6. Go语言数组的使用

    Go 语言数组 Go 语言提供了数组类型的数据结构. 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形.字符串或者自定义类型. 相对于去声明number0 ...

  7. Go 语言数组

    Go 语言提供了数组类型的数据结构. 数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形.字符串或者自定义类型. 相对于去声明number0, number ...

  8. C语言 > 数组和指针

    C语言 数组和指针 const: 关于指针和const需要注意一些规则.首先,把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的. 然而,只能把非const数据的地 ...

  9. Go语言数组和切片的原理

    目录 数组 创建 访问和赋值 切片 结构 初始化 访问 追加 拷贝 总结 数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,数组作为最常见的集合在编程语言中是 ...

随机推荐

  1. Delphi中拖动无边框窗口的5种方法

    1.MouseMove事件中加入: // ReleaseCapture;// Perform(WM_SYSCOMMAND, $F017 , 0); 2.MouseDown事件中加入: // POSTM ...

  2. IOS 可靠性测试 iosMonkey

    UI AutoMonkey UI AUtoMonkey是一款非常简单的IOS压力测试工具.通过它,你可以向ios设备发送滑动.拖动.旋转.甚至锁屏和解锁指令.原文github地址:https://gi ...

  3. Minor GC、Major GC和Full GC之间的区别(转)

    在 Plumbr 从事 GC 暂停检测相关功能的工作时,我被迫用自己的方式,通过大量文章.书籍和演讲来介绍我所做的工作.在整个过程中,经常对 Minor.Major.和 Full GC 事件的使用感到 ...

  4. 基于visual Studio2013解决面试题之0306打印第一次只出现一次的字符

     题目

  5. 动态规划-hdoj-4832-百度之星2014初赛第二场

    Chess Problem Description 小度和小良近期又迷上了下棋.棋盘一共同拥有N行M列,我们能够把左上角的格子定为(1,1),右下角的格子定为(N,M).在他们的规则中,"王 ...

  6. 4.锁--Synchronizer Framework Base Class—AbstractQueuedSynchronizer介绍

    1. AQS简单介绍 AQS是Java并发类库的基础.其提供了一个基于FIFO队列,可以用于构建锁或者其它相关同步装置的基础框架.该同步器(下面简称同步器)利用了一个int来表示状态,期望它可以成为实 ...

  7. ABP模块设计

    ABP模块设计 返回ABP系列 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)”的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术 ...

  8. NET Core RC2 and .NET Core SDK Preview

    NET Core RC2 and .NET Core SDK Preview 先看一下 .NET Core(包含 ASP.NET Core)的路线图: Beta6: 2015年7月27日 Beta7: ...

  9. mfc menu用法一

    #define IDM_NEW 0x1002#define IDM_EXIT 0x1003#define IDM_HELP 0x1004#define IDM_ABOUT 0x1005 hMenu = ...

  10. 深度RAMOS,把操作系统全部安装在内存上

     你看下深度RAMOS就知道了  RAMOS+音速启动+绿色软件+云端  很爽 http://www.shenduwin7.com/jiaocheng/52.html