产品开发这几年(1)函数指针

        函数指针是一个熟悉又陌生的主题,很多人都接触过函数指针,但却很少主动使用。然而,不讨论函数指针对于C/C++而言是不完整的,很多文献均有阐述,且大家都明白其本质上是一种特殊类型的指针。另一方面,尽管很多产品中都有使用函数指针,但其使用频率远远低于常见的for循环及if-else语句,因此仍较陌生。本系列文章最先介绍函数指针,为后续表驱动阐述奠定基础。本文所有阐述采用C/C++,不再赘述。

        很多人认为指针是C的精华,而事实上指针确实不负众望,以其巨大的魅力征服了诸多程序员,因此是软件设计开发中最常见的语法。然而,一般对指针的使用仅限定于普通数据类型,如int、double或自定义类型等。实际上,函数指针同样拥有巨大的魅力,甚至很多时候是程序设计的不二选择。

        以下从几个方面简述函数指针相关的基本概念。

1、  函数的地址

        变量有地址,即存储变量内存的存储区域地址,通过操作符&可以获取普通变量或对象的地址。如下代码中,通过操作符&获取int变量var的地址,而ptr则指向该地址。

int var = 0;
int *ptr = &var;

        与变量类似,函数也有地址,那什么是函数的地址呢?所谓函数的地址,即为存储函数代码的存储区域的地址。函数地址的获取无需操作符&,直接使用函数名即可。换而言之,函数名即为指向该函数的指针,其本身便是函数的地址。

        既然函数也有地址,与普通指针变量类似,也可以定义函数指针。定义函数指针后,不仅可直接调用其指向的函数,还可以与普通指针一样作为函数形参。关于函数指针的声明及使用,详见下文。

2、  函数指针声明及初始化

        声明普通指针变量时需要指定具体类型,如int、void或自定义类型等,函数指针同样需要指定具体的函数类型。用什么标识函数类型呢?函数声明由函数名、返回类型及形参列表三部分组成,而函数名标识了函数的地址,而真正标识函数类型的便是返回类型及形参列表。因此,声明函数指针时必须指定相关的返回类型及形参列表,基本形式如下:

返回类型 (*函数指针名称)(形参列表)

        上述声明形式是最基本的形式,并不涉及初始化等。而需要强调的是,上述声明形式中的两个括弧是不可缺少的,尤其函数指针名称的括弧。仔细观察不难发现,如果缺少了函数指针名称的括弧,那上述声明形式本质上便声明了一个函数。

        根据上述声明形式,可声明相应的函数指针,如下所示。

int (*pf1)(int, int);
double* (*pf2)(double);
        函数指针可以用0来初始化或赋值,以表示该指针不指向任何函数,如下所示。

int (*pf1)(int, int) = 0;

double* (*pf2)(double);
pf2 = 0;
        声明函数指针后,便可以像普通变量那样为其赋值,以指向类型相同的特定函数,如下所示。

int Max(int a, int b)
{
    return a >= b ? a : b;
}

int (*pf1)(int, int);
pf1 = Max;
        需要说明的是,在函数指针进行初始化或赋值时,其指向的对象类型必须和所声明的函数指针类型匹配,包括返回类型与形参列表,否则便是非法的。如下所示即为赋值错误,因为函数指针pf2的返回类型与形参列表与函数Max()不匹配。

int Max(int a, int b)
{
    return a >= b ? a : b;
}

double* (*pf2)(double) = Max;    // Invalid

3、  函数指针使用

        函数指针的使用主要体现在两方面,即直接调用、作为函数形参。直接调用是函数指针最简明的使用方式,如下代码所示。

#include <iostream>

using namespace std;

int Max(int a, int b)
{
    return a >= b ? a : b;
}

