由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 = ; 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(); }
也许我今天辛苦整理的心得,到了明天发现依然不够全面不够好。不过没关系,都是在不断完善中成长的!
水平有限,有纰漏之处还请指正。谢谢。。。。
由typedef和函数指针引起的危机的更多相关文章
- 用typedef定义函数指针的问题
在学习windows API的时候,遇到下面这段代码 以前见过的typedef的用法都是给一个数据类型取一个别名 typedef oldTypeName newTypeName 这种给数据类型 ...
- 【实习记】2014-08-27堆排序理解总结+使用typedef指代函数指针
过程记录 4个月前C语言版的七大排序算法实践让我在写C++版时轻车熟路.特别是冒泡,插入,希尔,选择这四种排序不用调试即运行成功.输出的效果与C语言做的版本完全一样,其中令我印象深刻的是,co ...
- typedef 复杂函数指针
下面是三个变量的声明,我想使用typedef分别给它们定义一个别名,请问该如何做? >1:int *(*a[5])(int, char*); >2:void (*b[10]) (void ...
- C语言结构体及typedef关键字定义结构体别名和函数指针的应用
结构体(struct)的初始化 struct autonlist { char *symbol; struct nlist nl[2]; struct autonlist *left, *right; ...
- c语言定义函数指针和typedef简写
二种方法来定义函数指针 #include<stdio.h> #include<stdlib.h> #include<Windows.h> int add(int a ...
- C++ 在容器中存放函数指针
注意,对一般c++ 98标准编译器而言,容器泛型模板是不支持直接存放函数指针的.需要typedef将函数指针重命名. 比如,一个void返回值参数也为void的函数指针,需要 typedef void ...
- C++学习笔记(八):函数重载、函数指针和函数对象
函数重载 函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数.重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于 ...
- C/C++ 函数指针使用总结
一 函数指针介绍 函数指针指向某种特定类型,函数的类型由其参数及返回类型共同决定,与函数名无关.举例如下: int add(int nLeft,int nRight);//函数定义 该函数类型为int ...
- C/C++中的函数指针的使用与总结
概要: 函数指针介绍 typedef简化函数指针的定义 指向函数的指针的初始化和赋值 通过指针调用函数 函数指针形参 返回指向函数的指针 指向重载函数的指针 参考<C++ Primer> ...
随机推荐
- jquery列队动画简单演示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- iOS应用的几个阶段
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...
- Fiddler抓取https原理?
参考:http://blog.csdn.net/xoopx/article/details/51577039 首先fiddler截获客户端浏览器发送给服务器的https请求, 此时还未建立握手.第一步 ...
- CSS3学习笔记(4)-CSS3函数
p{ font-size: 15px; text-indent: 2em; } .alexrootdiv>div{ background: #eeeeee; border: 1px solid ...
- CTF入门指南(0基础)
ctf入门指南 如何入门?如何组队? capture the flag 夺旗比赛 类型: Web 密码学 pwn 程序的逻辑分析,漏洞利用windows.linux.小型机等 misc 杂项,隐写,数 ...
- Twitter数据非API采集方法
说明:这里分三个系列介绍Twitter数据的非API抓取方法. 在一个老外的博看上看到的,想详细了解的可以自己去看原文. 这种方法可以采集基于关键字在twitter上搜索的结果推文,已经实现自动翻页功 ...
- MapReduce处理流程
MapReduce是Hadoop2.x的一个计算框架,利用分治的思想,将一个计算量很大的作业分给很多个任务,每个任务完成其中的一小部分,然后再将结果合并到一起.将任务分开处理的过程为map阶段,将每个 ...
- (一)一起学 Java Collections Framework 源码之 概述
. . . . . 目录 (一)一起学 Java Collections Framework 源码之 概述 JDK 中很多类 LZ 已经使用了无数次,但认认真真从源码级研究过其原理的还只占少数,虽然从 ...
- iptables配置详解
iptables主要参数 -A 向规则链中添加一条规则,默认被添加到末尾 -T指定要操作的表,默认是filter -D从规则链中删除规则,可以指定序号或者匹配的规则来删除 -R进行规则替换 -I插入一 ...
- Spring MVC执行原理
spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...