• 由typedef和函数指针引起的危机


    由typedef和函数指针引起的危机

    昨天阅读了大神强哥的代码,发现里面用到了函数指针,也用到的typedef。本来我自以为对这两个概念有一定的认识,但是突然发现这两个东西居然用到了一起!!!!(在一起了也不说一声,一点心理准备都没有):

    typedef int (* fp)(void *para, void *end);

    瞬间就蒙了,这是个啥东西???于是我开始看书,上网查资料,想弄明白。在这个过程中,我发现自己不仅仅是对这两个概念理解不够!!!而是,对数组、指针、变量的理解都不够。这引发了我对C语言认识的危机。想想看,一直在写C程序,突然有一天发现,你对它的认识始终是模糊的,你所做的事都是建立在这种模糊的认识之上的,可不可怕!!!!

    有点类似于数学危机呀!

    我希望我能够像数学的发展那样,在矛盾中发展,在危机中升华。

    下面就记录一下,我在解决函理解这个东西:

    typedef int (* fp)(void *para, void *end);

    的过程中的一些心得体会。

     

    心得1:由变量名到地址,可以理解为一种退化而指针就是地址

    在我之前的一篇随笔中,我简单写了一下对地址和变量名的理解。

     

    http://www.cnblogs.com/qingergege/p/6723509.html

    这里再简单谈一下。

    我觉得:

    变量名可以认为代表一个结构。代表什么结构呢?学过编译原理的同学都知道,有一个概念叫做符号表。变量名就代表了这个符号表中的某一项。至于符号表是什么样,我不知道,但是大概就类似于下面这个(其中的一条记录):

    变量名

    首地址

    类型

    空间大小

    a

    0x3333

    int

    4字节

    10

     

    编译器用一个叫符号引用的东西来访问符号表中的记录。

    这个符号引用就是变量名

    看上面一条记录,可以发现其中有一个字段叫首地址,而上面说了变量名可以代表整个结构,换句话说就是,地址仅仅是变量名的一部分或者从另一个角度讲,变量名是一个受到限制的地址,因为指针就是地址,这句话也可以说成:变量名是一个受到限制的指针(受什么限制呢?就是受到上面那条记录中其他字段的限制)

    学习C语言的同学都有体会,指针是一个让人又爱又恨的东西。爱她是因为她使用方便,效率高;恨她是因为她太不安全了。她的不安全在于她几乎可以不受任何限地访问内存。

    比如说void * p; 你就可以给她分配你想要的大小的空间。

    p = malloc(你想要的大小)。

    而且通过p可能会访问到未分配的空间,比如你执行p++,也许p原来指向一片有意义的内存,但是p++之后呢?就不一定了。

    但是变量名就没有这个问题,看看上面那条记录,已经限定了a能够访问的内存的首地址是0x3333,能够访问的内存大小就是4字节。这些都是受限制的,但是单单给一个指针,也就是个首地址就没有这些限制。所以可以说,变量名是个受限制的地址。学c++的时候,老师在讲引用和指针的区别时,经常会说,引用就是个受限制的指针。那引用是什么?比如 int &b = a, b和a就是一样的,上面那条记录可以用a进行访问,现在通过b也可以访问了,就是这么回事儿

    而从变量名到地址,实际上就是从整个结构退化到结构中的“首地址”字段,而这个过程是通过取地址符号&实现的

     

    心得2:数组名不是指针

    记得当大一学C语言那会儿,老师就说,数组名就相当于个指针。现在看来数组名也仅仅是“相当于”个指针而已。

    为什么我们会认为数组名是指针呢?答案很简单,他俩太像了!为什么他俩这么像?答案:都是编译器“惹的祸”。

    可能有两个用法导致了我们对“数组名就是指针”这个观点深信不疑。

    第一个就是数组名可以赋值给一个指针,比如

    int a[3];

    int *p = a;

    第二个是用指针也可以向用数组名那样,使用下标访问数组中的元素,比如:

    p[2]和a[2]是等价的。

     

    基于上面两种用法,很难让我们不相信“数组名就是指针”。

    这一切都是编译器的锅!!!

    当我们使用赋值符“=”,真的是仅仅执行了赋值语句这么简单吗?或者到汇编层,真的只是几个mov语句吗?一定不是的!

    比如说:

    int main()
    
    {
    
        int a = 100;
    
        short b;
    
        b = a;
    
        printf("b = %d
    ",b);
    
        getchar();
    
     
    
        return 0;
    
    }

     

     

    当将a的值赋值给b时,一定是存在一个类型转换的,应该是

    b = (short)a;

     

    但是我们并不需要显式地强制类型转换,这是因为编译器为我们做了。就像Java中即使我们不写构造方法,创建对象的时候也会调用构造方法,因为编译器会为我们生成一个默认的构造方法(编译器不容易啊,知道我们懒,活都帮我们干了)。同样,当我们将一个数组名赋值给指针时,编译器在私下也帮我们做了大量的工作

    上边那句 int *p = a。实际上编译器帮我们转换了,转换成类似

    int *p = &a[0];这样的语句。看起来好像是将一个数组名赋值给了指针,实际上底层还是讲数组的首元素的地址赋值给了指针!

     

    再来说说通过下标访问数组元素的方法,使用的是下标运算符[]。

    在我们看来就是通过下标访问的元素呀,但实际上编译器会将[]运算符转换成指针运算,比如a[2] + 1 ,在底层就是类似于*(a + 2) +1。有一个写法可以从侧面证明这一点:

    我们知道 *(p  + 2) + 1就是p指向的数组的第3个元素+1,这句话也可以写成p[2] +1, 当然因为a+b和b+a一样,所以也可以写成*(2 + p) + 1;  那么,神奇的事情出现了,也可以写成2[p]+1!!!!当时看到这种写法的时候,真的是颠覆了世界观!!这种写法也从侧面证明了,下标运算符[]实际上被编译器转换成了指针的运算!!

    下面是程序源码和输出结果:

    int tmain()
    
    {
    
        int a[4] = {1,2,3,4};
    
        int *p = a;
    
        printf("第三个元素为:%d
    ",p[2]  );
    
        printf("第三个元素为:%d
    ",*(p+2)  );
    
        printf("第三个元素为:%d
    ",*(2+p)  );
    
        printf("第三个元素为:%d
    ",2[p]  );
    
     
    
        getchar();
    
     
    
        return 0;
    
    }

    正是由于编译器为我们做了这么多,才让数组名看是来像是一个指针!!

    还有一个问题就是数组名可以赋值给指针,但是指针不能赋值给数组名,很多人给出解释是因为数组名是个指针常量,而常量的值是不能改变的。。但是上面已经解释了数组名根本就不是指针,更不用说是指针常量了!!变量名之所以能够赋值给指针变量,是因为编译器做了优化,但是这是变量名在=左边的情况呀!

     

    根据心得1,数组名也是个变量名,那么它也可以理解为一个首限制的指针,而从变量名转换成指针是需要取地址运算&的。事实 情况也是如此:编译器将int *p = a 优化成了 int *p = &a[0]

    而&a就表示的是数组a的地址!这就要谈心得3了。

     

    心得3:定义一个类型的指针变量的方法,就是先定义出该类型的普通变量,然后在变量名前加上*即可,但是要注意运算符的优先级

    上面的话肯能不太好理解,下面就是例子:

    比如说我想定义一个指向int类型的指针,那么我就可以先定义一个int类型的变量,然后再在变量名前面加上*即可

    如:

    int a;    ---》 int *a;

     

    同样如果我想定义一个指向int数组的指针,我可以先定义一个int类型的数组,然后再在数组名(变量名)前加上*

    int a[5]    -----》int (*a)[5]

    这里要加括号是因为[]的优先级比*高。

    这里可能有人会疑惑,指向一位数组的指针不应该定义成int *a就好了吗?

    这里需要解释的是int *p = a这种写法中p实际上指向的数组中的元素,而不是整个数组,上面心得2中提到int *p = a会被编译器优化成int *p = &a[0],所以这种定义下p指向的是数组中的元素,而不是整个数组,这也是为什么p++会指向下一个元素。

    而int (*p)[5]这种定义方法,p指向的是整个数组,而p++则是指向下一个数组(如果合法的话)

     

    发现没有:

    int a;
    
    int *p =&a;
    
     
    
    int a[5];
    
    int (*)p[5] =&a;

     

    都是有套路的!!!!

     

    同样对于函数指针也一样:

    int print(char *a); --》 int (*print)(char *)

     

    看到没有也仅仅是在函数名前面加上一个*即可(加括号是由于优先级的问题),只不过通常我们会把函数指针换个名字而已,比如

    int (*p)(char *)

     

    就是把print换成了p而已呀。

    问题来了,前面都是通过取地址得到指向相应类型变量的指针,为什么我们在给函数指针赋值的时候不用&符呢?比如:

    int print(char *);
    
    int (*p)(char *);
    
    p = print;

     

    为什么不用 p = &print呢?

    还记得将数组名赋值给指针变量吗?没错!还是编译器的“锅”,编译器帮我们做了优化,p = print会被编译器优化成p = &print,

    而且我们就写p = &print也没有任何问题!!!

    比如下面代码和运行结果:

     

    int print(char *a)
    
    {
    
        printf("值为:%s
    ",a);
    
        return getchar();;
    
    }
    
    int main()
    
    {
    
        int (*p)(char * a);
    
     
    
        p = &print;
    
     
    
        p("阿星");
    
     
    
       
    
        return 0;
    
    }

     

    心得4:定义某种类型的变量,在前面加上typedef就得到了该变量的类型

     

    比如 定义int类型的变量a,

    int a;

    如果前面加上typedef 那么a不再是变量,而是变量的类型!!

    typedef int a; 那么a就相当于int。

     

     

    同样 我们定义一个数组变量:

    int a[5];

    加上typedef之后 a就变成了有五个元素的数组类型

    typedef int a[5];

    此时a就不再是变量了,而是类型!!升级了!!!

    之后我们就可以用a定义数组变量了!!!

    一个代码和运行结果

    typedef int a[5];
    
    int main()
    
    {
    
        a arr = {1,2,3,4,5};
    
     
    
        printf("数组数据为:");
    
        for(int i = 0; i < 5; i++)
    
        {
    
            printf("%d ",arr[i]);
    
        }
    
     
    
        getchar();
    
        return 0;
    
    }

    看到了吗?  加上typedef之后小小的数组变量a就变成了类型(一步登天呀),然后他就可以定义变量啦。只不过通常情况下我们会把这个类型大写,而不是使用看是来更像是变量的a

     

    最后终于到了要解决引起我危机感的东西了,typedef加上函数指针变量。

    函数指针变量

    int (*p)(char *);

    p本来是个指针变量,加上typedef这个皇冠就麻雀变凤凰了。就从一个变量名变成了能定义函数指针变量的类型名了。

    看代码和运行结果

    typedef int (*p)(char *s);
    
     
    
    int print(char *s);
    
     
    
    int main()
    
    {
    
        p pf;
    
     
    
        pf = &print;
    
        pf("阿星");
    
       
    
        return 0;
    
    }
    
     
    
    int print(char *s)
    
    {
    
        printf("值为:%s
    ",s);
    
        return getchar();
    
    }

     

    也许我今天辛苦整理的心得,到了明天发现依然不够全面不够好。不过没关系,都是在不断完善中成长的!

     

    水平有限,有纰漏之处还请指正。谢谢。。。。

     

     

     

  • 相关阅读:
    Git的初步学习
    Git的初步学习
    微信小程序我的界面
    微信小程序我的界面
    Day2:html和css
    Day2:html和css
    Day1:html和css
    Day1:html和css
    Java之JDK7的新语法探索
    Java之JDK7的新语法探索
  • 原文地址:https://www.cnblogs.com/qingergege/p/6869814.html
Copyright © 2020-2023  润新知