函数指针和指针函数

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

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

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

#include<stdio.h>

void f(int);

int main()
{
    //定义函数指针pf并给pf赋值使其指向函数f的入口地址
    //pf先跟*结合,说明pf是一个指针,然后与括号结合,说明这个指针指向函数
    void (*pf)(int)=f;    //等价于void (*pf)(int)=&f;
    pf(1);
    (*pf)(2);//把函数指针转换成函数名,这个转换并不需要
    f(3);
    return 0;
}

void f(int a)
{
    printf("%d\n",a);
}

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

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

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

#include<stdio.h>

//各个主持者对应的函数声明
void Touch();
void DuanJiong();
void MeiKai();
void YinJun();
void JiangHaiLong();

void main()
{
    int i;
    scanf("%d",&i);
    switch(i){
    case 0:
        Touch();
        break;
    case 1:
        DuanJiong();
        break;
    case 2:
        MeiKai();
        break;
    case 3:
        YinJun();
        break;
    case 4:
        JiangHaiLong();
        break;
    }
}

void Touch()
{
    puts("我是Touch");
}

void DuanJiong()
{
    puts("我是段炯");
}

void MeiKai()
{
    puts("我是梅凯");
}

void YinJun()
{
    puts("我是殷俊");
}

void JiangHaiLong()
{
    puts("我是木子");
}

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

void main()
{
    int i;
    void (*p[])()={Touch,DuanJiong,MeiKai,YinJun,JiangHaiLong};
    scanf("%d",&i);
    p[i]();
}

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

 

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

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

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

//冒泡排序
void bubbleSort(int *a,int n)
{
    int i,j;
    for(i=1;i<n;i++)
        for(j=1;j<n-i+1;j++){
            if(a[j+1]<a[j]){
                a[j]=a[j]+a[j+1];
                a[j+1]=a[j]-a[j+1];
                a[j]=a[j]-a[j+1];
            }
        }
}

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

 

//回调函数对多种数据类型数组进行冒泡排序
//a表示待排序数组
//n表示数组长度
//size表示数组元素大小(即每个数组元素占用的字节数)
//int (*compare)(void *,void *) 声明了一个函数指针,在此作为参数
//void *类型的指针表示指向未知类型的指针,编译器并不会给void类型的指针分配空间,但我们可以把它进行强制类型转换
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))
{
    int i,j,k;
    char *p,*q;
    char temp;//交换时暂存一个字节的数据
    for(i=0;i<n;i++)
        for(j=0;j<n-i-1;j++){
            //注意p,q都是字符类型的指针,加一都只移动一个字节
            p=(char*)a+j*size;
                        q=(char*)a+(j+1)*size;
          if(compare(p,q)>0){
                //一个一个字节的交换,从而实现了一个数据类型数据的交换
                for(k=0;k<size;k++){
                    temp=*p;
                    *p=*q;
                    *q=temp;
                    p++;
                    q++;
            }
            }
        }
}         

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

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

#include<stdio.h>

typedef struct{
    int data;
}Node;

//函数声明
int charCompare(void *a,void *b);
int intCompare(void *a,void *b);
int floatCompare(void *a,void *b);
int doubleCompare(void *a,void *b);
int nodeCompare(void *a,void *b);
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *));

//比较两个char类型的数据的大小,a>b返回1,a<b返回-1,a==b返回0
int charCompare(void *a,void *b)
{
    if(*(char*)a==*(char*)b)
        return 0;
    return *(char*)a>*(char*)b?1:-1;
}
//比较两个int类型的数据的大小
int intCompare(void *a,void *b)
{
    if(*(int*)a==*(int*)b)
        return 0;
    return *(int*)a>*(int*)b?1:-1;
}
//比较两个float类型的数据的大小
int floatCompare(void *a,void *b)
{
    if(*(float*)a==*(float*)b)
        return 0;
    return *(float*)a>*(float*)b?1:-1;
}
//比较两个double类型的数据的大小
int doubleCompare(void *a,void *b)
{
    if(*(double*)a==*(double*)b)
        return 0;
    return *(double*)a>*(double*)b?1:-1;
}
//比较两个结构体类型(Node)的数据的大小
int nodeCompare(void *a,void *b)
{
    if(((Node*)a)->data == ((Node*)b)->data)
        return 0;
    return ((Node*)a)->data > ((Node*)b)->data ? 1 : -1;
}

