多维数组
数组由类型相同的元素构成,将离散的数据聚合起来并建立了迭代基础;二维数组需要表达两个维度,它允许我们以表格的形式组织数据,如:
int tab[3][4]; // 一个存储int型元素,3行4列名为tab的二维数组
这里用二维视图展示数组,便于我们直观地想象具有两个索引的数组。

但事实上,多维数组仍就是以顺序而非表格存储的。表格只是我们给予的便于观察数据的视角。
tab[0][0] tab[0][1] tab[0][2] tab[0][3] tab[1][0] tab[1][1] ... tab[2][2] tab[2][3]
从这个角度讲,二维数组实际上是一种特殊的一维数组,它的每个元素是一个一维数组。数组tab可以看作3个由4个整型组成的数组的数组。理解这个声明时可以先查看中间的部分。
int tab[3][4];
这部分说明tab是一个包含3个元素的数组,至于每个元素的情况,需要查看声明的其余部分。
int tab[3][4];
这说明每个元素的类型是int[4];也就是说,tab具有3个元素,每个元素都是包含4个int数值的数组。
对二维数组的初始化是建立在对一维数组的初始化之上的。针对tab[3][4]的初始化,我们可以假设有3个一维数组,定义如下:
int A[4] = {11, 12, 13, 14};
int B[4] = {21, 22, 23, 24};
int C[4] = {31, 32, 33, 34};
数组tab的元素由则是由这个三个数组构成,使用伪代码表达如下:
int tab[3][4] = { A, B, C };
接入来使用代入法,替换各个元素,则tab的初始化可以表达式,
int tab[3][4] = // 从一维数组角度讲
{ // 初始化以'{'开始
{11, 12, 13, 14}, // 有tab[0]、tab[1]、tab[2]三个元素
{21, 22, 23, 24}, // 元素之间以逗号分隔
{31, 32, 33, 34} // 每个元素包含4个数据
}; // 以'}'完成初始化
对于每个元素的初始化,如果初始化数值的个数少于所需数量,则这些未指定值默认初始化为0。
#include <stdio.h>
int main()
{
int tab[3][4] =
{
{11}, // 相当于:{11, 0, 0, 0}
{21, 22}, // 等价于:{21, 22, 0, 0,}
{31, 32, 33} // 等同于:{31, 32, 33, 0}
};
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 4; c++)
printf("%2d ", tab[r][c]);
printf("\n");
}
return 0;
}

初始化时可以省略内部的花括号,只需保证元素数量的正确性即可。当数量不足时,就按照顺序逐行赋值,前面的元素依次被赋值,后面的元素被初始化为0。
#include <stdio.h>
int main()
{
int a1[2][3] = {{10, 11, 12}, {20, 21, 22}};
int a2[2][3] = {10, 11, 12, 20, 21, 22}; // 等价于a1的初始化
int a3[2][3] = {10, 11, 12, 20}; // 逐行赋值,不够的元素以0初始化。
int tab[3][4] =
{
{11},
{21, 22},
{31, 32, 33}
};
for (int r = 0; r < 2; r++)
{
for (int c = 0; c < 3; c++)
printf("%2d ", a2[r][c]);
printf("\n");
}
printf("\n");
for (int r = 0; r < 2; r++)
{
for (int c = 0; c < 3; c++)
printf("%2d ", a3[r][c]);
printf("\n");
}
return 0;
}

初始化时也可以省略数组的行数维度信息,但列维度的信息必须指定。这时行数将等于数据个数除以列数。
float v[][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // 5行2列
与函数交互
如果将二维数组作为参数传递到函数,那么在函数的参数声明中必须指明数组的列数,而数组的行数则没有任何关系。如果将数组tab作为参数传递给函数f,理论上f的原型应该编写为:
f(int tab[3][4])
回想关于一维数组与函数的交互,数组传递到函数时会丢失掉维度信息。多维数组本质也是一维数组,所以与一维数组关联的维度信息同样会丢失,继而退化为指针形式,但后续的维度(如列数)必须指定,因此f的声明也可写成:
f(int tab[][4]) // ok
f(int tab[][]) // error
这种表达下,函数的实现通常会硬编码行计数。如:
void print_tab(int tab[][4])
{
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 4; c++)
printf("%2d ", tab[r][c]);
printf("\n");
}
}
你可能希望编写一个通用的二维数组访问函数,这时函数需要数组两个维度信息,但是这样的表达并不优雅,而且不能适应其它数组,如:
int arr1[2][3];
int arr2[3][4];
void print_2darray(int arr[][3], int rows, int cols);
由于列维度不能丢失,print_2darray的第一个参数导致数组arr2并不是它所能接收的类型。这也是下面程序中,对于a2的定义,我们不能将其退化为一维数组的原因:函数接收与输入的参数类型不匹配;即不能将a2定义为a[3]={1, 2, 3}。
#include <stdio.h>
void print_2darray(int arr[][3]/*列数固定行数可变的数组*/, int rows)
{
for (int r = 0; r < rows; r++)
{
for (int c = 0; c < 3; c++)
printf("%2d\t", arr[r][c]);
printf("\n");
}
}
int main()
{
int a1[][3] = {10, 11, 12, 20, 21, 22};
int a2[][3] = {1, 2, 3};
print_2darray(a1, 2);
print_2darray(a2, 1);
return 0;
}

更高维数组
2维或2维以上的数组可以称之为多维数组,维数越多理解就越复杂。对于多维数组的描述,使用组的概念会更加清晰,如3维数组arr[2][3][4],我们可以读作:数组arr有2个大组,每个大组包含3个小组,每个小组包含4个元素。因为4后面没有维度了,所以它是元素数了。多维数组中元素的个数就是各个维度相乘的结果,把步骤写出来会更清晰一些。
// #1 arr是一个数组
arr[2][3][4] =
{
};
arr[2][3][4] =
{
// #2 它包含两个大组,组与组之间使用逗号分隔。
{
},
{
}
};
arr[2][3][4] =
{
{
//#3 每个大组包含3个小组
{},
{},
{}
},
{
//#3 每个大组包含3个小组
{},
{},
{}
}
};
arr[2][3][4] =
{
{
{ 1, 2, 3, 4 }, //#4 每个小组包含4个元素。
{ 5, 6, 7, 8 },
{ 9, 0, 1, 2 }
},
{
{ 'a', 'b', 'c', 'd' }, //#4 每个小组包含4个元素。
{ 'e', 'f', 'g', 'h' },
{ 'i', 'j', 'k', 'l' }
}
};
掌握了这个概念之后,那这个4维数组就没有任何难度了。
#include <stdio.h>
int main()
{
int array_c[3][2][2][3] =
{
//==================================
{
{
{ 1, 2, 3 },
{ 3, 4, 5 }
},
{
{ 5, 6, 7 },
{ 7, 8, 9 }
}
},
//==================================
{
{
{ 11, 12, 13 },
{ 13, 14, 15 }
},
{
{ 15, 16, 17 },
{ 17, 18, 19 }
}
},
//==================================
{
{
{ 21, 22, 23 },
{ 23, 24, 25 }
},
{
{ 25, 26, 27 },
{ 27, 28, 29 }
}
}
};
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
for (int k = 0; k < 2; k++)
{
for (int m = 0; m < 3; m++)
printf("%d ", array_c[i][j][k][m]);
printf("\n");
}
}
printf("\n");
}
}
