8. 多维数组:二维数组、矩阵操作、数组作为函数参数
说实话,很多初学者一听到「多维数组」就觉得头大。我当年刚学C++时也这样——总觉得一维数组已经够用了,干嘛还要搞个二维的?直到我在项目中遇到一个实际问题:要处理一张灰度图像,每个像素点都有行和列两个坐标。这时候我才明白,二维数组不是花架子,它是实实在在的工程工具。
这一章,我们就来聊聊二维数组。说白了,它就是「数组的数组」。你把它想象成一个表格或者棋盘,就很好理解了。
8.1 二维数组的声明与初始化
先看最基本的声明方式:
// 声明一个3行4列的二维数组
int matrix[3][4];
// 声明并初始化
int grid[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 也可以全部写在一行里
int arr[2][3] = {1, 2, 3, 4, 5, 6};
嗯,这里要注意:行和列的概念容易搞混。我个人习惯把第一个维度叫「行」,第二个叫「列」。你想想看,matrix[行][列],是不是跟数学里的矩阵写法一致?
内存布局小知识:C++里的二维数组是行优先存储的。也就是说,先存完第一行的所有列,再存第二行。这个特性在你做性能优化时会很有用。
8.2 矩阵操作实战
我在项目中做过一个简单的图像滤镜程序,核心就是矩阵运算。下面这个例子,咱们实现一个矩阵转置:
#include <iostream>
using namespace std;
int main() {
int matrix[3][2] = {
{1, 2},
{3, 4},
{5, 6}
};
int transposed[2][3] = {0}; // 转置后是2行3列
// 转置操作
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
transposed[j][i] = matrix[i][j];
}
}
// 输出转置结果
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
cout << transposed[i][j] << " ";
}
cout << endl;
}
return 0;
}
输出结果:
1 3 5
2 4 6
你看,原来3行2列的矩阵,变成了2行3列。行和列互换了位置。
小技巧:写矩阵操作时,我习惯把外层循环写成行,内层循环写成列。这样代码读起来更直观,不容易出错。
8.3 数组作为函数参数
这是很多新手容易踩坑的地方。我曾经在面试时被问到:「C++里数组作为函数参数,到底传的是什么?」
答案是:传的是指针。数组名本质上就是指向第一个元素的指针。
// 错误的写法:试图直接传二维数组
void printMatrix(int matrix[][], int rows) { // 编译错误!
// ...
}
// 正确的写法:必须指定列数
void printMatrix(int matrix[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
// 或者用指针的指针
void printMatrix2(int (*matrix)[4], int rows) {
// 用法同上
}
我曾经踩过的坑:有一次我写了一个函数,想接收任意大小的二维数组。我用了 int** 作为参数类型,结果传进去后访问 matrix[i][j] 直接段错误。后来才明白,int** 和 int[][4] 在内存布局上完全不同。二维数组是连续内存块,而指针的指针是分散的。
8.4 动态二维数组
如果数组大小在编译时不确定,怎么办?我推荐用 vector 或者动态分配:
#include <iostream>
#include <vector>
using namespace std;
int main() {
int rows = 3, cols = 4;
// 方法1:使用vector(推荐)
vector<vector<int>> mat(rows, vector<int>(cols, 0));
// 方法2:动态分配(C风格)
int** dynMat = new int*[rows];
for (int i = 0; i < rows; i++) {
dynMat[i] = new int[cols];
}
// 使用...
// 别忘了释放内存!
for (int i = 0; i < rows; i++) {
delete[] dynMat[i];
}
delete[] dynMat;
return 0;
}
我个人强烈建议用 vector。为什么?因为它自动管理内存,不会出现内存泄漏。你想想看,如果代码里到处都是 new 和 delete,多容易出 bug 啊。
8.5 知识体系总览
下面这张图,帮你理清本章的核心脉络:
8.6 避坑指南
最后,我总结几个常见的坑,你写代码时多留个心眼:
- 越界访问:二维数组的索引从0开始,访问
matrix[rows][cols]时,最大行号是 rows-1,最大列号是 cols-1。我曾经在循环里写成了<=,结果数组越界,程序崩溃得莫名其妙。 - 函数传参忘记列数:编译器需要知道每行有多少个元素,才能正确计算偏移量。所以
int matrix[][]是错的,必须写成int matrix[][4]。 - 动态内存忘记释放:用
new分配的二维数组,一定要用delete[]释放。而且释放顺序要和分配顺序相反——先释放每一行,再释放行指针数组。
好了,二维数组的内容就聊到这儿。说白了,它就是一张表格,行和列的概念搞清楚了,剩下的就是多写多练。你试试自己实现一个矩阵乘法,或者用二维数组存一张小图片的像素值,动手做一遍比看十遍都管用。