关于函数指针及指针函数,以及指针的其他妙用

来源:互联网 发布:sql数据库基础知识pdf 编辑:程序博客网 时间:2024/06/06 19:57

本文转载自http://blog.csdn.net/touch_2011/article/details/6984029

觉得博主写的很好,转载过来学习。

1、函数指针(指向函数的指针)

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址(入口地址),所以函数名跟数组名很类似,都是指针常量。

函数指针就是指向这个入口地址的指针变量,注意函数指针是一个变量。

 

[cpp] view plain copy
  1. <span style="font-size:16px;">#include<stdio.h>  
  2.   
  3. void f(int);  
  4.   
  5. int main()  
  6. {  
  7.     //定义函数指针pf并给pf赋值使其指向函数f的入口地址  
  8.     //pf先跟*结合,说明pf是一个指针,然后与括号结合,说明这个指针指向函数  
  9.     void (*pf)(int)=f;  //等价于void (*pf)(int)=&f;  
  10.     pf(1);  
  11.     (*pf)(2);//把函数指针转换成函数名,这个转换并不需要  
  12.     f(3);  
  13.     return 0;  
  14. }  
  15.   
  16. void f(int a)  
  17. {  
  18.     printf("%d\n",a);  
  19. }  
  20. </span>  


 

void (*pf)(int)=&f;为什么我们可以这样定义函数指针呢?来自《c和指针》给出了这样的解释:函数名被使用时总是由编译器把它转换为函数指针,&操作符只是显示地说明了编译器将隐式执行的任务 。


2、妙用函数指针一: 函数指针数组

c语言协会定期集中讨论一次,每次讨论有一个主持者,每个主持者对应一个函数(函数功能可以是输出主持者姓名及讨论主题或者完成其他功能)。现在要编写这样一段程序,输入一个整数i(i>=0),根据输入的i调用不同主持者的函数。很快就能写出如下代码:

[cpp] view plain copy
  1. <span style="font-size:16px;">#include<stdio.h>  
  2.   
  3. //各个主持者对应的函数声明  
  4. void Touch();  
  5. void DuanJiong();  
  6. void MeiKai();  
  7. void YinJun();  
  8. void JiangHaiLong();  
  9.   
  10. void main()  
  11. {  
  12.     int i;  
  13.     scanf("%d",&i);  
  14.     switch(i){  
  15.     case 0:  
  16.         Touch();  
  17.         break;  
  18.     case 1:  
  19.         DuanJiong();  
  20.         break;  
  21.     case 2:  
  22.         MeiKai();  
  23.         break;  
  24.     case 3:  
  25.         YinJun();  
  26.         break;  
  27.     case 4:  
  28.         JiangHaiLong();  
  29.         break;  
  30.     }  
  31. }  
  32.   
  33. void Touch()  
  34. {  
  35.     puts("我是Touch");  
  36. }  
  37.   
  38. void DuanJiong()  
  39. {  
  40.     puts("我是段炯");  
  41. }  
  42.   
  43. void MeiKai()  
  44. {  
  45.     puts("我是梅凯");  
  46. }  
  47.   
  48. void YinJun()  
  49. {  
  50.     puts("我是殷俊");  
  51. }  
  52.   
  53. void JiangHaiLong()  
  54. {  
  55.     puts("我是木子");  
  56. }</span>  



这段代码有错误吗,肯定木有,运行结果完全正确。但是注意这里只列出了5种情况,如果总共有很多种情况呢,那么我们就要写一大堆的case语句。而且每次都是从case 1 开始判断。那么是否可以简化代码并且能让程序不做这么多判断呢?这就引出了函数指针数组,顾名思义,就是存放函数指针的数组。现主函数修改如下所示:

[cpp] view plain copy
  1. <span style="font-size:16px;">void main()  
  2. {  
  3.     int i;  
  4.     void (*p[])()={Touch,DuanJiong,MeiKai,YinJun,JiangHaiLong};  
  5.     scanf("%d",&i);  
  6.     p[i]();  
  7. }
    1. void Touch()  
    2. {  
    3.     puts("我是Touch");  
    4. }  
    5.   
    6. void DuanJiong()  
    7. {  
    8.     puts("我是段炯");  
    9. }  
    10.   
    11. void MeiKai()  
    12. {  
    13.     puts("我是梅凯");  
    14. }  
    15.   
    16. void YinJun()  
    17. {  
    18.     puts("我是殷俊");  
    19. }  
    20.   
    21. void JiangHaiLong()  
    22. {  
    23.     puts("我是木子");  
    24. }</span>  

  8.   
  9. </span>  

 

void (*p[])()={Touch,DuanJiong,MeiKai,YinJun,JiangHaiLong};声明了一个函数指针数组并赋值。把每个函数的入口地址存入这个数组,这样就不需要用switch语句了,根据下标i直接找到函数入口,省去了判断的时间。

 

