写点什么

C++ 函数指针详解

  • 2024-10-08
    福建
  • 本文字数:8124 字

    阅读完需:约 27 分钟

概述


本文详细介绍了 C/C++中的普通函数和类的成员函数的指针。结合 C++代码示例讲解了函数指针作为其他函数的输入、返回值以及 typedef 如何提高代码可读性的实用技巧。对于类的成员函数(方法)指针,则分为静态和非静态两种情况。最后总结了普通函数、类的非静态成员函数、类的静态成员函数的声明、赋值和定义的 C++语法以供查阅。


普通函数的指针


声明、定义和赋值


首先让我们来区别以下 4 条声明语句:


int x;      // x是一个int型的变量int *x;     // x是一个指向int型变量的指针int *x();   // x是一个返回int型指针的函数int (*x)(); // x是一个函数指针,输入参数为空,返回类型为int型指针
复制代码


因此,把一个名为 fun 的函数的声明语句变为函数指针变量 pfun 的声明语句,只需要将 fun 变为 (*pfun),其他的不变即可(注意:小括号必不可少!)。


double fun(string& str1, string &str2);       // 函数fun的声明double (*pfun)(string& str1, string &str2);   // 函数指针变量pfun的声明
复制代码


在 C/C++中,数组变量的名字是一个指向该数组元素类型的指针常量(存放首元素的地址),类似地,函数的名字也表示一个指向该函数的指针常量(存放该函数入口地址):


int array[5];      // array是一个指向int的指针常量int *p = array;    // p是一个指向int的指针变量,p = array合法p[3];              // 等价于array[3]
void test(int a){ cout << "a=" << a << endl;} // test是一个函数指针常量,输入参数int,返回类型voidvoid (*pf)(int); // 声明函数指针pf: pf是一个输入参数int,返回void的函数的指针pf = test; // 将test函数地址赋值给pf(二者输入参数列表、返回类型必须一致)pf(123); // 等价于test(123)
复制代码


有几点需要说明:


// 声明和赋值可以写在一起:void (*pf)(int) = test;  
// 2种赋值方式:(等价)pf = test; // 直接函数名赋值pf = &test; // 函数名取地址赋值,等价于pf = test
// 2种调用方式:(等价)pf(123); // 直接pf接参数调用(*pf)(123); // 先对函数指针解引用,再接参数调用,等价于pf(123)
复制代码


例 1:


