指针的指针
我们都知道,在C语言中声明一个变量,可以用同类型的指针指向原先那个变量,指针保存的就是前一个变量的地址:
int a = 12; int *b = &a;
它们如下图进行内存分配:
假定我们又声明了第三个变量叫c,并用c = &b对变量c进行初始化,那么它们在内存中的关系大致如下:
那么问题来了,c的类型是什么?我们都知道,能保存一个地址的变量类型只有指针,毫无疑问,c也是一个指针,只是,c是一个指向指针的指针,那么c该如何声明呢?这里用**c来声明一个指向指针的指针,如下:
int a = 12; int *b = &a; int **c = &b;
现在让我们来分析一下**c,*操作符具有从右向左的结合性,所以这个表达式相当于*(*c),我们必须从里向外逐层求值。*c访问的是c变量所保存的地址,这里面保存的是b的地址,即为变量a的地址,所以,*c为变量a的地址,但我们光光知道变量a的地址不够,我们想要获取变量a的值,所以需要在*c的基础上再加上一个*,即为*(*c),即可以获取对变量a的访问
表达式 | 相当的表达式 |
a | 12 |
b | &a |
*b | a,12 |
c | &b |
*c | b,&a |
**c | *b,a,12 |
指针表达式
现在,让我们观察各个不同的表达式,并看看当他们分别作为左值和右值时是如何求值的,首先我们先看下一段声明:
char ch = 'a'; char *cp = &ch;
表达式:ch
我们先来看表达式ch这个变量,当ch作为右值时,它表达的是存储的值'a',当ch作为左值时,它就是内存的地址,而不是该地址所包含的值
表达式:&ch
&ch作为右值时,代表的是变量ch的内存地址。但它不能作为左值,我们都知道C语言中,数组变量是不能赋值给另外一个数组变量的,因为数组变量就代表地址,地址即为常量,我们不能给常量赋值
表达式:cp
cp作为右值时,代表是cp这个地址所存储的值,即为ch的地址。作为左值时,代表的是cp本身的内存地址
表达式:&cp
&cp作为右值时,代表的是cp本身的内存地址,而它作为左值是非法的,因为&cp是内存地址,即为常量,我们不能给常量赋值
表达式:*cp
*cp作为右值时,代表的是cp指针指向的地址所存储的值,即为'a',作为左值的时,它代表的是ch这个内存地址,如果执行*cp = 'b',则修改的是ch这个地址所存储的值
表达式:*cp + 1
*的优先级高于+,所以不管作为左值还是右值,*一定都比+先参与运算。我们先看看作为右值时代表的是什么,我们*cp作为右值时,是ch变量所存储的值,所以是'a',而'a'+1则为'b',所以*cp + 1作为右值时它的结果是'b',然而,*cp + 1不能作为左值,原因很简单,这个表达式的运算结果是'b',是一个常量,常量是不能作为左值的
表达式:*(cp + 1)
这个表达式和上一个表达式不同,+在()的里面,所以+先参与表达式的运算,其次才是*,我们先看看作为右值时表达式运算结果是如何的,我们都知道cp存储的是ch的地址,+1则代表指向紧随ch之后的内存地址,然后再通过*间接访问这个地址所存储的值。再来是左值,*(cp + 1)作为左值是危险的,因为*(cp + 1)作为左值时,指向的是紧随ch之后的下一个内存地址,但我们不知道这个地址本身有没有保存值,又或者这个地址有没有其他进程在用,如果是其他进程在使用的话,我们要访问或操作这个内存地址,操作系统是不允许的,会报出错误,但如果这个是地址目前没有人使用或者是本进程在用,那么修改这个地址的值,势必会造成本进程其他功能在运行时因原先的值被修改而出错
表达式:++cp
由于cp本身存储的是一个地址,作为右值时,++cp先是将cp指向紧随ch之后的下一个内存地址,然后再返回指针的一份拷贝,而++cp是不能作为左值的,因为++cp是一个常量,常量不能作为左值
表达式:cp++
cp++作为右值时,首先先返回cp指针的一份拷贝,然后再将cp本身所存储的地址+1,指向紧随ch之后的下一个内存地址,而cp++不能作为作为,原因同上
表达式:*++cp
表达式在运算时,是从右到左,所以++的优先级会高于*,当*++cp作为右值时,cp指向ch的下一个内存地址,然后再返回cp指针的一份拷贝,接着*运算,间接访问ch下一个内存地址所存储的值。当*++cp作为左值时,则指向的是ch下一个内存地址
表达式:*cp++
这里依旧是++先于*参与运算,作为右值时,cp++首先返回的是cp指针的一份拷贝,即为ch地址,其次才是对cp地址所存储的值+1,指向ch之后的内存地址,然后是*,*接收到的是ch地址,所以通过间接访问,访问到的是ch地址所存储的值,虽然这个时候cp指针指向的已经是ch下一个内存地址了。而作为左值时,*cp++依旧指向的是ch这个地址
表达式:++*cp
这里,*与++先参与运算,先来看看表达式作为右值时是如何运算的,表达式会对cp所指向地址的值+1,然后返回增值后的值的一份拷贝,即为'b',而++*cp不能作为左值,因为这个表达式的运算结果返回的是一个常量
#include <stdio.h> int main(int argc, char const *argv[]) { char ch = 'a'; char *cp = &ch; printf("++*cp = %c ", ++*cp); printf("ch = %c ", ch); printf("++*cp = %c ", ++*cp); printf("ch = %c ", ch); printf("++*cp = %c ", ++*cp); printf("ch = %c ", ch); return 0; }
运行结果:
# gcc main.c -o main # ./main ++*cp = b ch = b ++*cp = c ch = c ++*cp = d ch = d
表达式:(*cp)++
作为右值,*先参与运算,所以*cp为指向ch地址的值,其次是++,先返回ch地址保存的值的拷贝,再对ch地址保存的值+1,即为'b',而它作为左值是非法的。
表达式:++*++cp
当这个表达式作为右值时,首先++cp会先将cp指针指向ch下一个内存地址,然后返回一份cp的拷贝,再用*进行间接访问,获取当前cp保存的地址的值,注意:这里cp已经指向ch之后的地址了,再来就是++,对指向地址的值+1,。而这个表达式是不能作为左值的
我们来验证一下这个表达式作为右值时的行为:
#include <stdio.h> int main(int argc, char const *argv[]) { int i = 0; char seq[] = {'a', 'b', 'e'}; char *cp = seq; printf("++*++cp = %c ", ++*++cp); for (; i < sizeof(seq) / sizeof(char); i++) { printf("seq[%d] = %c ", i, seq[i]); } return 0; }
运行结果:
# gcc main.c -o main # ./main ++*++cp = c seq[0] = a seq[1] = c seq[2] = e
可以看到,这个表达式作为右值时,他的确是把原先指向的值的下一个内存单元所保存的值+1
表达式:++*cp++
这个表达式作为右值时,我们先看cp++,它会返回一个cp指针的拷贝,然后对cp指针所保存的值+1,即指向ch之后的地址,再来就是*运算,进行间接访问,这里的*cp++访问的还是ch地址所保存的值,尽管这时候cp已经指向ch之后的内存地址了,然后是++*cp++,*cp++指向的是ch地址所保存的值,即为'a',++即为先对ch地址所保存的值+1,然后再返回一份对其值的拷贝,同样,这个表达式不能作为左值
让我们验证一下这个表达式的运算结果:
#include <stdio.h> int main(int argc, char const *argv[]) { int i = 0; char seq[] = {'a', 'b', 'e'}; char *cp = seq; printf("++*cp++ = %c ", ++*cp++); for (; i < sizeof(seq) / sizeof(char); i++) { printf("seq[%d] = %c ", i, seq[i]); } return 0; }
运算结果:
# gcc main.c -o main # ./main ++*cp++ = b seq[0] = b seq[1] = b seq[2] = e