我们在一维数组中已经说明了很多的内容,那么对于二维数组来说,二维数组的本质,也是一个一维数组,只不过,一维数组中的每个元素,又是一个一维数组而己,也就是存在一维数组的嵌套关系。
我们首先定义一个二维数组然后进行解释:
int array[3][4];上面代码表示定义一个数组名为array,3行4列的一个二维数组。 我们通常理解为3×4的一个矩阵。
我们可以这样来理解二维数组本质: int [4] arr[3] ; 前面int [ 4 ] 表示类型,后面arr[ 3 ]表示一维数组,里面有三个成员 arr[ 0 ],arr[ 1 ],arr[ 2 ] ,每一个成员都是int [ 4 ] 类型。
我们在一维数组中: int arr[10]; 等价于 int [ 10 ] arr;
那么在二维数组中: int arr[ 3 ] [ 4 ] ; 等价于 int [ 4 ] arr[ 3 ];
我们通过上面图解对于二维数组的本质理解,把 int [ 4 ] 看着 T 得到: T arr[ 3 ] ; 等价于 T [ 3 ] arr ; T代表一维数组arr [ 3 ] 元素的类型。
把T还原 T [ 3 ] arr ; 也就等价于 int [ 4 ] [ 3 ] arr;
那么也就是说 int arr[ 3 ] [ 4 ] ; 等价于 int [ 4 ] [ 3 ] arr ;
所以: int[4][3] 表示三行四列 表示 int [ 4 ] 类型的元素有3个。 在定义二维数组的时候 是 arr [ 3 ] [ 4 ] ;
int[3][4] 表示四行三列 表示 int [ 3 ] 类型的元素有4个。 在定义二维数组的时候 是 arr [ 4 ] [ 3 ] ;
代码演示:
#include <stdio.h> int main() { int arr[3][4]; printf("sizoef(arr) = %d\n", sizeof(arr)); printf("sizoef(int[4][3]) = %d\n", sizeof(int[4][3])); printf("sizoef(arr[0]) = %d\n", sizeof(arr[0])); printf("sizoef(int[4]) = %d\n", sizeof(int[4])); printf("sizoef(arr[0][0]) = %d\n", sizeof(arr[0][0])); return 0; }运行结果为:
上面代码可以帮助我们理解二维数组的本质。
再次强调:我们上面定义的二维数组是3行4列,int [ 4 ] [ 3 ]也代表3行4列。
二维数组初始化和一维数组一样,我们这里分为三类。
满初始化的意思就是,我们在定义二维数组的时候,二维数组每一个元素都进行了初始化,并且在初始化的 { } 里面每一行的内容用 { } 单独括起来初始化。我们也可以将二维数组的所有元素都放在同一个 { } 里面,二维数组也会自动识别行列,二维数组保持行优先规则。也即是说,只有一个 { } 里面包括二维数组所有元素的时候,数据存放先存放二维数组第一行,第一行存满之后开始存放第二行,依次存放数据。
#include <stdio.h> int main() { //满初始化 int arr[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; //满初始化 int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; //以上两种都是满初始化 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } printf("\n\n"); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr1[i][j]); } printf("\n"); } return 0; }打印结果为:
部分初始化的意思就是说,我们二维数组的内容只初始化了一部分,那么其他元素自动初始化为0。 那么部分初始化这里,我们又要区分:
int arr1[3][4] = { 1,2,3,4,5,6};和
int arr[3][4] = { {1}, {2,3}, {4,5,6} };我们先给出打印结果:
#include <stdio.h> int main() { //部分初始化 int arr[3][4] = { {1}, {2,3}, {4,5,6} }; //部分初始化 int arr1[3][4] = { 1,2,3,4,5,6}; //以上两种都是部分初始化 for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } printf("\n\n"); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr1[i][j]); } printf("\n"); } return 0; }打印结果为: 我们先解释:
int arr[3][4] = { {1}, {2,3}, {4,5,6} };我们首先知道,二维数组和一维数组一样,部分初始化中没有初始化的部分为0,但是如果初始化的每一行都用{}括起来的话,就像每一行单独的一样。初始化的时候,只把这一行里的元素从前向后初始化,没有初始化的元素为0,而不影响其他行里的元素。
也就是说一旦每一行都用 { } 初始化,每一行的元素之间是互不影响的。从而就出现了我们第一组的打印结果,第一行 { } 里面只有1而后面三个元素没有初始化的部分就为0,第二行{}里面有2,3 后面两个元素没有初始化的部分就为0,第三行类似,每一行之间互不影响。
int arr1[3][4] = { 1,2,3,4,5,6};上面的初始化很容易理解,按照二维数组行优先,开始初始化,没有初始化的部分为0。
那么我们也会遇到只初始化一个元素的情况: 例如:
#include <stdio.h> int main() { int arr[3][4] = {0}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } return 0; }打印结果为: 我们在这里需要注意的一点就是,这样并不是说把所有的元素都初始化为0,而是把第一个元素初始化为0,后面的值是因为按照部分初始化处理,所以后面的元素值都初始化为0。
如果我们只有定义部分,没有初始化部分:
#include <stdio.h> int main() { int arr[3][4]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } return 0; }打印结果为: 那么二维数组元素的初始化就是随机值。
我们可以直接这样定义: 或者
int arr2[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };这两个功能完全相同。 但是我们能不能只写行不写列呢? 结果是不行的,我们在这里分析:
int arr2[4][] = { 1,2,3,4,5,6,7,8,9,10,11,12 };如果要求是4行,那么如果划分呢? 1 2 3 4,5,6,7,8,9,10,11,12 还是 1,2,3 4,5 6 7,8,9,10,11,12, 还有很多更多的情况,所以是不能的。
我们可以不给出二维数组的行,但是必须给出二维数组的列,二位数组按照列优先初始化元素,给出列,就知道了数组在第几个元素的时候跳转到下一行。
当然上面的分析只是我们便于理解 如果你对于我上面的二维数组的本质已经理解的话,下面我们进行本质分析: 二维数组: int arr[ 2 ] [ 4 ] ; 本质就是 int[4] arr[2] ; int[4]是类型,2是元素个数,arr是二维数组变量名。 我们已经知道一维数组里可以不设置数组元素个数及就是arr[2],但是我们不能破坏数据类型。 例如我们的int类型,你能写成in吗?就会造成类型不完整。
也就是说一维数组arr[ 2 ] 的数据类型是int[ 4 ],里面的4及就是二维数组的列。 所以对于arr[2][4]二维数组,里面的4是不能省略,省略了就会造成一维数组arr[ 2 ] 数据类型不完整。 也就是说行可以省略,但是列是不能省略的。
总结: arr [ 3 ] [ 4 ] ; 省略3等价于省略了一维数组的大小,如果省略4等价于省略了类型的大小。数组中类型的大小是绝对不可以省略的。
按照二维数组本质,我们可以通过下面方式求出来不定义行初始化的二维数组有多少行: 代码演示:
#include <stdio.h> int main() { int arr[][4] = { 1,2,3,4,5,6,7,8,9 }; printf("二维数组有%d行\n", sizeof(arr) / sizeof(int[4])); printf("二维数组有%d行\n", sizeof(arr) / sizeof(arr[0])); for (int i = 0; i < sizeof(arr) / sizeof(int[4]); i++) { for (int j = 0; j < 4; j++) { printf("%3d", arr[i][j]); } printf("\n"); } return 0; }运行结果为:
我们这里只给简单说明
int arr2[3][4] = { {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };上面是越界初始化 很容易理解,只有12个格子要存放15个元素就会出现越界初始化。
#include <stdio.h> in main() { int arr[3][4] = {0}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } return 0; }上面是越界访问,访问到3行4列以外的元素。
二维数组通过二重循环嵌套进行访问: 代码演示:
#include <stdio.h> int main() { int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); } printf("\n"); } return 0; }运行结果:
运行结果为:
我们可以看到数组名就代表数组首元素的地址。
运行结果为: 我们可以看到步长为16,我们根据前面讲到的本质,arr[3][4] 本质元素类型是一个int [4] 所以步长为int[4],大小为16字节。
我们先给出二维数组的数组名所表示的范围,然后通过地址进行说明。
解析:
使用下面方式访问二维数组每一行的第一个元素及地址:
#include <stdio.h> int main() { int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; for (int i = 0; i <3; i++) { printf("%d\t", arr[i]); printf("%d\n", arr[i][0]); } return 0; }运行结果为:
代码演示:
#include <stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12}; printf("arr = %p\n", arr); //外层一维数组起始地址 printf("&arr[0] = %p\n", &arr[0]); //外层一维数组第一个元素的地址 printf("arr = %p ,arr+1 = %p\n", arr, arr + 1);//外层一维数组步长 printf("arr[0] = %p\n", arr[0]); //内嵌一维数组起始地址 printf("&arr[0][0] = %p\n", &arr[0][0]); //内嵌一维数组第一个元素的地址 printf("arr[0] = %p arr[0]+1 = %p", arr[0], arr[0] + 1);//内嵌一维数组步长 return 0; }运行结果为:
上面代码中: arr 步长为 int [4] ; 每次跳动16个字节。 地址从 A0 到 B0 相差16个字节。
arr[ 0 ] ; 步长为 int 每次跳动4个字节。 地址从 A0 到 A4 相差4个字节。
那么每次访问一个int[4]大小,再访问一次内嵌数组的下标,可以得到每一行的其他元素。
#include <stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9 }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", *(arr[i] + j)); printf("%p\n", arr[i] + j); } } return 0; }运行结果为:
我们再转变换一种方式:
#include <stdio.h> int main() { int arr[3][4] = {1,2,3,4,5,6,7,8,9}; for (int i = 0; i <3; i++) { for (int j = 0; j < 4; j++) { printf("%d\t", arr[i][j]); printf("%d\n", &arr[i][j]); } } return 0; }运行结果为:
上面就变成了我们常规的访问方式。
在分析完成之后我们对于解析图中的四个相同地址进行验证:
#include <stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9 }; printf("&arr = %d\n", &arr); //二维数组数组名取地址 printf("&arr + 1 = %d\n", &arr + 1); //二维数组数组名取地址 + 1 跨度为整个二维数组的大小 printf("\n======================\n"); printf("arr = %d\n", arr); //数组名代表外部一维数组首元素的地址 printf("arr + 1 = %d\n", arr + 1); //外部一维数组的步长 printf("\n======================\n"); printf("&arr[0] = %d\n", &arr[0]); //外部一维数组首元素的地址 printf("&arr[0] + 1 = %d\n", &arr[0] + 1); //外部一维数组的步长 printf("\n======================\n"); printf("arr[0] = %d\n", arr[0]); //内嵌一维数组的数组名表示一维数组首元素的地址 printf("arr[0] + 1 = %d\n", arr[0] + 1); //内嵌一维数组的步长 printf("\n======================\n"); printf("&arr[0][0] = %d\n", &arr[0][0]); //二维数组的第一行第一列元素的地址 printf("&arr[0][0] + 1 = %d\n", &arr[0][0] + 1); //二维数组的第一行第一列元素的地址 + 1 printf("\n======================\n"); printf("arr[0][0] = %d\n", arr[0][0]); //二维数组的第一行第一列元素数据 printf("arr[0][0] + 1 = %d\n", arr[0][0] + 1); //二维数组的第一行第一列元素数据 + 1 printf("\n======================\n"); return 0; }运行结果为:
我们从引用和解引用的角度进行分析。
#include <stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; printf("%d\n", *((*(arr)+0) + 0)); printf("%d\n", *((*(arr)+1) + 1)); printf("%d\n", *((*(arr)+2) + 2)); printf("%d\n", *((*(arr)+3) + 3)); return 0; }运行结果为:
在二维数组的实战中,对于数组下标的研究非常重要。
执行结果为:
运行结果为:
生成一个 10*10 的棋局,要求初始化为零。 随机置入 10 颗棋子,棋子位置处的值为1,并打印。 若在棋中出现连续三个棋子在一起,包含横行和竖行。 则输出好棋,否则输入臭棋。
#include <stdio.h> #include <stdlib.h> #include <time.h> int main() { srand(time(NULL)); int chess[10][10] = { 0 }; for (int i = 0; i < 10; i++) //打印二维数组 { for (int j = 0; j < 10; j++) { printf("%3d", chess[i][j]); } printf("\n"); } int number = 0; int l; int r; while (1) { r = rand() % 10; l = rand() % 10; if (chess[l][r] == 0) { chess[l][r] = 1; number++; } if (number == 10) break; } printf("\n\n===============================\n\n"); for (int i = 0; i < 10; i++) //打印二维数组 { for (int j = 0; j < 10; j++) { printf("%3d", chess[i][j]); } printf("\n"); } int count = 0; for (int i = 0; i < 10; i++) //统计二维数组中1的个数 { for (int j = 0; j < 10; j++) { if (chess[i][j] == 1) count++; } } printf("==%d==\n", count); int sum; int FlagGoodChess = 0; for (int i = 0; i < 10; i++) //横行扫描 { sum = 0; for (int j = 0; j < 10; j++) { if (chess[i][j] == 1) { sum++; if (sum == 3) { FlagGoodChess = 1; break; } } else { sum = 0; } } if (FlagGoodChess == 1) break; count = 0; //列扫描 sum = 0; for (int j = 0; j < 10; j++) { if (chess[j][i] == 1) { sum++; if (sum == 3) { FlagGoodChess = 1; break; } } else { sum = 0; } } if (FlagGoodChess == 1) break; } if (FlagGoodChess == 1) printf("好棋\n"); else printf("臭棋\n"); return 0; }运行结果为:
合并两个己经有序的数组 A[M], B[N],到另外一个数组 C[M+N]中去,使用另外一个数组依然有序。其中 M 和 N 均是宏常量。
#include <stdio.h> #include <stdlib.h> #define M 5 #define N 3 int main() { int a[M] = { 11,13,15,17,19 }; int b[N] = { 2,4,6}; int c[N + M] = { 0 }; int i = 0; int j = 0; int k = 0; while (i < M && j < N) { if (a[i] <= b[j]) { c[k++] = a[i++]; } else { c[k++] = b[j++]; } } while (i<M) { c[k++] = a[i++]; } while (j < N) { c[k++] = a[j++]; } for (int i = 0; i < N + M; i++) { printf("%d\t", c[i]); } printf("\n"); return 0; }运行结果为:
数组名,是数组的唯一标识符,既表示一种构造数据类型的大小,也表示访问数组中成员的首地址通过地址偏移来访问数据成员使用。
由于结构体成员所占内存大小不同,所以结构体的成员定义与成员访问是分开的,为此增加了成员运算符。而数组名,却是一身而兼二任的,这也是简洁的需要。
一维数组名,从总体来看,代表一种构造类型,同时又承担了访问每个数组元素的责任。 所以数组名, 就有两重性。 数组名是数组的惟一标识符
#include <stdio.h> #include <stdlib.h> int main() { int arr[10] = {5,6,7,8}; //一维数组数组名充当构造类型 printf("sizeof(arr) = %d\n", sizeof(arr)); printf("sizeof(arr) = %d\n", sizeof(int[10])); //一维数组数组名作为成员访问 printf("arr = %p\n", arr); printf("&arr[0] = %p\n", &arr[0]); //数组名访问地址 printf("*(arr + 0) = %d\n", *(arr + 0)); //数组名访问数据 return 0; }运行结果为:
数组名,是数组的唯一标识符。二维数组也是如此,二维数组本质是一种嵌套关系,其本质是一维数组,一维数组的每个成员又是一个一维数组。
#include <stdio.h> #include <stdlib.h> int main() { int arr[3][4] = {1,2,3,4,5,6}; //二维数组数组名充当一种构造类型 printf("sizeof(arr) = %d\n", sizeof(arr)); printf("sizeof(int[3][4])= %d\n", sizeof(int[3][4])); //二维数组数组名作为成员访问 printf("arr = %p\n", arr); printf("&arr[0] = %p\n", &arr[0]); //数组名访问地址 printf("*(arr[0] + 0)= %d\n", *(arr[0] + 0)); //数组名访问数据 return 0; }运行结果为:
到这里我们一维数组和二维数组的基础知识就写,当然,这里可能会比较简单一点,之后当我们把指针和数字结合到一起的时候,就会有一些难度,但是在我们使用指针之后,程序的效率就会更高,而且有时候在操作的时候必须使用指针才能完成。我们这里先给出一个概念,之后详细理解: 一维数组的数组名是一级指针 二维数组的数组名是数组指针
最后我们再来说明一下[ ] 基址变址运算符 A[x] 等价于 * (A + x) 代码演示:
#include <stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,0 }; printf("%d\n", arr[1][1]); printf("%d\n", *((arr+1)[1])); printf("%d\n", *(*(arr + 1)+1)); //数组指针变成了一维数组 return 0; }运行结果为:
二维数组的三要素可以总结为大数组的三要素和小数组的三要素。
数组的三要素不仅能够帮助我们更好的理解,并且在后面函数传参传递数组的时候,要传递数组的三要素。
二维数组的本质就是一维数组对于一维数组的嵌套。
数组名当作整体使用的情况并不多见,使用sizeof(array);的时候表示整个数组。使用 &array+1 的时候跳跃整个数组的大小。更多的情况使用的时候代表的是数组首元素的地址。