void main()
{
    int i=0;
    //用于测试的各种类型数组
    char c[]={'d','a','c','e','b'};
    int a[]={3,2,4,0,1};
    float f[]={4.4,5.5,3.3,0,1};
    double b[]={4.4,5.5,3.3,0,1};
     Node n[]={{2},{0},{1},{4},{3}};

    //对各种数组进行排序
    puts("对char类型数组进行排序:");
    bubbleSort(c,5,sizeof(char),charCompare);
    for(i=0;i<5;i++)
        printf("%c ",c[i]);
    puts("");

    puts("对int类型数组进行排序:");
    bubbleSort(a,5,sizeof(int),intCompare);
    for(i=0;i<5;i++)
        printf("%d ",a[i]);
    puts("");

    puts("对float类型数组进行排序:");
    bubbleSort(f,5,sizeof(float),floatCompare);
    for(i=0;i<5;i++)
        printf("%.2f ",f[i]);
    puts("");

    puts("对double类型数组进行排序:");
    bubbleSort(b,5,sizeof(double),doubleCompare);
    for(i=0;i<5;i++)
        printf("%.2lf ",b[i]);
    puts("");

    puts("对结构体(Node)类型数组进行排序:");
    bubbleSort(n,5,sizeof(Node),nodeCompare);
    for(i=0;i<5;i++)
        printf("%d ",n[i].data);
    puts("");
}

//回调函数对多种数据类型数组进行冒泡排序
//a表示待排序数组
//n表示数组长度
//size表示数组元素大小(即每个数组元素占用的字节数)
//int (*compare)(void *,void *) 声明了一个函数指针,在此作为参数
//void *类型的指针表示指向未知类型的指针,编译器并不会给void类型的指针分配空间,但我们可以把它进行强制类型转换
void bubbleSort(void *a,int n,int size,int (*compare)(void *,void *))
{
    int i,j,k;
    char *p,*q;
    char temp;//交换时暂存一个字节的数据
    for(i=0;i<n;i++)
        for(j=0;j<n-i-1;j++){
            //注意p,q都是字符类型的指针,加一都只移动一个字节
            p=(char*)a+j*size;
            q=(char*)a+(j+1)*size;
            if(compare(p,q)>0){
                //一个一个字节的交换,从而实现了一个数据类型数据的交换
                for(k=0;k<size;k++){
                    temp=*p;
                    *p=*q;
                    *q=temp;
                    p++;
                    q++;
                }
            }
        }
}

运行结果:

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

 

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

#include<stdlib.h>
#include<stdio.h>

//创建长度为n的动态数组
//这是一个指针函数
int* array(int n)
{
    int *a=(int*)malloc(sizeof(int)*n);
    return a;
}
void main()
{
    int i,n=3;
    int *a=array(n);
    for(i=0;i<n;i++)
        a[i]=i;
    free(a);//注意a不用时要free掉,否则内存泄露
}

5、参考资料

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

时间: 2024-09-17 01:30:11

函数指针和指针函数的相关文章

C++指针探讨(四)函数对象

函数对象不是函数指针.但是,在程序代码中,它的调用方式与函数指针一样,后面加个括号就可以了. 这是入门级的随笔,说的是函数对象的定义,使用,以及与函数指针,成员函数指针的关系. 沐枫小筑 函数对象实质上是一个实现了operator()--括号操作符--的类. 例如: class Add { public: int operator()(int a, int b) { return a + b; } }; Add add: // 定义函数对象 cout << add(3,2): // 5 函数指

C++指针探讨(二)函数指针

在C/C++中,数据指针是最直接,也最常用的,因此,理解起来也比较容易.而函数指针,作为运行时动态调用(比如回调函数 CallBack Function)是一种常见的,而且是很好用的手段. 我们先简单的说一下函数指针.(这一部份没什么价值,纯是为了引出下一节的内容) 2 常规函数指针 void(*fp)(): fp 是一个典型的函数指针,用于指向无参数,无返回值的函数. void(*fp2)(int): fp2 也是一个函数指针,用于指向有一个整型参数,无返回值的函数. 当然,有经验人士一般都会

C语言之free函数以及野指针介绍

