C语言——指针篇(四)多维数组和多维指针

前言:笔者本来是打算在上一篇文章中,把多维数组和多维指针与数组的访问方式结合起来一起写掉。但是在写作过程中,发现创作篇幅明显过长,不利于日后按图索骥,所以又重开了一篇,说说自己的感悟,仅供参考


多维数组和多维指针


1.一维数组和多维数组的存储区别

  假设我们现在有如下代码分别声明了一个一维数组和一个二维数组:

int array1[3];
int array2[3][3];
  • 一维数组的存储
      我们假设此代码编译运行在32位机上,一个int类型数据占4个字节,array1数组从地址0x5000_0000处开始分配空间,则可得array1数组的存储框图如下:

    在这里插入图片描述

      在上图中使用一个 ?? 符号代表一个未知具体数值的字节数据,array1中的3个int类型的数据其实实际所占字节数为12。作为数组元素连续存储,因此从数组首地址0x5000_0000向后地址递增存放数据,其实际所占地址为0x5000_0000至0x5000_000B。

  • 多维数组的存储
      多维数组中,以二维数组为代表重点阐述,其余三维四维乃至N维可以以此类推。在C语言中,多维数组的存储是以行序为主,下面的阐述也遵循这个规律。
      我们假设array2数组的首地址是0x6000_000C,其后按地址连续存储,则有如下存储框图:

    在这里插入图片描述

      从上图可以看出,该数组含有9个int类型元素,共占36个字节,从首地址开始连续存储分配。

  • 总结
    1)任何数组(不论是一维数组还是多维数组),其数组首地址都是一个和数组名直接关联的地址常量。
      即,如上述array1和array2等数组名直接和数组首地址关联,它们可以出现在赋值符号=的右边,作为一个地址量赋给指针,但是不能出现在=的左边,对其重新进行赋值。其首地址的常量值,在代码编译链接时期,由编译器和链接器确定,整个程序运行期间不能被更改。
    2)数组元素是以顺序方式连续存储的,这意味着,我们可以通过一个首地址和一个偏移量定位到数组中的每个元素。
      每次操作数组中的元素时,不管是通过下标引用还是指针访问的方式,都是先从数组的首地址开始,加减一个偏移量,找到该元素的地址,再对该元素进行操作。


2.一维指针和多维指针

  一维指针和多维指针没有特别需要阐述的地方,它们之间的区别主要是:一维指针仅仅进行了单次地址转换即可以取出数据操作;而多维指针则根据声明的维数需要进行多次地址转换才能够取到目标数据。但是,指针作为一个数据变量,可以多次赋值,使得其成为对数组操作访问的一大利器,所以指针和数组的结合才是重中之重。


3.数组指针和指针数组

  • 3.1 数组指针
      数组指针,顾名思义就是一个指向数组的指针。一维指针可以指向一个同类型的一维数组,但多维指针不一定可以直接指向一个多维数组。有如下声明:
int vector[10],*vp = vector;
int matrix[3][10],*mp = matrix;

  在上述声明中,vector是一个含有10个元素的数组,每个元素都是int类型,vector作为数组首地址,其类型是一个int的地址,因此可以赋值给int *类型的变量vp
  而在二维数组的声明中,结合行序优先的规律看,其实是先声明了一个数组matrix[3],含有三个元素,每个元素是int [10] 类型, matrix作为数组首地址,存储的 matrix[0] 元素的类型也是 int [10]类型,与被声明为int * 类型的mp类型不符合,不能被赋值。
  若想要声明一个指向matrix的指针,则应该如下声明:

int (*p)[10];

之前说到,下标引用 [ ]的优先级大于指针引用*,但若出现 ()则,其优先级最高。在上述声明中,先看 ( * p) 声明了一个指针,余下的int [10],则是其指向的数据类型。因此,matrix可以赋给p,它们的数据类型相同。而 matrix[0] 则可以赋给mp,它们的数据类型都是int *

  • 3.2 指针数组
      指针数组,其本质是一个数组,只不过其元素类型都是指针类型。如下所示:
