本节视频链接:点击这里

1、多维数组的定义和结构

        一个数组中可以支持各种数据类型,那么一个数组中的每一个元素同样也可以是一个数组。对于上次提到的一维数组,其每个元素都是一个简单数据类型的对象,其结构如同一个一维的数据排列;对于一个二维数组,它的每一个元素都是一个一维数组,其形式如同一个二维的表格,表格的宽度是其中作为数据元素的一维数组的长度,高度是这样的一维数组的个数。简而言之,二维数组的结构是一个矩阵的形式。
        例如,我们声明下面这样的一个二维数组:
int nMatrix[6][10];

这个二维数组nMatrix包含了6个一维数组,每个一维数组的长度为10,总计有60个int型数据元素。它的逻辑结构如下图所示:

nMatrix                    
nMatrix[0] nMatrix[0][0] nMatrix[0][1] nMatrix[0][2] nMatrix[0][3] nMatrix[0][4] nMatrix[0][5] …... …... …... …..
nMatrix[1] nMatrix[1][0] nMatrix[1][1] nMatrix[1][2] nMatrix[1][3] nMatrix[1][4] …... …... …... …... …..
nMatrix[2] nMatrix[2][0] nMatrix[2][1] nMatrix[2][2] nMatrix[2][3] …... …... …... …... ….. …..
nMatrix[3] nMatrix[3][0] nMatrix[3][1] nMatrix[3][2] ….. …... …... ….. ….. ….. …..
nMatrix[4] nMatrix[4][0] nMatrix[4][1] ….. ….. ….. ….. ….. ….. ….. …..
nMatrix[5] nMatrix[5][0] nMatrix[5][1] ….. ….. ….. ….. ….. ….. ….. …..
        由这样的结构我们也称作是一个“6行10列”的二维数组。由于这种结构特别适合表示图像和视频数据,这样的多维数组在多媒体处理等领域有着广泛的应用。
        我们知道,对于一维数组,其存储结构是一段连续的内存空间。比如一个一维数组int nArray[3],其存储结构为:
0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 0x0010 0x0011 0x0012
nArray[0]       nArray[1]       nArray[2]      

一个二维数组,相当于一个一维数据每一个元素都是另一个一维数组。二维数组首先从首地址开始按顺序存储第一行的数据,然后在第一行最末尾元素的下一个内存单元开始保存第二行数据,依次类推,直至保存完最后一行。从二维数组的下标来看,按照内存单元的向后依次遍历的顺序,最优先变化的是数组最右边的下标,即,变化顺序为:nMatrix[0][0],nMatrix[0][1],nMatrix[0][2]…...nMatrix[0][9],nMatrix[1][0],nMatrix[1][1], nMatrix[1][2]…...nMatrix[1][9], nMatrix[2][0], nMatrix[2][1]…...nMatrix[2][9], nMatrix[3][0]……….nMatrix[5][9]。C语言中二维数组的这种存储结构称为“行主序”。

        用另外一个例子说明,如果某个数值按照一天中每一小时、每分钟不同来变化,就可以定义一个二维数组int nValue[24][60]。每过一分钟,最右边的下标就增1,当增长到60时,最右边的下标重新归0,左边的下标增1。
        需要说明的是,这里将nMatrix解释为6行10列是一种表示方法,将其解释为10行6列也是可行的,具体哪种方法更优最好依据表示的数据的意义来决定。不过无论哪种表示方法,都不可能改变二维数组的在内存中的存储结构。

2、二维数组和指针

我们已经知道,一个一级指针可以指向一个一维数组,或者说,可以将一个一维数组名赋值给一个一级指针。同样道理,如果我们定义了一个二维数组,就可以定义一个二级指针指向这个二维数组:

int nMatrix[6][10];
int **ppMatrix = nMatrix;

这个值该如何理解?首先我们研究一下二维数组中的数值和地址的关系。在这个二维数组中,第一行的元素分别为nMatrix[0][0], nMatrix[0][1]…nMatrix[0][9],并且这些元素也构成一个数组。对比一下,数组a[0]、a[1]和a[2]的首地址为a,那么我们可以推测这第一行的各个元素的首地址是nMatrix[0]。事实上正是如此,一个二维数组中,每一行元素都是一个一维数组,这一个一维数组的首地址/数组名就是二维数组名+左索引构成。例如,第0行的首地址为nMatrix[0],第1行的首地址为nMatrix[1],第n-1行的首地址为nMatrix[n-1]。这n个首地址表示的是指向一维数组的地址,可以用一级指针指向。即:

int *pRow = nMatrix[0];

