c++多态

本文详细介绍了C++中的多态性,包括虚函数、重写、重载和隐藏的概念。强调了构成多态的条件,析构函数的特殊性,以及final和override的使用。还探讨了抽象类和纯虚函数的作用,以及虚表指针在多态中的作用。最后,解释了为何在多态中只能使用引用或指针,并展示了如何打印虚表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多态

子类中满足函数名相同,参数相同,返回值相同的虚函数(跟虚继承无关,只是关键字相同),叫做重写

在继承中构成多态有两个条件
1.必须通过父类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且子类必须对父类的虚函数进行重写

//构成多态,跟p的类型没有关系,传的是哪个类型的对象,调用的就是这个类型的虚函数--跟对象有关
//不构成多态,调用的就是p类型的函数 -- 跟类型有关

void func(Person& p)
{
	p.BuyTicket();//多态
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

虚函数和重写

虚函数:被virtual修饰的类的非静态成员函数称为虚函数,其他函数不能称为虚函数。
在这里插入图片描述
重写:子类中满足函数名相同,参数相同,返回值相同

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

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

重写要求返回值相同有一个例外:协变–要求返回值是父子关系的指针或者引用
在这里插入图片描述

析构函数是虚函数,构成重写
析构函数名被特殊处理,处理成destructor
普通对象,析构函数是否是虚函数,是否完成重写,都正确调用

普通对象调用析构函数不需要是虚函数,因为不符合多态的条件(没有用到父类的指针或者引用)

在这里插入图片描述
动态申请的父子对象如果给了父类指针管理,需要析构函数是虚函数,完成重写,构成多态,才能正确使用析构函数(并不是所有场景都需要)
因为这里我们希望p1调用父类的析构,p2调用子类的析构,不符合多态就跟类型有关,只把父类的成员销毁,子类资源还没销毁;其他场景析构函数是不是虚函数,都可以正确调用析构函数。
在这里插入图片描述
在这里插入图片描述
虚函数的重写允许两个都是虚函数或者父类是虚函数,再满足三同,就构成重写。
子类虽然没写virtual,但是他继承了父类的虚函数的属性,再完成重写,那么他也算虚函数
在这里插入图片描述

final 和 override

设计一个不能被继承的类
方法一:构造函数私有了就不能被继承了,因为子类要初始化父类,只有一种方式,就是调用父类的构造函数,虽然子类继承了,但是构造函数私有了,语法上子类就用不了,无法构造。

//间接限制,子类构造函数无法调用父类构造函数初始化成员,没办法实例化对象
//子类创建对象就会报错
class A
{
private:
	A(int a=0)
		:_a(a)
	{}
protected:
	int _a;
};

class B:public A
{

};

方法二:父类后+final


class A final
{
protected:
	int _a;
};
//子类定不定义对象都无法被继承
class B:public A
{

};

final也可以修饰虚函数,限制他不能被子类中的虚函数重写
在这里插入图片描述

override
override放在子类重写的虚函数后面,检查是否完成重写,没有重写就会报错。
在这里插入图片描述

重写,重载,隐藏区别

在这里插入图片描述

抽象类

在虚函数后面写上=0,则这个函数为纯虚函数,包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象纯虚函数一般只声明不实现,因为实现没有价值。
一个类型,在现实世界中,没有具体的对应实物就定义成抽象类

如果子类继承后重写虚函数,父类才能实例化出对象,调用的也只是子类的虚函数

class Car
{
public:

	virtual void Drive() = 0
	{
		cout << "virtual void Drive() = 0" << endl;
	}

	void f()
	{
		cout << "void f()" << endl;
	}
};

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

int main()
{
	Car* p = new Bus;
	p->Drive();

	return 0;
}

在这里插入图片描述
纯虚函数的类,本质上强制子类去完成虚函数的重写

虚表指针

先来看一道题目

//类A占几个字节
class A
{
public:
	virtual void func()
	{};
	virtual void func2()
	{};
private:
	int _a ;
	char _b;
};

int main()
{
	cout << sizeof(A) << endl;
	return 0;
}

答案不是8而是12字节,原因是类A多了一个虚表指针
在这里插入图片描述
在这里插入图片描述

多态的原理:父类指针/引用,指向谁,就去谁的虚函数表中找到对应位置的虚函数进行调用
同类型的对象,虚表指针是相同的,指向同一张虚表
普通函数和虚函数存储的位置一样,都在代码段,只是虚函数要把地址存一份到虚表,方便实现多态。
而虚函数表存放在常量区,而不是栈
不是多态,编译时确定地址;是多态,运行时确定地址,会到虚函数表中去找,再调用

首先子类继承父类,对父类的虚函数进行重写,指向虚表的指针就改变了,所以原理层,重写也叫覆盖
在这里插入图片描述
为什么实现多态只能用引用或者指针,不能用对象?
子类切片给父类类型的p2,进行了切片,只拷贝成员,没有拷贝虚表
在这里插入图片描述
指针和引用为什么可以?
因为指针和引用切片是不一样的,指针和引用不会进行拷贝,变成子类对象中父类部分的别名/指针。

多继承,子类重写了Person和Student虚函数func1,但是虚表中的func1地址却不一样,不过最终调到还是同一个函数

打印虚表

//函数指针跟常规typedef不一样
typedef void(*VF_PTR)();
//void PrintfVFTable(VF_PTR table[])
void PrintfVFTable(VF_PTR* table)
{
	for (int i = 0; table[i] != nullptr; i++)
	{
		printf("vft[%d]:%p\n", i, table[i]);
	}
}

int main()
{
	Person p;
	//虚表指针是指针,第一个存放
	//所以传就是传头四个字节
	PrintfVFTable((VF_PTR*)(*(int*)&p));
	Student s;
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值