C++(二)——继承(部分)&多态全部
C++核心编程
目录
四、类和对象
4.6继承
4.6.8菱形继承
-
概念
- 两个派生类继承一个基类
- 又有某个类同时继承这两个派生类
-
存在问题
- 出现二异性
- 相同的数据继承两份
- 出现二异性
-
解决问题
-
程序模拟菱形继承
#include<iostream>
using namespace std;
//动物类
class Animal{
public:
int m_Age;
};
//羊类
class Sheep :public Animal {};
//驼类
class Tuo :public Animal {};
//羊驼类
class SheepTuo :public Sheep, public Tuo {
};
void test01() {
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 20;
//菱形继承,两个父类拥有相同的数据,需要加以作用域区分
cout << "st.Sheep::m_Age:" << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age:" << st.Tuo::m_Age << endl;
//但是菱形数据使得数据存在两份,浪费资源
}
int main() {
test01();
system("pause");
return 0;
}
初次程序运行结果
- 更改为虚继承模式
#include<iostream>
using namespace std;
//动物类
class Animal{
public:
int m_Age;
};
//在加上virtual加上之后是虚继承
//动物类是虚基类
//羊类
class Sheep :virtual public Animal {};
//驼类
class Tuo :virtual public Animal {};
//羊驼类
class SheepTuo :virtual public Sheep, public Tuo {
};
void test01() {
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 20;
st.m_Age = 18;
//菱形继承,两个父类拥有相同的数据,需要加以作用域区分
cout << "st.Sheep::m_Age:" << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age:" << st.Tuo::m_Age << endl;
//但是菱形数据使得数据存在两份,浪费资源
}
int main() {
test01();
system("pause");
return 0;
}
运行结果
查看底层实现
- 打开开发人员命令提示符(开始菜单中VS下的工具)
E:\C++Study\5_继承>cl /d1reportSingleClassLayoutSheepTuo "08菱形继承.cpp"
3. 总结:
1. 在使用之前需要进行作用域区分
2. 解决多继承问题,虚继承共同访问一个数据,减少资源浪费
4.7多态
4.7.1多态的基本概念
- 多态分为两类
- 静态多态:函数以及运算符的重载
- 动态多态:派生类和虚函数实现运行时的多态
- 区别
- 静态多态函数地址早绑定
- 动态堕胎函数地址晚绑定
- 两种多态的展示
#include<iostream>
using namespace std;
//多态
//动物类
class Animal {
public:
void speak() {
cout << "I'm talking!" << endl;
}
};
//猫类
class Cat :public Animal {
public:
void speak() {
cout << "Cat is talking!" << endl;
}
};
//执行说话的函数
//地址早绑定,在编译阶段确定了函数的地址
void doSpeak(Animal &animal) {
animal.speak();
}
//
void test01() {
Cat cat;
doSpeak(cat);//父类的引用指向了子类的对象
}
int main() {
test01();
system("pause");
return 0;
}
- 实现的是动物说话
#include<iostream>
using namespace std;
//多态
//动物类
class Animal {
public:
//虚函数
virtual void speak() {
cout << "I'm talking!" << endl;
}
};
//猫类
class Cat :public Animal {
public:
void speak() {
cout << "Cat is talking!" << endl;
}
};
//执行说话的函数
//进行地址的晚绑定,在运行阶段进行绑定
void doSpeak(Animal &animal) {
animal.speak();
}
//
void test01() {
Cat cat;
doSpeak(cat);//父类的引用指向了子类的对象
}
int main() {
test01();
system("pause");
return 0;
}
- 实现的是猫说话
- 动态多态的满足条件
- 有继承关系
- 子类重写(内容均相同)父类的虚函数
- 函数返回值,函数名称,参数列表完全相同是重写
- 子类函数的时候可以不写virtual关键字
- 多态的使用
- 父类的指针或者引用,指向子类对象
- 总结
- 动态多态的原理
- 案例中进行重写父类虚函数
- 区分重载和重写
4.7.2多态的原理剖析
- 不含虚函数的对象所占用内存的大小
- 含虚函数的对象所占用内存的大小
- 占用的是指针
函数的内部结构
两种情况之下的函数的结构
4.7.3多态案例——计算器类
- 案例描述
- 分别使用两种不同的方法去写计算器:多态和普通方法
- 多态优点:
- 逻辑清晰
- 可读性强
- 利于前期拓展和后期维护
- 实现传统计算器
#include<iostream>
#include<string>
using namespace std;
//普通的写法
class Calculator {
public:
int getResult(string oper) {
if (oper == "+") {
return m_A + m_B;
}
else if (oper == "-")
return m_A - m_B;
else if (oper == "*")
return m_A * m_B;
}
int m_A;//操作数1
int m_B;//操作数2
};
void test01() {
//创建一个计算器的对象
Calculator c;
c.m_A = 10;
c.m_B = 20;
cout << c.m_A << "+" << c.m_B << "=" << c.getResult("+") << endl;
cout << c.m_A << "-" << c.m_B << "=" << c.getResult("-") << endl;
cout << c.m_A << "*" << c.m_B << "=" << c.getResult("*") << endl;
}
int main() {
test01();
return 0;
}
- 扩展新的功能,需要扩展源码,在此代码中在if else 中进行操作,真正的开发中会复杂。
- 真正的开发需要开闭原则:对扩展进行开放,对修改进行关闭
- 多态实现计算器
#include<iostream>
#include<string>
using namespace std;
////普通的写法
//class Calculator {
//public:
//
// int getResult(string oper) {
// if (oper == "+") {
// return m_A + m_B;
// }
// else if (oper == "-")
// return m_A - m_B;
// else if (oper == "*")
// return m_A * m_B;
// }
// int m_A;//操作数1
// int m_B;//操作数2
//};
//
//void test01() {
// //创建一个计算器的对象
// Calculator c;
// c.m_A = 10;
// c.m_B = 20;
// cout << c.m_A << "+" << c.m_B << "=" << c.getResult("+") << endl;
// cout << c.m_A << "-" << c.m_B << "=" << c.getResult("-") << endl;
// cout << c.m_A << "*" << c.m_B << "=" << c.getResult("*") << endl;
//}
//利用多态实现计算器
//实现计算器的抽象类
class AbstractCarculator {
public:
virtual int getResult() {
return 0;
}
int m_A;
int m_B;
};
//加法计算器的类
class AddCalculator :public AbstractCarculator {
public:
int getResult() {
return m_A + m_B;
}
};
//减法计算器的类
class SubCalculator :public AbstractCarculator {
public:
int getResult() {
return m_A - m_B;
}
};
//乘法计算器的类
class MulCalculator :public AbstractCarculator {
public:
int getResult() {
return m_A * m_B;
}
};
void test02() {
//多态的使用:父类的指针或者引用指向子类的对象
//加法运算
AbstractCarculator *abc = new AddCalculator;
abc->m_A = 10;
abc->m_B = 20;
cout << abc->m_A << "+" << abc->m_B << "=" << abc->getResult() << endl;
//用完后进行销毁
delete abc;
//减法运算
abc = new SubCalculator;
abc->m_A = 10;
abc->m_B = 20;
cout << abc->m_A << "-" << abc->m_B << "=" << abc->getResult() << endl;
//用完后进行销毁
delete abc;
//乘法运算
abc = new MulCalculator;
abc->m_A = 10;
abc->m_B = 20;
cout << abc->m_A << "+" << abc->m_B << "=" << abc->getResult() << endl;
//用完后进行销毁
delete abc;
}
int main() {
//test01();
test02();
return 0;
}
- 运算结果
- 多态的好处
- 组织结构清晰
- 可读性强:子类重写父类中的虚函数
- 对于前期和后期的扩展和维护性高
- 总结
- 提倡通过多态进行程序架构的设计
4.7.4纯虚函数和抽象类
- 纯虚函数
- 父类中的虚函数用处不大(似乎是)
- 语法
virtual 返回值类型 函数名称 (参数列表)= 0;
- 当一个类中存在了纯虚函数,这个类称之为抽象类
- 抽象类特点
- 无法实例化对象
- 子类中必须重写抽象类中的纯虚函数,否则也属于抽象类
- 示例
#include<iostream>
#include<string>
using namespace std;
//纯虚函数和抽象类
class Base {
public:
//虚函数直接等于0,为纯虚函数
//此类为抽象类:无法实例化对象;子类需要重写纯虚函数
virtual void func() = 0;
};
class Son:public Base {
public:
virtual void func() {
cout << "func函数的调用" << endl;
};
};
void test01() {
///报错
//Base b;
//子类需要重写父类中的纯虚函数,否则无法实例化对象
//Son s;
Base *base = new Son;
base->func();
}
int main() {
test01();
system("pause");
return 0;
}
- 总结
- 抽象类的子类需要重写父类中的对象
- 利用多态的技术调用接口
4.7.5多态案例2——制作饮品
- 案例描述:
- 流程:煮水——重拍——倒入杯中——加入辅料
- 多态实现:
- 父类抽象(纯虚函数)
- 子类实现(具体代码)
#include<iostream>
#include<string>
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:
void Boil() {
cout << "水已经煮好了" << endl;
}
void Brew() {
cout << "冲泡咖啡" << endl;
}
void PourInCup() {
cout << "导入咖啡杯" << endl;
}
void PutSomething() {
cout << "加入糖和奶" << endl;
}
};
//制作茶叶
class Tea :public AbstractDrinking {
public:
void Boil() {
cout << "水已经煮好了" << endl;
}
void Brew() {
cout << "冲泡红茶" << endl;
}
void PourInCup() {
cout << "导入茶杯" << endl;
}
void PutSomething() {
cout << "加入珍珠" << endl;
}
};
//工作的函数
void doWork(AbstractDrinking *drinking) {
drinking->makeDrink();
delete drinking;//进行内存释放
}
void test01() {
//制作咖啡
doWork(new Coffee);
cout << "============" << endl;
//制作茶叶
doWork(new Tea);
}
int main() {
test01();
system("pause");
return 0;
}
- 接口实现多态,设计类的时候扩展之前的代码即可
4.7.6虚析构和纯虚析构
- 父类的指针无法释放子类的虚构的代码,堆区的数据会造成内存的泄露
- 虚析构和纯虚析构:
- 可以解决父类指针释放子类对象的问题
- 需要具体的函数进行实现
- 二者区别:纯虚析构无法实例化对象
- 具体场景介绍
#include<iostream>
#include<string>
using namespace std;
//虚析构和纯虚析构
class Animal {
public:
//虚函数
virtual void speak() = 0;
Animal() {
cout << "Animal构造函数的调用" << endl;
}
~Animal() {
cout << "Animal析构函数的调用" << endl;
}
};
//猫类
class Cat :public Animal {
public:
void speak() {
cout <<*m_Name<< " Cat is talking!" << endl;
}
//Cat的构造函数,需要析构函数释放
Cat(string name) {
m_Name=new string(name);
cout << "Cat构造函数的调用" << endl;
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat析构函数的调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;//数据创建在堆区,用指针来进行维护
};
//执行说话的函数
//进行地址的晚绑定,在运行阶段进行绑定
void doSpeak(Animal &animal) {
animal.speak();
}
void test01() {
Animal *animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
- Cat的析构函数没有执行,堆区的数据没有释放,造成内存泄漏
- 内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
- 将虚构写成虚析构
#include<iostream>
#include<string>
using namespace std;
//虚析构和纯虚析构
class Animal {
public:
//虚函数
virtual void speak() = 0;
Animal() {
cout << "Animal构造函数的调用" << endl;
}
//利用虚析构解决 父类指针释放子类对象不干净的问题
virtual ~Animal() {
cout << "Animal析构函数的调用" << endl;
}
};
//猫类
class Cat :public Animal {
public:
void speak() {
cout <<*m_Name<< " Cat is talking!" << endl;
}
//Cat的构造函数,需要析构函数释放
Cat(string name) {
m_Name=new string(name);
cout << "Cat构造函数的调用" << endl;
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat析构函数的调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;//数据创建在堆区,用指针来进行维护
};
//执行说话的函数
//进行地址的晚绑定,在运行阶段进行绑定
void doSpeak(Animal &animal) {
animal.speak();
}
void test01() {
Animal *animal = new Cat("Tom");
animal->speak();
// 父类指针在析构时,不会调用子类的析构函数,导致子类中如果有堆区属性,出现内存的泄露情况
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
- 纯虚析构
- 纯虚析构也无法实例化对象
#include<iostream>
#include<string>
using namespace std;
//虚析构和纯虚析构
class Animal {
public:
//虚函数
virtual void speak() = 0;
Animal() {
cout << "Animal构造函数的调用" << endl;
}
////利用虚析构解决 父类指针释放子类对象不干净的问题
//virtual ~Animal() {
// cout << "Animal析构函数的调用" << endl;
//}
//纯虚析构
virtual ~Animal() = 0;
};
//纯虚析构,
Animal:: ~Animal() {
cout << "纯虚析构函数" << endl;
};
//猫类
class Cat :public Animal {
public:
void speak() {
cout <<*m_Name<< " Cat is talking!" << endl;
}
//Cat的构造函数,需要析构函数释放
Cat(string name) {
m_Name=new string(name);
cout << "Cat构造函数的调用" << endl;
}
~Cat() {
if (m_Name != NULL) {
cout << "Cat析构函数的调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;//数据创建在堆区,用指针来进行维护
};
//执行说话的函数
//进行地址的晚绑定,在运行阶段进行绑定
void doSpeak(Animal &animal) {
animal.speak();
}
void test01() {
Animal *animal = new Cat("Tom");
animal->speak();
// 父类指针在析构时,不会调用子类的析构函数,导致子类中如果有堆区属性,出现内存的泄露情况
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
- 运行结果
- 总结
- 子类中没有堆区数据可以不写析构
- 拥有纯虚析构函数也还是抽象类,无法实例化对象
- 虚析构和纯虚析构用来解决父类指针释放子类对象的问题
4.7.7多条案例三——电脑的组装
- 案例描述
- 电脑主要三部分组成部分:CPU,显卡,内存
- 每个部分抽象基类
- 创建电脑工作的函数,并且调用每个零件的接口
- 测试组装三台不同的电脑进行工作
- 图形描述
- 示意图
- 示意图
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//抽象CPU类
class CPU {
public:
virtual void calculate() = 0;
};
//抽象GPU类
class GPU {
public:
virtual void display() = 0;
};
//抽象内存条类
class Memory {
public:
virtual void storage() = 0;
};
//电脑类构造传入的指针以及工作的函数
//最难的部分
class Computer {
public:
Computer(CPU *cpu,GPU * gpu,Memory * mem) {
m_cpu = cpu;
m_gpu = gpu;
m_mem = mem;
}
//析构函数进行内存的释放
~Computer() {
if (m_cpu != NULL) {
delete m_cpu;
m_cpu = NULL;
}
if (m_gpu != NULL) {
delete m_gpu;
m_gpu = NULL;
}
if (m_mem != NULL) {
delete m_mem;
m_mem = NULL;
}
}
void doWork() {
m_cpu->calculate();
m_gpu->display();
m_mem->storage();
}
private:
CPU *m_cpu;
GPU *m_gpu;
Memory *m_mem;
};
class IntelCpu :public CPU {
public:
void calculate() {
cout << "Intel's CPU is working!" << endl;
}
};
class LevenCpu :public CPU {
public:
void calculate() {
cout << "Leven's CPU is working!" << endl;
}
};
class HuaWeiCpu :public CPU {
public:
void calculate() {
cout << "HuaWei's CPU is working!" << endl;
}
};
class IntelGpu :public GPU {
public:
void display() {
cout << "Intel's GPU is working!" << endl;
}
};
class LevenGpu :public GPU {
public:
void display() {
cout << "Leven's GPU is working!" << endl;
}
};
class HuaWeiGpu :public GPU {
public:
void display() {
cout << "HuaWei's GPU is working!" << endl;
}
};
class IntelMemory :public Memory {
public:
void storage() {
cout << "Intel's Memory is working!" << endl;
}
};
class LevenMemory :public Memory {
public:
void storage() {
cout << "Leven's Memory is working!" << endl;
}
};
class HuaWeiMemory :public Memory {
public:
void storage() {
cout << "HuaWei's Memory is working!" << endl;
}
};
void test01() {
cout << "Computer1 is working!!!" << endl;
//第一台电脑的组装
CPU *intelCpu = new IntelCpu;
GPU *intelGpu = new IntelGpu;
Memory *intelMenory = new IntelMemory;
Computer *computer1 = new Computer(intelCpu, intelGpu, intelMenory);
computer1->doWork();
delete computer1;
cout << "========================" << endl;
cout << "Computer2 is working!!!" << endl;
//第二台电脑的组装
Computer *computer2 = new Computer(new LevenCpu, new LevenGpu, new LevenMemory);
computer2->doWork();
delete computer2;
//电脑的零件内存没有释放,电脑对象的内存释放了。
//电脑的类中的三个指针维护着这三个零件,在电脑中进行析构函数的过程中对这三个对象进行释放
cout << "========================" << endl;
cout << "Computer3 is working!!!" << endl;
//第三台电脑的组装
Computer *computer3 = new Computer(new HuaWeiCpu, new HuaWeiGpu, new HuaWeiMemory);
computer3->doWork();
delete computer3;
}
int main() {
test01();
system("pause");
return 0;
}
- 重点
- 电脑指针的维护和析构函数书写