指针的指针(一)

指针的指针

我们都知道,在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\n", ++*cp);
    printf("ch = %c\n", ch);
    printf("++*cp = %c\n", ++*cp);
    printf("ch = %c\n", ch);
    printf("++*cp = %c\n", ++*cp);
    printf("ch = %c\n", 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\n", ++*++cp);
    for (; i < sizeof(seq) / sizeof(char); i++)
    {
        printf("seq[%d] = %c\n", 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\n", ++*cp++);
    for (; i < sizeof(seq) / sizeof(char); i++)
    {
        printf("seq[%d] = %c\n", i, seq[i]);
    }
    return 0;
}

  

运算结果:

# gcc main.c -o main
# ./main 
++*cp++ = b
seq[0] = b
seq[1] = b
seq[2] = e

  

 

posted @ 2018-07-13 21:03  北洛  阅读(337)  评论(0编辑  收藏  举报