2. 多维数组
多维数组是二维以及二维以上的数组,其中最常用的是二维数组。需要注意的是多维数组的元素存储顺序。以及元素的访问方式等。
当出现多维数组时,若再采用指针、或者指针和下标结合的方式去访问数组元素,将是稍微有点难度的问题。
2.1 存储顺序
在C语言中,多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序。
#include <stdio.h> #include <stdlib.h> #define ROW 3 #define COL 8 int main() { int matrix[ROW][COL]; int *p = &matrix[0][0]; for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { matrix[i][j] = i * ROW + j; } } //打印输出 printf("第一个值是%d \n", *p); printf("第二个值是%d \n", *++p); printf("第三个值是%d \n", *++p); system("pause"); return 0; }
打印输出:
从上面的例子可以看出,当指针增长的时候,指向的是数组按照最右侧率先变化的顺序的元素。而当一行扫描结束的时候,会自动指向下一行,继续访问。
2.2 数组名
一维数组名的值是一个指针常量,指向一个元素,而多维数组第一维的元素是另一个数组。例如下面的声明:
int matrix[3][10];
可以看作是一个一维数组,包含了3
个元素,每个元素是包含10
个整形元素的数组。
或者可以看本文后续的章节,慢慢体会就自然会明白。
2.3 下标
如果要标识一个多维数组的某个元素,必须按照与数组声明时相同的顺序为每一维都提供一个下标,而且都单独位于一对方括号内。
#include <stdio.h> #include <stdlib.h> #define ROW 8 #define COL 3 int main() { int matrix[ROW][COL]; for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { matrix[i][j] = i * COL + j; } } //打印输出 printf("第一个值是:%d \n", **matrix); printf("第二个值是:%d \n", *(*(matrix + 1))); printf("第三个值是:%d \n", *(*(matrix) + 2)); printf("第四个值是:%d \n", *(*(matrix + 1) + 2)); system("pause"); return 0; }
上面这个例子可能稍微有点难,按照指针移动的方向,仔细琢磨琢磨就可以想清楚。
2.4 指向数组的指针
指向多维(二维)数组的指针,应当如何定义呢?
int matrix[ROW][COL] = {{0,1,2},{3,4,5}}; int(*p)[COL] = matrix;
我们定义了一个指针p,指向了一个拥有COL
个元素的数组。当把p与一个整数值相加时,该整数值首先根据整形值的长度进行调整,然后再执行加法。参考下面的例子。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define ROW 2 #define COL 3 int main() { //定义二维数组 int matrix[ROW][COL] = {{0,1,2},{3,4,5}}; //定义指向数组的指针 int(*p)[COL] = matrix; //打印输出 printf("(*(*p))的值为:%d \n",(*(*p))); printf("(*(*p + 1))的值为:%d \n", *(*(p + 1))); printf("(*(*p) + 1的值为:%d \n", (*(*p) + 1)); printf("(*(*p + 1) + 1)的值为:%d \n", *(*(p + 1) + 1)); system("pause"); return 0; }
打印输出:
可以看到,直接利用p指针执行间接访问,肯定访问到的是第0行的第0个元素((*(*p))
)。而在对p直接进行加1操作的时候,移动的是一个一维数组,然后再间接访问,所以得到的是数组第1行的第0个元素,也就是*(*(p + 1))
这样的表达式;但如果间接访问一次之后在加1,则首先访问到的是二维数组的第0行,再加1自然就是第0行第一个元素,也就是上述(*(*p) + 1)
表达式;最后一个表达式(*(*(p + 1) + 1)
)自然不言而喻了。
2.5 作为函数参数的多维数组
多维(二维)数组作为函数参数的时候,函数声明也与一维数组有所不同。有两种声明方式:
方式1:
void func1(int(*mat)[5])
方式2:
void func2(int mat[][5])
这两种声明方式,在效果上是等同的,两种都可以。从以下的程序中就可以证明这一点:
#include <stdio.h> #include <stdlib.h> #include <string.h> #define ROW 3 #define COL 5 //声明方式1 void func1(int(*mat)[5]) { int add = 1; printf("在函数func1中\n"); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { mat[i][j] += 10; printf("数组的第%d个元素是:%d\n", add, mat[i][j]); add++; } } } //声明方式2 void func2(int mat[][5]) { int add = 1; printf("在函数func2中\n"); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { mat[i][j] += 10; printf("数组的第%d个元素是:%d\n", add, mat[i][j]); add++; } } } int main() { //定义二维数组 int matrix1[ROW][COL]; int matrix2[ROW][COL]; //定义累加变量 int add = 1; //数组初始化 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { matrix1[i][j] = i * COL + j; matrix2[i][j] = i * COL + j; printf("数组1的第%d个元素是:%d \t", add, matrix1[i][j]); printf("数组2的第%d个元素是:%d \n", add, matrix1[i][j]); add++; } } printf("-----------------"); //函数调用 func1(matrix1); printf("-----------------"); func2(matrix2); system("pause"); return 0; }
打印输出:
上述例子,定义了2个3*5
的二维数组,进行相同的初始化,然后分别传到两个函数进行处理,这两个函数仅仅是形参的声明方式不一样。两个函数对原来数组的每个元素进行加10处理,得到了相同的结果。
所以,这两种声明方式在效果上是等同的。
2.6 初始化
多维(二维)数组的初始化有两种常见的形式。
- 一种是直接给每个元素赋储值
- 一种是用花括号隔开每个维度,并赋值
两种方式都是正确的,只是第二种有方式有两个好处:
- 利于阅读
- 方便初始化,每个子初始列表都可以省略尾部的几个值(不完整的初始化列表)。
参考下面的程序:
#include <stdio.h> #include <stdlib.h> #define ROW 2 #define COL 3 int main() { //初始化形式1 int matrix1[ROW][COL] = {0,1,2,3,4,5}; //初始化形式2 int matrix2[ROW][COL] = { {0,1,2},{3,4,5}}; int add = 1; //打印输出 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { printf("matrix1的第%d个值为%d \t", add, matrix1[i][j]); printf("matrix2的第%d个值为%d \n", add, matrix2[i][j]); add++; } } system("pause"); return 0; }
打印输出:
从上面的例子可以看出,这两种形式,从实现效果上来说,是一致的。
2.7 数组长度自动计算
在多维数组中,只有第1
维才能根据初始化列表缺省地提供。剩下的几个维必须显式地写出,这样编译器就能推断出每个子数组维数的长度。例如:
#include <stdio.h> #include <stdlib.h> #define ROW 3 #define COL 5 int main() { int matrix3[][5] = { {0,1,2},{3,4,5},{6,7,8}}; int add = 1; //打印输出 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { printf("matrix1的第%d个值为%d \t", add, matrix3[i][j]); add++; } printf("\n"); } system("pause"); return 0; }
打印输出:
所以此时即使不写第1个维度的值,编译器在运行的时候,也可以根据初始化的值,以及花括号自动推断出来该维度的值。
3. 指针数组
指针数组很好理解,就是一个数组中的元素是指针,至于该指针指向什么样的数据,是由用户自己定义的。
比如下面这个例子,用指针数组存储了指向字符串(更严谨地说是字符数组)的指针。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define ROW 3 #define COL 5 char const *keyword[] = {"do","for"}; int main() { int add = 1; //打印输出 char const desired_word[] = "do"; char const **p; for (p = keyword; p < keyword + 2; p++) { if (strcmp(desired_word, *p) == 0) { printf("YES"); system("pause"); return 0; } } printf("NO"); system("pause"); return 0; }
打印输出如下:
上述的例子中,用数组存储了若干个字符数组的指针数组。然后实现了多个字符串的匹配功能。
或者用二维数组也可以实现,但是需要提前知道最长字符串的大小。
4. 总结
数组与指针的关系,不可能一两句话说清楚,需要在具体开发中慢慢体会,理解。
数组的元素可以通过下标和指针两种方式进行访问,而指针往往更加高效。
指针数组在开发中也会比较常用,而数组元素也不仅仅只会指向字符串(字符数组),也有可能指向结构体变量等数据类型。
----------------------------------------------------------------end----------------------------------------------------------------