学习:类和对象——多态和纯虚和抽象类等

多态的基本概念:

多态是C++面向对象三大特性之一

多态分为两类

1、静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名 ,那么这个我们之前都有用到过
2、动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

1、静态多态的函数地址早绑定 - 编译阶段确定函数地址

2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址

示例代码:

#include<iostream>
#include<string>

using namespace std;

class Animal {
public:

	virtual void speak() { // 进行virtual修饰成员函数,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
		cout << "动物在说话" << endl;
	}
};

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

};

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

void aaa(Animal & animal) {
	animal.speak();
}

void test01() {
	Cat c1;
	aaa(c1);
	Dog d1;
	aaa(d1);
	
}

int main() {
	test01();
	system("pause");
	return 0;
}

总结:

多态满足的条件:

1、有继承关系
2、子类重写父类中的虚函数

多态使用条件:

1、父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写


多态的原理:

不理解原理的话,真的很难受

我们先来了解下一个虚函数虚拟指针虚拟表的概念:

虚函数:当函数被virtual修饰过后,这个函数也就会被称为虚函数

虚拟指针::当成为了虚函数,就会生成一个虚拟指针,这个指针保存的地址就是虚拟表的位置

我们通过之前的工具来查看下类的构造

正常的Cat类

那么继承的是来自父类中的一切

我们接着看下父类加了virtual之后的Cat类

这时候的虚拟指针就指向虚拟表中的Cat类中speak函数的地址


纯虚函数和抽象类:

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,所以我们在父类中的虚函数中定义的内容都没啥用

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例代码:

#include<iostream>
#include<string>

using namespace std;

class Base {
public:

	virtual void to_take() = 0; //定义了一个纯虚函数to_take

};

class Son :public Base {
public:

	void to_take() {
		cout << "这是实现了父类的to_take";
	}

};

class Son2 :public Base {

};

int main() {

	//Base b1; //直接实例化错误,因为Base为抽象类,为什么呢? 因为Base中用virtual定义了纯虚函数
	//Son2 s2; //直接实例化错误,就算继承了Base但是没有实现to_take的纯虚函数,这个类还是一个抽象类,不是实体类

	//以上的两个都是在栈区进行实例化的,那我们如果通过堆区实例化可以吗
	//b1 = new Base; //答案是一样的,不可以
 

	Base * base = new Son; //实例化成功,因为实现了父类中的纯虚函数
	base->to_take(); //这里由于是指针,所以我们通过->来进行调用




	system("pause");
	return 0;
}

虚析构和纯虚析构:

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现

虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

示例代码:解决方法两种

#include<iostream>
#include<string>

using namespace std;

class A {
public:
	virtual void speak() = 0; //纯虚函数

	A() {
		cout << "这是A的构造函数" << endl;
	}
	//virtual ~A() { //利用虚析构可以解决父类指针释放子类对象时候不干净的问题
	//	cout << "这是A的析构函数" << endl;
	//}

	virtual ~A() = 0; //利用纯虚析构来解决父类指针释放子类对象时候不干净的问题,在全局实现A的析构函数
};

A::~A() {
	cout << "这是A的析构函数" << endl;
}

class B:public A {
public:
	B(int age) {
		cout << "这是B的构造函数" << endl;
		this->age = new int(age);

	}
	~B() {
		if (age != NULL) {
			delete age;
			age = NULL;
			cout << "这是B的析构函数" << endl;
		}
	}

	virtual void speak() {
		cout << "年龄为"<< *this->age << "岁的B在说话" << endl;
	}

	
public:
	int * age; //定义一个指针变量,来储存堆区的数据

};

void test01() {
	A * a = new B(100);
	a->speak();
	delete a; //进行释放 调用相应的父类的析构函数 但是不会走子类的析构函数


}

int main() {
	test01();
	system("pause");
	return 0;
}

总结:

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类

原文地址:https://www.cnblogs.com/zpchcbd/p/11870079.html