介绍一下函数指针

定义

函数指针是指向函数的指针变量。

函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。 C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。

用途

调用函数和做函数的参数,比如回调函数。

示例

char * fun(char * p)  {…}    // 函数fun
char * (*pf)(char * p);       // 函数指针pf 
pf = fun;            //函数指针pf指向函数fun 
pf(p);            //通过函数指针pf调用函数fun

如何理解函数指针

如果有int *类型变量,它存储的是int类型变量的地址;那么对于函数指针来说,它存储的就是函数的地址。函数也是有地址的,函数实际上由载入内存的一些指令组成,而指向函数的指针存储了函数指令的起始地址。如此看来,函数指针并没有什么特别的。我们可以查看程序中函数的地址:

#include <stdio.h>
int test()
{
    printf("this is test function");
    return 0;
}
int main(void)
{
    test();
    return 0;
}

编译:

gcc -o testFun testFun.c

查看test函数相对地址(并非实际运行时的地址):

$ nm testFun |grep test  #查看test函数的符号表信息
0000000000400526 T test

如何声明函数指针

声明普通类型指针时,需要指明指针所指向的数据类型,而声明函数指针时,也就要指明指针所指向的函数类型,即需要指明函数的返回类型和形参类型。例如对于下面的函数原型:

int sum(int,int);

它是一个返回值为int类型,参数是两个int类型的函数,那么如何声明该类型函数的指针呢?很简单,将函数名替换成(pf)形式即可,即我们把sum替换成(fp)即可,fp为函数指针名,结果如下:

int (*fp)(int,int);

这样就声明了和sum函数类型相同的函数指针fp。这里说明两点,第一,和fp为一体,说明了fp为指针类型,第二,fp需要用括号括起来,否则就会变成下面的情况:

int *fp(int,int);

这种情况下,意思就大相径庭了,它声明了一个参数为两个int类型,返回值为int类型的指针的函数,而不再是一个函数指针了。

在经常使用函数指针之后,我们很快就会发现,每次声明函数指针都要带上长长的形参和返回值,非常不便。这个时候,我们应该想到使用typedef,即为某类型的函数指针起一个别名,使用起来就方便许多了。例如,对于前面提到的函数可以使用下面的方式声明:

typedef int (*myFun)(int,int);//为该函数指针类型起一个新的名字
myFun f1;       //声明myFun类型的函数指针f1

上面的myFun就是一个函数指针类型,在其他地方就可以很方便地用来声明变量了。typedef的使用不在本文的讨论范围,但是特别强调一句,typedef中声明的类型在变量名的位置出现,理解了这一句,也就很容易使用typedef了。因而下面的方式是错误的:

typedef myFun (int)(int,int);   //错误
typedef (int)(int,int)  *myFun;   //错误

为函数指针赋值

赋值也很简单,既然是指针,将对应指针类型赋给它既可。例如:

#include<stdio.h>
int test(int a,int b)
{
    /*do something*/
    return 0
}
typedef int(*fp)(int,int);
int main(void)
{
    fp f1 = test; //表达式1
    fp f2 = &test;//表达式2
    printf("%p\n",f1);
    printf("%p\n",f2);
    return 0;
}

在这里,声明了返回类型为int,接受两个int类型参数的函数指针f1和f2,分别给它们进行了赋值。表达式1和表达式2在作用上并没有什么区别。因为函数名在被使用时总是由编译器把它转换为函数指针,而前面加上&不过显式的说明了这一点罢了。

调用

调用也很容易,把它看成一个普通的函数名即可:

#include<stdio.h>
int test(int a,int b)
{
    /*do something*/
    printf("%d,%d\n",a,b);
    return 0
}
typedef int(*fp)(int,int);
int main(void)
{
    fp f = test; 
    f(1,2);//表达式1
    (*f)(3,4);//表达式2
    return 0;
}

在函数指针后面加括号,并传入参数即可调用,其中表达式1和表达式2似乎都可以成功调用,但是哪个是正确的呢?ANSI C认为这两种形式等价。

函数指针有何用

函数指针的应用场景比较多,以库函数qsort排序函数为例,它的原型如下:

void qsort(void *base,size_t nmemb,size_t size , int(*compar)(const void *,const void *));

看起来很复杂对不对?拆开来看如下:

void qsort(void *base, size_t nmemb, size_t size, );

拿掉第四个参数后,很容易理解,它是一个无返回值的函数,接受4个参数,第一个是void*类型,代表原始数组,第二个是size_t类型,代表数据数量,第三个是size_t类型,代表单个数据占用空间大小,而第四个参数是函数指针。这第四个参数,即函数指针指向的是什么类型呢?

int(*compar)(const void *,const void *)

很显然,这是一个接受两个const void*类型入参,返回值为int的函数指针。
到这里也就很清楚了。这个参数告诉qsort,应该使用哪个函数来比较元素,即只要我们告诉qsort比较大小的规则,它就可以帮我们对任意数据类型的数组进行排序。

在这里函数指针作为了参数,而他同样可以作为返回值,创建数组,作为结构体成员变量等等,它们的具体应用我们在后面的文章中会介绍,本文不作展开。本文只介绍一个简单实例。

介绍一下虚函数

拥有Virtual 关键字的函数称之为虚函数,虚函数的作用是实现动态绑定的,也就是说程序在运行的时候动态的的选择合适的成员函数。要成为虚函数必须满足两点:

一就是这个函数依赖于对象调用,因为虚函数就是依赖于对象调用,因为虚函数是存在于虚函数表中,有一个虚函数指针指向这个虚表,所以要调用虚函数,必须通过虚函数指针,而虚函数指针是存在于对象中的。

二就是这个函数必须可以取地址,因为我们的虚函数表中存放的是虚函数函数入口地址,如果函数不能寻址,就不能成为虚函数。

虚函数主要用于实现多态,有纯虚函数和非纯虚函数。纯虚函数需要子类必须重写父类函数。虚函数的存在使得类不可以实例化。技术上实现是virtual =0.虚函数以函数指针的形式存放在虚函数表里。初始存放的是父类虚函数的地址。若子类重写了父类的函数,则对应地址被覆盖为相应子类函数的地址。



实习      C++ 牛课网

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!