#include <iostream>using namespace std;
int add(int x, int y){ return x + y;}
int substract(int x, int y){ return x - y;}
int main(){ int (*fp)(int, int); // 定义函数指针变量:fp fp = add; // 将同类型函数add赋值给fp cout << fp(1, 3) << endl; cout << (*fp)(1, 3) << endl; // 等价于fp(1,3) fp = &substract; // 将同类型函数substract赋值给fp, 取地址符号"&"可加可不加 cout << fp(1, 3) << endl; cout << (*fp)(1, 3) << endl; // 等价于fp(1,3) return 0;}
复制代码


输出:


44-2-2
复制代码


重载的函数自动匹配类型


如果有同名的重载函数,编译器会自动选择类型匹配的一个:


#include <iostream>using namespace std;
int add(int x, int y){ return x + y;}
int add(int x, int y, int z){ // 重载 return x + y + z;}
int main(){ int (*fp)(int, int) = add; // 编译器会自动选择add(int x, int y)与fp匹配 cout << "1 + 2 = " << fp(1,2) << endl;
int (*fp2)(int, int, int) = add; // 编译器会自动选择add(int x, int y, int z)与fp2匹配 cout << "1 + 2 + 3 = " << fp2(1,2,3) << endl; return 0;}
复制代码


输出:


1 + 2 = 31 + 2 + 3 = 6
复制代码


类型复杂的函数指针和 typedef 的妙用


当一个函数的输入列表、返回列表较为复杂时,尤其是参数/返回值中还嵌套了函数指针时,代码的可读性将会大大下降。


1、输入参数包含函数指针


函数指针作为输入参数(形参)有两种写法:将输入参数 int 变为 int (*pf)(int, int),表示输入参数为函数指针 pf(指向函数类型为 int (*pf)(int, int))。


  • void test(int fun(int, int)) // 直接把 fun 函数的声明写在参数的位置,在 test 内部 fun 作为形参变量,表示函数指针


  • void test(int (*pf)(int, int)) // 将函数指针的声明写在参数的位置,在 test 内部 pf 作为形参变量,与上面写法等价


例 2:下面例子中的 add2()和 add3() 就是输入参数为函数声明/函数指针的两种等价写法。


#include <iostream>using namespace std;
int add(int x, int y){ // 双参数函数add2:两个数相加 return x + y;}
int add2(int (*fun)(int, int), int x, int y, int z){ // 使用函数声明语句作为形参,构造三参数函数add2() return fun(fun(x,y),z);}
int add3(int fun(int, int), int x, int y, int z){ // 与上面等价, 使用函数指针作为形参,构造三参数函数add3() return fun(fun(x,y),z);}
int main(){ int (*fp)(int, int); fp = add; cout << "1 + 2 = " << fp(1,2) << endl;
int (*fp2)(int (*fun)(int, int), int, int, int); // 第一个参数是双参数函数指针,第二到四个参数为int fp2 = add2; // fp2赋值为类型一致的函数名 cout << "1 + 2 + 3 = " << fp2(fp,1,2,3) << endl;
fp2 = add3; // 与上面等价,add3和add2实际是一样的,都能被fp2接收 cout << "4 + 5 + 6 = " << fp2(fp,4,5,6) << endl; return 0;}
复制代码


输出结果:


1 + 2 = 31 + 2 + 3 = 64 + 5 + 6 = 15
复制代码


2、返回值为函数指针


函数指针作为返回值的写法:只需要将 int fun(int)变成 int (*fun(int))(double, double), 表示 fun(int)返回一个函数指针 pf(指向类型为 int (*pf)(double, double))。


  • int (*createAdd())(int, int); // 定义一个名为 createAdd()的函数,输入参数为空,返回值为一个函数指针 pTemp(其类型为 int (pTemp)(int, int))


  • int (*createAlgorithm(int type))(int, int); // 定义一个名为 createAlgorithm(int)的函数:输入参数为 int,返回值为对应运算符的函数指针 pTemp(其类型为 int (*pTemp)(int, int))


例 3:无参数输入的函数 createAdd()函数返回一个函数指针:


#include <iostream>using namespace std;
int add(int x, int y){ return x + y;}
// 定义一个名为createAdd()的函数// 输入参数为空// 返回值为一个函数指针pTemp(其类型为int (*pTemp)(int, int))int (*createAdd())(int, int){ return add;}
int main(){ int (*fp)(int, int); // 声明一个函数指针fp fp = createAdd(); // fp接收createAdd()返回值,二者都是同类型的函数指针,匹配 cout << "1 + 2 = " << fp(1,2) << endl; return 0;}
复制代码


输出:


1 + 2 = 3
复制代码


例 4:带参数输入的 createAlgorithm(int type)函数返回一个函数指针:


#include <iostream>using namespace std;
// 定义三种运算:空运算、加法、减法int none(int , int ){ return 0;}int add(int x, int y){ return x + y;}int substract(int x, int y){ return x - y;}
// 定义一个名为createAlgorithm(int)的函数:// 输入参数为int,表示选择哪一种运算符(1为加法,2为减法)// 返回值为对应运算符的函数指针pTemp(其类型为int (*pTemp)(int, int))int (*createAlgorithm(int type))(int, int){ switch(type){ case 1: return add; break; case 2: return substract; break; default: return none; }}
int main(){ int (*fp)(int, int); // 声明一个函数指针fp fp = createAlgorithm(1); // fp接收createAlgorithm(1)返回值,二者都是同类型的函数指针,匹配 cout << "1 + 2 = " << fp(1,2) << endl; fp = createAlgorithm(2); // fp接收createAlgorithm(2)返回值,二者都是同类型的函数指针,匹配 cout << "5 - 1 = " << fp(5,1) << endl; return 0;}
复制代码


输出:


1 + 2 = 35 - 1 = 4
复制代码


例 5:输入参数和返回参数都含有函数指针:(int (calculateAndTransfer(int (fun)(int, int), int x, int y))(int, int){...})


#include <iostream>using namespace std;
int add(int x, int y){ return x + y;}int substract(int x, int y){ return x - y;}
// 定义一个名为calculateAndTransfer(int)的函数:在内部执行一次计算,并且把输入的函数指针再返回,传递下去// 输入参数为函数指针(其类型为int (*pTemp)(int, int))和两个int变量// 返回值为同样的函数指针pTemp(其类型为int (*pTemp)(int, int))int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int){ cout << "fun(x,y) = " << fun(x,y) << endl; return fun;}
int main(){ int (*fp)(int, int); fp = calculateAndTransfer(add,1,2); cout << "1 + 2 = " << fp(1,2) << endl; fp = calculateAndTransfer(substract,5,1); cout << "5 - 1 = " << fp(5,1) << endl; return 0;}
复制代码


输出:


fun(x,y) = 31 + 2 = 3fun(x,y) = 45 - 1 = 4
复制代码


可见,当函数指针出现在输入参数或者返回值位置时,代码可读性将会大大下降,这不利于项目的开发和维护。


3、typedef 提高代码可读性


3.1 typedef 自定义类型别名


通过上面的例子发现,当函数输入参数或者返回值包含函数指针时,代码可读性将会大大降低。为了解决这个问题,C++中可以使用 typedef 预先给某一类型起别名,然后再用自定义的别名会写出更让人容易理解的代码。


函数/函数指针使用 typedef 的两种方式:


  • typedef int Fun(int, int); // 类型别名:Fun 代表一类函数


  • typedef int (*PFun)(int, int); // 类型别名:PFun 代表一类函数指针 (int (*pf)(int, int))


第一种方式的 Fun 是自定义的函数别名,第二种方式的 PFun 是自定义的函数指针别名。在作为形参输入时,二种方式等价(编译器会自动把 Fun 转换为函数指针),但仍然推荐第二种方式,无需隐式转换,让人更容易理解。


3.2 用 typedef 重写:函数指针作为输入参数


例 6、用 typedef 重写:函数指针作为输入参数:


#include <iostream>using namespace std;
typedef int Fun(int, int); // 类型别名:Fun代表一类函数 typedef int (*PFun)(int, int); // 类型别名:PFun代表一类函数指针
int add(int x, int y){ // 双参数函数add2:两个数相加 return x + y;}
int add2(int (*fun)(int, int), int x, int y, int z){ // 利用双参数输入的fun(), 构造三参数函数add2() return fun(fun(x,y),z);}
int add4(PFun fun, int x, int y, int z){ // 与add2等价, 利用typedef提高可读性,构造三参数函数add3() return fun(fun(x,y),z);}
int add5(Fun fun, int x, int y, int z){ // 与add4等价, 函数名会被自动转换为函数指针作为形参 return fun(fun(x,y),z);}
int main(){ // 函数指针直接使用: int (*fp)(int, int); fp = add; cout << "1 + 2 = " << fp(1,2) << endl;
// 同上,但使用typedef提高可读性 PFun fp2 = add; // 与fp是同一类型 cout << "1 + 2 = " << fp2(1,2) << endl;
// 函数指针作为输入参数(形参)时,使用typedef的add4和add5的代码可读性比add2更高: cout << "1 + 2 + 3 = " << add2(fp2, 1, 2, 3) << endl; cout << "1 + 2 + 3 = " << add4(fp2, 1, 2, 3) << endl; cout << "1 + 2 + 3 = " << add5(fp2, 1, 2, 3) << endl; return 0;}
复制代码


3.3 用 typedef 重写:函数指针作为返回值:


以下两者为等价写法:都是定义一个名为 createAlgorithm(int)的函数,输入参数为 int,返回值为函数指针 pTemp(其类型为 int (*pTemp)(int, int))


  • int (createAlgorithm(int type))(int, int){ ...} // 不使用 typedef,返回类型写法难以阅读


  • typedef int (*PFun)(int, int);

PFun createAlgorithm(int type){ ... } // 可见,用 typedef 定义的类型 PFun 写法同一般的返回类型写法,更容易理解


例 7:用 typedef 重写:函数指针作为返回值:


#include <iostream>using namespace std;
// PFun是一个函数指针的类型别名,指向函数的类型为int (*pTemp)(int, int)typedef int (*PFun)(int, int);
// 定义三种运算:空运算、加法、减法int none(int , int ){ return 0;}int add(int x, int y){ return x + y;}int substract(int x, int y){ return x - y;}
// 定义一个名为createAlgorithm(int)的函数:// 输入参数为int,表示选择哪一种运算符(1为加法,2为减法)// 返回值为对应运算符的函数指针pTemp(其类型为int (*pTemp)(int, int))// 等价于:int (*createAlgorithm(int type))(int, int){ xxx }PFun createAlgorithm(int type){ switch(type){ case 1: return add; break; case 2: return substract; break; default: return none; }}
int main(){ PFun fp = createAlgorithm(1); // fp接收createAlgorithm(1)返回值,二者都是同类型的函数指针,匹配 cout << "1 + 2 = " << fp(1,2) << endl; fp = createAlgorithm(2); // fp接收createAlgorithm(2)返回值,二者都是同类型的函数指针,匹配 cout << "5 - 1 = " << fp(5,1) << endl; return 0;}
复制代码


输出:


1 + 2 = 35 - 1 = 4
复制代码


注:类型别名除了 typedef 之外,还有 using 和 decltype,都可以实现函数指针的别名定义。例如下面三条语句定义的 PFun1, PFun2, PFun3 等价。


例 8:typedef、using 和 decltype 三种等价写法:


#include <iostream>using namespace std;
int add(int x, int y){ return x + y;}
// 以下为类型别名的三种等价写法:typedef int (*PFun1)(int, int); // 类型别名:PFun1代表一类函数指针using PFun2 = int (*)(int, int); // 类型别名:PFun2与PFun1等价typedef decltype(add) *PFun3; // 类型别名:PFun3与PFun1等价
int main(){ PFun1 fp1 = add; PFun2 fp2 = add; PFun3 fp3 = fp2; cout << "1 + 2 = " << fp1(1,2) << endl; cout << "1 + 2 = " << fp2(1,2) << endl; cout << "1 + 2 = " << fp3(1,2) << endl; return 0;}
复制代码


输出:


1 + 2 = 31 + 2 = 31 + 2 = 3
复制代码


3.4 用 typedef 重写:输入参数和返回参数都含有函数指针:以下两种写法等价:


  • int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int)


  • typedef int (*PFun)(int, int);

PFun calculateAndTransfer(PFun fun, int x, int y); // typedef 大大提高可读性


例 9:用 typedef 重写例 5,让代码具有更好的可读性:


#include <iostream>using namespace std;
typedef int (*PFun)(int, int);
int add(int x, int y){ return x + y;}int substract(int x, int y){ return x - y;}
// 定义一个名为calculateAndTransfer(int)的函数:在内部执行一次计算,并且把输入的函数指针再返回,传递下去// 输入参数为函数指针(其类型为int (*pTemp)(int, int))和两个int变量// 返回值为同样的函数指针pTemp(其类型为int (*pTemp)(int, int))// 下面等价于:int (*calculateAndTransfer(int (*fun)(int, int), int x, int y))(int, int){ PFun calculateAndTransfer(PFun fun, int x, int y){ cout << "fun(x,y) = " << fun(x,y) << endl; return add;}
int main(){ PFun fp; fp = calculateAndTransfer(add,1,2); cout << "1 + 2 = " << fp(1,2) << endl; fp = calculateAndTransfer(substract,5,1); cout << "5 - 1 = " << fp(5,1) << endl; return 0;}
复制代码


输出:


fun(x,y) = 31 + 2 = 3fun(x,y) = 45 - 1 = 6
复制代码


函数指针的数组


定义一个由函数指针组成的数组:([]的优先级高于*,因此直接把(*fp)变为(*fp[3])即可


  • int (*fp[3])(int, int) : fp 是一个数组,元素个数为 3,每个元素是一个函数指针(指向 int (*pf)(int, int)的类型)


  • int (*(fp[3])(int, int) : 与上面等价,fp 是一个数组


  • int ((*fp)[3])(int, int) : (非法,编译会报错:```error: declaration of 'gp' as array of functions):fp 是一个指针,指向一个数组,这个数组的元素个数必须为 3,每个元素是一个函数,注意,是函数而不是函数指针,*符号给到 fp 头上了,因此,编译器会认为你要创建函数的数组,这是不允许的,只允许创建函数指针的数组。


例 10:函数指针数组:


#include <iostream>using namespace std;
typedef int (*PFun)(int, int);
int add(int x, int y){ return x + y;}
int substract(int x, int y){ return x - y;}
int main(){ // fp是一个数组,元素个数为2,每个元素是一个函数指针(指向int (*pf)(int, int)的类型) int (*fp[2])(int, int); fp[0] = add; fp[1] = substract; cout << fp[0](5,3) << endl; cout << fp[1](5,3) << endl; // 下面会报错,从字面上理解:gp是一个指针,指向函数的数组,而函数的数组是非法的,函数指针的数组是允许的 // int ((*gp)[2])(int, int); \\ error: declaration of 'gp' as array of functions return 0;}
复制代码


输出:


82
复制代码


类的成员函数(方法)的指针


与普通函数指针类似,类的静态/非静态成员函数(方法)指针也有声明、赋值、调用三个步骤:



几个注意点:


  • 新引入三个运算符:声明::*, 调用.*->*

  • 声明时,只有类的非静态成员函数指针前才加类名。

  • 赋值时,函数指针前都不加类名,只有类的非静态成员函数指针的等号右侧的才加类名。

  • 调用时,只有类的非成员函数指针前要加对象名,因为必须通过 this 指针来决定调用的是哪个实例对象的函数。其中(a.*fp2)(x,y)和(pa->*fp2)(x,y)的小括号不能省略,因为.*->*的优先级低于()


总之,类的静态成员函数与普通函数非常类似,只有在赋值的时候需要等号右侧是类名::静态函数的形式,其他的语法完全一致。而类的非静态成员函数在声明时前面要加类名,赋值时等号右侧要加类名,调用时前面要加具体的对象名。


例 11:比较普通函数、类的非静态成员函数、类的静态成员函数的声明、赋值和调用:


#include <iostream>#include <string>using namespace std;
class Mobile{public: Mobile(string number):_number(number){} void from(string another_number){ cout << _number << " is receiving a call from " << another_number << endl; } void to(string another_number){ cout << _number << " is calling to " << another_number << endl; } static void printInfo(){ cout << "Static function: Mobile works good!" << endl; }private: string _number;};
void fun(string number){ cout << "Normal function: number " << number << endl;}
int main(){ // 普通函数的指针: void (*fp1)(string); // 声明 fp1 = fun; // 赋值 fp1("999"); // 调用 (*fp1)("999"); // 调用的第二种等价方式 // 类的非静态成员函数(方法)的指针: Mobile m("666888"), *mp = &m; void (Mobile::*fp2)(string); // 声明,使用 ::* (小括号不能少:写成Mobile::*fp2(string)会报错) fp2 = Mobile::from; // 赋值,也可以写成fp2 = &Mobile::from; 因为::优先级高于& (m.*fp2)("12345"); // 调用,不能写成 m.*fp2("12345") 因为.*的优先级低于() // (*(m.*fp2))("12345"); // 错误,调用没有像普通函数一样的第二种等价方式(前面加*) (mp->*fp2)("12345"); // 调用,不能写成 m->*fp2("12345") 因为->*的优先级低于() fp2 = Mobile::to; // 赋值 (m.*fp2)("54321"); // 调用 (mp->*fp2)("54321"); // 调用 // 类的静态成员函数(方法)的指针: void (*fp3)(); // 声明: 同普通函数 fp3 = Mobile::printInfo; // 赋值:等号右侧用类名::静态函数,也可以写成fp3 = &Mobile::printInfo; fp3(); // 调用:同普通函数 (*fp3)(); // 调用的第二种等价方式:同普通函数 return 0;}
复制代码


输出:


Normal function: number 999Normal function: number 999666888 is receiving a call from 12345666888 is receiving a call from 12345666888 is calling to 54321666888 is calling to 54321Static function: Mobile works good!Static function: Mobile works good!
复制代码


文章转载自:菲杰克PhyJack

原文链接:https://www.cnblogs.com/phyjack/p/18445368

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
C++函数指针详解_Java_不在线第一只蜗牛_InfoQ写作社区