栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++Day71.0

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++Day71.0

多态的实质:父类的引用或指针指向子类对象

怎么理解?
假设我们现在有如下的类,其中Animal为基类,Cat和Dog都是其派生类。

class Animal{
public:
	virtual void speak(){
		cout << "动物在说话" << endl;
	}
};
 
class Cat :public Animal{
public:
	void speak(){
		cout << "小猫在说话" << endl;
	}
};
 
class Dog :public Animal{
public:
	void speak(){
		cout << "小狗在说话" << endl;
	}
 
};

1.父类指针指向子类对象
调用时,可以定义父类的指针,然后指向子类的对象,指向哪个对象,便执行其对象的虚函数实现

int main() {
	方法1:
	
	Dog B;
	Animal *A=&B;
	A->speak();
	
	如果你不想给Dog类取名:
	Animal *A=new Dog;
	A->speak();
	
	方法2:
	
	A=new Cat;//如果小猫有名字A=new Cat("名字");
	A->speak();
	
	return 0;
}

2.父类引用指向子类对象
这种实现,我们需要额外定义一个“实现函数”,其参数是基类的引用(否则不能实现多态)。这样在调用时就实现了父类引用指向子类对象。

void DoSpeak(Animal & X){ //
	X.speak();
}
 
int main() {
	
	Cat A;
	DoSpeak(A);
 
	Dog B;
	DoSpeak(B);
	
	Animal C;
	DoSpeak(C);
	
	return 0;
}

.
.
.

一.静态联编与动态联编
多态分为:

  • 静态联编:如函数重载

    • 地址早绑定,编译阶段就确定好了地址(在写完这条语句就被绑定了)
  • 动态联编:如虚函数,继承关系
    -地址晚绑定,运行的时候才绑定好地址

#define _CRT_SECURE_NO_WARNINGS
#include
#include
using namespace std;

class Animal
{
public:
	virtual void speak()//加了virtual关键字,变成了虚函数
	{
		cout << "动物在说话" << endl;
	}

private:

};

class Cat:public Animal
{
public:
	void speak()//子类的virtual可写可不写
	{
		cout << "小猫在说话" << endl;
	}

private:

};

//当调用DoSpeak时,speak函数的地址早就绑定好了(静态联编),也就是绑定在class Animal中
//如果我们想要调用猫的DoSpeak函数,就不能提前绑定好speak函数的地址,而是在运行的时候再去确定(改成动态联编)

//动态联编的写法:把DoSpeak中的函数改成虚函数(+virtual关键字)
void DoSpeak(Animal& animal)	多态的实质:父类的引用或指针指向子类对象
{
	animal.speak();
}

//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
	Cat cat;
	DoSpeak(cat);//此行不会报错!因为Cat和Animal发生了继承的关系
}

int main()
{
	test01();

	system("pause");
	return 0;
}

.
.
.
二.多态原理解析

  • 当父类中有了虚函数(virtual)后,内部结构发生了改变

  • 内部多了一个virptf指针

    • virtual function pointer 虚函数表指针
    • 指向vftable虚函数表
  • 父类中的结构有:virptf虚函数表指针 vtfable虚函数表

  • 子类中进行继承,会继承父类的virptf虚函数表指针 vtfable虚函数表

  • 在子类的构造函数中,会自动将自己继承下来的virptf虚函数表指针指向自己的vtfable虚函数表

  • 如果子函数中发生了重写,会替换掉vtfable虚函数表中原有的speak,变成Cat::speak

    • 重写:子类写speak父类的虚函数,重写必须返回值,参数个数,类型,顺序都相同

.
.
.
三.抽象基类和纯虚函数
如果父类中有一些函数,它不需要任何的实现,只想让子类通过多态做新的实现(如计算机案例),此时可以用抽象类,也就是在类中加入至少一个纯虚函数,使得基类被称为抽象类

  1. 纯虚函数使用关键字virtual,并在其后面加上=0,如果试图去实例化一个抽象类,编译器会制止(报错)
  2. 当Calculator是一个抽象基类时Calculator cal ;和Calculator * cal = new Add_Calculator都会报错!(也就是Calculator里面含有纯虚函数)
  3. 当继承一个抽象类时,必须实现所有的纯虚函数(也就是通过多态进行改写),否则由抽象类派生的类也是一个抽象类
    .
    .
    .

四.虚析构和纯虚析构

虚析构:

  1. 调用形式:virtual~类名(){}
  2. 解决问题:通过父类指针指向子类对象释放时释放不干净的问题(普通的析构是不会调用子类的析构的,也就是子类的析构函数无法被调用,所以可能会释放不干净)

纯虚析构函数:

  1. 调用形式:virtual~类名()=0 类内声明,类外实现类名::~类名(){}
  2. 如果出现了纯虚构函数,这个类也算是抽象类,不能进行实例化

以给小动物取名为例

#define _CRT_SECURE_NO_WARNINGS
#include
#include
using namespace std;

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}

	~Animal()
	{
		cout << "Animal的析构函数调用" << endl;
	}
private:

};

class Cat :public Animal
{
public:
	//给小猫起个名字(拷贝构造函数)
	Cat(const char* name)
	{
		//由于是在堆上开辟的空间,所以要考虑深浅拷贝的问题
		this->m_name = new char[strlen(name) + 1];
		strcpy(this->m_name, name);
	}
	void speak()
	{
		cout << this->m_name << "在说话" << endl;
	}

	virtual ~Cat()
	{
		cout << "Cat的析构函数调用" << endl;
	}

private:
	char* m_name;
};





void test01()
{
	//多态的实质:父类的引用或指针指向子类对象
	Animal* animal = new Cat("TOM");
	animal->speak();

	delete animal;
}

int main()
{
	test01();

	
	system("pause");
	return 0;
}

如果不调用虚析构函数,那么Cat类的析构不会被调用

调用了虚析构函数,才会把子类的数据给清理掉!

纯析构函数的写法

class Animal
{
public:
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}

	virtual~Animal() = 0;
	
private:

};
Animal::~Animal()
{
	//纯虚析构函数实现
	cout << "纯虚析构函数调用" << endl;
}

文章转自布尔博客
欢迎关注技术公众号,获取更多硬件学习干货!

我们能为你提供什么?
技术辅导:C++、Java、嵌入式软件/硬件
项目辅导:软件/硬件项目、大厂实训项目
就业辅导:就业全流程辅导、技术创业支持
对接企业HR:培养输送优质性人才

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/297893.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号