C++ 多态
2023-12-16 16:03:53
目录
纯虚析构函数,不可只向纯虚函数一样只做声明,还需要析构函数实现
new ClassObj(constructAttribute)?和 new int(n)
?
?
多态是什么?
C++中的多态性是面向对象编程的重要特性之一,它允许不同的子类对象对同一消息做出独特的响应。C++中的多态性可以通过虚函数和继承来实现
-
静态多态性和动态多态性:
- 静态多态性(编译时多态)是指通过函数重载和运算符重载实现的多态性,编译器在编译时根据上下文决定实际调用的函数或运算符。
- 动态多态性(运行时多态)是指通过继承和虚函数实现的多态性,运行时根据对象的实际类型来确定调用的函数。
-
虚函数和动态多态性:
- 虚函数是在基类中以?
virtual
?关键字声明的成员函数。它允许在派生类中对其进行重写,从而实现运行时多态性。 - 当通过基类指针或引用来调用虚函数时,会根据对象的实际类型来决定调用哪个版本的函数。
- 虚函数是在基类中以?
-
纯虚函数和抽象类:
- 纯虚函数是在基类中以?
virtual
?关键字声明并赋予 0 初始值的虚函数。它使得基类成为抽象类,无法实例化对象,但可以作为其他类的基类。 - 派生类必须实现(重写)基类中的纯虚函数,否则它们也会成为抽象类。
- 纯虚函数是在基类中以?
-
多态性的优势:
- 灵活性:通过多态性,同一份代码可以适应各种不同类型的对象,提高了代码的灵活性和可重用性。
- 扩展性:在不修改现有代码的情况下,可以轻松地添加新的子类,并利用多态性机制自动适应新的子类对象。
多态的使用
多态满足条件
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
父类指针或引用指向子类对象
重写的概念
函数返回值类型??函数名?参数列表?完全一致称为重写
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 <iostream>
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;
}
文章来源:https://blog.csdn.net/dabaooooq/article/details/135031791
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!