指针是表示内存中(虚拟)地址的标识符
对于一个函数,它的函数名就代表了这段代码的地址
double multiplay(double a,double b)
{
return a*b;
}
cout<<hex<<(unsigned long long) multiplay<<endl; //输出:40157f
定义函数指针
采用如下方式定义函数指针:
返回类型 (*函数指针名)(参数列表)
不能把包裹 *函数指针名的括号拿掉,不然就变成了定义指针函数的语法
定义函数与定义函数指针
为了方便方便记忆,我们来看看定义函数和定义函数指针的有哪些联系:
函数签名
double multiplay(double a,double b)
↓ 将函数名替换成 (*指针名),就完成了函数指针的定义
double (*ptr)(double a,double b)
↓ 由于参数名对指针没有意义,可以进一步去掉
double (*ptr)(double,double)
使用函数指针调用函数
double multiplay(double a,double b)
{
return a*b;
}
double add(double a,double b)
{
return a+b;
}
//...
//函数指针指向函数,既可以直接赋予 函数名,也可以赋予 函数名+取地址运算符
double (*ptr1)(double,double) = multiplay;
double (*ptr2)(double,double) = &add;
//调用函数时,既可以用函数指针+参数的方式,也可以用括号+解引用运算符+函数指针+参数的方式
cout<<ptr1(4,5)<<endl;
cout<<(*ptr1)(4,5)<<endl;
//注意:此种写法相当于对函数返回值解引用,会导致错误
cout<< *ptr1(4,5) <<endl; //相当于*(ptr1(4,5))
使用函数指针更改指向
在定义函数指针时,定义的是一个指针指向的函数的形式
因此对于返回值与参数相同的函数,可以使用这样的指针来指向
double multiplay(double a,double b)
{
return a*b;
}
double add(double a,double b)
{
return a+b;
}
//...
double (*ptr)(double,double) = multiplay;
cout<< ptr(4,5)<<endl; //输出:20
ptr = &add;
cout<< ptr(4,5)<<endl; //输出:9
使用typedef给函数指针定义别名
前面的定义方式看起来比较繁琐,可以使用typedef定义类型别名进行简化
//在定义函数指针的语法前加上typedef
//这时FuncPtr就不再是一个函数指针,而表示这种指针的类型
typedef double (*FuncPtr)(double,double)
//在后续使用时,就可以使用这个类型定义指针
FuncPtr ptr = multiplay;
//也可以用typedef定义一个没有指针的函数类型
typedef double (MyFuncType)(double,double)
//在后续使用时,使用指针的形式定义函数
MyFuncType* ptr = multiplay;
使用auto 自动类型推断
double multiplay(double a,double b)
{
return a*b;
}
double add(double a,double b)
{
return a+b;
}
//...
typedef double (*FuncPtr)(double,double)
//可以使用auto自动推断出函数指针类型
auto ptr1 = multiplay;
cout<< ptr1(4,5) << endl;
//但auto不能推断函数指针数组
// auto ptrs[2] = {multiplay, add}; //错误
FuncPtr ptrs[2] = {multiplay, add}; //这时需要使用 函数指针类型 进行定义
cout<< ptrs[0](4,5) << endl;
函数指针的使用
函数指针可以用作回调函数
bool compare(float a,float b)
{
return a<b;
}
int main()
{
vector<float> numbers{0,1,3,0,0,4,9};
sort(numbers.begin(),numbers.end(),compare);
for(auto number:numbers) cout<< number <<" ";
return 0;
}
题外话: 仿函数/函数对象
不过现在来说,更倾向于使用函数对象做回调
class Compare
{
public:
Compare(float a,float b) { }
bool operator()(float a,float b)
{
return a<b;
}
};
int main()
{
vector<float> numbers{0,1,3,0,0,4,9};
sort(numbers.begin(),numbers.end(),Compare());
for(auto number:numbers) cout<< number <<" ";
return 0;
}
补充一个易混点
需要注意的是sort中的Compare()是创建一个对象,仿函数在sort内部调用
一般来说,使用类名()时 是在创建对象,使用对象名()时 是在调用仿函数
Compare comp;
Compare(1,2); //创建一个临时对象
comp(1,2); //调用函数
Compare(1,2)(1,2); // 创建一个临时对象并调用函数
仿函数的最大优势在于,它可以有自己的状态
class AddANum
{
public:
int Num;
int operator()(int x) const { return x + Num; }
};
类成员函数指针
类成员函数指针的定义方法:
返回类型 (类名::*函数指针名称)(参数列表);
class Calc
{
public:
double multiplay(double a,double b){ return a * b; }
double add(double a,double b){ return a + b; }
};
int main()
{
//给类成员函数指针赋值时前面建议加上& 不加&有的编译器会报错
double (Calc::*PtrMemberFunc)(double,double) = &Calc::multiplay;
//如下演示了使用对象和指针调用函数,语法要一致 括号的位置要一致、*号也不能丢
Calc c1;
cout<<(c1.*PtrMemberFunc)(4,5)<<endl; //结果:20
PtrMemberFunc = &Calc::add;
Calc* c2 = &c1;
cout<<(c2->*PtrMemberFunc)(4,5)<<endl; //结果:9
return 0;
}
类成员函数指针仍然具有多态性
class BaseClass
{
public:
virtual void print(){ cout<<"Base class"<<endl; }
};
class SubClass :public BaseClass
{
public:
virtual void print() override { cout<<"Sub class"<<endl; }
};
int main()
{
void (BaseClass::*pMenFunc)() = &BaseClass::print;
BaseClass* obj = new SubClass();
(obj->*pMenFunc)(); //输出:Sub class
}