以下是对C语言中的free函数与野指针进行了详细的分析介绍,需要的朋友可以参考下   [FROM MSDN && 百科]原型:void free(void *ptr); #include<stdlib.h>或#include <malloc.h> Deallocate space in memory 释放ptr指向的存储空间.被释放的空间通常被送入可用存储区池,以后可在调用malloc.realloc以及realloc函数来再分配.注意:连续两次使用free函数,肯

函数指针与指针函数的学习总结

函数指针是指向函数的指针,指针函数是指一个函数的返回值是一个指针,但下面的几道题还是感觉很迷惑.各位能否讲的详细点呢? (1) float(**def)[10]   def是什么? (2) double*(*gh)[10]   gh是什么? (3) double(*f[10])()   f是什么? (4) int*((*b)[10])    b是什么?这样老感觉有点乱,有什么窍门可以记得并理解的清楚一点么? ======================解答:   (1) def是一个指针, 指向

深入解析函数指针与返回函数的指针

以下是对函数指针与返回函数的指针进行了详细的分析介绍,需要的朋友可以过来参考下   先看看以下两个代码:1:出自STL-SGI源码<stl_alloc.h> 复制代码 代码如下: static void (*__set_malloc_handler(void (*__f)()))() {  void (*__old)()=__malloc_alloc_oom_handler;  __malloc_alloc_oom_handler=__f;  return (__old); } 2:Linux

c++函数指针和回调函数示例

  这篇文章主要介绍了c++函数指针和回调函数示例,需要的朋友可以参考下 1.函数指针 函数指针是一个指针,只是这个指针它不像普通的指针指向是是一个变量,此时它指向的是一个函数,也就是它存储的是一个函数的地址,如果我们改变它的值,让它所指向的地址由指向funA转变为指向funB,那么这个函数指针的作用就改变了. 2.回调函数 什么是回调函数呢?回调函数其实就是一个通过函数指针调用的函数!假如你把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数,这就是回调机制.B

C++中的指针、数组指针与指针数组、函数指针与指针函数

C++中的指针.数组指针与指针数组.函数指针与指针函数 本文从初学者的角度,深入浅出地详解什么是指针.如何使用指针.如何定义指针.如何定义数组指针和函数指针,并给出对应的实例演示:接着,区别了数组指针与指针数组.函数指针与指针函数:最后,对最常混淆的引用传递.值传递和指针传递做了区处. C++中一个重要的特性就是指针,指针不仅具有获得地址的能力,还具有操作地址的能力.指针可以用于数组.或作为函数的参数,用来访问内存和对内存的操作,指针的使用使得C++很高效,但是指针也非常危险,使用不当会带来比较

ucosiii中关于函数参数枚举指针

问题描述 ucosiii中关于函数参数枚举指针 这个指针不是要等这个函数运行结束,根据运行的结果,把结果赋值给他吗?为什么在函数的前面就判断了呢? 解决方案 http://blog.sina.com.cn/s/blog_98ee3a930102v9pi.html 解决方案二: 指针作为函数参数指针与函数参数指针作为函数参数(备忘)

c语言-关于c++在函数中创建指针的两个问题

问题描述 关于c++在函数中创建指针的两个问题 解决方案 delete[] ps,是释放了ps指向的内存,但是并没有擦除原内存中的数据,所以在该内存未被写入其他数据之前,还能输出ps: cout<<&p,输出的并非指针(动态申请的地址),而是局部变量p的地址,所以不变(输出的栈地址) 解决方案二: 第一个问题: delete确实已经释放了ps所指向的那块内存,但所谓释放并不是清空,而是把那块内存重新回收到系统中,告诉系统这块内存又可以重新分配了. delete的时候,系统只是将指针指向

向函数中传递指针和传递指针的引用的区别

如果是传递指针,那么会先复制该指针,在函数内部使用的是复制后的指针,这个指针与原来的指针指向相同的地址,如果在函数内部将复制后的指针指向了另外的新的对象,那么不会影响原有的指针:但 是对于传递指针应用,如果将传递进来的指针指向了新的对象,那么原始的指针也就指向了新的对象,这样就会造成内存泄漏,因为原来指针指向的地方已经不能再 引用了,即使没有将传递进来的指针指向新的对象,而是在函数结束的时候释放了指针,那么在函数外部就不能再使用原有的指针了,因为原来的内存已经被释放了   看个例子: #incl