不仅如此,这n个首地址的保存位置也构成一个一维数组。通过观察着n个首地址的命名,我们可以得知,包含着n个首地址的一维数组的首地址实际上就是二维数组名nMatrix。由于每行首地址的数据类型为int *,那么由它们组成数组的首地址类型就是int **,于是我们可以定义一个二级指针指向这个地址,即:

int **ppMatrix = nMatrix;

这也解释了本节开始时为什么可以定义二级指针指向二维数组名。

在定义了指向二维数组的指针之后,我们就可以通过该指针对二维数组进行读写等操作。例如我们可以根据该指针获取每一行元素的首地址。以下几行代码实现是等价的:

int *pRow2 = ppMatrix[2];
int *pRow2 = *(ppMatrix + 2);

也可以直接获取二维数组中的某个元素:

int nRow1Col3 = ppMatrix[1][3];
int nRow1Col3 = *(ppMatrix[1] + 3);
int nRow1Col3 = (*(ppMatrix+1))[3];
int nRow1Col3 = *(*(ppMatrix+1)+3);
        二维数组的初始化方法的原理与一维数组原理类似。一维数组的初始化使用大括号实现,而二维数组也是如此,只是其内部每个元素也是一个用大括号初始化的一维数组:
int nMatrix[3][2] =
{{9, 7, 5},{6, 4, 2}
};

3、三维和更高维度数组

事实上,C语言所支持的数组维度远不止二维数组,三维甚至更高维度的数组也是支持,甚至是很常用。经过了前面的知识,我们很容易归纳出多维数组的定义方法:

char cCubeArray[3][6][8];          //定义一个3层、6行、8列的三维数组
int nIntBuf[4][5][4][6];                 //定义一个4块、5层、4行、6列的四维数组

以三维数组为例,对于三维数组进行遍历,使用3层循环结构即可。最右一层下标变化最快,越向左下标变化越慢。例如我们定义一个多维数组表示日期和时间,则可定义这样一个数组:

int nDateTimeValue[12][31][24][60][60];

这几个下标从左到右分别表示月、日、小时、分、秒。当表示秒的下标循环到60后,分钟向下+1;当分钟循环到60后,小时向下+1;当小时循环到24后,日的下标向下+1,依次类推。

for (int month = 0; month < 12; month++)
{for(int day = 0; day < 31; day++){for(int hour = 0; hour < 24; hour++){for(int minute = 0;minute < 60; minute++){for(int second = 0; second<60; second++){printf(“Value of %d month, %d day, %d hour, %d minute, %d second: %d”, month, day, hour, minute, second, nDateTimeValue[month][day][hour][minute][second]);}}}}
}

4、指针数组和指向数组的指针

        当数组和指针相互结合的时候,程序的意义可能就会变得比较麻烦且难以理解。比如以下两种声明:
int *ptArr1[6];
int (*ptArr2)[6];

我们需要理解清楚这二者是否相同,以及两个指针ptArr1和ptArr2分别代表的含义。这两个复杂的声明包含了间接运算符、括号运算符、下标运算符等,要理解这两种声明,首先需要清楚不同运算符的顺序。下标运算符[ ]和括号( )的优先级为1,为各种运算符最高;间接运算符优先级为2,次于下标和括号。因此对于声明int *ptArr1[6],首先计算的是ptArr1[6]部分,因此ptArr1是一个长度为6的一维数组;然后间接运算符所代表的是数据类型,即为指向整型变量的指针。这种声明方式声明了一个指针数组,这是一个长度为6的一维数组,保存了6个整型变量指针,数据类型等同于int *。

另一种声明方式int (*ptArr2)[6],由于括号( )优先级最高,因此首先计算的是(*ptArr2)。从整个声明的格式上来看,(*ptArr2)是一个整型一维数组的数组名即一维数组的首地址。所以ptArr2是一个指向一维数组首地址的指针,数据类型等同于int **。因此,可以将二维数组名初始化给这个指针:

int (*ptArr2)[6] = nMatrix;

这种类型的指针定义和初始化创造了一个指向二维数组第一行的指针。对指针ptArr2,其增加某个整数的作用是一行一行地移动指针。因为在定义时制定了指向的数组的长度,因此在移动时系统会根据指向数组的长度进行调整。所以我们需要注意的是,如果我们希望以此方式处理数组的数据,定义时数组的长度不要指定错误,也不要设为空,如同下面的样子:

int (*ptArr2)[] = nMatrix;     //错误的用法

因为,编译器只有知道了第二个和后面各维的长度,才能在移动指针时确定每一步移动的实际长度,对各个下标进行求值。

