void Func1(int *p)
{
}
void Func2(int a[])
{
}
int main()
{
int a[10];
Func1(a);
int *p;
Func2(p);
}
即一维数组与指针作为函数参数的互换使用, 我们可以一眼看出这个程序是可以通过编译的.
a是数组a[10]第一个元素的地址, 作为参数传递到函数中去, 接收方是*p, 在函数中可以使用*p, 或者p[0]来访问元素. 看起来数组和指针可以这样互换着使用, 编译器会把它们编译成一样的东西.
问题的提出:
再来看下面的代码, 为什么这些代码会报错?
void Func1(int (*p)[10])
{
}
void Func2(int (*p)[])
{
}
void Func3(int **p)
{
}
int main()
{
int a[10][10];
int **p;
Func1(a); //Pass
Func2(a); //Error1
Func3(a); //Error2
Func1(p); //Error3
Func2(p); //Error4
Func3(p); //Pass
}
分析:
当把a作为参数传给函数时候, a代表的是"指向数组的指针", 该数组一共有10个int型的元素. a本身确实是一个指针, 但是这个指针是指向数组的, 不是指向"数组的首元素".
接收方也需要是此类型, int (*p)[10]表示p这个指针是指向数组, 该数组有10个int元素, 跟传入参数形式一致, 因此pass.
Error1和Error2自然就是因为不匹配.
当把p作为参数传给函数的时候, p代表的是"指向指针的指针", 也叫做双重指针, 所以接收的时候int **p类型相匹配, pass.
小结:
一维数组和指针可以互换使用, 这是由于编译器帮我们规定了数组名=首元素的地址, 我们才能够这样用.
因此不要以为二维数组和双重指针也可以用类似的方法来互换使用.
这当然不会是本文的最终目的. 我要说明的是, 当我们非得将**p和a[][]这样的形式混用时, 我们应该怎么做.
作为参数时:
看下面的程序:
void Func4(int *p[])
{
}
int main()
{
int **p;
Func4(p); //Pass
}
这次我们可以将**p送给*p[], 原因在于: *p[]表示一个指针数组, p就是这个指针数组的数组名, 也就是第一个元素(这个元素是指针)的地址, 说白了, 就是指针的地址. 当然, 作为形式参数, p这里就是一个变量, 即从"地址", 变成了"指针"(变量), 所以送过来的p是双重指针, 接收的p还是双重指针, 当然可以编译通过, 这又是编译器玩的一个花样. 可以看出, 这个过程其实我们还是没有摆脱上面所说的, 编译器帮我们规定了数组名=首元素的地址.
在函数内使用时:
看下面的程序:
#include <iostream>
using namespace std;
void Func1(int **c, int a, int b)
{
cout<<c[0][0]<<endl;
cout<<c[1][0]<<endl;
}
void main ()
{
int i;
int *a = new int[2*3];
int **p = &a;
for(i=0; i<2*3; i++) {
a[i] = i;
}
Func1(p, 2, 3);
}
编译运行, 如果你像我一样运气好的话, 程序不会崩溃, 你会得到一组数字:
0
3436320
如果定义a[2][3]这样的二维数组, 我们在使用的时候编译器会将a[x][y]采用形如p+3*x+y的寻址方式, 因为编译器知道我们的一行是3个元素.
在Func1函数里, 传入的是双重指针, 接收的是**p也是双重指针, 可是使用的时候呢? 上面的是错误的示例, 因为在函数内部编译器不知道你这个数组是几行几列的.
它没法将c[x][y]转换成p+3*x+y的寻址方式. 所以我需要同时传入两个额外的参数a和b来表示数组的行列, 在使用的时候我就可以手动将c[x][y]转换成p+a*x+y, 再加上取值符号就可以取出正确的值: *(p+a*x+y)
实际上如果你使用c[x][y]这样的形式(如上面的程序), 编译器不会报错, 但是你运行的时候, 就有很大的几率崩溃, 因为编译器将行列错误的进行了解析.
这时编译器是这样编译的:
它取出c[x]作为行的首地址, 再加上y.
由于c[0]一定是传入数组的首地址, 所以c[0][y]这样的访问都是OK的, 只要你不越界.
c[1]我们就不知道是个什么东西了, c[1][y]这样的访问就是一个随机数, 当访问到无法访问的地方时, 程序就会崩溃.
最后的总结:
1. 一维数组和指针可以比较简单的互换使用
2. 二维数组和双重指针尽量不要互换使用, 除非你真的知道这些底层的编译原理, 并且有十分的把握