【C++深陷】之“函数指针”

    技术2022-07-10  135

    0. 什么是函数指针

    指针是C++中的一种复合类型,是“指向(pointer to)”另外一种类型的复合类型,实现了对其他对象的间接访问。

    函数指针指向的不是对象,而是函数。它基于函数类型的定义。

    回想函数的三要素:返回类型、函数名、形参类型,函数类型指的就是由返回类型和形参类型共同决定的。

    bool shorter(const string &, const string &); // 函数的类型是 // bool(const string &, const string &) // 是这样的一类函数: // 返回值是bool类型,需要两个const string&类型的函数

    此时,可以将函数名位置用指针替换,就声明了指向上述函数类型的指针:

    bool (*pf)(const string &, const string &); // 未初始化

    解析上面的声明,依旧是按照从内向外、先右后左的口诀。

    很多读者看到函数指针的声明和定义方法就望而却步了,但其实记住从内向外、先右后左以及学会类型处理,理解函数指针很简单。

    关于函数指针,我们需要了解:

    函数指针的声明&定义函数指针的使用类型处理(typedef、using、auto、decltype)

    1. 函数指针的声明&定义

    // 声明了一个函数 bool shorter(const string &, const string &); // 声明并初始化了一个函数指针 bool (*pf)(const string &, const string &) = shorter; // 可以省略取地址运算符&,也可以使用 // bool (*pf)(const string &, const string &) = &shorter;

    注意:(*pf)的括号必不可少。

    上述代码在初始化pf时,并没有在等号=右侧使用&,原因是①把函数名作为一个值使用时,函数自动转换成指针(除了decltype)。

    2. 函数指针的使用

    2.1 函数指针的调用

    定义了一个有明确指向的函数指针后,可直接通过调用运算符()调用该函数:

    pf("hello", "goodbye");

    可以不解引用指针,也可以解引用,但是必须加括号:

    (*pf)("hello", "goodbye");

    其实,带有明确指向的函数指针属于可调用对象(callable object)。

    2.2 函数指针作为形参

    我们可以声明一个具有函数指针形参的函数:

    void callCompare(bool pf(const string &, const string &), int count); // 第一个参数是函数类型为 // bool(const string &, const string &) // 的函数指针

    注意:此处虽然看起来是函数类型的声明,但实际上②函数类型作为形参会自动的转换为相应类型的函数指针。当然写成指针类型也是可以的。

    void callCompare(bool (*pf)(const string &, const string &), int count); // 和上面等价,显式添加了*

    在使用callCompare时:

    callCompare(shorter, 1);

    这里也没有用取地址运算符&,和1.1叙述的原因一样。

    2.3 函数指针作为返回值

    假如我想定义一个返回与shorter同类型函数指针的函数,该如何声明呢?

    从里向外,先右后左。

    首先确定我想定义的函数的三要素:

    函数名,假设retFunc参数列表,假设接受一个int返回值,一个与shorter同类型函数指针bool (*)(const string &, const string &)

    自然而然想到可以这么写:

    // ERROR,这是错误的 bool (*)(const string &, const string &) retFunc(int);

    这么写一定是不行的,因为左边一大坨不符合编译器编译的原理,从retFunc向左看到了参数列表,完全不知道是什么。

    因此必须把指针*放在retFunc的左边。

    bool (*retFunc(int))(const string &, const string &);

    ①从标识符开始,向右看到了参数列表,因此retFunc是一个函数。

    ②右括号,不能向右了,向左,看到了指针*,因此返回的是一个指针。

    ③看到左括号,向外,继续向右,看到参数列表,因此返回的是函数指针。

    ④再向左,看到了bool,可以确定了返回的函数指针的类型。

    3. 类型处理

    若每次都像上面一样使用这么复杂的类型定义,程序一定很乱。

    这时,typedef、using、auto、decltype就派上了用场。

    我们可以用typedef、using给函数类型声明一个简单的名字;在明确知道使用的是哪个函数时,可以使用auto和decltype。

    例如:

    // 声明了一个函数类型 using FuncType = int(int &, int); // typedef int FuncType(int &, int); // using PFuncType = int (*)(int &, int); typedef int (*PFuncType)(int &, int); // 下面的函数就是上面的类型 int add_to(int &des, int ori) { return des += ori; } int minus_to(int &des, int ori) { return des -= ori; } int multiply_to(int &des, int ori) { return des *= ori; } int divide_to(int &res, int ori) { return res /= ori; } int main() { FuncType *pf0 = add_to; auto *pf1 = minus_to; decltype(multiply_to) *pf2 = multiply_to; PFuncType pf3 = divide_to; int a = 4; // 通过函数指针调用 pf0(a, 2); // 6 pf1(a, 3); // 3 pf2(a, 4); // 12 pf3(a, 3); // 4 }

    这样的声明就简单多了。

    注意:decltype和auto不会自动的获得函数指针类型,都需要添加*。

    4. 总结

    函数类型是根据函数定义的返回值和形参列表抽象出来的一类函数。

    可以使用函数类型定义指向同种类型的函数指针。

    使用函数指针可以间接调用函数,且可以实现函数指针作为形参、函数指针作为返回值等操作。

    在使用函数指针时,使用类型别名(typedef、using)和类型推断(auto、decltype),可以使代码更清晰。

    Processed: 0.015, SQL: 9