总结:尽量避免使用函数指针.

函数名与函数指针

double foo(int a,int b);//函数声明,double foo(int,int)为函数原型

上面这行代码为 函数声明 ,foo函数名 ,double (int,int)函数签名.

函数名是一个标识符,它是不可赋值的,它是可调用的.

&foo表示函数的地址,函数地址 也是可以调用的,例如:

(&foo)(1,2);//与foo(1,2)相同.

由于&foo表示foo函数的地址,则&foo的类型就是函数指针.函数指针指向函数签名相同的函数.

定义并初始化函数指针

double pam (int);
double (*pf)(int)=pam;
auto pn=pam;
//定义并初始化函数指针

pam(4);
(*pf)(4);//与pf(4)相同,因为函数地址是可以调用的
(*pn)(4);

可调用对象

可调用对象包括一切可以调用的对象或名称,它包括:函数名,函数指针,lambda表达式,函数对象,类的成员函数(需要绑定对象)等.

函数指针可以绑定函数名和函数指针,而不可以绑定其他可调用对象.

class foo{
public:
double operator()(int){return 1;}
};
class boo{
public:
double member(int){return 1;}
};
double pam (int);
double (*pf)(int)=pam;//函数指针绑定函数名
auto pn=pf;//函数指针绑定函数指针
pn=[](int)->double{return 1;};//错误,函数指针无法绑定lambda表达式(有些编译器能正确运行)
pn=foo();//错误,函数指针无法绑定函数对象.
double (*pt) (boo* ,int) =&boo::member; //错误 函数指针无法绑定类成员函数

而使用std::function<函数签名>就可以绑定一切可调用对象.

std::function<double (int)> a=foo();
std::function<double (int)> a=[](int)->double{return 1;};
std::function<double (int)> a=pam;
std::function<double (int)> a=pf;
std::function<double (boo*,int)> pt = &boo::member;

所以说,std::function<函数签名>可以完美替代函数指针.

模板参数中的函数指针

unordered_map<class _Key,class _Tp,class _Hash = hash<_Key>>中,第三个模板参数,要求是一个可调用对象的类型

#include <unordered_map>
#include <array>
#include <vector>
#include <string>
#include <functional>
#include<iostream>
// 自定义哈希函数
size_t myhash(const std::array<int, 26>& arr) {
size_t hash = 0;
for (const auto& val : arr) {
hash ^= std::hash<int>()(val); // 简单的哈希组合
}
return hash;
}

int main() {
// 使用自定义哈希函数初始化 unordered_map
std::unordered_map<std::array<int, 26>, std::vector<std::string>, size_t (*) (const std::array<int, 26>&)> ht(0, myhash);
//也可以使用function< size_t (const std::array<int, 26>&)> 实例化第三个参数
//decltype(&myhash) 可行
//decltye(myhash) 报错
return 0;
}

在上面代码中,myhash是一个函数名,myhash本身是一个可调用对象,但是我认为它是没有类型的,因为myhash的类型应当是它的函数签名,但是:

size_t (const std::array<int, 26>&) hasher = myhash;

这段代码是错误的,因为size_t (const std::array<int, 26>&)不能作为类型 来定义对象,所以我认为函数名是没有类型的.

但是其余可调用对象都是有类型的:

size_t (* hasher) (const std::array<int, 26>&)  = myhash;
function<size_t (const std::array<int, 26>&)> hasher = myhash;

复杂的函数指针

阅读这一篇幅,需要您熟练掌握,C语言中的指针。

//一些函数原型
const double* f1(const double ar[],int n);
const double* f2(const double *,int);
const double* f3(const double *,int);
//函数指针
const double* (*p1)(const double ar[],int n)=f1;
auto p2=f2;//感谢auto

//调用函数
cout<<(*p1)(av,3)<<*(*p1)(av,3);
cout<<p2(av,3)<<*p2(av,3);
//实际上 *p2(av,3)和*(*p2)(av,3)是一样的。不理解的看上面内容。

//包含3个函数指针的数组
const double* (*pa[3])(const double *,int)={f1,f2,f3};
//注意:
//1、[]优先级高于* 所以这是个数组不是指针。
//2、不能使用auto定义并初始化列表

auto pb=pa;
//既然已经声明了数组,数组名就是指针,采用auto可以定义初始化指针,这是合法的。

//调用函数

double x=*pa[0](av,3);

double y=*(*pb[1])(av,3);//由于[]优先级高于* 所以pb[1]是个函数指针。然后(*pb[1])就是调用函数了。

//更加深入
const double *(*(*pd)[3])(const double *,int) = &pa;
//首先把函数指针的壳子去掉即 const double *(* ···)(const double *,int),然后得到(*pd)[3]这里 pd先和* 结合 再和[]结合,所以pd是个指针,这个指针指向一个数组,这个数组的元素又是函数指针。
//是不是特别绕?

//感谢auto
auto pc=&pa;

//调用
(*pd)[0](av,3);
//pd是指向数组的指针,则(*pd)[0]就是数组的元素,数组的元素是函数指针,所以可以采用这种方式调用函数。
//或者采用 (*(*pd)[0])(av,3)调用函数也是等价的。

double z=*(*pd)[0](av,3);
//或者 采用 double z=*(*(*pd)[0])(av,3) 也是等价的


我们对于语法的了解不能仅仅潜于认识,对于这种const double *(*(*pd)[3])(const double *,int) = &pa; 我们不光要认识,更要会使用,再次重温一遍,我们想要一个指向数组的指针,这个数组里的元素是函数指针。

第一步,数组元素的类型是函数指针,所以壳子要有 const double *(* ···)(const double *,int)
第二步,指向数组的指针 (*pd)[3] ,由于[]比*优先级高,所以我们必须采用(),否则 *pd[3] 就是一个数组,数组的元素是指针。
第三步,结合得 const double *(*(*pd)[3])(const double *,int) = &pa

使用typedef 简化

typedef const double *(* p_fun)(const double *,int);
p_fun p1=f1;
p_fun pa[3]={f1,f2,f3};
p_fun (*pd)[3]=&pa;

typedef 使得代码量减少很多,而且更容易理解