3、妙用函数指针二: 回调函数

什么是回调函数,来着百度百科的解释:回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。这里函数指针是作为参数传递给另一个函数。

大家都写过冒泡排序吧,其代码如下:

[cpp] view plain copy
  1. <span style="font-size:16px;">//冒泡排序  
  2. void bubbleSort(int *a,int n)  
  3. {  
  4.     int i,j;  
  5.     for(i=1;i<n;i++)  
  6.         for(j=1;j<n-i+1;j++){  
  7.             if(a[j+1]<a[j]){  
  8.                 a[j]=a[j]+a[j+1];  
  9.                 a[j+1]=a[j]-a[j+1];  
  10.                 a[j]=a[j]-a[j+1];  
  11.             }  
  12.         }  
  13. }</span>  


请注意到这样一个不足,这个冒泡排序只能对int型数组进行排序。如果我们想写这样一个函数,能同时对int型、float型、double型、char型、结构体类型...数组进行排序,该怎么写呢?也许你会想到函数重载,但是C语言没有这个概念。这里可以用函数指针来实现,其代码比重载更简洁,更高效这也是函数指针的最大用处,参考代码:

 

[cpp] view plain copy
  1. <span style="font-size:16px;">//回调函数对多种数据类型数组进行冒泡排序  
  2. //a表示待排序数组  
  3. //n表示数组长度  
  4. //size表示数组元素大小(即每个数组元素占用的字节数)  
  5. //int (*compare)(void *,void *) 声明了一个函数指针,在此作为参数  
  6. //void *类型的指针表示指向未知类型的指针,编译器并不会给void类型的指针分配空间,但我们可以把它进行强制类型转换  
  7. void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))  
  8. {  
  9.     int i,j,k;  
  10.     char *p,*q;  
  11.     char temp;//交换时暂存一个字节的数据  
  12.     for(i=0;i<n;i++)  
  13.         for(j=0;j<n-i-1;j++){  
  14.             //注意p,q都是字符类型的指针,加一都只移动一个字节  
  15.             </span><span style="font-size:16px;"><span style="color:#cc0000;">p=(char*)a+j*size;  
  16. </span>         </span><span style="font-size:16px;"><span style="color:#cc0000;">q=(char*)a+(j+1)*size;  
  17. </span>         if(compare(p,q)>0){  
  18.                 //一个一个字节的交换,从而实现了一个数据类型数据的交换  
  19.                 for(k=0;k<size;k++){  
  20.                     temp=*p;  
  21.                     *p=*q;  
  22.                     *q=temp;  
  23.                     </span><span style="font-size:16px;"><span style="color:#cc0000;">p++;  
  24. </span>                 </span><span style="font-size:16px;"><span style="color:#cc0000;">q++;  
  25. </span>             }  
  26.             }  
  27.         }  
  28. }</span>  


请注意代码中红色部分代码,要看懂这段代码需明确两个问题:(1)void*类型的指针未分配空间的,我们可以把它进行强制类型转换成char*。(2)对数组元素进行交换时,并不是一次就把两个数交换了,因为我们并不知道数据的确切类型。但知道数组元素的大小,这样就可以逐个字节进行交换。比如对int类型(占用四个字节)的值a、b进行交换,先交换a、b的第一个字节,然后第二个字节...

理解了这个代码,该怎么用呢?参数要传入一个函数指针,于是必须要写一个比较两个数大小的函数,且函数原型必须与int (*compare)(void *,void *)相匹配。下面是测试各种类型数组排序的代码:

 