int main(int argc, _TCHAR* argv[])
{
    int (*pf)(int, int) = Max;

    int a = Max(2, 5);
    cout<<a<<endl;

    int b = (*pf)(2, 5);
    cout<<b<<endl;

    int c = pf(2, 5);
    cout<<c<<endl;

	return 0;
}

        上述对函数指针直接调用的代码中,三种调用最终返回结果是相同的。第一种方式是直接调用函数Max(),而后两种方式则调用指向函数Max()的指针pf。很多人很奇怪为何后两种看似矛盾的使用方式是合法的,pf与(*pf)二者竟然等价!实际上对二者的使用是有争议的:一种观点认为pf是指针,则(*pf)是函数,故应该使用(*pf)的方式调用;另一种观点则认为函数名本身便是函数指针,而调用函数直接使用函数名即可,故而函数指针的调用应该与之类似,即直接使用pf调用。尽管逻辑上二者是冲突的,但却都有各自的道理,故而进行了无奈的折衷,同时允许两种使用方式的存在。

        虽然函数指针pf与(*pf)的两种调用方式均是合法的,但个人推荐使用(*pf),因为其明确指出pf本身是指针函数,避免了混淆,有利于团队开发维护中的一致性。

        函数指针另一种广泛使用的方式便是作为函数形参,尤其在标准库中非常常见。如下代码中,函数Max()与Sum()以函数指针的形式传入函数Statistic(),以完成不同的功能。

#include <iostream>

using namespace std;

int Max(int a, int b)
{
    return a >= b ? a : b;
}

int Sum(int a, int b)
{
    return (a + b);
}

int Statistic(int a, int b, int (pf)(int, int))
{
    return (*pf)(a, b);
}

int main(int argc, _TCHAR* argv[])
{
    int m = Statistic(2, 5, Max);
    cout<<m<<endl;

    int n = Statistic(2, 5, Sum);
    cout<<n<<endl;

	return 0;
}

4、  使用typedef简化函数指针声明

        以上对函数指针的声明、初始化、赋值及使用进行了简述,至此,已可正常使用函数指针了。然而,函数指针的声明相当繁琐,尤其当需要声明某一类型函数的多个函数指针时,上述声明方法是十分笨拙的,导致函数指针的形参列表大量重复呈现。程序员从来都是懒惰的,这种惰性促使程序员不断寻找更加优美的方法,而最常用的一种方法是使用typedef简化定义函数指针声明。

        关键字typedef的功能是创建类型的别名,借助该特性可按照如下代码所示形式声明函数指针。其中,pf成为新的类型名,fun则为该类型的一个变量,如此定义函数指针变量则十分简明。

typedef int (*pf)(int, double);
pf fun;
        根据上述代码,可抽象得到typedef简化函数指针声明的基本形式,如下所示。其中,函数指针类型名称可看做前述的函数指针名称的替代。

typedef 返回类型 (*函数指针类型名称)(形参列表)

5、  函数指针与指针函数

        经常有人纠结函数指针与指针函数的区别,实际上二者确实不是一个概念。

        每个函数均有返回类型,一般为int、double或void等,但也可以返回指针类型,而返回指针类型的函数即为指针函数,如下所示。

int* f1(int a);
double* f2(double, int);
void* f3(void);
        对上述指针函数进行变更,即将返回类型的操作符*与函数名一起放入括弧中,其本质便发生了变化,成为真正的函数指针了,如下所示。

int (*f1)(int, double);
double (*f2)(double, int);
void (*f3)(void);

        因此,通常在声明指定特定类型的函数指针时,可先编写该函数的原型,然后使用(*pf)替换函数名,如此pf即为该类函数的指针。

        简而言之,函数指针是一个指向函数的指针,其本质是一个指针;而指针函数则指返回值为指针的函数,其本质是一个函数。

 

        以上对函数指针的相关概念进行了简单讨论,更深入的探讨分析可参考C/C++相关文献及标准,在此不再赘述。至此,有人不禁会问到底什么时候使用函数指针呢?该问题是软件工程的范畴,本系列文章并不深究,但在后面的表驱动文章中大家可以看到一种函数指针的经典甚至是无可替代的应用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值