由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和函数指针引起的危机的更多相关文章

  1. 用typedef定义函数指针的问题

    在学习windows API的时候,遇到下面这段代码   以前见过的typedef的用法都是给一个数据类型取一个别名 typedef oldTypeName newTypeName   这种给数据类型 ...

  2. 【实习记】2014-08-27堆排序理解总结+使用typedef指代函数指针

        过程记录 4个月前C语言版的七大排序算法实践让我在写C++版时轻车熟路.特别是冒泡,插入,希尔,选择这四种排序不用调试即运行成功.输出的效果与C语言做的版本完全一样,其中令我印象深刻的是,co ...

  3. typedef 复杂函数指针

    下面是三个变量的声明,我想使用typedef分别给它们定义一个别名,请问该如何做? >1:int *(*a[5])(int, char*); >2:void (*b[10]) (void ...

  4. C语言结构体及typedef关键字定义结构体别名和函数指针的应用

    结构体(struct)的初始化 struct autonlist { char *symbol; struct nlist nl[2]; struct autonlist *left, *right; ...

  5. c语言定义函数指针和typedef简写

    二种方法来定义函数指针 #include<stdio.h> #include<stdlib.h> #include<Windows.h> int add(int a ...

  6. C++ 在容器中存放函数指针

    注意,对一般c++ 98标准编译器而言,容器泛型模板是不支持直接存放函数指针的.需要typedef将函数指针重命名. 比如,一个void返回值参数也为void的函数指针,需要 typedef void ...

  7. C++学习笔记(八):函数重载、函数指针和函数对象

    函数重载 函数重载是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数.重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于 ...

  8. C/C++ 函数指针使用总结

    一 函数指针介绍 函数指针指向某种特定类型,函数的类型由其参数及返回类型共同决定,与函数名无关.举例如下: int add(int nLeft,int nRight);//函数定义 该函数类型为int ...

  9. C/C++中的函数指针的使用与总结

    概要: 函数指针介绍 typedef简化函数指针的定义 指向函数的指针的初始化和赋值 通过指针调用函数 函数指针形参 返回指向函数的指针 指向重载函数的指针 参考<C++ Primer> ...

随机推荐

  1. jquery列队动画简单演示

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. iOS应用的几个阶段

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...

  3. Fiddler抓取https原理?

    参考:http://blog.csdn.net/xoopx/article/details/51577039 首先fiddler截获客户端浏览器发送给服务器的https请求, 此时还未建立握手.第一步 ...

  4. CSS3学习笔记(4)-CSS3函数

    p{ font-size: 15px; text-indent: 2em; } .alexrootdiv>div{ background: #eeeeee; border: 1px solid ...

  5. CTF入门指南(0基础)

    ctf入门指南 如何入门?如何组队? capture the flag 夺旗比赛 类型: Web 密码学 pwn 程序的逻辑分析,漏洞利用windows.linux.小型机等 misc 杂项,隐写,数 ...

  6. Twitter数据非API采集方法

    说明:这里分三个系列介绍Twitter数据的非API抓取方法. 在一个老外的博看上看到的,想详细了解的可以自己去看原文. 这种方法可以采集基于关键字在twitter上搜索的结果推文,已经实现自动翻页功 ...

  7. MapReduce处理流程

    MapReduce是Hadoop2.x的一个计算框架,利用分治的思想,将一个计算量很大的作业分给很多个任务,每个任务完成其中的一小部分,然后再将结果合并到一起.将任务分开处理的过程为map阶段,将每个 ...

  8. (一)一起学 Java Collections Framework 源码之 概述

    . . . . . 目录 (一)一起学 Java Collections Framework 源码之 概述 JDK 中很多类 LZ 已经使用了无数次,但认认真真从源码级研究过其原理的还只占少数,虽然从 ...

  9. iptables配置详解

    iptables主要参数 -A 向规则链中添加一条规则,默认被添加到末尾 -T指定要操作的表,默认是filter -D从规则链中删除规则,可以指定序号或者匹配的规则来删除 -R进行规则替换 -I插入一 ...

  10. Spring MVC执行原理

    spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...