- 作者: 雪山肥鱼
- 时间:20210912 12:32
- 目的: 了解成员函数指针,vcall再谈
成员函数地址,编译时就确定好了,但是调用成员函数,是需要通过对象调用的。
所有常规(非静态)成员函数,都需要一个对象进行
成员函数指针
直接上代码
class A
{
public:
void myfunc1(int tmpvalue1)
{
cout << "tmpvalue1 = " << tmpvalue1 << endl;
}
void myfunc2(int tmpvalue2)
{
cout << "tmpvalue2 = " << tmpvalue2 << endl;
}
static void mysfunc(int tmpvalue)
{
cout << "static::tmpvalue = " << tmpvalue << endl;
}
};
void fun()
{
A mya;
void (A::*pmypoint)(int tmpvalue) = &A::myfunc1;//定义一个成员函数指针并给初值,成员函数指针!,成员函数需要 this指针
pmypoint = &A::myfunc2;//给成员函数指针赋值
(mya.*pmypoint)(15);//通过成员函数指针来调用成员函数,必须通过对象
A *pmya = new A();
(pmya->*pmypoint)(20);
//编译器视角
//pmypoint(&mya, 15);
//pmypoint(pmya, 15);
//针对static 则需要普通的函数指针即可
void(*pmyspoint)(int tmpvalue) = &A::mysfunc;
pmyspoint(80);
}
注意上述成员函数指针的基础用法。
然而对于 static 静态成员函数,则需要普通的 函数指针即可。
成员函数指针的应用
基本应用: 来源于网络
class A
{
public:
void strcpy( char *, const char *);
void strcat( char *, const char *);
}
typedef void (A::*PMA) (char *, const char *);
PMA pmf = &A::strcat;
void dispather(A a, PMA p)
{
char str[4] = { 0 };
(a. *p)(str, "abc");
}
void dispather(A *pa, PMA p)
{
char str[4] = { 0 };
(pa-> *p)(str, "abc");
}
int main(int argc,char **argv)
{
A a1;
PMA p1 = &A::strcpy;
dispather( a1, p1);
dispather( &a1, p1);
return 0;
}
从基础用法来看,好像没什么用,直接用对象掉函数不就行了?
此种用法多见于 桌面开发的菜单选项。
示例1: 来源网络
enum MENU_OPTIONS {COPY, CONCAT};
PMA pmf[2] = {&A::strcpy, &A::strcat};
int main( int argc,char **argv)
{
MENU_OPTIONS option; char str[4];
swtich(option)
{
case COPY:
(pa ->*pmf[COPY])(str, "abc");
break;
case CONCAT:
(pa->*pmf[CONCAT])(str, "abc");
break;
...
}
}
示例2:函数表驱动,来源网络
#include <iostream>
#include <string>
#include <map>
using namspace std;
class A;
typedef int (A::*pClassFun)(int, int);
class A {
public:
A() {
table["+"] = &A::add;
table["-"] = &A::mns;
table["*"] = &A::mul;
table["/"] = &A::dev;
}
int add(int m, int n){
cout << m << " + " << n << " = " << m+n << endl;
return m+n;
}
int mns(int m, int n){
cout << m << " - " << n << " = " << m-n << endl;
return m-n;
}
int mul(int m, int n){
cout << m << " * " << n << " = " << m*n << endl;
return m*n;
}
int dev(int m, int n){
cout << m << " / " << n << " = " << m/n << endl;
return m/n;
int call(string s, int m, int n) {
return (*table[s]) (m, n);
}
private:
map<string, pClassFun> table;
};
int main(int argc,char **argv) {
A a;
a.call("+", 8, 2);
a.call("-", 8, 2);
a.call("*", 8, 2);
a.call("/", 8, 2);
return 0;
}
上述举例的成员函数指针的应用,像是 对 类内 返回值和参数相同的 同类函数的收纳。
再谈vcall
class A
{
public:
void myfunc1(int tmpvalue1)
{
cout << "tmpvalue1 = " << tmpvalue1 << endl;
}
void myfunc2(int tmpvalue2)
{
cout << "tmpvalue2 = " << tmpvalue2 << endl;
}
static void mysfunc(int tmpvalue)
{
cout << "static::tmpvalue = " << tmpvalue << endl;
}
virtual void myvirfun(int tmpvalue)
{
cout << "tmpvalue in virtual func = " << tmpvalue << endl;
}
};
void fun()
{
//只要涉及this指针,都需要用成员函数
void(A::*pmyvirfun)(int tmpvalue) = &A::myvirfun;//需要用成员函数指针去接
A *pvobj = new A();
(pvobj->*pmyvirfun)(190);//pvobj确认了虚函数表,再用vcall 在虚函数表里进行偏移查找
delete pvobj;
}
}
vcall(vcall trunk) = virtual call 虚函数的调度
代表一段执行代码的地址,这段代码引导我们去寻找正确的虚函数。
简单粗暴把vcall 看成虚函数表,vcall[0] 代表第一个虚函数,vcall[4]达标第二个虚函数
&A::myvirfun,打印出来的是一个地址,这个地址中有一段代码,这个代码记录虚函数表中的偏移值,vcall[0],vcall[4],有了偏移值,再有了对象指针。我们就知道调用的是哪张虚函数表里的哪个函数。
&A::myvirfun 需要用成员函数指针去接
再用具体的对象or 对象指针进行调用。
vcall 在继承中的体现
namespace _nmsp3
{
//vcall 在继承中的体现
class A
{
public:
void myfunc1(int tmpvalue1)
{
cout << "tmpvalue1 = " << tmpvalue1 << endl;
}
void myfunc2(int tmpvalue2)
{
cout << "tmpvalue2 = " << tmpvalue2 << endl;
}
static void mysfunc(int tmpvalue)
{
cout << "static::tmpvalue = " << tmpvalue << endl;
}
virtual void myvirfun(int tmpvalue)
{
cout << "tmpvalue in A = " << tmpvalue << endl;
}
virtual ~A()
{
}
};
class B:public A
{
public:
virtual void myvirfun(int tmpvalue)
{
cout << "tmpvalue in B = " << tmpvalue << endl;
}
};
void fun()
{
B *pmyb = new B();
void(B::*mybvirfun)(int tempvalue) = &A::myvirfun;
//vcall 里的偏移 不管是在A 类 还是 B 类,myvirfun 都在虚函数表的第一个即 vcall[0]
//调的是 B对象指针pmyb, 也就是说调用 的是 B 的虚函数表, 偏移是 vcall[0] 的虚函数
//&A::myvirfun 和 &B::myvirfun 地址不同,但是因为偏移是相同的,用的vcall[0]
//执行的还是B
(pmyb->*mybvirfun)(180);
}
}
理解注释:
- B 对象,确认了 this指针,确认了哪张虚函数表
- &A::myvirfun,vcall[0] 确认了偏移
- 调用
PS: &A::myvirfun 和 &B::myvirfun 的地址不同,但是偏移是相同的 ,都是vcall[0]。所以最后执行的依旧是子类的函数
inline
顺便谈一下inline吧。
inline 有效果的意思是,在编译时就会展开成汇编代码。inline 无效时,直接就成了成员函数的调用。区分这一点就可以了。编译器会自己决定,inline是否值得被优化。
网友评论