11. 【C语言】表格与矩阵:多维数组

📅 发布时间:2026/7/5 14:56:26 👁️ 浏览次数:
11. 【C语言】表格与矩阵:多维数组
上一篇文章我们学会了一维数组——它像一排连续的抽屉整齐地排成一条直线。但现实中的数据往往比“一排”更复杂课程表有行有列棋盘有纵横坐标图像是由像素组成的矩形网格。这时候一维数组就有些力不从心了。好在 C 语言允许我们把这些“行”再堆叠起来形成多维数组。二维数组用得最多三维偶尔露脸四维以上更多是理论存在。今天就主要攻克二维再浅探多维让你以后碰到矩阵运算、图像处理、游戏棋盘之类的问题能心中有数。一、二维数组的声明与理解1. 声明方式类型 数组名[行数][列数];比如存储一个班级 3 门课、5 个学生的成绩表intscores[3][5];// 3 行 5 列它对应这样一张表学生0 学生1 学生2 学生3 学生4 课程0: ? ? ? ? ? 课程1: ? ? ? ? ? 课程2: ? ? ? ? ?逻辑上我们把它理解成“行”和“列”但在物理内存中它依然是一维连续排列的。C 语言用的是行优先存储先完整存第 0 行然后紧接着存第 1 行再存第 2 行……像把一张表格一行行拆开拼成一条长链。内存顺序: [0][0] [0][1] [0][2] [0][3] [0][4] [1][0] [1][1] ... [2][4]这个知识点非常重要。因为行优先访问scores[i][j]时编译器实际上计算的是地址偏移 i * 列数 j知道这个原理你以后写遍历循环时就会把行循环放外层、列循环放内层——尽量让内存访问连续能大幅提升性能缓存友好。2. 初始化二维数组完全初始化嵌套花括号intmatrix[2][3]{{1,2,3},{4,5,6}};结果为第0行: 1, 2, 3 第1行: 4, 5, 6部分初始化未指定的自动为 0intmatrix[2][3]{{1,2}// 第0行1, 2, 0第1行全0};省略内层花括号不推荐可读性差intmatrix[2][3]{1,2,3,4,5,6};// 按行优先依次填充省略第一维大小编译器根据初始化列表推断intmatrix[][3]{{1,2,3},{4,5,6}};// 自动推断为 2 行 3 列注意只有最左边的维度可以省略其他维度必须写清楚否则编译器算不出地址偏移公式。二、访问二维数组元素通过数组名[行下标][列下标]访问下标都从 0 开始。intmatrix[2][3]{{10,20,30},{40,50,60}};printf(%d\n,matrix[0][1]);// 20matrix[1][2]99;// 修改第1行第2列遍历二维数组通常用嵌套的for循环#includestdio.hintmain(void){intmatrix[2][3]{{1,2,3},{4,5,6}};for(inti0;i2;i){// 外层遍历行for(intj0;j3;j){// 内层遍历列printf(%d ,matrix[i][j]);}printf(\n);// 每行结束后换行}return0;}输出1 2 3 4 5 6遍历顺序很重要外层是行、内层是列才能让内存访问是连续的先 [0][0]、[0][1]、[0][2]然后 [1][0]…。如果反过来外层列、内层行CPU 缓存会频繁落空在大数据量下性能差距可达数倍。记住一个原则让内层循环对应最右边的下标。三、二维数组作为函数参数把二维数组传给函数时必须告诉编译器除了最左边维度以外的所有维度大小。voidprint_matrix(introws,intcols,intmat[][cols]){for(inti0;irows;i){for(intj0;jcols;j){printf(%d ,mat[i][j]);}printf(\n);}}注意参数int mat[][cols]最左边的维度行可以省略写成空的[]。列的大小必须给定因为编译器需要它来计算i * cols j的偏移。调用时intmatrix[2][3]{{1,2,3},{4,5,6}};print_matrix(2,3,matrix);四、经典案例矩阵运算矩阵加法是最直观的二维数组应用——两个相同大小的矩阵对应元素相加。#includestdio.h#defineROWS2#defineCOLS3voidadd_matrices(inta[ROWS][COLS],intb[ROWS][COLS],intresult[ROWS][COLS]){for(inti0;iROWS;i){for(intj0;jCOLS;j){result[i][j]a[i][j]b[i][j];}}}voidprint_matrix(introws,intcols,intmat[][cols]){for(inti0;irows;i){for(intj0;jcols;j){printf(%3d ,mat[i][j]);}printf(\n);}}intmain(void){intA[ROWS][COLS]{{1,2,3},{4,5,6}};intB[ROWS][COLS]{{6,5,4},{3,2,1}};intC[ROWS][COLS]{0};add_matrices(A,B,C);printf(矩阵 A:\n);print_matrix(ROWS,COLS,A);printf(\n矩阵 B:\n);print_matrix(ROWS,COLS,B);printf(\nA B \n);print_matrix(ROWS,COLS,C);return0;}输出矩阵 A: 1 2 3 4 5 6 矩阵 B: 6 5 4 3 2 1 A B 7 7 7 7 7 7%3d让每个数字占 3 个字符宽度排版整齐。二维数组在数值计算、图像处理中无处不在——图像本身就是一个像素值的二维数组核心操作几乎都是对行列的遍历。五、多维数组三维及更高二维之上可以扩展出三维、四维乃至更高维度的数组。三维数组可以理解为“多页表格”——每一“页”是一个二维矩阵。// 2页每页3行4列intcube[2][3][4]{{// 第0页{1,2,3,4},{5,6,7,8},{9,10,11,12}},{// 第1页{13,14,15,16},{17,18,19,20},{21,22,23,24}}};遍历三重循环for(intp0;p2;p){for(inti0;i3;i){for(intj0;j4;j){printf(%2d ,cube[p][i][j]);}printf(\n);}printf(---\n);}更高维度同理层层嵌套。但维度越高对内存的消耗呈指数增长实际开发中很少超过三维。保持对高维数组“层层嵌套”的感知即可。六、字符串数组与命令行参数预告二维char数组的一个典型用途是存储多个字符串charnames[3][20]{Alice,Bob,Charlie};这里names[0]是一个长度为 20 的字符数组存放Alice带\0结尾。后面我们讲到字符串、指针数组时会深入这个主题包括argv命令行参数的本质。七、常见错误与陷阱下标顺序搞反intmat[3][5];mat[0][1]10;// 正确mat[1][0]20;// 正确// 不要写成 mat[1,0]那是逗号运算符结果是 0给二维数组赋值忘记列下标intmat[2][3];mat[0]{1,2,3};// 错误数组名不能赋值只能逐个元素赋值或者在声明时整体初始化。传递二维数组时漏写列数voidfunc(intmat[][])// 错误必须指定列数voidfunc(intmat[3][])// 也错列数必须给出voidfunc(intmat[][5])// 正确把sizeof用在函数参数里的二维数组voidfunc(intmat[][5]){introwssizeof(mat)/sizeof(mat[0]);// 错误mat 是指针}和一维数组一样函数参数中的数组名退化为指针sizeof失效。遍历时行列循环写反导致性能骤降内层循环应遍历最右边的下标保证内存访问连续。八、小结今天你把一维的“队伍”扩展成了二维的“矩阵”还学会了更高维度的基本概念。二维数组是图像处理、游戏开发、科学计算中的核心数据结构矩阵的加法、乘法、转置都建立在它之上。你学到的“行优先存储”和遍历原则以后也会直接影响程序的运行速度。现在我们已经能管理大量的数据了但代码依然堆在main函数里。当程序逐渐庞大单靠main是远远不够的——你需要把功能拆分成可复用的模块。下一篇文章我们就进入函数的世界如何把代码封装成函数如何传递参数如何获得返回值。它会让你的代码从“长篇大论”进化成“团队协作”。课后小练习声明一个 3×3 的矩阵初始化为 1 到 9按行递增。打印出它的转置矩阵行列互换。实现两个 3×3 矩阵的乘法并打印结果。矩阵乘法的规则C[i][j] sum(A[i][k] * B[k][j])k 从 0 到 2。用二维数组打印一个 5×5 的单位矩阵对角线为 1其余为 0。小挑战输入一个 3×3 的整数方阵判断它是否为幻方即每行、每列、两条对角线的和都相等。输出判断结果。我们下期见获取本系列示例代码请访问 GitCode 仓库。