最近看C++编程思想,看到第十三章动态内存管理的时候把自己给绕进去了,主要是在数据和指针这块弄混了。现在把找到的一些资料总结如下:
1. 数组是数组,指针是指针,两者并不等价;
2.数组在作为左值的时候一般是数组自己,而放在右值往往被转换成一个常量指针;
3.下标运算符其实是针对指针的,只是数组变成指针后也适用于了数组;
4.对数组取地址符在很早以前是错误的,因为数组名本身就是数组的地址,对地址去地址显然不对,后来被C++标准定为合法的,表示指向数组对象的指针。
对于 int a[100]; int b[10][100];
1. a表示数组本身,放在右值时是数组第一个元素的地址,其类型是&a[0],因此可以有*(a+1); a[i],但是如果是sizeof(a),则为数组的大小100*4。
2. &a的类型是int (*)[100],因此&a + 1得到的地址是a的地址加上整个数组的大小400,但是sizeof(&a)得到的是a的大小400。
3.下标符只能由指针使用,除非是重载,p[1] 等价于*(p+1),在使用二维数组可能会陷入混乱,例如:
int **ptr = (int **)&b;
ptr[i]的值类型是指针(int *),但是其值是b的元素值(按照内存中的位置顺序取),如果使用ptr[1][0]很可能是未定义的,因为这时是取了b的元素并把它作为指针再取一次值,除非b的元素本来就是指针。
4. a+1的类型是指向a的行元素的指针,对它取值*(a+1)得到的类型是a的行元素的类型。这里是int型指针,和int型的值。
如果是b的话,b+1的值和*(b+1)的值相同,但是类型不同,b+1表示的是指向数组的普通指针,而*(b+1)表示的是类型为int [100]的数组。
因此sizeof(b+1)=4, 而sizeof(*(b+1)) = 400,但是两者的值都是相同的地址值。
资料:
C语言中,数组和指针都有a[N]取元素的语法,因此也是初学者容易混淆的地方。这个问题要知其所以然并不容易。理解这个问题,需要涉及到C语言中最基本但也最容易忽略的概念:左值/右值 (lvalue/rvalue)。
在开始这个问题之前,首先要区别“变量”和“值”(/对象)是两个不同的概念。“变量”是一块有名字的存储区域,“值”是某种类型的二进制数据,可能存储在某段内存,或者在寄存器、或者是代码中的字面量。“变量”是对某个值起一个名字,这样可以索引到值的存储位置。
C语言中,表达式(expression)的结果是一个“值”。单独的一个变量名(如a)或者单独的字面量(如1)也是表达式。左值/右值 是值的一个基本属性,C语言中非左值即右值。左值表达式表示了某个值的存储位置,右值表达式表示某个值所存储的数据。
左值性的具体讨论请参看参考链接1。一般来说,赋值操作等号左边是(可修改的)左值,右边取右值。所以
int i,j;
i = 0; // OK: i is lvalue, 1 is rvalue
0 = i; // error: 0 is not lvalue
i = j; // OK. why?
为什么可以写 i = j 这样的赋值操作呢?因为C语言中有一个叫做lvalue-to-rvalue conversion的默认类型转换。当赋值操作右边是一个左值时,会默认转换为右值。这样的一个转换代表了对左值表达式 j 所存储内容的一次“读取”。类似的,C语言中按值传参,用左值表达式作为函数参数时,也会进行 lvalue-to-rvalue conversion。
void foo(int);
foo(0); // OK, need rvalue
foo(i); // OK. lvalue-to-rvalue conversion
讲到这里,我们切入正题:数组、指针是左值吗?答案是当然,因为他们都代表了一个存储位置。不同的是,数组a作为变量,所代表的位置是数组元素的存储位置;指针a作为值(右值),是一个内存地址,而作为变量(左值),所代表的位置是这个地址的存储位置。数组是一个不可修改的左值,指针如果没有指定const,则是可以修改的。
int a[4];
int *p, i, j;
a = 1; // error
p = &i; // OK
p = &(a[1]); // OK
数组和指针的存储区域如图所示(仅为示意,并不代表实际的内存layout)
理解了这个概念,“取地址”怎么用的问题就很显然了。“取地址”的操作就是对于一个左值表达式,取出其表示的内存位置。(仅为示意,并不代表实际的内存layout)
&a; // 0xa000 类型为int (*) [4],数组的地址
&(a[1]); // 0xa004 类型为 int*,某个整型元素的地址
&p; // 0xa010 类型为 int**,指针的地址
现在问题来了,挖掘机..哦不,数组,为什么可以赋值给指针?
p = a; // OK. why?
在C语言中,对于数组类型的左值,lvalue-to-rvalue conversion有一个特例,叫做array-to-pointer conversion(类似的还有function-to-pointer conversion)。在需要右值的场合,如果给出一个数组名,那么数组名会被转换为其首个元素的地址,类型为指向元素的指针。
void foo(int *p);
foo(a); // OK. array-to-pointer conversion
int **q = &a; // warning: initialization from incompatible pointer type
在需要左值的场合,a仍然是数组类型,所以&a的类型是指向数组的指针,而非指向元素的指针!
那么,下标运算符[]是什么?为什么数组和指针兼容下标运算?下标运算符的左侧表达式,是左值还是右值?稍稍想一下会发现,原来下标运算符只需要一个指针类型的右值即可。
int k = a[1]; // equivalent to *(a+1)
int **q;
(*q)[2]; // OK. *q is rvalue of int*
那么结论就很清楚了,在下标表达式中,数组会通过array-to-pointer conversion转换为指针右值,而指针类型,无论左值右值,都可以转换为指针右值。所以下标操作符自古以来就是为指针准备的,数组只是通过内置转换,捎带着实现了下标运算。这个结论,是否有些让人震惊呢?
参考链接: