多态和虚函数

虚函数表:

单继承时的虚函数表:

1、无虚函数覆盖

假如现有单继承关系如下:

class Base
{
public:
	virtual void x() { cout << "Base::x()" << endl; }
	virtual void y() { cout << "Base::y()" << endl; }
	virtual void z() { cout << "Base::z()" << endl; }
};

class Derive : public Base
{
public:
	virtual void x1() { cout << "Derive::x1()" << endl; }
	virtual void y1() { cout << "Derive::y1()" << endl; }
	virtual void z1() { cout << "Derive::z1()" << endl; }
};

在这个单继承的关系中,子类没有重写父类的任何方法,而是加入了三个新的虚函数。Derive类实例的虚函数表布局如图示:

  • Derive class 继承了 Base class 中的三个虚函数,准确的说,是该函数实体的地址被拷贝到 Derive 实例的虚函数表对应的 slot 之中。
  • 新增的 虚函数 置于虚函数表的后面,并按声明顺序存放。

2、有虚函数覆盖

如果在继承关系中,子类重写了父类的虚函数:

class Base
{
public:
	virtual void x() { cout << "Base::x()" << endl; }
	virtual void y() { cout << "Base::y()" << endl; }
	virtual void z() { cout << "Base::z()" << endl; }
};

class Derive : public Base
{
public:
	virtual void x() { cout << "Derive::x()" << endl; }  // 重写
	virtual void y1() { cout << "Derive::y1()" << endl; }
	virtual void z1() { cout << "Derive::z1()" << endl; }
};

则Derive类实例的虚函数表布局为:

 

相比于无覆盖的情况,只是把 Derive::x() 覆盖了Base::x(),即第一个槽的函数地址发生了变化,其他的没有变化。

这时,如果通过绑定了子类对象的基类指针调用函数 x(),会执行 Derive 版本的 x(),这就是多态。

 

多重继承时的虚函数表

1、无虚函数覆盖

现有如下的多重继承关系,子类没有覆盖父类的虚函数:

class Base1
{
public:
	virtual void x() { cout << "Base1::x()" << endl; }
	virtual void y() { cout << "Base1::y()" << endl; }
	virtual void z() { cout << "Base1::z()" << endl; }
};

class Base2
{
public:
	virtual void x() { cout << "Base2::x()" << endl; }  
	virtual void y() { cout << "Base2::y()" << endl; }
	virtual void z() { cout << "Base2::z()" << endl; }
};

class Derive : public Base1, public Base2
{
public:
	virtual void x1() { cout << "Derive::x1()" << endl; }  
	virtual void y1() { cout << "Derive::y1()" << endl; }
};

对于 Derive 实例 d 的虚函数表布局,如下图:

可以看出:

  • 每个基类子对象对应一个虚函数表
  • 派生类中新增的虚函数放到第一个虚函数表的后面

注意,这也就是为什么《深度探索C++对象模型里》对于多重继承的情况下要不断调整指针的指向的原因:

比如Base2* pb2=new Derive();

由于Base2是与Base1平级的基类,因此pb2的指向要从new Derive()的起始地址调整到Base2 subobject处,因为pb2只能调用Derived与Base2的共有部分的函数,不能调用Base1部分的函数,因此要调整到Base2 subobject处,因为Base2的虚指针在此处,不然将使用Base1的虚指针进行跳转,从而不能调用Base2的方法。

而当delete pb2时又要跳转到new Derive()的起始处,因为派生类对析构函数是进行了重写的,而派生类中重写的函数将写入第一个基类,也就是Base1里的vptr所指向的虚表中,因此要调整到new Derive()首处,这样才能正确的调用析构函数。

2、有虚函数覆盖

将上面的多重继承关系稍作修改,让子类重写基类的 x() 函数:

class Base1
{
public:
	virtual void x() { cout << "Base1::x()" << endl; }
	virtual void y() { cout << "Base1::y()" << endl; }
	virtual void z() { cout << "Base1::z()" << endl; }
};

class Base2
{
public:
	virtual void x() { cout << "Base2::x()" << endl; }  
	virtual void y() { cout << "Base2::y()" << endl; }
	virtual void z() { cout << "Base2::z()" << endl; }
};

class Derive : public Base1, public Base2
{
public:
	virtual void x() { cout << "Derive::x()" << endl; }     // 重写
	virtual void y1() { cout << "Derive::y1()" << endl; }
};

相比于无覆盖的情况,只是将Derive::x()覆盖了Base1::x()Base2::x()而已,你可以自己写测试代码测试一下,这里就不再赘述了。