[cpp] view plain copy
  1. <span style="font-size:16px;">#include<stdio.h>  
  2.   
  3. typedef struct{  
  4.     int data;  
  5. }Node;  
  6.   
  7. //函数声明  
  8. int charCompare(void *a,void *b);  
  9. int intCompare(void *a,void *b);  
  10. int floatCompare(void *a,void *b);  
  11. int doubleCompare(void *a,void *b);  
  12. int nodeCompare(void *a,void *b);  
  13. void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *));  
  14.   
  15. //比较两个char类型的数据的大小,a>b返回1,a<b返回-1,a==b返回0  
  16. int charCompare(void *a,void *b)  
  17. {  
  18.     if(*(char*)a==*(char*)b)  
  19.         return 0;  
  20.     return *(char*)a>*(char*)b?1:-1;  
  21. }  
  22. //比较两个int类型的数据的大小  
  23. int intCompare(void *a,void *b)  
  24. {  
  25.     if(*(int*)a==*(int*)b)  
  26.         return 0;  
  27.     return *(int*)a>*(int*)b?1:-1;  
  28. }  
  29. //比较两个float类型的数据的大小  
  30. int floatCompare(void *a,void *b)  
  31. {  
  32.     if(*(float*)a==*(float*)b)  
  33.         return 0;  
  34.     return *(float*)a>*(float*)b?1:-1;  
  35. }  
  36. //比较两个double类型的数据的大小  
  37. int doubleCompare(void *a,void *b)  
  38. {  
  39.     if(*(double*)a==*(double*)b)  
  40.         return 0;  
  41.     return *(double*)a>*(double*)b?1:-1;  
  42. }  
  43. //比较两个结构体类型(Node)的数据的大小  
  44. int nodeCompare(void *a,void *b)  
  45. {  
  46.     if(((Node*)a)->data == ((Node*)b)->data)  
  47.         return 0;  
  48.     return ((Node*)a)->data > ((Node*)b)->data ? 1 : -1;  
  49. }  
  50.   
  51. void main()  
  52. {  
  53.     int i=0;  
  54.     //用于测试的各种类型数组  
  55.     char c[]={'d','a','c','e','b'};  
  56.     int a[]={3,2,4,0,1};  
  57.     float f[]={4.4,5.5,3.3,0,1};  
  58.     double b[]={4.4,5.5,3.3,0,1};  
  59.     Node n[]={{2},{0},{1},{4},{3}};  
  60.   
  61.     //对各种数组进行排序  
  62.     puts("对char类型数组进行排序:");  
  63.     bubbleSort(c,5,sizeof(char),charCompare);  
  64.     for(i=0;i<5;i++)  
  65.         printf("%c ",c[i]);  
  66.     puts("");  
  67.   
  68.     puts("对int类型数组进行排序:");  
  69.     bubbleSort(a,5,sizeof(int),intCompare);  
  70.     for(i=0;i<5;i++)  
  71.         printf("%d ",a[i]);  
  72.     puts("");  
  73.   
  74.     puts("对float类型数组进行排序:");  
  75.     bubbleSort(f,5,sizeof(float),floatCompare);  
  76.     for(i=0;i<5;i++)  
  77.         printf("%.2f ",f[i]);  
  78.     puts("");  
  79.   
  80.     puts("对double类型数组进行排序:");  
  81.     bubbleSort(b,5,sizeof(double),doubleCompare);  
  82.     for(i=0;i<5;i++)  
  83.         printf("%.2lf ",b[i]);  
  84.     puts("");  
  85.   
  86.     puts("对结构体(Node)类型数组进行排序:");  
  87.     bubbleSort(n,5,sizeof(Node),nodeCompare);  
  88.     for(i=0;i<5;i++)  
  89.         printf("%d ",n[i].data);  
  90.     puts("");  
  91. }  
  92.   
  93.   
  94. //回调函数对多种数据类型数组进行冒泡排序  
  95. //a表示待排序数组  
  96. //n表示数组长度  
  97. //size表示数组元素大小(即每个数组元素占用的字节数)  
  98. //int (*compare)(void *,void *) 声明了一个函数指针,在此作为参数  
  99. //void *类型的指针表示指向未知类型的指针,编译器并不会给void类型的指针分配空间,但我们可以把它进行强制类型转换  
  100. void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))  
  101. {  
  102.     int i,j,k;  
  103.     char *p,*q;  
  104.     char temp;//交换时暂存一个字节的数据  
  105.     for(i=0;i<n;i++)  
  106.         for(j=0;j<n-i-1;j++){  
  107.             //注意p,q都是字符类型的指针,加一都只移动一个字节  
  108.             p=(char*)a+j*size;  
  109.             q=(char*)a+(j+1)*size;  
  110.             if(compare(p,q)>0){  
  111.                 //一个一个字节的交换,从而实现了一个数据类型数据的交换  
  112.                 for(k=0;k<size;k++){  
  113.                     temp=*p;  
  114.                     *p=*q;  
  115.                     *q=temp;  
  116.                     p++;  
  117.                     q++;  
  118.                 }  
  119.             }  
  120.         }  
  121. }</span>  

运行结果:

再看看C语言标准库中的快速排序函数,它的实现原理及用法同上述冒泡排序

 

4、指针函数(返回指针的函数,确切的说是返回指针类型的函数)

 

[cpp] view plain copy
  1. <span style="font-size:16px;">#include<stdlib.h>  
  2. #include<stdio.h>  
  3.   
  4. //创建长度为n的动态数组  
  5. //这是一个指针函数  
  6. int* array(int n)  
  7. {  
  8.     int *a=(int*)malloc(sizeof(int)*n);  
  9.     return a;  
  10. }  
  11. void main()  
  12. {   
  13.     int i,n=3;  
  14.     int *a=array(n);  
  15.     for(i=0;i<n;i++)  
  16.         a[i]=i;  
  17.     free(a);//注意a不用时要free掉,否则内存泄露  
  18. }</span>  


5、参考资料

《C和指针》、《the c programming language》、《c语言程序设计》谭浩强版、c标准库、《冒泡排序 && 快速排序 》  、其它网上资料


0 0
原创粉丝点击