C++ 函数指针, 类成员函数指针, 模板类成员函数指针
-
类成员函数指针
- 类成员函数指针
- 外文名
- member function pointer
目录
- 1 定义
- 2 语法
- 3 语义
1 |
typedef void (CCmdTarget::*AFX_PMSG)(void);
|
1 |
BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()
|
语义
不同于普通函数,类成员函数的调用有一个特殊的不写在形参表里的隐式参数:类实例的地址。因此,C++的类成员函数调用使用thiscall调用协议。类成员函数是限定(qualification)于所属类之中的。 同样,类成员函数指针与普通函数指针不是一码事。前者要用.*与->*运算符来使用,而后者可以用*运算符(称为“解引用”dereference,或称“间址”indirection)。普通函数指针实际上保存的是函数体的开始地址,因此也称“代码指针”,以区别于C/C++最常用的数据指针。而类成员函数指针就不仅仅是类成员函数的内存起始地址,还需要能解决因为C++的多重继承、虚继承而带来的类实例地址的调整问题。因此,普通函数指针的尺寸就是普通指针的尺寸,例如32位程序是4字节,64位程序是8字节。而类成员函数指针的尺寸最多有4种可能:C/C++函数指针与回调函数总结(函数指针数组)
墨痕诉清风 C/C++
函数有它的地址,程序运行起来了,程序里肯定有地方要放这个函数。我们知道可以用printf %p来输出一个变量的地址,数组的地址,同样我们定义了一个函数后,用这个函数的名字做输出,就可以得到这个函数的地址。那么我们得到一个函数的地址,有什么用呢?
我们可以想一下,在程序里我们定义一个变量i,然后定义一个指针p,让指针p指向i得到i的地址,然后通过*p我们就可以对i进行赋值等操作。那么,既然函数的名字可以得到函数地址,也就是说我们应该也可以用一个指针指向一个函数,得到函数的地址后,对函数做点事情,比如通过指针来调用这个函数?我们来试一下:
程序编译通过了,但是给了一个warning,warning里说这个“函数指针的初始化是有矛盾的”。但是程序通过了编译,也就是说我们是可以用一个指针指向一个函数的,只是初始化时没有对应上类型,因为我们定义的指针p是int*类型,而函数是void()类型。找对类型还不够,因为定义一个函数指针它还有特定的格式。
一、定义函数指针
定义函数指针的格式:例如:void (*p)();
void表示返回的类型,第二个括号是参数表。这样格式的意思是我们定义了一个(*p)函数指针。之后我们就可以对它做初始化,比如让它指向f函数:
这样编译程序就没有了warning。那么定义了一个函数指针,我们让它指向了函数f,之后我们怎样调用指针里的内容?也就是调用指针所指的那个f函数?
二、调用函数指针
调用函数指针的格式:例如:(*p)();
我们来试一下这样的方式调用函数指针p所指的那个函数:
在f函数里我们让它输出一句话,这样当我们的函数指针成功调用所指的函数后,函数里的那句话就会被输出,我们就知道函数调用成功了。
成功进入了f函数。
那么我们可以用函数指针来做些什么来使程序更方便灵活?
三、回调函数
定义:把一个函数的指针作为参数传递到另一函数的参数表中,让这个函数的指针被调用它所指的那个函数时,这种行为就是回调函数。也就是说这个指针函数的调用方式不是直接调用,而是在特定的条件下由另一方调用。
我们先来看这样一个例子:
函数指针
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型变、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
1 |
typedef int (*fun_ptr)( int , int ); // 声明一个指向同样参数、返回值的函数指针变量
|
实例
以下实例声明了函数指针变量 p,指向函数 max:
#includeint max(int x, int y){ return x > y ? x : y; } int main(void){ /* p 是函数指针 */ int (* p)(int, int) = & max; // &可以省略 int a, b, c, d; printf("请输入三个数字:"); scanf("%d %d %d", & a, & b, & c); /* 与直接调用函数等价,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的数字是: %d\n", d); return 0; } #include int max(int x, int y){ return x > y ? x : y; } int main(void){ /* p 是函数指针 */ int (* p)(int, int) = & max; // &可以省略 int a, b, c, d; printf("请输入三个数字:"); scanf("%d %d %d", & a, & b, & c); /* 与直接调用函数等价,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的数字是: %d\n", d); return 0; }
编译执行,输出结果如下:
请输入三个数字:1 2 3
最大的数字是: 3
回调函数
函数指针作为某个函数的参数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
实例
实例中 populate_array 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
实例中我们定义了回调函数 getNextRandomValue,它返回一个随机值,它作为一个函数指针传递给 populate_array 函数。
populate_array 将调用 10 次回掉函数,并将回掉函数的返回值赋值给数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include
#include
// 回调函数void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)){
for ( size_t i=0; i |
编译执行,输出结果如下:
1680728247524916226500739849436581144108930470211272101027544145785087814587779232007237709
C++基础入门教程(九):函数指针之回调
2020-11-10 09:41:27 阅读数 2766 收藏 0在Java,要实现某个时间调用某段代码,是很简单的事情,那就是使用接口。
而在C++里,有一个比较高深的方式,那就是使用函数指针。
比如Cocos2d-x的定时器(schedule)、消息订阅(NotificationCenter)都使用了函数指针来完成回调的功能。
这也是为什么我们总是能把某个函数作为参数传进去,然后在某个时刻这个函数会被调用。
一、函数的地址
要获取一个int变量的地址很简单,比如int num; 那么num的地址就是&num。
而获取函数的地址更简单,函数的名字就是函数的地址,如下代码:
void hello();
int _tmain(int argc, _TCHAR* argv[])
{
auto p = hello;
p();
return 0;
}
void hello()
{
cout << "helloworld";
}
我们定义了一个hello函数,然后直接把函数名字赋值给指针p,于是,就可以把p当成了hello函数来使用了。
这很简单吧。
二、声明函数指针
获取函数的地址很简单,但是,如何声明函数指针就变得不那么简单了。
我们总不能每次都使用auto来逃避吧?有时候我们不得不显式地声明函数指针,那么,如何声明呢?
还记得我们说过的typedef定义类型别名吗?函数指针的声明也是一样的规则,先声明一个函数,如:void hello();
然后把函数名字换成指针,如:void (*p)();
没错,就是这么简单,void (*p)(); 就是void hello(); 的声明了。
立刻再来试试,这个函数:int getValue(float dt);
它的函数指针声明是什么?没错,就是:int (*p) getValue(float dt)
没错,就是这么简单int getValue(float dt); 就是int (*p) getValue(float dt);的函数指针声明了。
立刻再来试试,这..(小若:停~!别以为我不在你就可以乱来!)
好吧,那就不继续试了,我们来看看,刚刚那段代码可以这样写:
代码如下:void hello();
int _tmain(int argc, _TCHAR* argv[])
{
void (*p)();
p = hello;
p();
(*p)(); // 偷偷加了这句
return 0;
}
void hello()
{
cout << "helloworld";
}
好了,很简单,不多说了~
另外,有没有发现我偷偷又加了一句代码?
没错,用(*p)();的方式也通过能成功调用hello函数,这是为什么呢?
三、历史原因
由于p是指针,它指向的是hello函数的地址,所以,*p就代表hello函数,于是,(*p)()就等于hello(),这是正常的逻辑。
所以,其实(*p)()才是比较正常的调用方式。
然而,由于函数名本来就是指向了函数的指针,也就是说,hello其实也是指向了函数的地址。
换句话说,p和hello其实都是指针,那么,p的调用方式和hello的调用方式应该也是一样的,于是,p()就相当于hello()。
这两种方式都是正确的,其实语法这东西,就是人定的,历史上前辈对这两种方式各持所见,于是就容忍了这两种看似冲突的方式同时存在了。
不过,我想,大部分人都会更喜欢直接用p(),而不是(*p)()吧。
四、typedef挽救复杂的函数指针
如下代码:
代码如下:string hehe1(int num, float value);
string hehe2(int num, float value);
string hehe3(int num, float value);
int _tmain(int argc, _TCHAR* argv[])
{
/* 声明函数指针数组 */
string(*p[3])(int num, float value) = {hehe1, hehe2, hehe3};
string result = p[1](1, 2);
cout << result.c_str() << endl;
return 0;
}
string hehe1(int num, float value)
{
return "haha1";
}
string hehe2(int num, float value)
{
return "haha2";
}
string hehe3(int num, float value)
{
return "haha3";
}
这段代码有三个参数和返回值都相同的函数,分别是hehe1、hehe2、hehe3
然后,我们要声明一个数组,这个数组用来存放这三个函数指针。
这里的函数还算是比较简单的,所以看起来不算复杂。
但如果这样的声明出现太多的话,未免会让人很沮丧。
于是,typedef挽救了我们,我们可以复杂的声明变成这样:
代码如下:int _tmain(int argc, _TCHAR* argv[])
{
/* 用HeheFunc来代替复杂的函数声明 */
typedef string(*HeheFunc)(int num, float value);
/* 声明函数指针数组 */
HeheFunc p[3] = { hehe1, hehe2, hehe3 };
string result = p[1](1, 2);
cout << result.c_str() << endl;
return 0;
}
使用typedef代替函数声明之后,我们就能很轻松地使用它,并且会让我们的代表变得很简单,很好理解。
现在,HeheFunc就代表了一种类型,什么类型呢?就是参数为(int num, float value),返回值为string的函数类型。
五、结束
C++ 类成员函数的函数指针 模板类成员函数指针
阿飞__
于 2018-08-23 18:40:17 发布
39526
收藏 134
分类专栏: C/C++ 文章标签: 函数指针
当我们在 C++ 中直接像 C 那样使用类的成员函数指针时,通常会报错,提示你不能使用非静态的函数指针:
reference to non-static member function must be called
两个解决方法:
把非静态的成员方法改成静态的成员方法
正确的使用类成员函数指针(在下面介绍)
关于函数指针的定义和使用你还不清楚的话,可以先看这篇博客了解一下:
https://blog.csdn.net/afei__/article/details/80549202
二、语法
1. 非静态的成员方法函数指针语法(同C语言差不多):
void (*ptrStaticFun)() = &ClassName::staticFun;
2. 成员方法函数指针语法:
void (ClassName::*ptrNonStaticFun)() = &ClassName::nonStaticFun;
注意调用类中非静态成员函数的时候,使用的是 类名::函数名,而不是 实例名::函数名。
三、实例:
#include
#include
using namespace std;
class MyClass {
public:
static int FunA(int a, int b) {
cout << "call FunA" << endl;
return a + b;
}
void FunB() {
cout << "call FunB" << endl;
}
void FunC() {
cout << "call FunC" << endl;
}
int pFun1(int (*p)(int, int), int a, int b) {
return (*p)(a, b);
}
void pFun2(void (MyClass::*nonstatic)()) {
(this->*nonstatic)();
}
};
int main() {
MyClass* obj = new MyClass;
// 静态函数指针的使用
int (*pFunA)(int, int) = &MyClass::FunA;
cout << pFunA(1, 2) << endl;
// 成员函数指针的使用
void (MyClass::*pFunB)() = &MyClass::FunB;
(obj->*pFunB)();
// 通过 pFun1 只能调用静态方法
obj->pFun1(&MyClass::FunA, 1, 2);
// 通过 pFun2 就是调用成员方法
obj->pFun2(&MyClass::FunB);
obj->pFun2(&MyClass::FunC);
delete obj;
return 0;
}
阿飞__
C++指向类成员函数的指针详细解析 2019-02-14 14:10:59
转自
首先 函数指针是指向一组同类型的函数的指针;而类成员函数我们也可以相似的认为,它是指向同类中同一组类型的成员函数的指针,当然这里的成员函数更准确的讲应该是指非静态的成员函数。前者是直接指向函数地址的,而后者我们从字面上也可以知道 它肯定是跟类和对象有着关系的。
typedef int (*p)(int,int);//定义一个接受两个int型且返回int型变量的函数指针类型
int func(int x,int y)
{
printf("func:x=%d,y=%d/n",x,y);
return (x
int main()
{
p fun=func;//定义函数指针并给它赋上一个函数指针
cout<<"min:"<<(*fun)(4,5)<
}
而“指向类成员函数的指针”却多了一个类的区别:
class A
{
public:
int func(int x,int y)
{
printf("A::func:x=%d,y=%d/n",x,y);
return (x
};
typedef int (A::*p)(int,int);//指针名前一定要加上所属类型类名 A::的限定
int main()
{
p fun=&A::func;
A a; //因为成员函数地址的解引用必须要附驻与某个对象的地址,所以我们必须创建某个对象。
cout<<"min:"<<(a.*fun)(4,5)<
}
只是用起来 .* 感觉怪怪滴。
接下来 我们可以再扩展一下:
#include
#include
#include
using namespace std;
class A
{
public:
int func1(int x,int y)
{
printf("A::func:x=%d,y=%d/n",x,y);
return (x
virtual int func2(int x,int y)
{
printf("A::func:x=%d,y=%d/n",x,y);
return (x>y?x:y);
}
};
class B:public A
{
public:
virtual int func2(int x,int y)
{
printf("B::func:x=%d,y=%d/n",x,y);
return (x+y);
}
};
typedef int (A::*p)(int,int);//指针名前一定要加上所属类型类名 A::的限定
typedef int (B::*p0)(int,int);
int main()
{
A a; //因为成员函数地址的解引用必须要附驻与某个对象的地址,所以我们必须创建某个对象。
p fun=&A::func1;
cout<<(a.*fun)(4,5)<
cout<<(a.*fun)(4,5)<
fun=(int (A::*)(int,int))&B::func2;//应该进行强制转换
cout<<(a.*fun)(4,5)<
p0 fun0=&B::func2;
cout<<(a.*fun)(4,5)<
fun0=&A::func2; //正确,因为这里进行了隐式转换
cout<<(a.*fun)(4,5)<
//基类成员函数的布局被认为是派生类成员函数布局的一个子集
return 0;
}
接下 是有关模板类的类成员函数指针的使用
实例如下:
#include
#include
#include
using namespace std;
class A
{
public:
int func(int x,int y)
{
printf("A::func : x=%d,y=%d/n",x,y);
return (x
};
class B
{
public:
int func(int x,int y)
{
printf("B::func : x=%d,y=%d/n",x,y);
return (x>y?x:y);
}
};
template
class C
{
public:
T c;
void Print()
{
int (T::*p)(int,int)=&T::func;
(c.*p)(4,5);
}
};
int main()
{
C ca;
C cb;
ca.Print();
cb.Print();
return 0;
}
从上面 可以很清晰地看到。。其实它和普通的模板没有什么区别。。只不过将限定名称该为参数名就OK啦