注:若虚函数是 private 或 protected 的,我们照样可以通过访问虚函数表来访问这些虚函数,即上面的测试代码一样能运行。

附:编译器对指针的调整

在多重继承下,我们可以将子类实例绑定到任一父类的指针(或引用)上。以上述有覆盖的多重继承关系为例:

Derive b;
Base1* ptr1 = &b;   // 指向 b 的初始地址
Base2* ptr2 = &b;   // 指向 b 的第二个子对象
  • 因为 Base1 是第一个基类,所以 ptr1 指向的是 Derive 对象的起始地址,不需要调整指针(偏移)。
  • 因为 Base2 是第二个基类,所以必须对指针进行调整,即加上一个 offset,让 ptr2 指向 Base2 子对象。
  • 当然,上述过程是由编译器完成的。
Base1* b1 = (Base1*)ptr2;  
b1->y();                   // 输出 Base2::y()
Base2* b2 = (Base2*)ptr1;   
b2->y();                   // 输出 Base1::y()
这里,由于ptr2指向的是Base2子对象的地址,因此尽管b1是Base1型指针,同时也将ptr2转换成了Base1,但ptr2的地址仍然是Base2的地址,因此调用y()也将调用Base2中的y();
同理,由于ptr1指向的是Base1的地址,因此尽管b2是Base2型指针,同时也将ptr1转换成了Base2,但ptr1的地址仍然是Base1的地址,因此调用y()也将调用Base1中的y()
其实,通过某个类型的指针访问某个成员时,编译器只是根据类型的定义查找这个成员所在偏移量,用这个偏移量获取成员。由于 ptr2 本来就指向 Base2 子对象的起始地址,所以b1->y()调用到的是Base2::y(),而 ptr1 本来就指向 Base1 子对象的起始地址(即 Derive对象的起始地址),所以b2->y()调用到的是Base1::y()

 

父类:Father

子类:Son

1.  Father* fa=new Son()

    实例1:

#include<iostream>
using namespace std;
 
class Father{
public:
	Father(){
		cout << "this is the Father constructor!" << endl;
	}
	void watchTv(){
		cout << "Father is watching tv!" << endl;
	}
	virtual void say(){
		cout << "Father is saying!" << endl;
	}
};
 
class Son :public Father{
public:
	Son(){
		cout << "this is the Son constructor!" << endl;
	}
	void watchTv(){
		cout << "Son is watching tv!" << endl;
	}
	void say(){
		cout << "Son is saying!" << endl;
	}
};
 
int main(){
	Father* fa = new Son();
	fa->watchTv();
	fa->say();
	system("pause");
	return 0;
}

运行结果:

 

结果分析:

1.首先运行代码 Father* fa=new Son();,会首先调用父类构造函数,再调用子类构造函数,所以第一、第二行如图所示;

2.运行代码fa->watchTv(),根据fa的类型,因为是Father类型的,所以会先查看Father类中的watchTv函数,因为不是虚函数,所以直接调用,结果如第三行所示;

3.运行代码fa->say(),根据fa的类型,因为是Father类型的,所以先查看Father类中的say函数,因为是虚函数,所以会查看虚函数表,查看实现实例,找到子类Son有实现虚函数say,所以会调用子类Son的say函数,结果如第四行所示。

 

  实例2: 在构造函数中调用虚函数

#include<iostream>
#include<string>
using namespace std;
class Father{
public:
	Father():val("HunanTv"){
		cout << "this is the Father constructor!" << endl;
		say();
	}
	virtual void watchTv(){
		cout << "Father is watching tv:" <<val<< endl;
	}
	void say(){
		cout << "Father is saying to watchTv!" << endl;
		watchTv();
	}
	string val;
};
 
class Son :public Father{
public:
	Son(){
		cout << "this is the Son constructor!" << endl;
		say();
	}
	virtual void watchTv(){
		val = "GuangdongTv";
		cout << "Son is watching tv:"<<val << endl;
	}
};
 
int main(){
	Father* fa = new Son();
	fa->say();
	system("pause");
	return 0;
}

运行结果:

结果分析:

