这个是C/C++中最容易混淆,最容易头晕的一个话题。
我们先从一个简单的例子看起(一维数组)
void f(char* buf); | void f(char* buf);
int main(...){ | int main(...){
char buf="abc"; | char* pbuf="abc";
f(buf); | f(pbuf); ->相同的生成代码
buf[2]='x'; | pbuf[2]='x' ->不同的生成代码
上面这两个程序有区别吗? 答案是:
(1)对于一维数组的处理,传递参数的地时候统统作为指针来看待,也就是f(buf)的调用被编译器等效成了
char* pbuf="abc",f(pbuf)这样的调用。
(2)对于寻址和赋值:
buf[2] 是编译器计算(buf的地址+2),放入x
pbuf[2]是编译器计算pbuf的地址,得到pbuf中的值,再以这个值为基地址,+2,放入x
也就是说,pbuf的赋值语句是2次跳转的,效率比不上buf[2]这样的语句。
--------------------------------------------------------------
考虑复杂一点的情况,多维数组怎么办?
int main(...){
int buf[2][3];//这个buf数组在内存中仍然是1维连续内存!
那么buf[10][10]=6;这样的语句是如何计算的呢? buf的结构被看成一个矩阵被拉直为行向量的表示,10行10列,buf[1][2]的地址就是:
第二行的起始(1*10)+第3个元素的偏移(2),等效于((int*)(buf))[12]。
这样说很清楚了吧,如果我们要把buf传递给一个函数作为参数,怎么办呢? 只需要保证编译器能看出,这个被拉直的,2维数组,每一行多少个元素:
void f(int buf[][10]){
buf[1][2]=6;//编译器能够通过f的形式参数声明来决定buf[1][2]是从buf偏移多少。
...
上面这个声明和void f(int buf[10][10])甚至void f(int buf[20][10])是等效的。因为我们只需要知道每行包括多少个元素,至于有多少行,(数组多大),不是编译器控制的,是程序元的责任。
--------------------------------------------------------------
如果f的声明是f(int buf[][])呢? 它等效于f(int *buf[])或者f(int ** ppbuf)这样的声明,传入参数必须是一个真正的2维数组。像下面这样
int** buf=new int*[10];
for(int i=0;i<10;++i)buf[i]=new int[10];
f(buf);
buf数组本身必须是一个指针数组,buf[1][2]这样的计算是:
(a)计算buf[1]的值
(b)这个值是一个地址,指向一个数组,取这个数组的偏移量2中的值。
如果我混用f(int buf[][10])和f(int buf[][]),我就会得到一个编译警告或者错误:
void f2(int ppi[][2]){}
void f3(int *ppi[2]){}
int p2[3][2]={ {1,2},{3,4}, };
f2(p2);正确的用法
f3(p2);警告:传递参数 1 (属于 ‘f3’)时在不兼容的指针类型间转换。
由于f3的生成代码是2次跳转,因此传入p2作为参数的时候,会把一个真正的数组元素的值作为地址看待,再次计算一个内存地址偏移量中的值,可能导致程序崩溃。
参考文献:《C专家编程》