int  *a[10];

下标引用的优先级高于间接访问,所以在这个表达式中首先执行下标引用,可以得出a是一个含10个元素的数组,其元素类型为int *。其数据类型与上述vector相同,因此 a[0] = vector 成立。


3.数组指针的数组和多维数组的关联

  让我们回到一开始的多维数组,在脑海中重新组织我们对数组的理解。对于 int array2 [3][3],我们一般对于其最常见的理解是array2是一个数组的首地址,它含有3个元素,每个元素又都是另外一个数组的首地址,如下图所示(灵魂手稿突现):

在这里插入图片描述

  • 4.1数组指针数组
      当我们采用数组指针数组的方式存储这些数据时,其操作方式类似于多级索引。我们不仅要花空间存储原来的数据,还要花多余的空间存储索引项,即这里的数组指针项。如下图:

    在这里插入图片描述

      并且,由于数组指针已经存储了实际数据数组的索引,所以实际的数组可以采用数组间离散存储的方式,其每个数组的大小也可以不相等

  • 4.2多维数组存储
      采用多维数组存储数据时,由于其子数组大小相等,都采用顺序存储方式存储数据,每个子数组的首地址可以通过二维数组的首地址+偏移量计算所得,所以不需要采用额外的空间存储子数组首地址的索引。如下图所示:

    在这里插入图片描述

  • 总结:
      我们可以将多维数组的第一维看作是一个数组指针,其存放的指针地址指向其随后的每一维子数组,但实际存储上,二者有很大差别:
    1)多维数组必须顺序存储,大小固定,所以其子数组的首地址是常量,可通过二维数组首地址+偏移量计算所得,不需要占用额外的存储空间。
    2)数组指针数组需要占用额外的存储空间记录子数组的首地址,因此子数组大小可变,也可以采用离散存储方式,更加灵活。
    3)关于二种方式的应用场景:
      如果存储的数据大小比较接近紧凑,建议声明为多维数组会更加节省存储空间,操作方便;若数据集合中,数据多为长短不一且最长数据和最短数据相差较大,例如:字符串存储,建议声明为数组指针数组会更加节省空间。


5.作为函数参数的数组和指针

  函数调用传参的过程都是都先将实际参数复制给形式参数。作为数组传参是将数组的首地址传给形式参数,作为指针是将指针变量的内容传入。

  • 5.1一维数组和一维指针的函数原型
    void fun (int * v); //一维指针
    void fun (int v [ ]); //一维数组
      上面两个函数的声明功能相等,其传入形参的类型都是int类型的地址。对于数组当形参的声明来说,编译器并不关心数组有多少个元素(即数组下标越界是代码编写者需要关注的事),编译器只关心数组的首地址。因此上述形参中数组下标[ ]中的数值缺省。
  • 5.2多维数组和多维指针的函数原型
    void fun (int( * v)[10]); //数组指针
    void fun (int v [ ][10]); //二维数组
      上述两个函数声明的功能相等,其都传入了一个首地址,且该首地址都指向一个int [10]数据类型。值得注意的是,数组作形参时,其第一维长度必须省略,其后所有维度的长度必须列出

  • Q:为什么数组除第一维长度外的所有长度都不能省略?
  • A: 当数组作为形参时,我们需要传入其数组首地址,然后对首地址进行加减运算以得到下一个数据地址,在第一个例子中,int类型占4个字节,所以每次对地址进行加减时,都是以4为步进单位。第二个例子中,int [10]类型占40个字节,所以对传入地址的加减步进值为40.除第一维外的所有维数的长度是为了确定该数组首地址的加减步进值,因此不能省略
     而有时可以省略第一维长度的场合,比如:
    int a [ ][2] = { {1,2},{3,4}};
     编译器会根据初始化内容自动判定数组的第一维长度为2.因此,在某些情况下,数组的第一维长度可省略
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容