由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();

}

 

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

 

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

 

 

 

原文地址:https://www.cnblogs.com/qingergege/p/6869814.html