C++ 多态

目录

多态是什么?

多态的使用

多态满足条件

多态使用条件

重写的概念

纯虚函数和抽象类

虚析构和纯虚析构

虚析构

虚析构函数

纯虚析构函数,不可只向纯虚函数一样只做声明,还需要析构函数实现

多态实现计算器

new ClassObj(constructAttribute) 和 new int(n)


 

C++ 多态_第1张图片 

多态是什么?

C++中的多态性是面向对象编程的重要特性之一,它允许不同的子类对象对同一消息做出独特的响应。C++中的多态性可以通过虚函数和继承来实现

  1. 静态多态性和动态多态性

    • 静态多态性(编译时多态)是指通过函数重载和运算符重载实现的多态性,编译器在编译时根据上下文决定实际调用的函数或运算符。
    • 动态多态性(运行时多态)是指通过继承和虚函数实现的多态性,运行时根据对象的实际类型来确定调用的函数。
  2. 虚函数和动态多态性

    • 虚函数是在基类中以 virtual 关键字声明的成员函数。它允许在派生类中对其进行重写,从而实现运行时多态性。
    • 当通过基类指针或引用来调用虚函数时,会根据对象的实际类型来决定调用哪个版本的函数。
  3. 纯虚函数和抽象类

    • 纯虚函数是在基类中以 virtual 关键字声明并赋予 0 初始值的虚函数。它使得基类成为抽象类,无法实例化对象,但可以作为其他类的基类。
    • 派生类必须实现(重写)基类中的纯虚函数,否则它们也会成为抽象类。
  4. 多态性的优势

    • 灵活性:通过多态性,同一份代码可以适应各种不同类型的对象,提高了代码的灵活性和可重用性。
    • 扩展性:在不修改现有代码的情况下,可以轻松地添加新的子类,并利用多态性机制自动适应新的子类对象。

多态的使用

多态满足条件

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

多态使用条件

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

重写的概念

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

class Animal
{
public:
	// speak函数就是虚函数
	// 函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。需运行阶段才能确定
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

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

class Dog :public Animal
{
public:

	void speak()
	{
		cout << "小狗在说话" << endl;
	}

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal) // 父类的引用/地址 class Animal *const animal
{
	animal.speak();
}

/*
	多态满足条件: 
	1、有继承关系
	2、子类重写父类中的虚函数

	多态使用:
	父类指针或引用指向子类对象地址
*/


void test01()
{
	Cat cat;
	DoSpeak(cat); // 小猫在说话 如果不使用virtual关键字修饰父类的speak函数,那么这里和下面执行的都是父类的speak函数


	Dog dog;
	DoSpeak(dog); // 小狗在说话
}


int main() {

	test01();

	return 0;
}

纯虚函数和抽象类

  • 类中只要有一个纯虚函数就称为抽象类
  • 抽象类无法实例化对象
  • 子类必须重写父类中的纯虚函数,否则也属于抽象类
class Base
{
public:
	/*
		纯虚函数
		类中只要有一个纯虚函数就称为抽象类
		抽象类无法实例化对象
		子类必须重写父类中的纯虚函数,否则也属于抽象类
	*/

	virtual void func() = 0;
};

class Son :public Base
{
public:
	virtual void func()
	{
		cout << "func调用" << endl;
	};
};

void test01()
{
	Base * base = NULL; // Base 类对象的指针,并将其初始化为 NULL。在 C++ 中,这通常被用作将指针初始化为空值的惯用做法。
	//base = new Base; // 错误,抽象类无法实例化对象
	base = new Son;
	base->func();
	delete base; // 记得销毁
}

int main() {

	test01();

	return 0;
}

虚析构和纯虚析构

  • 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 拥有纯虚析构函数的类也属于抽象类

通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏。怎么解决?给基类增加一个虚析构函数。虚析构函数就是用来解决通过父类指针释放子类对象

虚析构

class Animal {
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}

	/* 纯虚函数 */
	virtual void Speak() = 0;

	/* 
        正常析构函数,通过堆区的父类指针释放自身内存时,不会释放子类对象
    	~Animal()
    	{
    		cout << "Animal虚析构函数调用!" << endl;
	    }
     */


	/* 析构函数加上virtual关键字,变成虚析构函数,可以释放子类的堆区数据 */
	virtual ~Animal()
	{
		cout << "Animal虚析构函数调用!" << endl;
	}
};



class Cat : public Animal {
public:
	/* 在堆区开辟数据 */
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name); // 自身成员m_Name指向由堆区开辟的数据的地址
	}
	void Speak()
	{
		cout << *m_Name << "小猫在说话!" << endl;
	}
	/* 父类有虚析构函数,才会执行子类的虚析构函数 */
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom"); // 父类类型指针变量 animal 指向堆区开辟的子类Cat对象地址
	animal->Speak(); // 小猫在说话!

	通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	怎么解决?给基类增加一个虚析构函数
	虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}

