前言
最近再刷《剑指Offer》的面试题,其中遇到有一题跟二维数组相关,所以需要这样形式的函数,void func(int** matrix, int rows, int columns)
,那么问题来了,再调用这个函数的时候,应该传入怎样的实参呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void func(int** matrix, int rows, int columns) { cout << matrix[rows-1][columns-1] << endl; }
int matrix[2][2] = {1,2,3,4}; func(matrix, 2, 2);
int** matrix = new int*[2]; for (int i=0; i < 2; i++) { matrix[i] = new int[2]; for (int j=0; j < 2; j++) { matrix[i][j] = i*2 + j + 1; } } func(matrix, 2, 2);
|
踩坑过程
其实上面的答案很简单,直接敲代码测试一下,就知道第一种传参方式编译器直接报错,提示你类型不匹配。但是我头铁啊,愣是用强制转换 func((int**)matrix, 2, 2)
的方式,然后就是程序崩溃报错了。
这是为什么呢?现在来理性分析一波,以下面的测试代码来进行说明。
首先matrix
是一个二维数组,它(数组名)对应的指针类型实际上是 int(*)[2]
,即指向数组的指针(有人称作是行数组指针),也就是说如果把matrix
看成是一个指针的话,指向的类型是一个长度为2的int数组,因此在对matrix
执行指针加法(matrix+1)
的时候,一次移动的字节数为一个数组的长度即 2*4 = 8 字节。那么执行解引用时, matrix[1]
得到是第2行一维数组的首地址,其类型就是一维数组。同时一维数组名对应指针类型为int *
,因此再对 matrix[1]
执行指针加法操作(matrix[1] + 1)
时,一次移动的字节数为一个int类型的字节长度。
其次对于二级指针(指针的指针)p
来说,因为p
指向的是一个int *
指针,所以 p
执行指针加法的时候,一次移动的长度为 int *
指针类型的字节数(即8字节),那么解引用得到数据类型也就是 int *
,所以p[0]
得到的元素就是一个int *
指针,打印 p[0]
的值就是打印该指针的值,而指针的值就是一个地址。为什么这里打印出来的值这么怪异呢?因为它把matrix
二维矩阵存储的int值当做指针的值来进行解析了,例如3 和 5 两个int值合起来为8字节,这个8个字节被当做地址的值而不是元素int的值,再加上我的电脑是小端(即低字节在低地址),最后打印出来的 p[0]
就是0x500000003
了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int matrix[2][2] = {3,5,7,8}; cout << "matrix address: " << matrix << endl; cout << "matrix[0] address: " << matrix[0] << endl; cout << "matrix+1 address: " << matrix+1 << endl; cout << "matrix[1] address: " << matrix[1] << endl;
int** p = (int**) matrix; cout << "p address: " << p << endl; cout << "p[0] address: " << p[0] << endl; cout << "p+1 address: " << p+1 << endl; cout << "p[1] address: " << p[1] << endl;
int ** p_matrix = new int*[2]; p_matrix[0] = new int[2]; p_matrix[1] = new int[2]; p_matrix[0][0] = 3; p_matrix[0][1] = 5; p_matrix[1][0] = 7; p_matrix[1][1] = 8; cout << "p_matrix address: " << p_matrix << endl; cout << "p_matrix[0] address: " << p_matrix[0] << endl; cout << "p_matrix+1 address: " << p_matrix+1 << endl; cout << "p_matrix[1] address: " << p_matrix[1] << endl;
|
啰里啰嗦说了这么多,可能还是没解释清楚,反而把自己绕晕了,所以再用下面这张图解释一下。
弄清楚内存中数据分布后,现在应该知道为什么不能直接把二维数组matrix直接传给二级指针了吧。不过还有一个点,可能还是比较令人感到疑惑的,就是为什么matrix+1
和 matrix[1]
打印出来的值是相同的?
我是这么理解的(如果不对,还请在评论区中指出):因为matrix 类型时 int(*)[2]
,是一个指向一维数组的指针,因此解引用得到的应该就是个数组,所以可以认为 matrix[1]
就是个一维数组,而一维数组名不就是数组首元素的地址嘛,因此我觉得编译器在对 matrix+1
这个地址解引用取值的时候,并没有像 二级指针 int** p
那样去取内存里的值作为地址值,而是直接将当前的地址值作为数组名的地址值反悔了。所以最终 matrix+1
和 matrix[1]
打印得到的结果相同。
其实还遇到了另一种比较奇怪的形式,见下面的函数。我试了好几种传参方式,但总是编译通过,编译器一直提示我类型不匹配,告诉我形参matrix
的真实类型为int(*)[columns]
,这就和尴尬,因为columns
是个变量,我怎么传一个数组长度为变量的数组指针?
我们都知道C++里,数组的长度是一个固定值,那么这里的func2
为什么可以通过编译?更痛苦的是我还无法传参。。。
1 2 3 4
| void func2(int rows, int columns, int matrix[rows][columns]) { cout << matrix[rows-1][columns-1]; }
|
后来我在stackoverflow上提了这个问题(结果标记为 模板参数自动推导 的问题,但我感觉还是不太一样),有人回复我说,这种函数定义方式在C里面是有效的,但在C++里是不合法,所以这不是如何传参的问题,而是这样的写法在C++标准里是不允许的。虽然感觉还是没说明白为什么编译可行,但是姑且认为是C++标准不允许这样的写法,所以导致没法传参。
结论
最后总结一下,二维数组是不能直接赋值给二级指针的,二维数组对应的指针类型是一个数组指针,下面表格给出数组和指针的参数匹配。
实参 | 例子 | 所匹配的形参 |
---|
二维数组 | char c[4][3] | char (*c)[3] |
指针数组 | char * c[4] | char ** c |
数组指针 | char (*c)[3] | char (*c)[3] |
二级指针 | char ** c | char ** c |
然后再给出几种正确的传递二维数组的姿势。
1.形参使用二级指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void func(int **matrix, int rows, int columns) { cout << matrix[rows - 1][columns - 1]; } int main() { int rows = 2, columns = 2; int **matrix = new int *[rows]; for (int i = 0; i < rows; i++) { matrix[i] = new int[columns]; for (int j = 0; j < columns; j++) { matrix[i][j] = i * columns + j + 1; } } func(matrix,rows, columns); for (int i = 0; i<rows;i++) { delete[] matrix[i]; } delete[] matrix; }
|
2.形参使用一级指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void func(int *matrix, int rows, int columns) { cout << matrix[(rows - 1) * columns + (columns - 1)]; }
int main() { int rows = 2, columns = 2; int *matrix = new int[rows * columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { matrix[i * columns + j] = i * columns + j + 1; } } func(matrix, rows, columns); }
|
3.形参使用数组指针
采用这种方式,需要形参指定二维数组的列数
1 2 3 4 5 6 7 8 9 10
| void func(int (*matrix)[2], int rows, int columns) { cout << matrix[rows-1][columns-1]; }
int main() { int rows = 2, columns = 2; int matrix[2][2] = {1,2,3,4}; func(matrix, rows, columns); }
|
4.形参使用指针数组
采用这种方式,需要形参指定二维数组的行数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void func(int* matrix[3], int rows, int columns) { cout << matrix[rows - 1][columns - 1]; } int main() { int rows = 3, columns = 2; int *matrix[rows]; for (int i = 0; i < rows; i++) { matrix[i] = new int[columns]; for (int j = 0; j < columns; j++) { matrix[i][j] = i * columns + j; } } func(matrix, rows, columns); for (int i = 0; i < rows; i++) { delete[] matrix[i]; } }
|
5.形参使用 vector 容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void func(vector<vector<int>>& matrix, int rows, int columns) { cout << matrix[rows - 1][columns - 1]; }
int main() { int rows = 3, columns = 2; vector<vector<int>> matrix(rows); for (int i=0;i<rows;i++) { for (int j=0;j<columns;j++) { matrix[i].push_back(i * columns + j + 1); } } func(matrix, rows, columns); }
|