C++ 函数指针, 类成员函数指针, 模板类成员函数指针


类成员函数指针

本词条由“科普中国”科学百科词条编写与应用工作项目 审核 。 函数指针是指向函数的指针变量。函数指针可以像一般函数一样,用于调用函数、传递参数。函数指针只能指向具有特定特征的函数。因而所有被同一指针运用的函数必须具有相同的参数和返回类型。类成员函数指针(member function pointer),是C++语言的一类指针数据类型,用于存储一个指定类具有给定的形参列表与返回值类型的成员函数的访问信息。    中文名
类成员函数指针
外文名
member function pointer

目录

  1. 定义
  2. 语法
  3. 语义
MFC类体系中,Windows消息传递处理机制是基于CCmdTarget类及其派生类的静态数据成员与静态成员函数GetThisMessageMap()。用户所写的类中的Windows消息处理函数(例如OnCommand)必须转换为CCmdTarget::*的成员函数指针类型AFX_PMSG,保存在该用户类的_messageEntries静态数组中。
1 typedef void (CCmdTarget::*AFX_PMSG)(void);
调用用户类中该消息处理函数时,根据该函数保存在_messageEntries中的signature(一个无符号整型表示的函数的形参类型列表与返回值类型),把类型为void (CCmdTarget::*AFX_PMSG)(void)的成员函数指针强制转为其它类型的CCmdTarget成员函数指针(例如void (AFX_MSG_CALL CWnd::*pfn_v_i_i)(int, int),在union MessageMapFunctions中列出了近百种CCmdTarget成员函数指针),然后调用转换后的成员函数指针。这是基于Visual C++编译器把单继承的成员函数指针编译为只保存了函数的内存起始地址,因此可以在同一个单继承类中把一种类型的成员函数指针强制转换为另一种成员函数指针,或者把单继承派生类的成员函数指针强制转换为基类成员函数指针。这是打破了C++标准的违例办法。例如,对于CWnd::OnCommand函数,转换过程是:
1 BOOL (CWnd::*)(WPARAM, LPARAM lParam) => void (CWnd::*)() => void (CCmdTarget::*)()
函数指针不能直接调用类的成员函数,需采取间接的方法,原因是成员函数指针与一般函数指针有根本的不同,成员函数指针除包含地址信息外,同时携带其所属对象信息 [1]C++运算符优先级列表中,函数调用运算符()的优先级高于.*与->*,因此成员函数指针所指的函数被调用时,必须把实例对象或实例指针、.*或->*运算符、成员函数指针用括号括起来,如上例所示。 C++标准规定,非静态成员函数不是左值,因此非静态成员函数不存在表达式中从函数左值到指针右值的隐式转换,非静态成员函数指针必须通过&运算符显式获得。所以上例中,pmf = X::f; 将编译报错。

语义

编辑 播报 不同于普通函数,类成员函数的调用有一个特殊的不写在形参表里的隐式参数:类实例的地址。因此,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:

#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;
}
#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   array[i] = getNextValue(); } // 获取随机值 int getNextRandomValue(void){ return rand(); } int main(void){ int myarray[10]; populate_array(myarray, 10, getNextRandomValue); for(int i = 0; i < 10; i++) {   printf("%d ", myarray[i]);  } printf("\n"); return 0; }

编译执行,输出结果如下:

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)< return 0;
}
   而“指向类成员函数的指针”却多了一个类的区别:
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)< return 0;
}

只是用起来 .*  感觉怪怪滴。

接下来 我们可以再扩展一下:

#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<<(b.*fun)(4,5)< fun=&A::func2;
 cout<<(a.*fun)(4,5)< cout<<(b.*fun)(4,5)< //fun=&B::func2;         //这样式错误滴,因为不存在派生类的"指向类成员函数的指针"到基类的"指向类成员函数的指针"的隐式转换
 fun=(int (A::*)(int,int))&B::func2;//应该进行强制转换 
 cout<<(a.*fun)(4,5)< cout<<(b.*fun)(4,5)<
 p0 fun0=&B::func2;
 cout<<(a.*fun)(4,5)< cout<<(b.*fun)(4,5)<
 fun0=&A::func2;           //正确,因为这里进行了隐式转换
 cout<<(a.*fun)(4,5)< cout<<(b.*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啦