int main() {

	/*
		Animal 构造函数调用!
		Cat构造函数调用!
		Tom小猫在说话!
		Cat析构函数调用!
		Animal 纯虚析构函数调用!
	*/
	test01();

	return 0;
}

和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

虚析构函数

纯虚析构函数,不可只向纯虚函数一样只做声明,还需要析构函数实现

class Animal {
public:

	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}

	/* 纯虚函数 */
	virtual void Speak() = 0;

	/* 
        正常析构函数,通过堆区的父类指针释放自身内存时,不会释放子类对象
    	~Animal()
    	{
    		cout << "Animal虚析构函数调用!" << endl;
    	}
     */

	/* 纯虚析构函数 */
	virtual ~Animal() = 0;
};

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


class Cat : public Animal {
public:
	/* 在堆区开辟数据 */
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name); // 自身成员m_Name指向由堆区开辟的数据的地址
	}
	void Speak()
	{
		cout << *m_Name << "小猫在说话!" << endl;
	}
	/* 父类有虚析构函数,才会执行子类的虚析构函数 */
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom"); // 父类类型指针变量 animal 指向堆区开辟的子类Cat对象地址
	animal->Speak(); // 小猫在说话!

	通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	怎么解决?给基类增加一个虚析构函数
	虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}

int main() {

	/*
		Animal 构造函数调用!
		Cat构造函数调用!
		Tom小猫在说话!
		Cat析构函数调用!
		Animal 纯虚析构函数调用!
	*/
	test01();

	return 0;
}

多态实现计算器

AbstractCalculator *abc = new AddCalculator

        创建了一个指向 AddCalculator 对象的 AbstractCalculator 指针。这允许你通过基类指针来访问派生类的对象。由于 AbstractCalculator 类中的 getResult() 函数被声明为虚函数,因此它是多态的,在运行时将根据实际对象的类型来调用相应的函数。因此,如果你调用 abc->getResult() 的话,实际上将调用 AddCalculator 类中重写的 getResult() 函数。在使用完毕后,一定要记得释放 abc 对象以避免内存泄漏,即使用 delete abc; 来释放对应的内存空间。

new ClassObj(constructAttribute) 和 new int(n)

  • new AddCalculator 将在堆内存中分配足够的空间来存储一个 AddCalculator 对象,并在内存中调用 AddCalculator 的构造函数进行初始化。
  • 由于返回的是指向 AddCalculator 对象的指针,所以可以将其赋值给指向基类的指针(这里是 AbstractCalculator*)。
  • 相比之下,像 new int(10) 这样的语句是在堆内存中分配了存储一个整型数值的空间,并将其初始化为 10。这里直接在堆内存中分配了可以存储整数值的内存空间,并不涉及到类的构造函数或继承关系。
  • 因此,在 C++ 中,new 运算符会在堆内存中分配空间来存储对象,并调用对象的构造函数进行初始化。而对于基本数据类型(如 int),也可以使用 new 来动态分配内存。

C++ 中可以使用父类的指针来接收由子类创建的对象的地址。这是由于C++中的多态性和继承机制。可以实现针对基类的指针或引用来调用派生类的成员函数,这种行为被称作多态。

#include 
using namespace std;

//普通实现
class Calculator {
public:
	int getResult(string oper)
	{
		if (oper == "+") {
			return m_Num1 + m_Num2;
		}
		else if (oper == "-") {
			return m_Num1 - m_Num2;
		}
		else if (oper == "*") {
			return m_Num1 * m_Num2;
		}
		//如果要提供新的运算,需要修改源码
	}
public:
	int m_Num1;
	int m_Num2;
};

void test01()
{
	//普通实现测试
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;
	cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

	cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

	cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}



//多态实现 父类指针指向子类对象地址
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public:

	/* 如果不加 vritual 关键字,那么都会走这个函数,结果都是0 */
	virtual int getResult() 
	{
		return 0;
	}

	int m_Num1;
	int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 + m_Num2;
	}
};

//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 - m_Num2;
	}
};

//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return m_Num1 * m_Num2;
	}
};


void test02()
{

	AbstractCalculator *abc = new AddCalculator; // 创建加法计算器
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; // 20
	delete abc;  //用完了记得销毁

	//创建减法计算器
	abc = new SubCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl; // 0
	delete abc;

	//创建乘法计算器
	abc = new MulCalculator;
	abc->m_Num1 = 10;
	abc->m_Num2 = 10;
	cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl; // 100
	delete abc;

}

int main() {

	//test01();

	test02();

	return 0;
}

你可能感兴趣的:(C++,c++,开发语言)