C语言二维数组
一、示例int matrix[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };二、初始化三、数组越界四、数组作为函数参数五、二维数组的使用六、二维数组在内存中的存储二维数组在内存中也是连续存储的。它遵循“行优先”的原则即先存储第一行的所有元素然后是第二行的所有元素依此类推。七、注意事项数组名是地址数组名本身代表数组首元素的地址即arr[0]。它是一个指针常量不能被修改例如arr some_other_address;是非法的。数组不能整体赋值/拷贝你不能像arr1 arr2;这样直接将一个数组赋值给另一个数组。必须使用循环或memcpy等函数逐个元素复制。数组大小sizeof(数组名)可以获取整个数组占用的字节数。对于静态数组可以用sizeof(整个数组) / sizeof(数组中一个元素)来计算元素个数但这只在数组定义的作用域内有效不能用于函数参数中的数组。八、二维数组的指针1. 二维数组的内存布局int matrix[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };这个matrix在内存中是连续存放的1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12。每一行matrix[0],matrix[1],matrix[2]都可以看作是一个包含4个int的一维数组。2. 二维数组名的衰变当二维数组名matrix出现在表达式中时它会衰变为指向其第一行即一个包含4个int的数组的指针。matrix的类型是int [3][4](一个包含3行4列的二维数组)。当作为值使用时matrix的值是matrix[0]其类型是int (*)[4]。这个类型读作“指向包含4个int元素的数组的指针”。3. 声明和使用指向二维数组行的指针我们可以显式地声明一个与二维数组名衰变后类型相同的指针。示例代码#include stdio.h int main() { int matrix[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; // pm 是一个指向包含4个int元素的数组的指针 int (*pm)[4] matrix; // 等价于 int (*pm)[4] matrix[0]; printf(二维数组的地址 (matrix): %p\n, (void*)matrix); printf(pm 指向的地址: %p\n, (void*)pm); printf(matrix[0] 的地址 (matrix[0]): %p\n, (void*)matrix[0]); printf(pm 的大小 (指针大小): %zu 字节\n, sizeof(pm)); printf((*pm) 的大小 (一行数组的大小): %zu 字节\n, sizeof(*pm)); // 4 * sizeof(int) // 如何访问元素 // pm 指向第一行 {1, 2, 3, 4} // *(pm 0) 就是第一行 {1, 2, 3, 4}类型是 int[4] // *(pm 1) 就是第二行 {5, 6, 7, 8}类型是 int[4] // *(pm i) 就是第 i 行类型是 int[4] // (*(pm i))[j] 就是第 i 行第 j 列的元素 // pm[i][j] 是 *(pm i)[j] 的简写形式与二维数组的访问语法一致 printf(\n通过 pm[i][j] 访问:\n); for (int i 0; i 3; i) { for (int j 0; j 4; j) { printf(%d , pm[i][j]); // 等价于 (*(pm i))[j] } printf(\n); } // 指针算术 pm; // pm 现在指向第二行 (matrix[1]) printf(\npm 后pm 指向: %p\n, (void*)pm); printf(此时 pm[0][0] %d\n, pm[0][0]); // pm[0][0] 等价于 matrix[1][0]输出 5 printf(此时 pm[0][1] %d\n, pm[0][1]); // 输出 6 return 0; }int (*pm)[4] matrix;pm被初始化为指向matrix的第一行。sizeof(*pm)返回*pm即pm指向的整个行数组的大小这里是4 * sizeof(int)。pm[i][j]这是访问二维数组的标准方式。pm i计算出第i行的地址*(pm i)解引用得到第i行这个一维数组最后[j]访问该行的第j个元素。pm指针pm的步长是它所指向的对象的大小即一行数组的大小4 * sizeof(int)个字节。因此pm会让pm直接跳到下一行的起始地址。4. 二维数组与函数参数这是二维数组指针最重要的应用场景之一。当我们将二维数组传递给函数时数组的第一维大小可以省略但第二维列数必须指定。这是因为函数需要知道每一行有多少个元素才能正确地进行地址计算。函数定义#include stdio.h // 方式1: 使用指针语法明确表示指向行的指针 void printMatrix1(int rows, int (*matrix)[4]) { // 列数必须指定 printf(在 printMatrix1 中matrix 的类型是 int(*)[4]\n); for (int i 0; i rows; i) { for (int j 0; j 4; j) { printf(%d , matrix[i][j]); // matrix[i][j] 等价于 *(*(matrix i) j) } printf(\n); } } // 方式2: 使用数组语法效果相同更易读 void printMatrix2(int rows, int matrix[][4]) { // 列数必须指定 printf(在 printMatrix2 中matrix 的类型也是 int(*)[4]\n); for (int i 0; i rows; i) { for (int j 0; j 4; j) { printf(%d , matrix[i][j]); } printf(\n); } } // 方式3: 使用变长数组 (VLA) - C99 标准引入 void printMatrix3(int rows, int cols, int matrix[rows][cols]) { // 列数可以是变量 printf(在 printMatrix3 中使用 VLA\n); for (int i 0; i rows; i) { for (int j 0; j cols; j) { printf(%d , matrix[i][j]); } printf(\n); } } int main() { int my_matrix[3][4] { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int rows 3, cols 4; printMatrix1(rows, my_matrix); // 传递二维数组名 printf(---\n); printMatrix2(rows, my_matrix); printf(---\n); printMatrix3(rows, cols, my_matrix); return 0; }关键点列数必须指定无论是int (*matrix)[4]还是int matrix[][4]方括号中的4都不能省略。这告诉编译器如何计算matrix[i]的地址即第i行的地址。第一维可以省略int matrix[][4]和int matrix[3][4]在函数参数中是等价的都表示一个指向包含4个int的数组的指针 (int (*)[4])。为什么这样设计因为二维数组在内存中是连续的编译器需要知道每行的宽度列数才能从第0行的地址计算出第1行、第2行...的地址。5. 总结二维数组名arr在传递给函数或赋值给指针时会衰变为指向其第一行的指针类型为int (*)[列数]。声明这种指针的语法是数据类型 (*指针名)[列数]。这种指针p支持p[i][j]的访问方式其原理是*(*(p i) j)。指针算术p的步长是一行数组的大小。在函数参数中二维数组必须指定列数这是实现arr[i][j]随机访问的关键。