在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. CSS之float属性解读

    在web标准的网页中,页面各个元素都是以标准流的方式来进行布局的.即块元素占满指定的宽度,不指定宽度则占满整行(如<p>.<div>元素),内联元素则是在行内一个接一个的从左到 ...

  2. LeetCode: LRU Cache [146]

    [题目] Design and implement a data structure for Least Recently Used (LRU) cache. It should support th ...

  3. POJ 2175 spfa费用流消圈

    题意:给出n栋房子位置和每栋房子里面的人数,m个避难所位置和每个避难所可容纳人数.然后给出一个方案,判断该方案是否最优,如果不是求出一个更优的方案. 思路:很容易想到用最小费用流求出最优时间,在与原方 ...

  4. Android下QQ空间查看大图特效

    近期在做一个项目,里面有一个功能是实现Android QQ好友动态里面的缩略图放大,查看大图的效果.用过都知道,这个特效非常赞的,没用过的下载个玩玩吧.我刚開始以为放大的那个大图是一个Activity ...

  5. hbase memstorelab

    关于MemStore的补充 在通过HStore.add向store中加入�一个kv时,首先把数据写入到memstore中.这一点没有什么说明: publiclongadd(finalKeyValue ...

  6. Android——与查询联系人相关的3张表

  7. 内核空间和用户空间的分界 PAGE_OFFSET

      PAGE_OFFSET 首先看看PAGE_OFFSET的功能   内存映射 |            用户空间                  |   内核空间   | |——————+———— ...

  8. iOS 开发学习35 本地化

    增新语言 打开Project-Info-Localizations 点击Localization下的+ 新增语言 定义多语言文件 新增String Files 在Supporting Files上.新 ...

  9. xcode6 cocos2dx开玩笑git和github学习记录

    1. git Xcode4开始,它一直Git作为一个内置的源代码控制(Source Control)工具,所以对于新项目的用途git要管理非常方便.在新建项目向导.可以直接选择Git作为源控制工具.项 ...

  10. html中加入超链接方式的汇总

    在CSS样式中,对超链接的样式有以下几种定义(1)设置链接未被访问时的样式,具体写法如下:a:link{font-size:10px;... }(2)设置链接在鼠标经过时的样式,具体写法如下:a:ho ...