理解C语言——从小菜到大神的晋级之路(9)——多维数组相关推荐

  1. 理解C语言——从小菜到大神的晋级之路(1)——引言:C语言的前世今生

    第一课的视频链接点这里 C语言是现在应用最为广泛的编程语言之一,也是现在依然流行的编程语言中历史最悠久的一种之一.在目前业界广泛使用的编程语言中,许多 种语言是以C为基础发展而来.在多类大学的工程类专 ...

  2. c语言的调试是对源文件进行,理解C语言——从小菜到大神的晋级之路(3)——C源程序的基本结构与调试方法...

    本期视频点击这里 在上一篇中,我们进行了Visual Studio 2013的安装以及第一个demo程序"HelloWorld"的建立.现在我们看一下其中的源代码及相关的C语言基础 ...

  3. C语言/C++程序员大神打造纯C的电子时钟(加图形库+源码)

    C语言/C++程序员大神打造纯C的电子时钟,每天看时间再也不麻烦了 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进 ...

  4. linux 修改用户组_linux小白到大神的成长之路:linux系统用户组的管理!

    linux小白到大神的成长之路:linux系统用户组的管理! 本经验由宗龙龙原创,全文共500多字,阅读需要14分钟,如果文中存在错误,还请大家多多指点,我会积极改进的! 上一篇文章为大家详细介绍有关 ...

  5. linux云自动化运维,Liunx运维一线大神亲授 全新Linux云计算运维基础与Linux Shell自动化运维实战课程...

    Liunx运维一线大神亲授 全新Linux云计算运维基础与Linux Shell自动化运维实战课程 全新Linux云计算运维基础与Linux Shell自动化运维实战课程,由于国内一线大神亲自授课与教 ...

  6. C语言是菜鸟和大神的分水岭

    作为一门古老的编程语言,C语言已经坚挺了好几十年了,初学者从C语言入门,大学将C语言视为基础课程.不管别人如何抨击,如何唱衰,C语言就是屹立不倒:Java.C#.Python.PHP.Perl 等都有 ...

  7. 秒表c语言程序代码,求大神帮忙写一篇简单的C语言秒表程序, 谢谢。

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 #include Unsigned char code Tab[10]{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0 ...

  8. PMCAFF | 一个CRM产品大神的产品之路

    文 | 吴波 文前声明:此文,我纯属想到哪里写到哪里,请用审视的目光看此文,因为,会很疼! 看到这个题目,估计兄弟们会说,这孙子终于沦落到记流水账的地步了.哇哈哈!BOB.WU出品必属精品.不按常理出 ...

  9. python代码少的作品_原创 8行python代码展示程序员从入门到大神(或跑路)的全部状态...

    一行python代码可以做什么? 人生苦短,我用python.python的世界里无处不在的简洁和短小,往往一行代码可以实现很多有意思功能. 你敢想象你从入门python代码.网络达人.反重力怪才.爱 ...

最新文章

  1. “坑爹”排行榜:Java语言最违反常识的功能点TOP 10
  2. Win8兼容ARM十大意义:打破垄断拉低价格
  3. 使用Datastax Java驱动程序与Cassandra进行交互
  4. 蚂蚁金服隗华:十五年时间见证分布式数据库的崛起
  5. python爬取多页数据_python爬虫实现爬取同一个网站的多页数据代码实例
  6. python类继承实例_python类继承与子类实例初始化用法分析
  7. 从 Wi-Fi 6 的到来,看家庭网络的优化之路!
  8. 【TWVRP】基于matalb粒子群算法求解带时间窗的车辆路径规划问题【含Matlab源码 1272期】
  9. 银行的起源---》阮一峰,
  10. 开源可视化网页抓取工具Portia 爬虫
  11. C++制作植物大战僵尸
  12. H5页面,华为手机打开不加载JS的问题
  13. 香港银行账户被关,应如何取走余额
  14. PayPal社交游戏及移动娱乐产业的海外商机
  15. [Linux] USB-Storage驱动 源码阅读笔记(一)
  16. 精品基于PHP实现的商城电商网站
  17. 暴力破解攻击工具汇总——字典很关键,肉鸡也关键
  18. Anser to Somebody
  19. 上海店宝宝:从考拉海购新思路的启发
  20. 《远程开关机工具 1.0》软件使用手册

热门文章

  1. python函数调用的例子_Python案例|混用C函数
  2. spring 字面值 特殊字符 配置使用
  3. visual什么颜色好用_口红哪个牌子好用?浓郁显白的口红超合适你der
  4. chrome怎么调用硬件_浏览器发展简史——IE帝国是如何被chrome打败的?
  5. python自动化上传文件_python接口自动化测试二十三:文件上传
  6. java object 判断null_java判断object为null
  7. python带界面的人脸识别_PyQt5+Caffe+Opencv搭建人脸识别登录界面
  8. Python 语言程序设计(4-3) Random 随机库
  9. 北京大学生物信息学 (4)序列数据库
  10. oracle自增列问题i,Oracle序列 和 SQL SERVER 自增列的问题-oracle