【C++进阶】多态

一、多态的概念及定义

1.1 多态的概念

多态简单来说就是多种形态

同一个行为,不同对象去完成时

会产生出不同的状态

多态分为静态多态动态多态
静态多态指的是编译时
在程序编译期间确定了程序的行为

比如:函数重载
动态多态指的是运行时
在程序运行期间,根据具体拿到的类型
确定程序的具体行为,调用具体的函数

1.2 在继承中要构成多态的两个条件

  1. 必须通过父类指针或引用调用虚函数

  2. 虚函数的重写
    函数名、参数类型、返回值都要相同

被virtual修饰的类成员函数称为虚函数

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

1.3 虚函数的重写(覆盖)

派生类中有一个跟基类完全相同的虚函数

(即派生类虚函数与基类虚函数的返回值类

型、函数名字、参数列表完全相同)

称子类的虚函数重写了基类的虚函数
普通函数的继承是实现继承

派生类继承了基类函数,可以使用函数

继承的是函数的实现
虚函数的继承是接口继承

派生类继承的是基类虚函数的接口

目的是为了重写,达成多态,继承的是接口

如果不实现多态,不要把函数定义成虚函数

cpp 复制代码
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
	
	// 只要父类析构加了virtual就构成多态,子类加不加都可以正确释放
	virtual ~Person() { cout << "~Person" << endl; };
};

class Student : public Person {
public: 
	// 子类可以不写virtual,因为他继承父类的接口,重写实现
    virtual void BuyTicket() { cout << "买票-半价" << endl; }
	
	~Student() { cout << "~Student" << endl; }
};

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

int main()
{
Person ps;
Student st;

// 构成多态后
Func(ps); // 传父类调用父类的虚函数
Func(st); // 传子类调用子类的虚函数
 return 0;
}

1.4 协变

如果是父子关系的指针或引用

返回值可以不同也构成多态

cpp 复制代码
class A{};
class B : public A {};
class Person {
public:
 virtual A* f() {return new A;}
};
class Student : public Person {
public:
 virtual B* f() {return new B;}
};

1.5 final和override

final: 修饰虚函数

表示该虚函数不能再被重写

现实中不常用,不能实现多态的虚函数

意义不大

cpp 复制代码
class Car
{
public:
 virtual void Drive() final {}
};
class Benz :public Car
{
public:
 virtual void Drive() {cout << "Benz-舒适" << endl;}
};

override: 检查派生类虚函数

是否重写了基类某个虚函数

如果没有重写编译报错

cpp 复制代码
class Car{
public:
 virtual void Drive(){}
};
class Benz :public Car {
public:
 virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

1.6 重载、覆盖(重写)、隐藏(重定义)的对比

面试题经常被问到

1.7 抽象类

在虚函数后面加上 =0

这个函数就叫纯虚函数

包含纯虚函数的类叫做抽象类

抽象类不能实例化出对象

派生类继承后也不能实例化出对象

只有重写纯虚函数,派生类才能实例化出对象

cpp 复制代码
class Car
{
public:
	// 纯虚函数 --- 抽象类
	virtual void Drive() = 0;
};

int main()
{
	Car car; // 无法实例化对象
	
	return 0;
}

二、多态的原理

2.1 虚函数表

这里常考一道笔试题:sizeof(Base)是多少?

cpp 复制代码
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

int main()
{
	cout << sizeof(Base) << endl;

	return 0;
}

在32位操作系统下是8 bit

在64位操作系统下是16 bit

通过调试发现还有个指针_vfptr

这个指针叫做虚函数表指针

本质是指针数组
用来存放虚函数的地址

2.2 多态的原理

通过下面代码观察父子类
的虚表之间的关系

cpp 复制代码
class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};
int main()
{
	Base b;
	Derive d;
	return 0;
}

监视窗口

通过监视窗口可以发现
派生类对象d中也有一个虚表指针
证明基类和派生类的虚表是不一样的

结论:

虚函数表本质是存虚函数指针的指针数组

一般情况这个数组最后面放了一个nullptr

对象中存的是虚表指针

虚表存的是虚函数指针

虚函数和普通函数一样的,都是存在代码段的

派生类的虚表生成:
a.先将基类中的虚表内容拷贝一份到派生类虚表中
b.如果派生类重写了基类中某个虚函数

用派生类自己的虚函数覆盖虚表中基类的虚函数
c.派生类自己新增加的虚函数按其在派生类中的

声明次序增加到派生类虚表的最后

2.3 在多态下建议把基类的析构函数定义成虚函数

如果基类析构函数不是虚函数

就调不到派生类的析构函数

(指针类型是父类,所以调用父类的析构函数)

从而造成内存泄漏

cpp 复制代码
class Person {
public:
 	virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
 	virtual ~Student() { cout << "~Student()" << endl; }
};

int main()
{
 	Person* ps = new Student;
 	delete ps;
 	
 	return 0;
}

形成多态的条件之一便是
只能通过父类去调用
所以子类对象只能强转成父类类型
如果父类的析构函数不是虚函数
那子类便调不到自己的析构函数
因为子类对象的类型是父类
所以只能调用父类的析构函数
子类成员无法释放从而造成内存泄漏
父类析构函数定义成虚函数便能解决问题

✨✨✨✨✨✨✨✨

本篇博客完,感谢阅读🌹

如有错误之处可评论指出

博主会耐心听取每条意见

✨✨✨✨✨✨✨✨

相关推荐
白拾9 分钟前
使用Conda管理python环境的指南
开发语言·python·conda
咖啡里的茶i27 分钟前
C++之继承
c++
从0至135 分钟前
力扣刷题 | 两数之和
c语言·开发语言
总裁余(余登武)35 分钟前
算法竞赛(Python)-万变中的不变“随机算法”
开发语言·python·算法
NormalConfidence_Man36 分钟前
C++新特性汇总
开发语言·c++
一个闪现必杀技41 分钟前
Python练习2
开发语言·python
有梦想的咕噜1 小时前
target_link_libraries()
开发语言
风清扬_jd1 小时前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
liu_chunhai1 小时前
设计模式(3)builder
java·开发语言·设计模式
姜学迁1 小时前
Rust-枚举
开发语言·后端·rust