六、继承
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
6.1 继承的基础语法
例如我们看到很多网站中, 都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//继承实现页面
//继承的好处:减少重复代码
//语法: class 子类 : 继承方式 父类
//子类 也称为 派生类
//父类 也称为 基类class BasePage {
public:void header() {cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer() {cout << "帮助中心、交流合作、站内地图...()" << endl;}void left() {cout << "Java、Python、C++...(公共分类列表)" << endl;}
};//Java页面
class Java : public BasePage {
public:void content() {cout << "Java学科视频" << endl;}
};//Python页面
class Python : public BasePage {
public:void content() {cout << "Python学科视频" << endl;}
};//C++页面
class CPP : public BasePage {
public:void content() {cout << "CPP学科视频" << endl;}
};void test01() {cout << "Java下载视频页面如下:" << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "---------------" << endl;cout << "Python下载视频页面如下:" << endl;Python py;py.header();py.footer();py.left();py.content();cout << "---------------" << endl;cout << "C++下载视频页面如下:" << endl;CPP cpp;cpp.header();cpp.footer();cpp.left();cpp.content();}int main() {test01();system("pause");return 0;}
总结:
继承的好处:可以减少重复的代码 class A : public B;
A类称为子类或派生类 。B类称为父类或基类
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。
6.2 继承方式
继承的语法:
class 子类 : 继承方式 父类
继承方式一共有三种:
公共继承 保护继承 私有继承
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//公共继承
class Base1 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son1 : public Base1 {
public:void func() {m_A = 10;//父类中的公共权限成员,到子类中依然是公共权限m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限//m_C = 10;父类中私有访问权限 子类访问不到}
};void test01() {Son1 s1;s1.m_A = 100;//s1.m_B = 100;//报错,到Son1中m_B为保护权限类外无法访问}//保护继承
class Base2 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son2 : private Base2 {
public:void func() {m_A = 10;//父类中的公共权限成员,到子类中变为了保护权限m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限//m_C = 10;父类中私有访问权限 子类访问不到}
};void test02() {Son2 s2;//s2.m_A = 100; //在Son2中m_A变为保护权限,因此类外访问不到//s2.m_B = 100; //在Son2中mB保护权限不可以访问}//私有继承
class Base3 {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son3 : private Base3 {
public:void func() {m_A = 10;//父类中的公共权限成员,到子类中变为了私有权限m_B = 10;//父类中的保护权限成员,到子类中变为了私有权限//m_C = 10;父类中私有访问权限 子类访问不到}
};void test03() {Son3 s3;//s3.m_A = 100; //在Son3中m_A变为私有权限,因此类外访问不到//s3.m_B = 100; //在Son2中m_B私有权限不可以访问}
//
//class GrandSon3 : private Son3 {
//public:
// void func() {
// m_A = 100;//变为私有无法访问
// }
//};int main() {test01();test02();test03();system("pause");return 0;}
6.3 继承中的对象类型
问题:从父类继承过来的成员,哪些属于子类对象中?
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//继承中的对象模型
class Base {
public:int m_A;
protected:int m_B;
private:int m_C;
};class Son : public Base {
public:int m_D;
};void test01() {//16 //父类中所有非静态成员属性都会被子类继承下去//父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了cout << "size of son = " << sizeof(Son) << endl;
}int main() {test01();system("pause");return 0;}
利用Developer Command Prompt for vs 2022查看对象模型
跳转到盘符 D:
跳转文件路径cd具体路径下
查看命名
cl /d1 reportSingleClassLayout类名 文件名
6.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构顺序是谁先谁后?
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//继承中构造和析构的顺序
class Base {
public:Base() {cout << "Base的构造函数" << endl;}~Base() {cout << "Base的析构函数" << endl;}
};class Son : public Base {
public:Son() {cout << "Son的构造函数" << endl;}~Son() {cout << "Son的析构函数" << endl;}
};void test01() {//继承中的构造和析构顺序如下://先构造父类,再构造子类,析构的顺序与构造的顺序相反Son s;
}int main() {test01();system("pause");return 0;}
总结:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
6.5 继承同名成员的处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
·访问子类同名成员,直接访问即可
·访问父类同名成员,需要加作用域
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Base {
public:Base() {m_A = 100;}void func() {cout << "Base - func() 调用" << endl;}void func(int a) {cout << "Base - func(int a) 调用" << endl;}int m_A;
};class Son : public Base {
public:Son() {m_A = 200;}void func() {cout << "Son - func() 调用" << endl;}int m_A;
};
//同名成员属性处理
void test01() {Son s;cout << "Son 下的 m_A = " << s.m_A << endl;//如果通过子类对象―访问到父类中同名成员,需要加作用域cout << "Base 下的 m_A = " << s.Base::m_A << endl;
}
//同名成员函数处理
void test02() {Son s;s.func();//直接调用调用是子类中的同名成员s.Base::func();//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数//所以加作用域s.Base::func(100);}int main() {test01();test02();system("pause");return 0;}
总结:
1.子类对象可以直接访问到子类中同名成员
2.子类对象加作用域可以访问到父类同名成员
3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
6.6 继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致
1.访问子类同名成员直接访问即可
2.访问父类同名成员需要加作用域
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Base {
public:static int m_A;static void func() {cout << "Base - static void func()" << endl;}
};int Base::m_A = 100;class Son : public Base {
public:static int m_A;static void func() {cout << "Son - static void func()" << endl;}
};
int Son::m_A = 200;//同名静态成员属性
void test01() {Son s;//1.通过对象访问cout << "通过对象访问" << endl;cout << "Son 下的 m_A = " << s.m_A << endl;cout << "Base 下的 m_A = " << s.Base::m_A << endl;//2.通过类名访问cout << "通过类名访问" << endl;cout << "Son 下的 m_A = " << Son::m_A << endl;//第一个::代表通过类名方式访问// 第二个::代表访问父类作用域下cout << "Base 下的 m_A = " << Son::Base::m_A << endl;
}//同名静态函数
void test02() {Son s;//1.通过对象访问cout << "通过对象访问" << endl;s.func();s.Base::func();//2.通过类名访问cout << "通过类名访问" << endl;Son::func();Son::Base::func();}int main() {test01();test02();system("pause");return 0;}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类名)
6.7 多继承语法
C++允许一个类继承多个类
语法:
class 子类∶继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Base1 {
public:Base1() {m_A = 100;}int m_A;
};class Base2 {
public:Base2() {m_A = 200;}int m_A;
};//子类需要继承Base1和Base2
class Son : public Base1 ,public Base2{
public:Son() {m_C = 300;m_D = 400;}int m_C;int m_D;
};void test01() {Son s;//16个cout << "sizeof Son = " << sizeof(Son) << endl;//当父类中出现同名成员,需要加作用域区分cout << "Base1 下的 m_A = " << s.Base1::m_A << endl;cout << "Base2 下的 m_A = " << s.Base2::m_A << endl;
}int main() {test01();system("pause");return 0;}
总结:多继承中如果父类中出现了同名情况,子类使用时候要加作用域
6.8 菱形继承
菱形继承概念:
两个派生类继承同一个基类 又有某个类同时继承者两个派生类 这种继承被称为菱形继承,或者钻石继承
eg.
菱形继承问题:
1.羊继承了动物的数据,范同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
⒉草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Animal {
public:int m_Age;
};//利用虚继承解决菱形继承的问题
//继承之前加上关键字virtual变为虚继承
//Animal类称为虚基类
//羊类
class Sheep : virtual public Animal{};//驼类
class Tuo : virtual public Animal{};//羊驼类
class SheepTuo : public Sheep,public Tuo{};void test01() {SheepTuo st;st.Sheep::m_Age = 18;st.Tuo::m_Age = 28;//当菱形继承,两个父类拥有相同数据,需要加以作用域区分cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;cout << "st.m_Age = " << st.m_Age << endl;//这份数据我们知道只有有一份就可以,菱形继承导致数据有两份,资源浪费}int main() {test01();system("pause");return 0;}
总结:
1.菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
2.利用虚继承可以解决菱形继承问题
七、多态
7.1 多态的基本概念
多态是C++面向对象三大特性之一多态分为两类
1.静态多态:函数重载和这算符重载属于静态多态,复用函数名
2.动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
·静态多态的函数地址早绑定–编译阶段确定函数地址
·动态多态的函数地址晚绑定–运行阶段确定函数地址
下面通过案例进行讲解多态
#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://重写函数返回值类型函数名参数列表完全相同virtual这里写不写都行virtual 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;Dog dog;doSpeak(cat);doSpeak(dog);
}int main() {test01();system("pause");return 0;}
总结:
多态满足条件:
1.有继承关系
2.子类重写父类中的虚函数
多态使用条件:父类指针或引用指向子类对象
重写:函数返回值类型函数名参数列表完全—致称为重写
原理剖析:
7.2 多态案例一-计算器类
案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类
多态的优点:
1.代码组织结构清晰
2.可读性强
3.利于前期和后期的扩展以及维护
#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;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;//如果想扩展新的功能,需求修改源码//在真实开发中提倡开闭原则//开闭原则:对扩展进行开发,对修改进行关闭
}//利用多态实现计算器
//多态好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高
class AbstractCaluator {
public:virtual int getResult() {return 0;}int m_Num1;int m_Num2;
};//加法计算器类
class AddCalculator : public AbstractCaluator {int getResult() {return m_Num1 + m_Num2;}
};
//减法计算器类
class SubCalculator : public AbstractCaluator {int getResult() {return m_Num1 - m_Num2;}
};
//乘法计算器类
class MulCalculator : public AbstractCaluator {int getResult() {return m_Num1 * m_Num2;}
};void test02() {//多态使用条件//父类指针或者引用指向子类对象//加法运算AbstractCaluator* 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 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;//乘法运算abc = new MulCalculator;abc->m_Num1 = 10;abc->m_Num2 = 10;cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;delete abc;
}int main() {//test01();test02();system("pause");return 0;}
总结:C++开发提倡利用多态设计程序架构,因为多态优点很多
7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:
virtual 返回值类型 函数名 (参数列表) = 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Base {
public://纯虚函数//只要有一个纯虚函数,这个类称为抽象类//抽象类特点://1、无法实例化对象//2、抽象类的子类必须要重写父类中的纯虚函数,否则也属于抽象类virtual void func() = 0;
};class Son : public Base {
public:virtual void func() {cout << "func函数调用" << endl;}
};void test01() {/*Base b;无法实例化对象new Base;*///Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象Base* base = new Son;base->func();}int main() {test01();system("pause");return 0;}
7.4 多态案例二-制作饮品
案例描述:
制作饮品的大致流程为:煮水-冲泡–倒入杯中–加入辅料
利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶
#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);cout << "-----------" << endl;//制作茶叶doWork(new Tea);}int main() {test01();system("pause");return 0;}
7.5 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
虚析构和纯虚析构区别:如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
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;m_Name = new string(name);}~Cat() {if (m_Name!= NULL) {cout << "Cat析构函数的调用" << endl;delete m_Name;m_Name = NULL;}}virtual void speak() {cout << *m_Name <<"小猫在说话" << endl;}string* m_Name;
};void test01() {Animal* animal = new Cat("Tom");//父类指针在析构时候不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏animal->speak();delete animal;}int main() {test01();system("pause");return 0;}
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
7.6 多态案例三-电脑组装
案例描述:
电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)
将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例Intel厂商和Lenovo厂商创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口
测试时组装三台不同的电脑进行工作
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//抽象不同零件类
//抽象CPU类
class 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;}~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;}}void work() {m_cpu->calculate();m_vc->display();m_mem->storage();}private:CPU* m_cpu;//CPU零件指针VideoCard* m_vc;//显卡零件指针Memory* m_mem;//内存条零件指针};//具体厂商
//Intel
class IntelCPU : public CPU {
public:virtual void calculate() {cout << "Intel的CPU开始计算了!" << endl;}
};class IntelVideoCard : public VideoCard {
public:virtual void display() {cout << "Intel的显卡开始显示了!" << endl;}
};class IntelMemory : public Memory {
public:virtual void storage() {cout << "Intel的内存条开始存储了!" << endl;}
};//Lenovo
class LenovoCPU : public CPU {
public:virtual void calculate() {cout << "Lenovo的CPU开始计算了!" << endl;}
};class LenovoVideoCard : public VideoCard {
public:virtual void display() {cout << "Lenovo的显卡开始显示了!" << endl;}
};class LenovoMemory : public Memory {
public:virtual void storage() {cout << "Lenovo的内存条开始存储了!" << endl;}
};void test01() {CPU* intelCPU = new IntelCPU;VideoCard* intelCard = new IntelVideoCard;Memory* intelMem = new IntelMemory;cout << "第一台电脑开始工作" << endl;Computer* computer1 = new Computer(intelCPU, intelCard, intelMem);computer1->work();delete computer1;cout << "--------------------------" << endl;cout << "第二台电脑开始工作" << endl;Computer* computer2 = new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);computer2->work();delete computer2;cout << "--------------------------" << endl;cout << "第三台电脑开始工作" << endl;Computer* computer3 = new Computer(new IntelCPU, new LenovoVideoCard, new IntelMemory);computer3->work();delete computer3;
}int main() {test01();system("pause");return 0;}