C++多态(下):虚函数进阶 与 虚表

本文详细介绍了C++中的抽象类(接口类)及其纯虚函数,解释了为何包含纯虚函数的类无法实例化。接着探讨了多态的概念,包括接口继承和实现继承,并通过实例解释了动态多态的实现。文章还深入讨论了虚函数表(虚表)和虚函数表指针(虚表指针),以及它们在对象内存布局中的位置。此外,还讨论了多继承下的虚表情况以及虚基表的作用。最后,通过代码示例展示了如何打印和理解虚表的内容。
摘要由CSDN通过智能技术生成

 ----------------"没有人在乎你的过程,但我在乎"


(1)抽象类:纯虚函数

在虚函数后面写上 =0,则这个虚函数就成为 “纯虚函数”。这种包含纯虚函数的类,叫做抽象类(接口类)。

值得注意,包含纯虚函数的类不能实例化出对象,当然 继承这样类的子类,也不能实例化出对象,除非派生类重写

①纯虚函数语法

class Car
{
public:
	virtual void Drive() = 0
	{}
};

class BMW :public Car
{
public:
};

class Car
{
public:
	virtual void Drive() = 0
	{}
};

class BMW :public Car
{
public:
	virtual void Drive()  
	{
		cout << "BMW" << endl;
	}
};

 ②接口继承和实现继承

可能你会对 接口继承感到陌生。所谓的接口继承,是和实现继承是对应关系。

普通函数的继承,就是实现继承。继承的实现是为了复用。

虚函数的继承是一种接口继承,派生类继承基类虚函数的接口,实现函数的重写,达到多态。


 (2)多态原理:

上一篇,仅仅是使用讲解了 多态的语法。并没有深入谈及多态的原理。

比如: 编译器是如何在动态的多态下,找到指定的函数 并进行调用?

          为什么虚函数有重写 ,重写又叫做覆盖呢? 

要回答上面的问题,我们就要知道,一个包含虚函数的类,其中有多少成员?


①虚函数表(虚表)和虚函数表指针(虚表指针)

一道笔试题:下面Base类 多少字节

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
	char _ch = '\0';
};

可能你会觉得,这仅仅是一道考内存对齐的题。也就得8字节。

然而结果并非8字节。

这里可能会有概念混淆

虚函数表!=虚基表

虽然 他们两者 都使用了virtual 关键字,但他们的使用的情况,解决的问题都不一样。

虚继承产生虚基表,虚基表里面存的是,基类偏移量。

(vfptr)虚函数表存的是,指向虚函数的指针(地址).

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}

	virtual void Func2()
	{}

	void Func3()
	{}
private:
	int _b = 1;
	char _ch = '\0';
};

②深度理解多态(父类调用父类 子类调用子类):

class Person
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	int _p = 1;
};

class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	int _s = 2;
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main() 
{
	Person Mike;
	Student Jack;
	Func(Mike);
	Func(Jack);

    return 0;
}

满足动态多态的条件:

 父类指针或是引用,调用虚函数。不是在编译的时候就确定的(静态多态是这样),而是在运行时,父类引用、指针 指向的是父类 就调用父类的虚函数,指向的是子类调用的就是子类的虚函数。

所以:   如果不够成多态,那么跟你传过来什么对象,是没有关系的。

           而是你接收 的对象指向什么,你就调用什么函数。

class Person
{
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	int _p = 1;
};

class Student : public Person 
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	int _s = 2;
};

综上:

构成多态:接收哪个对象 ,就去调用哪个对象的虚函数

不构成多态: 接收对象类型是什么,就去调用该类型的函数。

多态与不构成多态,在汇编角度>_:

 

 为什么,实现多态的条件之一:父类的引用/指针 调用虚函数?


③虚表和虚函数:

问一:对象中,虚表指针是在何时进行初始化的?虚表又是在上面时候生成?

                                 初始化列表                              编译时

问二:虚函数放在虚表 这句话对吗?

         不太准确,虚表只是保存了虚函数的地址。虚函数还是存在代码段  

问三:一个类的虚表,存放在哪里的?!  请验证一下(问得刁钻)。 

           大概存放在代码区

 虚表和 代码段(常量区)的地址相近。

问四: 为什么虚函数的重写 ,也叫作覆盖?!

            重写是在语法概念    虚表底层概念


④动态绑定和静态绑定(可以了解)

1.静态绑定(前期绑定),即程序在编译时期确定的程序行为。esg:函数重载

2.动态绑定(晚绑定),程序运行期间,根据拿到的不同类型确定自己的行为。esg:动态多态 


⑤打印虚表:

class Base
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }

private:
	int a;
};

     //只让子类重写func1 
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};

这里可以认为,是编译器监视窗口的优化 或者是bug。此时没有显示子类的虚函数。

我们在内存中可以猜测,这两个函数的地址。

注:虚表的内的 虚函数指针都会在末尾放上0;

class Base
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }

private:
	int a;
};

     //只让子类重写func1 
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b;
};

//定义一个 函数指针 指向的是虚表
typedef void(*VTFTPEY)();

void PrintVTF(VTFTPEY* p)  //传入函数指针数组
{
	printf("虚表地址:\n");

	//虚表 最后末尾放0
	for (int i = 0;p[i]!=nullptr;i++)
	{
		printf("VTF[%d]:%p->", i, p[i]);  
		p[i]();//拿到虚函数地址 并调用
	}
}

int main()
{
	Base b;
	Derive d;
                                     //强转               //拿到虚表
	VTFTPEY* ptr1 =(VTFTPEY*) (*(int*)&b);
	PrintVTF(ptr1);  //接收 虚表指针
	//我们可以通过 虚表指针--->找到虚表  从而可以从虚表中---->拿到函数指针去调用虚函数

	VTFTPEY* ptr2 = (VTFTPEY*)(*(int*)&d);
	PrintVTF(ptr2);
	return 0;
}


(3)多继承虚表:

在多继承中,状况也更复杂。

class Base1
{
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }

private:
	int a;
};

class Base2
{
public:
	virtual void func3() { cout << "Base::func3" << endl; }
	virtual void func4() { cout << "Base::func4" << endl; }

private:
	int a;
};

     //只让子类重写func1 
class Derive :public Base1,public Base2 
{
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
	virtual void func5() { cout << "Derive::func5" << endl; }
private:
	int b;
};

此时,Derive继承Base1 Base2

子类会继承 两个虚表。此时有个问题。当子类自己的虚函数,会放在哪一个虚表中呢?

我们借助上面打印虚表地址的函数;

 所以,多继承下,子类的虚函数,会放在第一个虚表里。 


(4)虚基表 和 虚函数表:

class A
{
public:
	virtual void func()
	{
		cout << "A::func()" << endl;
	}
public:
	int _a;
};

class B : public A
//class B : virtual public A
{
public:
	virtual void func()
	{
		cout << "B:func()" << endl;
	}

	virtual void func1()
	{
		cout << "B:func1()" << endl;
	}
public:
	int _b;
};

class C : public A
//class C : virtual public A
{
public:
	virtual void func()
	{
		cout << "C::func()" << endl;
	}

	virtual void func1()
	{
		cout << "C::func1()" << endl;
	}
public:
	int _c;
};

class D : public B, public C
{
public:
	virtual void func()
	{
		cout << "D::func()" << endl;
	}

	virtual void func1()
	{
		cout << "D::func1()" << endl;
	}
public:
	int _d;
};

当我们引入虚继承后:

可以看出一个类可以同时拥有虚基表 和 虚函数表。


内容也就这么多了。感谢你的阅读

祝你好运~ 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值