设为首页 加入收藏

TOP

22.8 函数类型和函数指针类型
2013-10-12 06:58:54 来源: 作者: 【 】 浏览:108
Tags:22.8 函数 类型 指针

22.8  函数类型和函数指针类型

C语言中,函数也是一种类型,可以定义指向函数的指针。我们知道,指针变量的内存单元里存放一个地址值,而函数指针的内存单元里存放的就是函数的入口地址(位于.text段)。下面看一个简单的例子:

例22.3  函数指针

  1. #include <stdio.h> 
  2.  
  3. void say_hello(const char *str)  
  4. {  
  5.         printf("Hello %s\n", str);  
  6. }  
  7.  
  8. int main(void)  
  9. {  
  10.         void (*f)(const char *) = say_hello;  
  11.         f("Guys");  
  12.         return 0;  

分析一下变量f的类型声明void (*f)(const char *),f首先跟*号结合在一起,因此是一个指针。(*f)外面是一个函数原型的格式,参数是const char *,返回值是void,所以f是指向这种函数的指针。而say_hello函数的参数是const char *,返回值是void,正好是这种函数,因此f可以指向say_hello函数。注意,表达式say_hello是一种函数类型,函数类型和数组类型相似,做右值使用时自动转换成函数指针类型,因此可以直接赋给f,也可以写成void (*f)(const char *) = &say_hello;,把函数say_hello先取地址再赋给f,就不需要自动类型转换了。

数组取下标运算符[]要求操作数是指针类型,a[1]等价于*(a+1),如果a是数组类型则要自动转换成指向首元素的指针类型。同样道理,函数调用运算符()要求操作数是函数指针类型,所以f("Guys")是最直接的写法,而say_hello("Guys")或(*f)("Guys")则是把函数类型自动转换成函数指针类型然后做函数调用。

下面再举几个例子区分函数类型和函数指针类型。首先定义函数类型F:

  1. typedef int F(void); 

这种类型的函数不带参数,返回值是int。那么可以这样声明f和g:

  1. F f, g; 

相当于声明:
  1. int f(void);  
  2. int g(void); 

下面这个函数声明是错误的:
  1. F h(void); 

因为一个函数可以返回void类型、标量类型、结构体或联合体,但不能返回函数类型,也不能返回数组类型。而下面这个函数声明是正确的:
  1. F *e(void); 

函数e返回一个F *类型的函数指针,函数指针是标量类型,可以做函数的返回值。如果给e多套几层括号仍然表示同样的意思:
  1. F *((e))(void); 

但如果把*号也套在括号里就不一样了:
  1. int (*fp)(void); 

这样声明的fp是一个函数指针而不是函数。也可以这样声明:
  1. F *fp; 

现在给这种函数指针起一个类型名叫FP:
  1. typedef int (*FP)(void); 

假设有一个地址0x12345678正好是这样一个函数的首地址,我们把它强制转换成一个函数指针并调用它,以下两种写法皆可:
  1. ((FP)0x12345678)();  
  2. (*(FP)0x12345678)(); 

不用类型名FP也可以写出强制转换运算符:
  1. ((int (*)(void))0x12345678)();  
  2. (*(int (*)(void))0x12345678)(); 

下面这段程序(感谢xinwu网友提供)对于辨析函数类型和函数指针类型也非常有帮助:
  1. #include <stdio.h> 
  2.  
  3. void hello(void)  
  4. {}  
  5.  
  6. int main(void)  
  7. {  
  8.         void (*hello_ptr)(void);  
  9.  
  10.         printf("hello: %p\n", hello); /* function type as rvalue */  
  11.         printf("&hello: %p\n", &hello); /* function type as lvalue */  
  12.         printf("*hello: %p\n", *hello); /* function type as rvalue */  
  13.  
  14.         hellohello_ptr = hello;  
  15.         printf("hellohello_ptr = hello;\n");  
  16.  
  17.         printf("hello_ptr: %p\n", hello_ptr); /* value of a 
    function            pointer */  
  18.         printf("&hello_ptr: %p\n", &hello_ptr); /* address 
    of a                 function pointer */  
  19.         printf("*hello_ptr: %p\n", *hello_ptr); /* 
    dereference a f          unction pointer */  
  20.  
  21.         return 0;  

通过函数指针调用函数和通过函数名直接调用函数相比有什么好处呢?我们研究一个例子。回顾第 7.3 节的习题1,由于结构体中多了一个类型字段,需要重新实现real_part、img_part、magnitude、angle这些函数,你当时是怎么实现的?大概是这样吧:

  1. double real_part(struct complex_struct z)  
  2. {  
  3.         if (z.t == RECTANGULAR)  
  4.                 return z.a;  
  5.         else  
  6.                 return z.a * cos(z.b);  

现在类型字段有两种取值,RECTANGULAR和POLAR,每个函数都要用if ... else ...分别处理两种情况。如果类型字段有三种取值呢?每个函数都要有if ... else if ... else ...,或者switch ... case ...。这样维护代码是不够理想的,现在我用函数指针给出一种实现:

  1. double rect_real_part(struct complex_struct z)  
  2. {  
  3.         return z.a;  
  4. }  
  5.  
  6. double rect_img_part(struct complex_struct z)  
  7. {  
  8.         return z.b;  
  9. }  
  10.  
  11. double rect_magnitude(struct complex_struct z)  
  12. {  
  13.         return sqrt(z.a * z.a + z.b * z.b);  
  14. }  
  15.  
  16. double rect_angle(struct complex_struct z)  
  17. {  
  18.         double PI = acos(-1.0);  
  19.  
  20.         if (z.a > 0)  
  21.                 return atan(z.b / z.a);  
  22.         else  
  23.                 return atan(z.b / z.a) + PI;  
  24. }  
  25.  
  26. double pol_real_part(struct complex_struct z)  
  27. {  
  28.         return z.a * cos(z.b);  
  29. }  
  30.  
  31. double pol_img_part(struct complex_struct z)  
  32. {  
  33.         return z.a * sin(z.b);  
  34. }  
  35.  
  36. double pol_magnitude(struct complex_struct z)  
  37. {  
  38.         return z.a;  
  39. }  
  40.  
  41. double pol_angle(struct complex_struct z)  
  42. {  
  43.         return z.b;  
  44. }  
  45.  
  46. double (*real_part_tbl[])(struct complex_struct) =
    { rect_real_         part, pol_real_part };  
  47. double (*img_part_tbl[])(struct complex_struct) =
    { rect_img_part,  pol_img_part };  
  48. double (*magnitude_tbl[])(struct complex_struct)
    =  { rect_magnitude, pol_magnitude };  
  49. double (*angle_tbl[])(struct complex_struct) = { rect_angle,    pol_angle };  
  50.  
  51. #define real_part(z) real_part_tbl[z.t](z)  
  52. #define img_part(z) img_part_tbl[z.t](z)  
  53. #define magnitude(z) magnitude_tbl[z.t](z)  
  54. #define angle(z) angle_tbl[z.t](z) 

当调用real_part(z)时,用类型字段z.t做索引,从指针数组real_part_tbl中取出相应的函数指针来调用,也可以达到if ... else ...的效果。相比之下这种实现更好,每个函数都只做一件事情,而不必用if ... else ...兼顾好几件事情,比如rect_real_part和pol_real_part各做各的,互相独立,而不必把它们的代码都耦合到一个函数中。"低耦合,高内聚"(Low Coupling, High Cohesion)是程序设计的一条基本原则,这样可以更好地复用现有代码,更容易维护。如果类型字段z.t又多了一种取值,只需要添加一组新的函数,修改函数指针数组,原有的函数仍然可以不加改动地复用。

另外,这种实现用数据(函数指针数组)取代了代码(if ... else ...),和例 8.4有异曲同工之处,也是应用了Data-driven Programming的思想。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇21.4 自动处理头文件的依赖关系 下一篇2.4.2 GNU C

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: