目录
一、什么是多态?
1.介绍
多态,这个词听起来就像是某个超级英雄有多个形态一样,能在需要的时候变成任何东西!不过,在我们编程的世界里,多态并没有那么神奇,但它确实让我们的代码变得更加灵活多变。想象一下,你有一个动物园,里面有各种各样的动物,比如狮子、大象和企鹅。现在,你想让这些动物都做出“叫”这个动作。但是,每种动物的叫声都是不同的,对吧?狮子会吼叫,大象会鸣叫,而企鹅则会唧唧叫。
如果没有多态,你可能需要为每种动物编写一个不同的“叫”函数,然后在需要的时候调用对应的函数。但是,这样做不仅代码量庞大,而且维护起来也非常麻烦。每次添加一种新动物,你都需要编写一个新的“叫”函数,并且确保在正确的地方调用它。
现在,让我们来看看多态是如何拯救这个混乱的动物园的!
通过多态,你可以定义一个通用的“动物”类,并在其中包含一个虚函数“叫”。然后,你可以为每种具体的动物创建一个继承自“动物”类的子类,并在子类中实现自己的“叫”函数。这样,当你调用一个动物的“叫”函数时,程序会自动根据动物的实际类型来调用正确的函数。
这就像是在动物园里安装了一个“叫声转换器”。不管哪种动物走过来,只要按下“叫”按钮,转换器就会自动识别动物类型,并播放出对应的叫声。真是太神奇了!
所以,多态就像是编程世界里的超级英雄,让我们的代码变得更加简洁、灵活和易于维护。现在,你可以放心地添加更多种类的动物到你的动物园里,而不用担心叫声的问题了!
2.语法
多态允许你以统一的方式处理不同类型的对象,即使这些对象具有不同的实现。多态的语法主要涉及虚函数的声明、定义和调用
1. 虚函数的声明
在基类中,使用virtual
关键字声明一个函数为虚函数。这告诉编译器,该函数可能会在派生类中被重写(Override)。
class Base {
public:
virtual void func() { /* 基类实现 */ }
};
2. 虚函数的定义
在派生类中,你可以重写基类中的虚函数。重写意味着派生类提供一个与基类虚函数具有相同签名(函数名和参数列表)的函数。通常,你还会使用override
关键字来指示这是重写基类中的虚函数,尽管这不是强制性的,但它可以帮助编译器检查你的意图是否正确。
class Derived : public Base {
public:
void func() override { /* 派生类实现 */ }
};
3. 多态的调用
多态的调用通常通过基类指针或引用来实现。当你通过基类指针或引用调用一个虚函数时,实际调用的是对象实际类型所对应的实现,而不是指针或引用类型的实现。这就是动态绑定或运行时多态。
Base* ptr = new Derived();
ptr->func(); // 调用的是Derived类的func实现,而不是Base类的
4. 纯虚函数
纯虚函数是一种特殊的虚函数,它在基类中没有实现,并且要求任何派生类都必须提供实现。包含纯虚函数的类被称为抽象类,不能被实例化。纯虚函数在类定义中以= 0
结尾。
class AbstractBase {
public:
virtual void pureVirtualFunc() = 0; // 纯虚函数
};
5. 动态类型识别
有时,你可能需要在运行时确定对象的实际类型。这可以通过typeid
操作符或dynamic_cast
操作符来实现。
typeid
操作符返回一个type_info
对象,该对象包含了关于对象类型的信息。Base* ptr = new Derived(); cout << typeid(*ptr).name() << endl; // 输出Derived的类型信息
dynamic_cast
操作符用于在类层次结构中进行安全的向下和侧向类型转换Base* basePtr = new Derived(); Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); if (derivedPtr != nullptr) { // 安全的转换,可以使用derivedPtr }
3.虚函数特点
a.声明方式:在基类的成员函数声明前加上virtual
关键字。
b.重写:派生类可以重写(覆盖)基类中的虚函数。重写时,函数签名(函数名、参数列表)必须相同。
c.动态绑定:当使用基类指针或引用调用虚函数时,程序会在运行时根据对象的实际类型来决定调用哪个实现。这称为动态绑定或运行时多态。
d.默认实现:基类中的虚函数必须有定义(实现),除非它被声明为纯虚函数。
e.访问控制:虚函数的访问控制修饰符(如public
、protected
、private
)在基类和派生类中必须保持一致或更为宽松。
f.构造函数和析构函数:构造函数和析构函数不能是虚函数,但它们可以调用虚函数。在构造或析构过程中,对象的类型是已知的,因此虚函数机制在这种情况下不适用。
4.举例
在有继承关系的类之间,通过虚函数和动态绑定,使得基类指针或引用可以调用派生类的函数,实现不同类型对象对同一消息的不同响应。
Cat和Dog是Animal的子类,他们之间有继承关系。
在下面例子中,Animal
是一个抽象类,因为它包含了一个虚函数speak。Cat和Dog是Animal
的子类,并提供了speak的实现。通过Animal
类型的指针Cat,我们可以调用Cat对象的speak方法,实现多态。在运行时,根据Animal &animal实际指向的对象类型(Dog
),动态地绑定了speak的调用。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
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、得有继承关系
//2、子类重写父类的虚函数
//动态多态使用
//父类的指针或者引用 执行子类对象
void doSpeak(Animal& animal)//Animal &animal = cat;
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test01();
return 0;
}
二、案例
1.计算器
a.普通实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
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;
}
}
int m_Num1;//操作数1
int m_Num2;//操作数2
};
void test01()
{
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 20;
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;
}
缺点:
1).如果想扩展新的功能,需要修改源码
2).在真实开发中 提倡 开闭原则
3).开闭原则:对外扩展进行开放,对修改进行关闭
b.多态实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 + m_Num2;
}
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
public:
virtual int getResult()
{
return m_Num1 - m_Num2;
}
};
//加法计算器类
class MulCalculator :public AbstractCalculator
{
public:
virtual 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;
//用完后要释放
delete abc;
//减法运算
abc = new SubCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//乘法
abc = new MulCalculator;
abc->m_Num1 = 100;
abc->m_Num2 = 100;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main()
{
//test01();
test02();
return 0;
}
虽然代码量增多,但可拓展性大大增强,添加新功能更加方便。
优点:
1).组织结构清晰
2).可读性强
3).对于前期和后期扩展及维护
2.制作饮品
a.编写基础功能
//饮品基础步骤
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作函数
void doWork(AbstractDrinking* abs)
{
abs->makeDrink();
delete abs;
}
void test01()
{
//制作咖啡/茶叶
doWork(new Coffee);
}
int main()
{
test01();
return 0;
}
int main()
{
return 0;
}
b.制作咖啡
class Coffee :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入糖和牛奶" << endl;
}
};
c.制作茶叶
//制作茶叶
class Tea :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮矿泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入枸杞" << endl;
}
};
d.完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class AbstractDrinking
{
public:
//煮水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//制作饮品
void makeDrink()
{
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮农夫山泉" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡咖啡" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入糖和牛奶" << endl;
}
};
//制作茶叶
class Tea :public AbstractDrinking
{
public:
//煮水
virtual void Boil()
{
cout << "煮矿泉水" << endl;
}
//冲泡
virtual void Brew()
{
cout << "冲泡茶叶" << endl;
}
//倒入杯中
virtual void PourInCup()
{
cout << "倒入杯中" << endl;
}
//加入辅料
virtual void PutSomething()
{
cout << "加入枸杞" << endl;
}
};
//制作函数
void doWork(AbstractDrinking* abs)
{
abs->makeDrink();
delete abs;
}
void test01()
{
//制作咖啡
doWork(new Coffee);
}
int main()
{
test01();
return 0;
}
3.组装电脑
a.零件和电脑类
//零件类
class CPU //抽象CPU类
{
public:
//抽象计算函数
virtual void calculate() = 0;
};
class VideoCard //抽象显示类
{
public:
//抽象显示函数
virtual void display() = 0;
};
class Memory //抽象存储类
{
public:
//抽象存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
private:
CPU* m_cpu; //CPU的零件指针
VideoCard* m_vc;//显卡零件指针
Memory* m_mem;//内存条零件指针
};
int main()
{
test01();
return 0;
}
b.具体零件厂商
1.Inter厂商
class InterCpu : public CPU
{
void calculate()
{
cout << "Inter的CPU开始计算了!" << endl;
}
};
class InterVideoCard : public VideoCard
{
void display()
{
cout << "Inter的VideoCard开始显示了!" << endl;
}
};
class InterMemory : public Memory
{
void storage()
{
cout << "Inter的Memory开始存储了!" << endl;
}
};
2.Lenovo厂商
class LenovoCpu : public CPU
{
void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
void display()
{
cout << "Lenovo的VideoCard开始显示了!" << endl;
}
};
class LenovoMemory : public Memory
{
void storage()
{
cout << "Lenovo的Memory开始存储了!" << endl;
}
};
c.实现函数
1.运行函数
void test01()
{
//第一台电脑零件
CPU* interCpu = new InterCpu;
VideoCard* interCard = new InterVideoCard;
Memory* interMem = new InterMemory;
//创建第一台电脑
Computer* computer1 = new Computer(interCpu, interCard, interMem);
computer1->work();
delete computer1;
cout << "-----------------------------" << endl;
//第二台电脑
Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
cout << "-----------------------------" << endl;
//第三台电脑
Computer* computer3 = new Computer(new LenovoCpu, new InterVideoCard, new InterMemory);
computer3->work();
delete computer3;
}
2.调用函数
电脑类中添加调用函数
//提供工作的函数
void work()
{
//让零件工作起来,调用
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
3. 析构函数
数据开辟在栈区,需要手动释放,以免造成内存泄漏。
//提供析构函数 释放3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
4.完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//电脑案例
//零件类
class CPU //抽象CPU类
{
public:
//抽象计算函数
virtual void calculate() = 0;
};
class VideoCard //抽象显示类
{
public:
//抽象显示函数
virtual void display() = 0;
};
class Memory //抽象存储类
{
public:
//抽象存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU* cpu, VideoCard* vc, Memory* mem)
{
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
//提供工作的函数
void work()
{
//让零件工作起来,调用
m_cpu->calculate();
m_vc->display();
m_mem->storage();
}
//提供析构函数 释放3个电脑零件
~Computer()
{
if (m_cpu != NULL)
{
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL)
{
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL)
{
delete m_mem;
m_mem = NULL;
}
}
private:
CPU* m_cpu; //CPU的零件指针
VideoCard* m_vc;//显卡零件指针
Memory* m_mem;//内存条零件指针
};
//具体零件厂商
//Inter厂商
class InterCpu : public CPU
{
void calculate()
{
cout << "Inter的CPU开始计算了!" << endl;
}
};
class InterVideoCard : public VideoCard
{
void display()
{
cout << "Inter的VideoCard开始显示了!" << endl;
}
};
class InterMemory : public Memory
{
void storage()
{
cout << "Inter的Memory开始存储了!" << endl;
}
};
//Lenovo厂商
class LenovoCpu : public CPU
{
void calculate()
{
cout << "Lenovo的CPU开始计算了!" << endl;
}
};
class LenovoVideoCard : public VideoCard
{
void display()
{
cout << "Lenovo的VideoCard开始显示了!" << endl;
}
};
class LenovoMemory : public Memory
{
void storage()
{
cout << "Lenovo的Memory开始存储了!" << endl;
}
};
void test01()
{
//第一台电脑零件
CPU* interCpu = new InterCpu;
VideoCard* interCard = new InterVideoCard;
Memory* interMem = new InterMemory;
//创建第一台电脑
Computer* computer1 = new Computer(interCpu, interCard, interMem);
computer1->work();
delete computer1;
cout << "-----------------------------" << endl;
//第二台电脑
Computer* computer2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
cout << "-----------------------------" << endl;
//第三台电脑
Computer* computer3 = new Computer(new LenovoCpu, new InterVideoCard, new InterMemory);
computer3->work();
delete computer3;
}
int main()
{
test01();
return 0;
}
三、虚析构
1.问题
下面这个例子中,Cat类的析构函数没有被调用的原因是因为基类Animal的析构函数没有被声明为虚函数(virtual)。当使用基类指针删除派生类对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
~Animal()
{
cout << "Animal虚析构函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
};
class Cat :public Animal
{
public:
Cat(string name)
{
m_Name = new string(name);
}
virtual void speak()
{
cout << "Cat构造函数调用" << endl;
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
m_Name = NULL;
}
}
string* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tmo");
animal->speak();
delete animal;
}
int main()
{
test01();
return 0;
}
2.解决
将Animal类中的析构函数设为虚析构,即可解决子类析构不被调用的问题。
class Animal
{
public:
Animal()
{
cout << "Animal构造函数调用" << endl;
}
//虚析构
//利用虚析构可以解决 父类指针释放子类对象时不干净的问题
/*virtual ~Animal()
{
cout << "Animal虚析构函数调用" << endl;
}*/
//纯虚析构 需要声明也需要实现
//有了纯虚析构 之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
//纯虚函数
virtual void speak() = 0;
};
Animal::~Animal()
{
cout << "Animal纯虚析构函数调用" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
m_Name = new string(name);
}
virtual void speak()
{
cout << "Cat构造函数调用" << endl;
cout <<*m_Name<<"小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat析构函数调用" << endl;
m_Name = NULL;
}
}
string* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tmo");
animal->speak();
//父类指针在析构时候,不会调用子类中的析构函数,
// 导致子类如果有堆区属性,出现内存泄露
delete animal;
}
int main()
{
test01();
re
多态的有关知识暂时分享到这里,感谢观看。
看到这里,不妨点个攒,关注一下吧!
最后,谢谢你的观看