先看代码,这个代码很简单,看你能不能准确地说出答案。

#include <stdio.h>

 

struct A {

        int a;

};

 

struct A g_ta = {

        .a = 1,

};

 

struct A g_tb = {

        .a = 2,

};

 

void fun1(struct A * p1)

{

        p1->a = 3;

}

 

void fun2(struct A * p2)

{

        p2 = &g_tb;

}

 

void fun3(struct A ** p3)

{

        *p3 = &g_tb;

}

 

int main()

{

        struct A *p = &g_ta;

        printf("p->a = %d\n",p->a);

 

        fun1(p);

        printf("p->a = %d\n",p->a);

 

        fun2(p);

        printf("p->a = %d\n",p->a);

 

        fun3(&p);

        printf("p->a = %d \n",p->a);

        return 0;

}

 

gcc编译,运行结果如下:

p->a = 1

p->a = 3

p->a = 3

p->a = 2

对了吗?如果你对了,说明你对指针和函数参数传递已经理解。

如果你和我一样,答案和打印结果相悖,继续看。

第一行和第二行输出应该没问题,很简单的,是通过指针方式进行传值的,所以改变形参的值,实参也会跟着改变。

再看第三行输出,为什么经过fun2 后值不是p->a = 2,而是p->a = 3?fun2不也是通过指针方式来进行传值的吗?怎么没有改变p的值呢?

再看第四行输出,p->a = 2?怎么形参为指针的指针就对了呢?

 

带着疑问看下面:

首先不要觉得指针是个很神奇的东西,我之前一直对指针不理解,或者是理解不透彻,这两天在写电子书的代码,感觉对指针这个东西有了些顿悟,我可以告诉你指针就是一个变量,他和别的变量(比如int,char)没有什么本质的区别,都是一个内存A里存放变量的值!唯一区别就是指针被多设计了一个 * 号,该 * 号的意思就是将这个内存A里面的值当成另一个内存B的地址,并取出这个内存B的值,说到这里,这就是指针的全部!

下面是函数参数传递,回头看我说的一句话“通过指针方式进行传值的,所以改变形参的值,实参也会跟着改变”,这句话其实是障眼法,学C语言的时候老师就告我们,值传递方式,形参改变不能影响实参,而地址传递传递是可以的,这句话不能算错,但是不能说对,地址传递方式形参改变确实能影响实参,但是也是要看改变的是什么,影不影响实参是有道理的!

为了更好得说明,我将内存抽块象成如下小方块,一个方块代表一块内存。

另注,1. 我将一个结构体看成一块小内存,2. 变量的内存地址是我假设的。3.地址就是指此块内存的地址,每块内存都有自己地址。


变量

地   址


现在开始分析代码:

 

首先申明两个struct A全局变量,其内存模型如下,(地址是我假设的,以下都是,不再赘述)  

     g_ta

g_ta

1

0x10

 

 

      

     g_tb

g_tb

2

0x11

 

 

 

在main数里面申明了结构体A指针,其指向g_ta,其内存模型如下

        P

p

0x10

0x50

 

 

 

注意看,p的值就是 0x10,g_ta的地址,没什么特别的。


现在开始分析fun2,fun2弄懂了fun1自然就理解了,首先p经过了fun1之后,将g_ta里面的值变成了3,所以,现在他们内存情况如下:(这三块内存方格是分开的,贴到这里就连在一起了,以下同样,不再赘述。。。)

      g_ta            g_tb            p

g_ta

3

0x10

g_tb

2

0x11

p

0x10

0x50

 

      

 

调用fun2(p);

         | |

         \/ 

void fun2(struct A * p2)

{

        p2 = &g_tb;

}


进入函数fun2,会生成一个和形参类型相同副本,其将实参值拷贝,即结构体指针p2,其内存模型如下                     

        p                p2

p

0x10

0x50

p2

0x10

0xa0

 

 

 

可以看到,他和指针p的不同就是内存地址不同,而他们的值是相同的,都表示g_ta的地址。


p2 = &g_tb;

这句就是将g_tb的地址赋值给p2,那么p2内存模型就会变成如下:

         p                p2

p

0x10

0x50

p2

0x11

0xa0

 

 

 

好了,p2的值不是改变了吗,p2确实指向了g_tb了啊?怎么打印得不对呢?哈哈,你再看看,printf打印得是p啊,p指向的还是是g_ta,当然输出的是3了啊!p2在退出fun2时候就自动销毁了!


跟着内存模型来看,很容易理解吧,那下面继续来看fun3,fun3为什么就能正确地打印出g_tb的值呢?继续用内存模型来分析:

此时内存情况如下:

       g_ta          g_tb              p

g_ta

3

0x10

g_tb

2

0x11

  p 

0x10

0x50

 

      

  

执行函数fun3(&p);

                 | |

                 \/

void fun3(struct A ** p3)

{

        *p3 = &g_tb;

}

经过fun3,将p的地址 0x50传给了fun3,这个0x50没有什么特别得,他就是一个数值,对于fun3来说他根本不知道这个0x50代表什么意思!所以我们就要告诉fun3,这个0x50是个内存地址,这个地址内存里面的内容仍然是一个地址!转变成C语言来理解就是要设计一个形参,这个形参能够进行两次取内存值的操作,好,struct A ** p3就应运而生了!

 p3:指针的指针,感觉好复杂啊!其实一点都不复杂,说到底p3就是一个指针嘛,不过他指向的内存里面的数据也是指针类型罢了,既然p3还是一个指针那再来看看他的内存模型

     g_ta            g_tb            p                 p3

g_ta

3

0x10

g_tb

2

0x11

p

0x10

0x50

P3

0x50

0xa1

                    

 

      

进过fun3,实参传递了p的地址0x50给p3,所以p3的值就是0x50,看清楚了!


执行*p3= &g_tb;

*p3就是取地址为0x50内存的值,地址为0x50的内存里面存放是谁的值啊?

对!就是p的值,所以这条代码其实改变了的是p的值,将g_tb的值赋给了p!

    g_ta              g_tb              p                p3

g_ta

3

0x10

g_tb

2

0x11

p

0x11

0x50

P3

0x50

0xa1

                    

 

      

好了,基本都已经清楚了,退出fun3,p3销毁,打印p指向的值,就是p->a = 2

分析已经结束了,还有fun1,可以自己用这种内存模型方法来自己分析一下 :)

 

总结:想要改变指针指向的内容,就传指针给函数,想要改变指针的值,那就得传递指针的地址了,相应的函数参数就要设计成指针的指针了!