网站首页 > 基础教程 正文
继承机制下的虚函数及动态机制才是真正的面向对象技术,否则充其量也只是基于对象技术。
继承允许把对象作为它自己的类型或它的基类类型处理。这个能力很重要,因为它允许很多类型(从同一个基类派生的)被等价地看待就像它们是一个类型,允许同一段代码同样地工作在所有这些不同类型上。虚函数反映了一个类型与另一个类似类型之间的区别,只要这两个类型都是从同一个基类派生的。这种区别是通过其在基类中调用的函数的表现不同来反映的。例如,我们可以定义一个基类(包含虚函数)指针或引用做参数,那这个参数便可以是这个基类继承链上的任一子类对象,在基类扩充子类时,这一函数的代码不会受到影响。
1 虚函数指针及其目标对象(虚函数表)在内存中的相对位置
拥有虚函数的类会有一个虚表,而且这个虚表存放在类定义模块的数据段中。模块的数据段通常存放定义在该模块的全局数据和静态数据,这样我们可以把虚表看作是模块的全局数据或者静态数据。
类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表,从这个意义上说,我们可以把虚表简单理解为类的静态数据成员。值得注意的是,虽然虚表是共享的,但是虚表指针并不是,类的每一个对象有一个属于它自己的虚表指针。
虚表中存放的是虚函数的地址。
#include <stdio.h>
class A{
int m_i;
int m_j;
public:
virtual void f(int a){printf("A:f(%d)\n",a);}
virtual void g(double d){printf("A:f(%.2f)\n",d);}
};
int main()
{
A *obj = new A;
printf("对象指针obj地址:%x\n",&obj);
printf("对象指针obj目标对象地址:%x\n",obj);
long *vtp = (long *)*((long*)obj);
printf("对象指针obj目标对象前4个字节构成的值(虚函数表指针):%x\n",vtp);
printf("对象指针obj目标对象虚函数表指针目标虚函数1:%x\n",vtp[0]);
printf("对象指针obj目标对象虚函数表指针目标虚函数2:%x\n",vtp[1]);
void(*vfp1)(int) = (void(*)(int))vtp[0]; // 虚函数1
void(*vfp2)(double) = (void(*)(double))(*(vtp+1)); // 虚函数2
vfp1(0); // debug模式下可能有关于esp错误信息的提示,因为调用方式不一样
vfp2(1);
getchar();
return 0;
}
/*
对象指针obj地址:12ff38
对象指针obj目标对象地址:3128b0
对象指针obj目标对象前4个字节构成的值(虚函数表指针):429118
对象指针obj目标对象虚函数表指针目标虚函数1:40100f
对象指针obj目标对象虚函数表指针目标虚函数2:401005
A:f(0)
A:f(1.00)
*/
2 虚函数表项引用
虚函数表其实质是一个虚函数指针数组。每一个包含虚函数表的类的对象中会由编译器增加一个指向虚函数指针数组的指针。
#include <stdio.h>
#include <iostream>
using namespace std;
typedef void (*FUNC)(); // 函数指针类型定义
void (*pbArr[3])() = {0}; // 函数指针数组
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
void base()
{
Base b;
printf("基类对象地址:%x\n",&b);
printf("由基类b的起始地址推导虚函数表地址:%x\n",*(int*)(&b));
long *vpf = (long*)*(int*)(&b);
printf("由虚函数表地址推导第1个虚函数地址:%x\n",vpf[0]);
printf("由虚函数表地址推导第2个虚函数地址:%x\n",vpf[1]);
printf("由虚函数表地址推导第3个虚函数地址:%x\n",vpf[2]);
printf("直接由虚函数名打印的地址:%x\n",&Base::f);
printf("直接由虚函数名打印的地址:%x\n",&Base::g);
printf("直接由虚函数名打印的地址:%x\n",&Base::h);
FUNC fc1 = FUNC(vpf[0]);
fc1();
typedef void(Base::*PFUNC)();
PFUNC fc2 = &Base::f;
(b.*fc2)();
FUNC pf = NULL;
pf = (FUNC)*((int**)*(int*)(&b)+0);
pf();
pf = (FUNC)*((int**)*(int*)(&b)+1);
pf();
pf = (FUNC)*((int**)*(int*)(&b)+2);
pf();
for(int i=0;i<3;i++){
pbArr[i] = (FUNC)*((int**)*(int*)(&b)+i);
pbArr[i]();
}
}
void (*pdArr[3])() = {0};
class Derive:public Base{
public:
void g() { cout << "Derive::g" << endl; }
void func() { cout << "Derive::func" << endl; }
};
void derive()
{
Derive d;
printf("派生类对象地址:%x\n",&d);
printf("由派生类b的起始地址推导虚函数表地址:%x\n",*(int*)(&d));
long *vpf = (long*)*(int*)(&d);
printf("由虚函数表地址推导第1个虚函数地址:%x\n",vpf[0]);
printf("由虚函数表地址推导第2个虚函数地址:%x\n",vpf[1]);
printf("由虚函数表地址推导第3个虚函数地址:%x\n",vpf[2]);
printf("直接由虚函数名打印的地址:%x\n",&Base::f);
printf("直接由虚函数名打印的地址:%x\n",&Base::g);
printf("直接由虚函数名打印的地址:%x\n",&Base::h);
FUNC fc1 = FUNC(vpf[1]);
fc1();
typedef void(Derive::*PFUNC)();
PFUNC fc2 = &Derive::g;
(d.*fc2)();
FUNC pf = NULL;
pf = (FUNC)*((int**)*(int*)(&d)+0);
pf();
pf = (FUNC)*((int**)*(int*)(&d)+1);
pf();
pf = (FUNC)*((int**)*(int*)(&d)+2);
pf();
for(int i=0;i<3;i++){
pdArr[i] = (FUNC)*((int**)*(int*)(&d)+i);
pdArr[i]();
}
}
int main()
{
base();
derive();
Base *b;
Derive d;
b = &d;
b->g();
Derive dd;
dd.g();
return 0;
}
/*
基类对象地址:12fee4
由基类b的起始地址推导虚函数表地址:432118
由虚函数表地址推导第1个虚函数地址:4010a5
由虚函数表地址推导第2个虚函数地址:401028
由虚函数表地址推导第3个虚函数地址:4010e1
直接由虚函数名打印的地址:4010b4
直接由虚函数名打印的地址:4010be
直接由虚函数名打印的地址:4010c8
Base::f
Base::f
Base::f
Base::g
Base::h
Base::f
Base::g
Base::h
派生类对象地址:12fee4
由派生类b的起始地址推导虚函数表地址:432194
由虚函数表地址推导第1个虚函数地址:4010a5
由虚函数表地址推导第2个虚函数地址:40107d
由虚函数表地址推导第3个虚函数地址:4010e1
直接由虚函数名打印的地址:4010b4
直接由虚函数名打印的地址:4010be
直接由虚函数名打印的地址:4010c8
Derive::g
Derive::g
Base::f
Derive::g
Base::h
Base::f
Derive::g
Base::h
Derive::g
Derive::g
*/
3 通过类(包含虚函数的类)对象包含的虚函数表指针找到虚函数表
#include <iostream>
using namespace std;
class A
{
public:
A(int _a1 = 1) : a1(_a1) { }
virtual void f() { cout << "A::f" << endl; }
virtual void g() { cout << "A::g" << endl; }
virtual void h() { cout << "A::h" << endl; }
~A() {}
private:
int a1;
};
class C : public A
{
public:
C(int _a1 = 1, int _c = 4) :A(_a1), c(_c) { }
virtual void f() { cout << "C::f" << endl; }
virtual void g() { cout << "C::g" << endl; }
virtual void h() { cout << "C::h" << endl; }
private:
int c;
};
// 通过访问类对象的前4字节(32位编译器)找到虚函数表。
// 虚函数表最后一项用的是0,代表虚函数表结束。
typedef void(*FUNC)(); //重定义函数指针,指向函数的指针
void PrintVTable(long* vTable) //访问虚函数表
{
if (vTable == NULL)
{
return;
}
cout << "vtbl:" << vTable << endl;
int i = 0;
for (; vTable[i] != 0; ++i)
{
printf("function : %d :0X%x->", i, vTable[i]);
FUNC f = (FUNC)vTable[i];
f(); //访问虚函数
}
cout << endl;
}
void main()
{
A a1;
long *p = (long *)(*(long*)&a1);
PrintVTable(p);
C c;
long *p2 = (long *)(*(long*)&c);
PrintVTable(p2);
system("pause");
}
/*
vtbl:00471048
function : 0 :0X40105a->A::f
function : 1 :0X4012c6->A::g
function : 2 :0X4010b9->A::h
vtbl:00471070
function : 0 :0X4010eb->C::f
function : 1 :0X4011d1->C::g
function : 2 :0X401280->C::h
*/
3 图解实例
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Cflat }; // Etc.
class Instrument {
public:
virtual void play(note) const {
cout << "Instrument::play" << endl;
}
virtual char* what() const {
return "Instrument";
}
// Assume this will modify the object:
virtual void adjust(int) {}
};
class Wind : public Instrument { // 管乐器,吹奏乐器
public:
void play(note) const {
cout << "Wind::play" << endl;
}
char* what() const { return "Wind"; }
void adjust(int) {cout<<"\nwind adjust!\n";}
};
class Percussion : public Instrument { // 打击乐器
public:
void play(note) const {
cout << "Percussion::play" << endl;
}
char* what() const { return "Percussion"; }
void adjust(int) {}
};
class Stringed : public Instrument { // 弦乐器
public:
void play(note) const {
cout << "Stringed::play" << endl;
}
char* what() const { return "Stringed"; }
void adjust(int) {}
};
class Brass : public Wind { // 铜管乐器
public:
void play(note) const {
cout << "Brass::play" << endl;
}
char* what() const { return "Brass"; }
};
class Woodwind : public Wind { // 木管乐器
public:
void play(note) const {
cout << "Woodwind::play" << endl;
}
char* what() const { return "Woodwind"; }
};
// Identical function from before:
void tune(Instrument& i) {
// ...
i.play(middleC);
}
// New function:
void f(Instrument& i) { i.adjust(1); }
// Upcasting during array initialization:
Instrument* A[] = {
new Wind,
new Percussion,
new Stringed,
new Brass,
};
int main() {
Wind flute; // 长笛
Percussion drum; // 鼓
Stringed violin; // 小提琴
Brass flugelhorn; // 粗管短号
Woodwind recorder; // 竖笛
cout<<"________函数调用(基类指针参数)______"<<endl;
tune(flute);
tune(drum);
tune(violin);
tune(flugelhorn);
tune(recorder);
f(flugelhorn);
cout<<"____________基类指针对象调用_______"<<endl;
for(int i=0;i<4;i++)
{
tune(*A[i]);
}
cout<<"________通过虚函数表手工调用虚函数_________"<<endl;
Woodwind ww;
long *wvpt = (long*)*(long*)&ww;
void(*fp0)(note) = (void(*)(note))(wvpt[0]);
fp0(middleC); // debug模式下的错误信息可忽略,esp针对不同参数在不同调用约定下要做堆栈平衡
char*(*fp1)() = (char*(*)())(wvpt[1]);
char *str = fp1();
printf(str);
void(*pf2)() = (void(*)())(wvpt[2]);
pf2();
getchar();
return 0;
}
/*
________函数调用(基类指针参数)______
Wind::play
Percussion::play
Stringed::play
Brass::play
Woodwind::play
wind adjust!
____________基类指针对象调用_______
Wind::play
Percussion::play
Stringed::play
Brass::play
________通过虚函数表手工调用虚函数_________
Woodwind::play
Woodwind
wind adjust!
*/
派生对象数组定义:
基类对象指针指向派生类对象,派生类对象内的虚函数表指针指向具体的虚函数:
ref
Bruce Eckel《Thinking in C++, 2nd ed. Volume 1》
-End-
- 上一篇: C++的虚函数可以是内联函数吗 c++中虚函数
- 下一篇: C++ 虚函数基础知识 c++虚函数的使用
猜你喜欢
- 2024-11-12 金三银四不跳槽更待何时?安卓开发1年字节5面面经,已成功上岸
- 2024-11-12 C++要学到什么程度才能找到实习? c++学完学什么
- 2024-11-12 C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?
- 2024-11-12 C++基类中虚析构函数 c++ 虚析构函数
- 2024-11-12 C和C++代码精粹:C语言和C++有什么区别么?
- 2024-11-12 3个面试C++开发岗位的高频笔试题 c++软件开发面试
- 2024-11-12 一文在手,"类间关系"不再困惑
- 2024-11-12 c++的面试总结 c++面试知识点
- 2024-11-12 C++ 虚函数 实例学习 简单易懂 c++虚函数的使用
- 2024-11-12 C++里面的虚析构函数 虚构造函数与虚析构函数
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)