1.运行代码Father* fa=new Son(),会先调用父类构造函数,父类构造函数中会执行以下操作:a)首先给变量val赋值为“HunanTv",其次,调用函数say(),此时会输出前两行内容;b)因为say()函数会调用虚函数watchTv(),而且在子类中有watchTv的实例实现,但是根据输出结果第三行发现,调用的还是父类的虚函数,为什么呢?因为,父类构造函数中调用虚函数,表现的虚函数为父类的虚函数,所以结果为第三行的显示;c)父类构造函数调用完后,执行子类构造函数,因为子类没有定义say()函数,所以调用继承自父类的say()函数,所以输出结果为第四、第五行;d)say函数调用虚函数watchTv(),因为是在子类Son的构造函数中调用虚函数,所以调用的是子类的watchTv,所以输出结果如第六行所示;e)执行完子类构建后,执行fa->say();,首先看fa的类型,所以调用父类的say(),say()调用虚函数watchTv时,因为watchTv在子类中有实例实现,所以会调用子类的watchTv,所以输出结果如第七、第八行所示。

 

总结:

1.当在构造函数中调用虚函数时,虚函数表现为该类中虚函数的行为,即父类构造函数调用虚函数,则虚函数为父类中的虚函数;子类构造函数中调用虚函数,则调用的是子类中的虚函数;

2.如果不是在构造函数中调用虚函数,则会首先查看虚函数表,如果有实例实现,则调用实例。比如:父类中有虚函数watchTv,则调用父类中watchTv时,则因为子类实现了watchTv,则调用子类的watchTv。

 

 

#include<iostream>

using namespace std;

class Base1

{

public:

virtual void f() {cout<<"Base1::f()"<<endl;}

virtual void g() {cout<<"Base1::g()"<<endl;}

virtual void h() {cout<<"Base1::h()"<<endl;}

};

 

int main()

{

typedef void(*fun)();

fun pFun;

Base1 d;

cout<<"the virtual function table address is "<<(int*)(&d)<<endl;

cout<<"the first virtual function address is "<<(int*)*(int*)(&d)<<endl;

cout<<"the first virtual function address is "<<(int*)*(int*)(&d) +1<<endl;

pFun = (fun)*((int*)*(int*)(&d) + 1);

pFun();

}

 

附:

下面是一个关于多重继承的虚函数表访问的例程:

 

#include <iostream>

using namespace std;

 

class Base1 {

public:

            virtual void f() { cout << "Base1::f" << endl; }

            virtual void g() { cout << "Base1::g" << endl; }

            virtual void h() { cout << "Base1::h" << endl; }

 

};

 

class Base2 {

public:

            virtual void f() { cout << "Base2::f" << endl; }

            virtual void g() { cout << "Base2::g" << endl; }

            virtual void h() { cout << "Base2::h" << endl; }

};

 

class Base3 {

public:

            virtual void f() { cout << "Base3::f" << endl; }

            virtual void g() { cout << "Base3::g" << endl; }

            virtual void h() { cout << "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

            virtual void f() { cout << "Derive::f" << endl; }

            virtual void g1() { cout << "Derive::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

            Fun pFun = NULL;

 

            Derive d;

            int** pVtab = (int**)&d;

 

            //Base1's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

            pFun = (Fun)pVtab[0][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

            pFun = (Fun)pVtab[0][1];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

            pFun = (Fun)pVtab[0][2];

            pFun();

 

            //Derive's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

            pFun = (Fun)pVtab[0][3];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[0][4];

            cout<<pFun<<endl;

 

 

            //Base2's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[1][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[1][1];

            pFun();

 

            pFun = (Fun)pVtab[1][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[1][3];

            cout<<pFun<<endl;

 

 

 

            //Base3's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[2][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[2][1];

            pFun();

 

            pFun = (Fun)pVtab[2][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[2][3];

            cout<<pFun<<endl;

 

            return 0;

}

C++中的继承、多态虚函数是面向对象编程的重要概念。 继承是指一个类可以从另一个类继承属性和方法。子类可以继承父类的公有成员和保护成员,但不能继承私有成员。通过继承,子类可以重用父类的代码,并且可以添加自己的特定功能。继承可以实现代码的重用和层次化的设计。 多态是指同一个函数可以根据不同的对象调用不同的实现。多态可以通过虚函数来实现。虚函数是在基类中声明为虚拟的函数,它可以在派生类中被重写。当通过基类指针或引用调用虚函数时,实际调用的是派生类中的实现。这样可以实现动态绑定,即在运行时确定调用的函数。 虚函数的原理是通过虚函数表来实现的。每个包含虚函数的类都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,编译器会根据对象的类型在虚函数表中查找对应的函数地址并调用。 综上所述,C++中的继承、多态虚函数是实现面向对象编程的重要机制,它们可以提高代码的灵活性和可扩展性。 #### 引用[.reference_title] - *1* *3* [C++多态虚函数虚函数表](https://blog.csdn.net/weixin_46053588/article/details/121231465)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c++多态虚函数表内部原理实战详解](https://blog.csdn.net/bitcarmanlee/article/details/124830241)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值