4.6 继承

继承是面向对象三大特性之一

子类拥有父类的一些共性
利用继承的技术可以减少重复的代码

4.6.1 继承方式

语法: class 子类 : 继承方式 父类{}

子类 也被称为派生类
父类 也被称为基类

示例:

// 继承
class Animal
{
public:string name; // 动物名称int age;     // 动物年龄
};class Dog
{
public:string leg;      // 狗腿
};
**总结:**
继承的好处:`可以减少重复代码`
派生类中的成员,包含两大部分- 一类是从基类继承过来的,一类是自己的成员
- 从基类继承继承过来表现其共性,而新增的成员体现了个性。

4.6.2 继承的方式

继承的方式一共有三种

  • 公共继承
  • 保护继承
  • 私有继承

4.6.3 继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

示例:

#include<iostream>
using namespace std;
#include <string>// 继承中的对象模型
class Base
{
public:int m_A;protected:int m_B;private:int m_C;
};class Son : public Base
{
public:int m_D;};// 利用VS提供的开发人员命令提示工具查看对象模型
// 在文件路径下操作:cl /d1 reportSingleClassLayout class_namevoid test01()
{// 最终结果是 16cout << "sizeof of Son = " << sizeof(Son) << endl;// 结论:父类中所有的非静态的成员属性都会被子类继承下去// 父类中私有成员属性 是被编译器给隐藏了,因此访问不到,但是确实被继承了
}int main()
{test01();return 0;
}

结论: 父类中所有的非静态的成员属性都会被子类继承下去,父类中私有成员属性是被编译器给隐藏了,因此访问不到,但是确实被继承了

4.6.4 继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题: 父类和子类中的构造函数和析构函数顺序呢?

示例:

#include<iostream>
using namespace std;
#include <string>// 继承中的对象模型
class Base
{public:Base(){cout << "Base 构造函数执行了" << endl;}~Base(){cout << "Base 析构函数执行了" << endl;}
public:int m_A;};class Son : public Base
{
public:Son(){cout << "Son 构造函数执行了" << endl;}~Son(){cout << "Son 析构函数执行了" << endl;}};void test01()
{Son son;
}int main()
{test01();return 0;
}

运行结果:

结论: 先构造父类,然后构造儿子,析构则是相反。

4.6.5 继承中同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或者父类中的同名数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

#include<iostream>
using namespace std;
#include <string>// 继承中同名成员处理
class Base
{public:Base(){m_A = 100;}void func(){cout << "父类中的函数调用" << endl;}void pr(){cout << "父类中的pr()函数调用" << endl;}
public:int m_A;};class Son : public Base
{
public:Son(){m_A = 200;}void func(){cout << "子类中的函数调用" << endl;}public:int m_A;
};// 同名的成员属性
void test01()
{Son son;cout << "子类中 m_A = " << son.m_A << endl;cout << "父类中 m_A = " << son.Base::m_A << endl;
}// 同名的成员函数处理
void test02()
{Son son;son.func();son.pr();son.Base::func();// 如果子类中出现和父类同名的成员函数,子类的同名成员会影藏掉父类中所有同名的成员函数:重载的也被影藏,仍然要添加作用域
}int main()
{test01();test02();return 0;
}

运行结果:

总结:

  1. 子类对象可以直接访问到子类中的同名成员
  2. 子类对象加作用域可以访问到父类中的同名成员,示例:son.Base::func();
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中的同名成员函数,加作用域可以访问到父类中同名函数

4.6.6 继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

#include<iostream>
using namespace std;
#include <string>// 继承中同名静态成员处理
class Base
{public:static void func(){cout << "父类中 func() 的调用" << endl;}public:static int m_A;};
// 静态成员初始化
int Base::m_A = 100;class Son : public Base
{
public:static void func(){cout << "子类中 func() 的调用" << endl;}public:static int m_A;
};int Son::m_A = 200;// 同名的静态成员属性
void test01()
{Son son;cout << "通过对象访问:" << endl;cout << "子类中 m_A = " << son.m_A << endl;cout << "父类中 m_A = " << son.Base::m_A<< endl;cout << "通过类名访问:" << endl;cout << "子类中 m_A = " << Son::m_A << endl;    // 此处的双冒号代表通过类名访问cout << "父类中 m_A = " << Son::Base::m_A << endl;   // 此处双冒号代表父类作用域下
}// 同名的静态成员函数
void test02()
{Son son;cout << "通过对象访问:" << endl;son.func();son.Base::func();cout << "通过类名访问:" << endl;Son::func();Son::Base::func();
}int main()
{test01();test02();return 0;
}

