由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 = ;

    short b;

    b = a;

    printf("b = %d\n",b);

    getchar();

    return ;

}

当将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[] = {,,,};

    int *p = a;

    printf("第三个元素为:%d\n",p[]  );

    printf("第三个元素为:%d\n",*(p+)  );

    printf("第三个元素为:%d\n",*(+p)  );

    printf("第三个元素为:%d\n",[p]  );

    getchar();

    return ;

}

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

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

根据心得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[];

int (*)p[] =&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\n",a);

    return getchar();;

}

int main()

{

    int (*p)(char * a);

    p = &print;

    p("阿星");

    return ;

}

心得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[];

int main()

{

    a arr = {,,,,};

    printf("数组数据为:");

    for(int i = ; i < ; i++)

    {

        printf("%d ",arr[i]);

    }

    getchar();

    return ;

}

看到了吗?  加上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 ;

}

int print(char *s)

{

    printf("值为:%s\n",s);

    return getchar();

}

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

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

最新文章

  1. 蓝牙技术BlueTooth
  2. DP:Dollar Dayz(POJ 3181)
  3. linux下安装7z命令及7z命令的使用
  4. UIButton 设置为圆形,并且使用图片(UIImage)当做背景
  5. s3c-u-boot-1.1.6源码分析
  6. Git教程(7)用合并还是变基?
  7. Template 使用注意问题和范例
  8. 写后台SQL的一些心得
  9. 修改linux系统时间、rtc时间以及时间同步
  10. Visual Studio 2015使用EF6的CodeFirstFromDB模式操作Sqlite数据库时Provider问题
  11. 处理eclipse启动时报java.lang.IllegalStateException
  12. Windows下RabbitMQ安装及入门
  13. TabTopUnderLineLayout【自定义顶部选项卡(带下划线)】
  14. Android开发 静态static类与static方法持有Context是否导致内存泄露的疑问
  15. 【opencv基础】图像的几何变换
  16. vue项目中px自动转换为rem
  17. python之函数的参数
  18. Python学习笔记014——迭代器 Iterator
  19. EntityFramework:EF Migrations Command Reference
  20. 解决:actual_tessdata_num_entries_ &lt;= TESSDATA_NUM_ENTRIES:Error:Assert failed:in file ..\..\ccutil\tessdatamanager.cp p, line 50

热门文章

  1. CentOS 7 Root用户密码重置 2017-04-02
  2. Swift开发
  3. display与visibility的使用(区别)
  4. php Redis常用命令
  5. ngrok把本地主机映射到公网域名
  6. JS学习中遇到的一些题目
  7. 银盛支付ecshop,shopex,shopnc在线支付接口,php版本支付接口开发
  8. PHP站内搜索
  9. 第三章 Struts2配置详解
  10. 在 Intellij 中设置集成 Jenkins 服务器连接