虚函数也是在编译阶段就确定了固定的地址,本质上通过this指针去调用。加上是否需要通过vptr指针去查找虚函数表,找到真正的地址。
零:验证虚函数也是在编译阶段就固定了地址,打印虚函数指针的方法在window上和linux上不一样。
其他成员函数,全局函数,静态函数打印地址的的方式和window上一样,在linux上也一样。
window下打印:编译后打印多次都是同一个地址。
//打印虚函数的地址
printf("Teacher21:;func1的地址是%p\n",&Teacher21::func1);
cout << &Teacher21::func1 << endl;//使用cout打印alway是1,这样不行
//打印虚函数的地址
printf("Teacher21:;func1的地址是%p\n",&Teacher21::func1);
cout << &Teacher21::func1 << endl;//使用cout打印alway是1,这样不行
在linux 上的写法为:
Teacher21 tea89;
void (Teacher21::*vfp)() = &Teacher21::func1;
printf("address:%p \n",(void *)(tea89.*vfp) );
一,虚成员函数调用方法
通过前面的知识,我们知道,虚成员函数的调用 ,在类对象 和 类对象指针/类对象引用 时是不一样的。
如果是对象调用,则是通过静态绑定的方式,是和普通成员函数的调用一样的。
class Teacher21 {
public:
void virtual func1() {
cout << "Teacher21 vir func1 called this = " << this << endl;
}
};
void main() {
Teacher21 tea;
tea.func1();
cout << "======" << endl;
Teacher21 *ptea = new Teacher21();
ptea->func1();
}
如果是指针或者引用调用,则是动态绑定的方式,是要通过 vptr找到虚函数表中的真正的地址,然后调用
验证此说法:上述代码,debug查看反汇编。可以看到指针的调用,做了很多事情,实际上就是通过vptr查找虚函数表,然后找到真正的地址,然后再调用。
tea.func1();
0081AF2A lea ecx,[tea]
0081AF2D call Teacher21::func1 (08117D5h)
ptea->func1();
0081AFA4 mov eax,dword ptr [ptea]
0081AFA7 mov edx,dword ptr [eax]
0081AFA9 mov esi,esp
0081AFAB mov ecx,dword ptr [ptea]
0081AFAE mov eax,dword ptr [edx]
0081AFB0 call eax
0081AFB2 cmp esi,esp
0081AFB4 call __RTC_CheckEsp (08114BAh)
虚函数的调用除了上述操作外,也会加一个this指针。
在虚函数中调用虚函数的情况分析
class Teacher21 {
public:
void virtual func1() {
cout << "Teacher21 vir func1 called this = " << this << endl;
func2();
}
void virtual func2() {
cout << "Teacher21 vir func2 called this = " << this << endl;
}
};
void main() {
Teacher21 tea;
tea.func1();
cout << "======" << endl;
}
在调用 tea.func1();时debug ,查看反汇编代码,看到,
在虚函数中直接调用虚函数,也会使用vptr指针的方式,去查找,反汇编代码如下:
void virtual func2() {
00A38900 push ebp
00A38901 mov ebp,esp
00A38903 sub esp,0CCh
00A38909 push ebx
00A3890A push esi
00A3890B push edi
00A3890C push ecx
00A3890D lea edi,[ebp-0CCh]
00A38913 mov ecx,33h
00A38918 mov eax,0CCCCCCCCh
00A3891D rep stos dword ptr es:[edi]
00A3891F pop ecx
00A38920 mov dword ptr [this],ecx
00A38923 mov ecx,offset _60E5D22F_consoleapplication2@cpp (0A4C028h)
00A38928 call @__CheckForDebuggerJustMyCode@4 (0A314A1h)
cout << "Teacher21 vir func2 called this = " << this << endl;
那么怎么样 在虚函数中调用虚函数 能使用虚函数指针和虚函数表呢?
替换成如下的代码就好了
Teacher21::func2();
class Teacher21 {
public:
void virtual func1() {
cout << "Teacher21 vir func1 called this = " << this << endl;
Teacher21::func2();
}
void virtual func2() {
cout << "Teacher21 vir func2 called this = " << this << endl;
}
};
void main() {
Teacher21 tea;
tea.func1();
cout << "======" << endl;
}
这么改有啥实际意义吗?--是有的
明显我们看到,如果您的代码使用多态调用到func1了,那么您的代码当前的this对象肯定是Teacher21,不可能是Teacher21的子类或者Teacher21 的父类,因此这么调用是有实际意义的,可以节省时间。
二,静态成员函数调用方法
静态成员函数调用的4种方式
//static 函数
Teacher21::funcstatic();
tea.funcstatic();
ptea->funcstatic();
((Teacher21 *)0)->funcstatic();
//如上4行,在编译器眼中是一样的。通过反编译可以证明这一点
//静态函数可以看成是全局函数。也是在编译阶段就有了明确的地址的
//static 函数
Teacher21::funcstatic();
00379486 call Teacher21::funcstatic (03717E4h)
tea.funcstatic();
0037948B call Teacher21::funcstatic (03717E4h)
ptea->funcstatic();
00379490 call Teacher21::funcstatic (03717E4h)
((Teacher21 *)0)->funcstatic();
00379490 call Teacher21::funcstatic (03717E4h)
//如上4行,在编译器眼中是一样的。通过反编译可以证明这一点
//静态函数可以看成是全局函数。也是在编译阶段就有了明确的地址的
静态成员函数特性:
静态成员没有this指针,
无法直接存取类中的普通的非静态成员变量。
静态成员函数不能再屁股后面使用const,也不能设置为virtual
可以用类对象调用,也可以用类直接调用
静态成员函数等同于全局函数,因此,有需要提供回调函数的这种场合,可以将静态成员函数作为回调函数。