总结: 同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象和通过类),此外,如果函数重载,会隐藏父类中的同名函数,需要添加作用域才可以访问。

4.6.7 多继承语法

C++允许一个类继承多个类
语法:class 子类: 继承方式 父类1, 继承方式 父类二 ...{}
多继承可能会引发父类中有同名成员出现,需要加作用域区分

C++实际开发中不建议使用多继承

示例:

#include<iostream>
using namespace std;
#include <string>class Base1
{
public:int m_A = 100;
};class Base2
{
public:int m_A = 200;
};class Son : public Base1, public Base2
{public:int m_B =  300;
};void test01()
{Son son;cout << "子类中 m_B = " << son.m_B << endl;cout << "父类Base1中 m_A = " << son.Base1::m_A<< endl;cout << "父类Base2中 m_A = " << son.Base2::m_A << endl;
}int main()
{test01();return 0;
}

运行结果:

总结: 多继承中如果父类中出现了同名的情况,访问需要使用作用域区别

4.6.8 菱形继承

概念:
  两个派生类继承同一个基类
  又有某个类同时继承两个派生类
  这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例:

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据就会产生二义性
  2. 草泥马继承自动物的数据继承了两份,但是这个数据只需要一份就可以。

问题解决:使用虚继承
示例:

#include<iostream>
using namespace std;
#include <string>// 动物类
class Animal
{public:public:int age = 100;};// 利用虚继承 解决菱形继承的问题
// 继承之前 加上关键字 virtual 变为虚继承
// Animal 被称为 虚基类
// 羊类
class Sheep: virtual public Animal
{};// 驼类
class Camel : virtual public Animal
{};// 羊驼类 alpaca
class Alpaca: public Sheep, public Camel
{};void test01()
{Alpaca alpaca;alpaca.Sheep::age = 18;alpaca.Camel::age = 20;// 当出现菱形继承的时候,两个父类拥有相同的数据,需要加以作用域区分cout << "alpaca.Sheep::age = " << alpaca.Sheep::age << endl;cout << "alpaca.Camel::age = " << alpaca.Camel::age << endl;cout << "alpaca.age = " << alpaca.age << endl;cout << "sizeof Sheep = " << sizeof(Sheep) << endl;cout << "sizeof Camel = " << sizeof(Camel) << endl;cout << "sizeof Alpaca = " << sizeof(Alpaca) << endl;}int main()
{test01();return 0;
}

运行结果:

4.7 多态

4.7.1 多态的基本概念

多态是C++面向对象三大特性之一
多态分为两类

  • 静态多态:函数重载,和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

下面通过案例进行讲解多态:

#include<iostream>
using namespace std;
#include <string>// 动物类
class Animal
{public:// 关键字 virtual 使函数地址在运行时绑定virtual void speak(){cout << "父类对应为虚函数:动物在说话" << endl;}void bark(){cout << "父类对应为普通:动物在咬" << endl;}};class Cat:  public Animal
{
public:void speak(){cout << "父类对应为虚函数:小猫在说话" << endl;}void bark(){cout << "父类对应为普通:函数小猫在咬" << endl;}
};// 执行说话的函数:父类对应函数为虚函数,
// 函数地址晚绑定, 在运行阶段确定函数地址
void doSpeak(Animal &animal)    // Animal& animal = cat
{animal.speak();
}// 执行说话的函数:父类对应函数为普通函数
// 地址早帮定,在编译阶段确定函数地址
void doBark(Animal &animal)
{animal.bark();
}void test01()
{Cat cat;doSpeak(cat);doBark(cat);
}int main()
{test01();return 0;
}

执行结果:

动态多态满足条件

  1. 有继承关系
  2. 子类重写父类的虚函数

动态多态的使用:
父类的指针或者引用 执行子类对象。

Animal& animal = cat;
animal.speak()

重写: 函数返回值类型、函数名、参数列表完全一致称为重写

虚函数底层:

4.7.2 多态案例–计算器

案例描述:
分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

传统方法示例:


// 普通的写法
class Calculator
{
public:int a;int b;
public:int getResult(string oper){if (oper == "+"){return a + b;}else if (oper == "-"){return a - b;}else if (oper == "*"){return a * b;}else if (oper == "/"){return a / b;}// 如果想扩展新的功能,需要修改源码// 在真实的开发中,提供开闭原则// 开闭原则:对扩展进行开放,对修改进行关闭}
};void test01()
{// 创建一个Calculate对象Calculator c;c.a = 10;c.b = 20;cout << c.a << "+" << c.b << " = " << c.getResult("+") << endl;cout << c.a << "-" << c.b << " = " << c.getResult("-") << endl;cout << c.a << "*" << c.b << " = " << c.getResult("*") << endl;cout << c.a << "/" << c.b << " = " << c.getResult("/") << endl;
}int main()
{test01();return 0;
}

运行结果:

利用多态的示例:

// 利用多态实现计算器
class AbstractCaluclator
{
public:virtual int getResult(){return 0;}int a;int b;
};// 设计一个加法计算器类
class AddCalculator : public AbstractCaluclator
{
public:int getResult(){return a + b;}
};// 设计一个减法计算器类
class SubCalculator : public AbstractCaluclator
{
public:int getResult(){return a - b;}
};// 设计一个乘法计算器类
class MulCalculator : public AbstractCaluclator
{
public:int getResult(){return a * b;}
};// 设计一个除法计算器类
class DivideCalculator : public AbstractCaluclator
{
public:int getResult(){return a / b;}
};void test02()
{// 加法AbstractCaluclator* c = new AddCalculator();c->a = 10;c->b = 20;cout << c->a << "+" <<c->b << " = " << c->getResult() << endl;// 使用完毕,记得销毁delete c;    // 销毁的只是指正指向的堆区的数据// 减法c = new SubCalculator;c->a = 10;c->b = 20;cout << c->a << "-" << c->b << " = " << c->getResult() << endl;// 使用完毕,记得销毁delete c; // 销毁的只是指正指向的堆区的数据// 乘法c = new MulCalculator;c->a = 10;c->b = 20;cout << c->a << "*" << c->b << " = " << c->getResult() << endl;// 使用完毕,记得销毁delete c; // 销毁的只是指正指向的堆区的数据
}int main()
{test02();return 0;
}

运行结果:

利用多态的好处总结:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期的可扩展性以及可维护性高

4.7.3 纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 ( 参数列表) = 0;
当类中有了纯虚函数,这个类也被称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

// 纯虚函数和抽象类
class Base
{
public:// 纯虚函数:本类只要有一个纯虚函数,此类就是抽象类,无法实例化virtual void func() = 0;
};class Son :public Base
{
public:// 对父类中纯虚函数进行重写:否则无法实例化对象 virtual 关键字可以不写void func(){cout << "哈哈哈哈" << endl;}
};void test01()
{Son s;s.func();
}int main()
{test01();return 0;
}

4.7.4 多态案例二

案例:煮茶和煮咖啡

示例:

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* abs)
{abs->makeDrink();delete abs;        // 释放
}void test01()
{cout << "泡咖啡****************" << endl;// 制作咖啡doWork(new Coffee);cout << endl << "泡茶****************" << endl;// 泡茶doWork(new Tea);
}int main()
{test01();return 0;
}

运行结果

4.7.5 虚析构和纯虚构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数实现

虚析构和纯析构区别

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0

示例:

#include<iostream>
using namespace std;
#include <string>// 虚析构和纯虚析构
class Animal
{
public:Animal(){cout << "Animal 构造函数调用" << endl;}// 利用虚析构解决子类对象释放不干净的问题/*virtual ~Animal(){cout << "Animal 析构函数调用" << endl;}*/// 纯虚析构 也需要代码实现,但并不是在子类中实现// 有纯虚析构,则是抽象类,不可实例化virtual ~Animal() = 0;// 纯虚函数virtual void speak() = 0;
};// Animal 中纯虚析构实现
Animal::~Animal()
{cout << "Animal中纯虚析构函数调用" << endl;
}class Cat : public Animal
{
public:string *name;public:Cat(string name){cout << "Cat的构造函数调用" << endl;this->name = new string(name);}~Cat(){if (name != NULL){cout << "Cat 析构函数调用" << endl;delete name;name = NULL;}}virtual void speak(){cout << *this->name << "小猫在说话" << endl;}
};void test01()
{Animal* animal = new Cat("Tom");animal->speak();// 父类指针在析构的时候,不会调用子类中析构函数,导致子类堆区数据有内存泄露情况delete animal;
}int main()
{test01();return 0;
}

运行结果:

总结:

  1. 虚析构活纯虚析构就是用来解决通过父类指针释放子类对象的问题
  2. 如果子类中没有堆区数据,就可以不写虚析构或纯虚析构
  3. 拥有纯虚析构,此类为抽象类

C++核心编程(三)相关推荐

  1. Python 核心编程(三)

    Python 核心编程 第三章 正则表达式 ​ 在开发中会有大量的字符串处理工作,其中经常会涉及到字符串格式的校验. 思考1 ​ 场景:如何判断一个字符串是手机号呢? ​ 测试文件 aesdf1381 ...

  2. python核心编程第三版_Python之父:自学python,这3本书能节约你一大半时间编程...

    今天给大家推荐三本书,有两本是属于一个系列,即<Python核心编程>第二版和第三版,一本讲基础和一本讲进阶,非常适合Python的初学者和有一定基础的学习者.还有一本书适合所有想学Pyt ...

  3. C++学习笔记 - 阶段三:C++核心编程 - Chapter7:类和对象-C++运算符重载

    阶段三:C++核心编程 Chapter7:类和对象-C++运算符重载 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型 7.1 加号运算符重载 作用:实现两个自定义数 ...

  4. python核心编程第三版_《Python核心编程(第3版)》

    <Python核心编程(第3版)>是经典畅销图书<Python核心编程(第二版)>的全新升级版本,本书适合具有一定经验的Python开发人员阅读,总共分为3部分.第1部分为讲解 ...

  5. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  6. SparkSQL核心编程

    目录 基本介绍 DataFrame 创建 DataFrame DataSet 创建 DataSet RDD 转换为 DataSet DataSet 转换为 RDD DataFrame 和 DataSe ...

  7. python 如何判断一个函数执行完成_Python核心编程的四大神兽迭代器、生成器 、闭包以及装饰器...

    本文将主要分为4大部分,分别介绍Python核心编程中的迭代器.生成器 .闭包以及装饰器. 生成器 生成器是生成一个值的特殊函数,它具有这样的特点:第一次执行该函数时,先从头按顺序执行,在碰到yiel ...

  8. 拒绝从入门到放弃_《Python 核心编程 (第二版)》必读目录

    目录 目录 关于这本书 必看知识点 最后 关于这本书 <Python 核心编程 (第二版)>是一本 Python 编程的入门书,分为 Python 核心(其实并不核心,应该叫基础) 和 高 ...

  9. 窗口消息——Windows核心编程学习手札之二十六

    窗口消息 --Windows核心编程学习手札之二十六 Windows允许一个进程至多建立10000个不同类型的用户对象(user object):图符.光标.窗口类.菜单.加速键表等,当一个线程调用一 ...

  10. 异常处理程序和软件异常——Windows核心编程学习手札之二十四

    异常处理程序和软件异常 --Windows核心编程学习手札之二十四 CPU负责捕捉无效内存访问和用0除一个数值这种错误,并相应引发一个异常作为对错误的反应,CPU引发的异常称为硬件异常(hardwar ...

最新文章

  1. LINUX下文件字符集编码查看与转换并文件名编码转换
  2. C++继承详解三 ----菱形继承、虚继承
  3. java 进度条jsp,jsp进度条_jsp技巧
  4. Postgres访问其他PostgresQL数据库的功能DBLINK
  5. 一篇图像识别的科普文
  6. leetcode 高薪_利用两种不同的方法解LeetCode第1312题:让字符串成为回文串的最少插入次数
  7. algorithm头文件常用函数
  8. ArcGIS Maritime 发布海图切片服务详解
  9. 【优化算法】混合增强灰狼优化布谷鸟搜索算法(AGWOCS)【含Matlab源码 1331期】
  10. windows补丁下载地址
  11. python + selenium:怎么实现控制左右滑动开关
  12. 微信公众号自定义菜单修改
  13. socket网络编程 poll的简单用法
  14. HTML+CSS+JS体育网页制作 DW静态网页设计(篮球NBA 5页 带psd文件 )
  15. 用proteus实现STM32仿真
  16. Logit-Probit:非线性模型中交互项的边际效应解读
  17. linux超级好用检索跳转工具hg:hyperlinked_grep (grep+kitty)
  18. 【STM32学习笔记】(13)——外部中断详解
  19. 图卷积神经网络 | Python实现基于GCN-GRU图卷积门控循环单元网络模型
  20. 35 个提高千倍效率的 Java 代码小技巧

热门文章

  1. 2022-2028年中国房车旅游行业深度调研及投资前景预测报告
  2. 2022-2028年中国冶金工业节能减排投资分析及前景预测报告
  3. RPC 笔记(07)— socket 通信(多进程服务器)
  4. REST接口设计规范
  5. 翻转二叉树 c语言实现 递归 栈 队列
  6. “Attention is All You Need 翻译
  7. LeetCode简单题之寻找数组的中心下标
  8. TensorFlow Keras API用法
  9. 激光雷达基础-光探测和测距-遥感
  10. 管道:介绍和基本服务