• 指针与函数传参的思考


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

    #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 ",p->a);

     

            fun1(p);

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

     

            fun2(p);

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

     

            fun3(&p);

            printf("p->a = %d ",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,可以自己用这种内存模型方法来自己分析一下 :)

     

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

     

  • 相关阅读:
    Linux下的tar压缩解压缩命令详解
    python学习笔记-day9-2【面向对象】
    python学习笔记-day9-1【发送邮件模块 yagmail】
    python学习笔记-day8-4-【python 内置函数补充:zip,map,filter】
    python学习笔记-day8-3-【python 网络请求及requests模块】
    python学习笔记-day8-2-【python 异常处理 try except】
    python学习笔记-day8-1-【python flask接口开发】
    day9-Python学习笔记(二十一)单元测试
    day9-Python学习笔记(二十)数据库备份,修改父类的方法
    day8-Python学习笔记(十八)面向对象,self,私有,属性方法
  • 原文地址:https://www.cnblogs.com/pangblog/p/3306543.html
Copyright © 2020-2023  润新知