继承
继承是面向对象三大特性之一
定义类时,下级别的成员除了拥有上一级的共性,还有自己的特性,就可以考虑使用继承的技术,减少代码的重复
继承的基本语法
语法:class 子类 : 继承方式 父类
- 子类也被成为派生类
- 父类也被称为基类
class A { public: string name; }; class B :public A { public: int age; }; int main() { B b; b.name = "张三"; b.age = 10; cout << b.name << b.age << endl; return 0; }
继承方式
继承方式一共有三种:
- 公共继承
- 访问权限不变
- 保护继承
- 除私有内容外,都变为保护权限
- 私有继承
- 除私有内容外,都变为私有权限
父类中的私有内容,三种继承方法都无法访问
class A { public: int a; protected: int b; private: int c; }; class B :public A//公共继承 { }; class C :protected A//保护继承 { }; class D :private A//私有继承 { };
继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
父类中私有的成员属性,是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
利用开发人员命令提示工具查看对象模型:
- 跳转盘符:
盘符:
- 跳转文件路径:
cd 具体路径下
- 查看命名:
dir
- 报告单个类的布局:
cl /d1 reportSingleClassLayout类名 文件名
文件名可按Tap建自动补齐
class A { public: int a; protected: int b; private: int c; }; class B :public A//公共继承 { int c; };
class B size(16): +--- 0 | +--- (base class A) 0 | | a 4 | | b 8 | | c | +--- 12 | c +---
继承中构造和析构顺序
先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
继承同名成员处理方式
子类对象可以直接访问到子类中的同名成员
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中所有同名成员函数,加作用域可以访问到父类中同名函数
class A { public: void test() { cout << "A" << endl; } }; class B :public A//公共继承 { public: void test() { cout << "B" << endl; } }; int main() { B b; b.test(); b.A::test(); return 0; }
继承同名静态成员处理方式
静态成员跟非静态成员出现同名,处理方法一致,只不过有两种处理方法:
- 通过对象
.
- 通过类名
::
class A { public: static string a; }; class B :public A//公共继承 { public: static string a; }; //类内声明,类外初始化 string B::a = "B"; string B::A::a = "A"; int main() { //通过对象访问 B b; cout << b.a << endl; cout << b.A::a << endl; //通过类名访问 cout << B::a << endl; //第一个::表示通过类名方式访问,第二个::代表访问父类作用域下 cout << B::A::a << endl; return 0; }
多继承语法
C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
class A { public: int a; }; class B { public: int a; }; class C :public A, public B { }; int main() { C c; c.A::a = 10; c.B::a = 20; cout << c.A::a << endl; cout << c.B::a << endl; return 0; }
菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承这两个派生类
- 这种继承被称为菱形继承,也被称为钻石继承
典型的菱形继承问题:
菱形继承问题:子类继承两份相同的数据,导致资源浪费以及毫无意义
- 羊继承了动物的数据,驼也继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承自动物的数据继承了两份,只需要一份就可以
利用虚继承,解决菱形继承的问题:
- 继承之前,加上关键字
virtual
变为虚继承 - 公共的父类被称为虚基类
class A { public: int a; }; //A为虚基类 class B :virtual public A{}; class C :virtual public A{}; class D:public B,public C{}; int main() { D d; d.a = 10; cout << d.a << endl; return 0; }
vbptr虚基类指针:
- v-virtual
- b-base
- ptr-pointer
虚基类指针指向vbtable虚基类表
- 实际继承了两个指针,通过偏移量,找到那份唯一的数据
class D size(24): +--- 0 | +--- (base class B) 0 | | {vbptr} | | <alignment member> (size=4) | +--- 8 | +--- (base class C) 8 | | {vbptr} | | <alignment member> (size=4) | +--- | <alignment member> (size=4) +--- +--- (virtual base A) 16 | a +--- D::$vbtable@B@: 0 | 0 1 | 16 (Dd(B+0)A) D::$vbtable@C@: 0 | 0 1 | 8 (Dd(C+0)A) vbi: class offset o.vbptr o.vbte fVtorDisp A 16 0 4 0
多态
多态的基本语法
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定:编译阶段确定函数地址
- 动态多态的函数地址晚绑定:运行阶段确定函数地址
动态多态满足条件:
- 有继承关系
- 子类要重写父类的虚函数
- 使用父类的指针或引用,执行子类对象
重写不同于函数重载:
- 函数返回值类型、函数名、参数列表完全相同
C++中父子之间的类型转换不需要做强制类型转换,父类的指针或引用可以直接指向子类对象
class A { public: //虚函数 virtual void test() { cout << "A" << endl; } }; class B :public A { public: void test() { cout << "B" << endl; } }; void test(A& a) { a.test(); } int main() { B b; test(b);//A & a = b; return 0; }
多态的原理剖析
class A size(8): +--- 0 | {vfptr} +--- A::$vftable@: | &A_meta | 0 0 | &A::test class B size(8): +--- 0 | +--- (base class A) 0 | | {vfptr} | +--- +--- B::$vftable@: | &B_meta | 0 0 | &B::test
vfptr虚函数(表)指针:
- v-virtual
- f-function
- ptr-pointer
- 指向vftable虚函数表
- 表内记录虚函数地址
&A::test
当子类重写父类虚函数,子类中的虚函数表,内部会替换成子类的虚函数地址
多态案例-计算器类
案例描述:分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。
多态的优点:
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展和维护
在真实开发中,提倡开闭原则:
- 对拓展进行开放
- 对修改进行关闭
#include <iostream> using namespace std; //实现计算器抽象类 class Abstract { public: virtual int result() { return 0; } int m_num1; int m_num2; }; //实现计算器加法类 class add :public Abstract { public: virtual int result() { return m_num1 + m_num2; } }; //实现减法类 class subtraction :public Abstract { public: virtual int result() { return m_num1 - m_num2; } }; int main() { //父类指针指向子类对象 Abstract* a = new add; a->m_num1 = 10; a->m_num2 = 10; cout << a->result() << endl; //用完记得销毁 delete a; //a不需要再定义和初始化 a = new subtraction; a->m_num1 = 10; a->m_num2 = 10; cout << a->result() << endl; return 0; }
纯虚函数和抽象类
在多态中,通常父类中的虚函数实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类,特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream> 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 { virtual void Boil() { cout << "煮矿泉水" << endl; } virtual void Brew() { cout << "冲泡咖啡" << endl; } virtual void PourInCup() { cout << "倒入杯中" << endl; } virtual void PutSomeThing() { cout << "添加牛奶" << endl; } }; class Tea :public AbstractDrinking { virtual void Boil() { cout << "煮矿泉水" << endl; } virtual void Brew() { cout << "冲泡茶叶" << endl; } virtual void PourInCup() { cout << "倒入杯中" << endl; } virtual void PutSomeThing() { cout << "添加枸杞" << endl; } }; void dowork(AbstractDrinking *a) { a->MakeDrink(); delete a; } int main() { AbstractDrinking* a = new Tea; dowork(a); return 0; }
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方法:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名() {};
纯虚析构语法:virtual ~类名() = 0;
总结:
- 虚析构和纯虚析构是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构和纯虚析构
- 拥有纯虚析构的类也属于抽象类
#include <iostream> using namespace std; class A { public: A() { m_name = new string("A"); } virtual void speak() { cout << *m_name << endl; } //利用虚析构可以解决父类指针释放子类对象时不干净的问题 virtual ~A() { if (m_name != NULL) { cout << "A析构" << endl; delete m_name; m_name = NULL; } } string* m_name; }; class B :public A { public: B() { m_name = new string("B"); } virtual ~B() { if (m_name != NULL) { cout << "B析构" << endl; delete m_name; m_name = NULL; } } }; int main() { A* a = new B;//先构造父类再构造子类 a->speak(); //父类指针在析构时,不会调用子类中析构函数,导致子类如果有堆区属性,会导致内存泄露 delete a; return 0; }
#include <iostream> using namespace std; class A { public: A() { m_name = new string("A"); } virtual void speak() { cout << *m_name << endl; } //纯虚析构声明 //有了纯虚析构后,这个类也属于抽象类,无法实例化对象 virtual ~A() = 0; string* m_name; }; class B :public A { public: B() { m_name = new string("B"); } virtual ~B() { if (m_name != NULL) { cout << "B析构" << endl; delete m_name; m_name = NULL; } } }; //纯虚析构实现 A::~A() { cout << "A纯虚析构" << endl; } int main() { A* a = new B;//先构造父类再构造子类 a->speak(); //父类指针在析构时,不会调用子类中析构函数,导致子类如果有堆区属性,会导致内存泄露 delete a; return 0; }
多态案例-电脑组装
案例描述:
- 电脑主要组成部件为CPU、显卡、内存条
- 将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件
- 创建电脑类提供让电脑工作的函数,并且调用每个函数工作的接口
- 测试时组装三台不同的电脑进行工作
#include <iostream> using namespace std; //抽象不同零件类 //抽象CPU类 class CPU { public: //抽象的计算函数 virtual void calculate() = 0; }; //抽象显卡类 class VideoCard { public: //抽象的显示函数 virtual void dispaly() = 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_cpu; m_vc = NULL; } if (m_mem != NULL) { delete m_cpu; m_mem = NULL; } } //提供工作函数 void work() { //让零件工作起来,调用接口 m_cpu->calculate(); m_vc->dispaly(); 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 dispaly() { 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 dispaly() { cout << "Lenovo的显卡开始显示了" << endl; } }; class LenovoMemory :public Memory { public: virtual void storage() { cout << "Lenovo的内存开始存储了" << endl; } }; int main() { //电脑零件 CPU* intelcpu = new IntelCPU; VideoCard* intervc = new IntelVideoCard; Memory* intermem = new IntelMemory; //创建第一台电脑 Computer* computer1 = new Computer(intelcpu, intervc, intermem); computer1->work(); delete computer1; //创建第二台电脑 Computer* computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory); computer2->work(); delete computer2; //创建第三台电脑 Computer* computer3 = new Computer(new LenovoCPU, new IntelVideoCard, new LenovoMemory); computer3->work(); delete computer3; return 0; }