【C++基础】类和对象——多态
一、多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类
- 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 动态多态:派生类 和 虚函数 实现运行时多态
静态多态 和 动态多态 的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
动态多态满足条件
- 有继承关系
- 子类重写父类的虚函数
重写:函数返回值类型
、函数名
、参数列表
完全一致称为重写
动态多态使用
- 父类的指针或引用 , 指向子类对象
示例:
静态多态引发的问题:
#include<iostream>
using namespace std;
//基类
class Animal
{
public:
//添加virtual关键字,使成为虚函数,虚函数 为动态多态 晚绑定
void show()
{
cout << "这是 动物。" << endl;
}
};
//派生类
class Cat :public Animal
{
public:
void show()
{
cout << "这是 猫。" << endl;
}
};
//基类引用可以接收派生类对象
//地址早绑定,在编译阶段确定函数地址
void doShow(Animal& animal)
{
animal.show();
}
int main()
{
Cat cat;
//父类的指针或引用 , 指向子类对象
doShow(cat); //Animal & animal = cat;
}
输出:
想要的结果是调用猫的show()函数。
输出出错的原因如下:
因为地址早绑定,所以调用的是基类的show()函数。
解决方案:
完整代码如下:
#include<iostream>
using namespace std;
//基类
class Animal
{
public:
//添加virtual关键字,使成为虚函数,虚函数 为动态多态 晚绑定
virtual void show()
{
cout << "这是 动物。" << endl;
}
};
//动态多态满足条件
//1. 有继承关系
//2. 子类重写父类的虚函数
//派生类
class Cat :public Animal
{
public:
void show()
{
cout << "这是 猫。" << endl;
}
};
//基类引用可以接收派生类对象
//地址早绑定,在编译阶段确定函数地址
void doShow(Animal& animal)
{
animal.show();
}
int main()
{
Cat cat;
doShow(cat);
}
输出:
二、案例一:计算器实现
在C++中提倡使用多态
示例:
#include<iostream>
using namespace std;
//利用多态实现计算器的好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高
//实现计算机抽象类
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int num1;
int num2;
};
//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return num1 + num2;
}
};
int main()
{
//new 在堆区开辟空间,返回地址
AbstractCalculator* ac = new AddCalculator;
ac->num1 = 1;
ac->num2 = 2;
cout << "ac->getResult = " << ac->getResult() << endl;
//别忘了释放空间
delete ac;
}
三、纯虚函数和抽象类
在多态中,通常父类中的虚函数的实现是无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表 ) = 0 ;
当类中有了纯虚函数,这个类就称为 抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
无法实例化对象:
子类必须重写抽象类中的纯虚函数,否则也属于抽象类:
示例:
#include<iostream>
using namespace std;
//利用多态实现计算器的好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类就称为抽象类
//抽象类特点:
// 1、无法实例化对象
// 2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son :public Base
{
//c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
//因此,在子类从新声明该虚函数时,可以加,也可以不加,但习惯上每一层声明函数时都加virtual,使程序更加清晰。
virtual void func()
{
cout << "func函数" << endl;
}
};
int main()
{
Base* b = new Son;
b->func();
delete b;
}
四、多态案例二:制作饮品
#include<iostream>
using namespace std;
class AbstractDrink
{
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PourInCup() = 0;
virtual void PutSomething() = 0;
void makeDrink()
{
this->Boil();
this->Brew();
this->PourInCup();
this->PutSomething();
}
};
class Coffee :public AbstractDrink
{
public:
virtual void Boil()
{
cout << "加入自来水" << endl;
}
virtual void Brew()
{
cout << "地上捡的咖啡" << endl;
}
virtual void PourInCup()
{
cout << "WC里捡的杯子" << endl;
}
virtual void PutSomething()
{
cout << "加芥末,不要糖" << endl;
}
};
void doWork(AbstractDrink* ad)
{
ad->makeDrink();
delete ad;
}
int main()
{
doWork(new Coffee);
}
五、虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构
或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法(既要声明,也要实现):
声明virtual ~类名() = 0;
,实现类名::~类名(){}
问题:父类指针在释放时无法调用到子类的析构代码
示例:
#include<iostream>
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)
{
cout << "Cat()调用" << endl;
c_Name = new string(name);
}
virtual void speak()
{
cout << *c_Name << "的 猫··在说话" << endl;
}
string* c_Name;
~Cat()
{
if (c_Name != NULL)
{
cout << "~Cat()调用" << endl;
delete c_Name;
c_Name = NULL;
}
}
};
void test1()
{
Animal* a = new Cat("张三");
a->speak();
//父类指针在析构时,不会调用子类中的析构函数
//这导致子类如果有堆区属性,会出现内存泄露
delete a;
}
int main()
{
test1();
}
输出:
Cat类的析构函数没有调用,说明c_Name
对象没有被释放
将父类中的析构函数改为虚析构
或者纯虚析构
注:
- 纯虚析构,需要声明,也需要实现
- 有了纯虚析构之后,这个类也属于抽象类,无法实例化
- 一般在子类含有堆区数据时,才需要用到虚析构
示例:
#include<iostream>
using namespace std;
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)
{
cout << "Cat()调用" << endl;
c_Name = new string(name);
}
virtual void speak()
{
cout << *c_Name << "的 猫··在说话" << endl;
}
string* c_Name;
~Cat()
{
if (c_Name != NULL)
{
cout << "~Cat()调用" << endl;
delete c_Name;
c_Name = NULL;
}
}
};
void test1()
{
Animal* a = new Cat("张三");
a->speak();
//父类指针在析构时,不会调用子类中的析构函数
//这导致子类如果有堆区属性,会出现内存泄露
delete a;
}
int main()
{
test1();
}
输出:
总结:
- 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
六、多态案例三:电脑组装
#include<iostream>
using namespace std;
//抽象出CPU,内存,显卡
//抽象CPU类
class CPU
{
public:
//抽象的计算函数
virtual void calculate() = 0;
};
//抽象VideoCard类
class VideoCard
{
public:
//抽象的显示函数
virtual void display() = 0;
};
//抽象内存条类
class Memory
{
public:
//抽象的存储函数
virtual void storage() = 0;
};
//电脑类
class Computer
{
public:
Computer(CPU* cpu, VideoCard* videoCard, Memory* memory)
{
c_Cpu = cpu;
c_VideoCard = videoCard;
c_Memory = memory;
}
//提供工作函数
void work()
{
c_Cpu->calculate();
c_VideoCard->display();
c_Memory->storage();
}
//提供析构函数,释放CPU,内存,显卡
~Computer()
{
if (c_Cpu != NULL)
{
delete c_Cpu;
c_Cpu = NULL;
}
if (c_VideoCard != NULL)
{
delete c_VideoCard;
c_VideoCard = NULL;
}
if (c_Memory != NULL)
{
delete c_Memory;
c_Memory = NULL;
}
}
private:
CPU* c_Cpu;
VideoCard* c_VideoCard;
Memory* c_Memory;
};
//具体厂商
//Intel厂商
class IntelMemory :public Memory
{
public:
virtual void storage()
{
cout << "Intel的 Memory" << endl;
}
};
class IntelVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Intel的 VideoCard" << endl;
}
};
class IntelCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Intel的 CPU" << endl;
}
};
//Lenovo厂商
class LenovoMemory :public Memory
{
public:
virtual void storage()
{
cout << "Lenovo的 Memory" << endl;
}
};
class LenovoVideoCard :public VideoCard
{
public:
virtual void display()
{
cout << "Lenovo的 VideoCard" << endl;
}
};
class LenovoCPU :public CPU
{
public:
virtual void calculate()
{
cout << "Lenovo的 CPU" << endl;
}
};
void myComputer()
{
CPU* ic = new IntelCPU;
VideoCard* iv = new IntelVideoCard;
Memory* lm = new LenovoMemory;
Computer* computer = new Computer(ic,iv,lm);
computer->work();
delete computer;
}
int main()
{
myComputer();
}