《黑马程序员》C++核心变成(二)——4.6继承

  • 4.6继承
    • 4.6.1 继承的基本语法
    • 4.6.2 继承方式
    • 4.6.3 继承中的对象模型
    • 4.6.4 继承中构造和析构顺序
    • 4.6.5 继承同名成员处理方式
    • 4.6.6 继承同名静态成员处理方式
    • 4.6.7 多继承语法
    • 4.6.8 菱形继承

4.6继承

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

有些类与类之间存在特殊的关系,例如下图中:

我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。

这个时候我们就可以考虑利用继承的技术,减少重复代码

4.6.1 继承的基本语法

例如我们看到很多网站中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同

接下来我们分别利用普通写法和继承的写法来实现网页中的内容,看一下继承存在的意义以及好处

普通实现:

#include<iostream>
#include<string>
using namespace std;//Java页面
class Java
{public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "JAVA学科视频" << endl;}
};
//Python页面
class Python
{public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "Python学科视频" << endl;}
};
//C++页面
class CPP
{public:void header(){cout << "首页、公开课、登录、注册...(公共头部)" << endl;}void footer(){cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl;}void left(){cout << "Java,Python,C++...(公共分类列表)" << endl;}void content(){cout << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}


继承实现:

#include<iostream>
#include<string>
using namespace std;//公共页面
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 << "C++学科视频" << endl;}
};void test01()
{//Java页面cout << "Java下载视频页面如下: " << endl;Java ja;ja.header();ja.footer();ja.left();ja.content();cout << "--------------------" << endl;//Python页面cout << "Python下载视频页面如下: " << endl;Python py;py.header();py.footer();py.left();py.content();cout << "--------------------" << endl;//C++页面cout << "C++下载视频页面如下: " << endl;CPP cp;cp.header();cp.footer();cp.left();cp.content();}int main() {test01();system("pause");return 0;
}


总结:

继承的好处:可以减少重复的代码

class A : public B;

A 类称为子类 或 派生类

B 类称为父类 或 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过过来的表现其共性,而新增的成员体现了其个性

4.6.2 继承方式

继承的语法:class 子类 : 继承方式 父类

继承方式一共有三种:

  1. 公共继承
  2. 保护继承
  3. 私有继承
    父类中的private,子类中都不能访问
#include<iostream>
#include<string>
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; //可访问 public权限m_B; //可访问 protected权限//m_C; //不可访问}
};void myClass()
{Son1 s1;s1.m_A; //其他类只能访问到公共权限
}//保护继承
class Base2
{public:int m_A;
protected:int m_B;
private:int m_C;
};
class Son2 :protected Base2
{public:void func(){m_A; //可访问 protected权限m_B; //可访问 protected权限//m_C; //不可访问}
};
void myClass2()
{Son2 s;//s.m_A; //不可访问
}//私有继承
class Base3
{public:int m_A;
protected:int m_B;
private:int m_C;
};
class Son3 :private Base3
{public:void func(){m_A; //可访问 private权限m_B; //可访问 private权限//m_C; //不可访问}
};
class GrandSon3 :public Son3
{public:void func(){//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到//m_A;//m_B;//m_C;}
};

4.6.3 继承中的对象模型

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

示例:

#include<iostream>
#include<string>
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()
{cout << "sizeof Son = " << sizeof(Son) << endl;//16 = 4*4//父类中的所有非静态成员属性都会被子类继承下去//父类中私有成员属性是被被编译器隐藏了,因此是访问不到的,但是确实被继承下去了
}int main() {test01();system("pause");return 0;
}


利用工具查看

打开工具窗口后,定位到当前CPP文件的盘符
然后输入: cl /d1 reportSingleClassLayout查看的类名 所属文件名

效果如下图:

结论: 父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到

4.6.4 继承中构造和析构顺序

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

问题:父类和子类的构造和析构顺序是谁先谁后?

示例:

#include<iostream>
#include<string>
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;
}

总结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

4.6.5 继承同名成员处理方式

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

  1. 访问子类同名成员 直接访问即可:s.m_A,s.func();
  2. 访问父类同名成员 需要加作用域:s.Base::m_A,s.Base::func();

示例:

#include<iostream>
#include<string>
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;}public:int m_A;
};class Son : public Base {public:Son(){m_A = 200;}//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数(包括重载的函数)//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域void func(){cout << "Son - func()调用" << endl;}
public:int m_A;
};void test01()
{Son s;cout << "Son下的m_A = " << s.m_A << endl;cout << "Base下的m_A = " << s.Base::m_A << endl;s.func();s.Base::func();s.Base::func(10);}
int main() {test01();system("pause");return EXIT_SUCCESS;
}


总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数(包括重载成员函数,虽然参数不同,但是也被隐藏),加作用域可以访问到父类中同名函数

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

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

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

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

示例:

#include<iostream>
#include<string>
using namespace std;class Base {public:static void func(){cout << "Base - static void func()" << endl;}static void func(int a){cout << "Base - static void func(int a)" << endl;}static int m_A;
};int Base::m_A = 100;class Son : public Base {public:static void func(){cout << "Son - static void func()" << endl;}static int m_A;
};int Son::m_A = 200;//同名成员属性
void test01()
{//1.通过对象访问cout << "通过对象访问: " << endl;Son s;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 = " << Base::m_A << endl;cout << "Base 下 m_A = " << Son::Base::m_A << endl;//第一个::代表通过类名的方式访问  第二个::代表访问父类作用域下
}//同名成员函数
void test02()
{//1.通过对象访问cout << "通过对象访问: " << endl;Son s;s.func();s.Base::func();//2.通过类名访问cout << "通过类名访问: " << endl;Son::func();Son::Base::func();//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问Son::Base::func(100);
}
int main() {test01();test02();system("pause");return 0;
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)

4.6.7 多继承语法

C++允许一个类继承多个类

语法:class 子类 :继承方式 父类1 , 继承方式 父类2…

多继承可能会引发父类中有同名成员出现,需要加作用域区分

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

示例:

#include<iostream>
#include<string>
using namespace std;class Base1 {public:Base1(){m_A = 100;}
public:int m_A;
};class Base2 {public:Base2(){m_A = 200;  //开始是m_B 不会出问题,但是改为mA就会出现不明确}
public:int m_A;
};//语法:class 子类:继承方式 父类1 ,继承方式 父类2
class Son : public Base2, public Base1
{public:Son(){m_C = 300;m_D = 400;}
public:int m_C;int m_D;
};//多继承容易产生成员同名的情况
//通过使用类名作用域可以区分调用哪一个基类的成员
void test01()
{Son s;cout << "sizeof Son = " << sizeof(s) << endl;//16 = 2*4 +4 +4cout << s.Base1::m_A << endl;cout << s.Base2::m_A << endl;
}int main() {test01();system("pause");return 0;
}

总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域

4.6.8 菱形继承

菱形继承概念:

​ 两个派生类继承同一个基类

​ 又有某个类同时继承者两个派生类

​ 这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例:

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性——加上作用域就可以

  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

继承前加virtual关键字后,变为虚继承
此时公共的父类Animal称为虚基类

示例:

#include<iostream>
#include<string>
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 = 100;st.Tuo::m_Age = 200;//使用虚继承之后,m_Age就只有一份存储在Animal中//Tuo和Sheep中存储的是VBPTR(virtual base pointer,虚基类表),// 这是一个指针,指向虚基类表中m_Age的存储地址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. 利用虚继承可以解决菱形继承问题

《黑马程序员》C++核心编程(二)——4.6继承相关推荐

  1. 【C++】黑马程序员-C++核心编程学习笔记

    前言 根据黑马程序员C++课程内容,结合讲义,将自己学习C++的过程中将自己觉得有必要记下的笔记进行整理,方便复习回顾,编程环境为VSCode. 本阶段主要针对C++面向对象编程技术做详细讲解,探讨C ...

  2. 【C++】黑马程序员C++核心编程学习笔记(完结)

    目录 前言: 一.内存 1.1 内存四区 1.2 new操作符 二.引用 2.1 引用基本使用和注意事项 2.2 引用做函数参数 2.3 引用函数返回值 2.3 常量引用 三.函数提高 3.1 函数默 ...

  3. 黑马程序员 python快速编程入门课后答案_Python快速编程入门课后程序题答案

    前言 本文只是简单的整理了一下课后习题的编程题,具体的填空.选择.判断可以见:Python快速编程入门课后习题答案 第一章 1.编写一个Python程序,输出如下图效果. ++++++++++ +  ...

  4. 从零学python黑马程序员_Python实战编程--从零学Python/Python应用编程丛书

    导语 内容提要 黑马程序员编著的<Python实战编程--从零学Python>涵盖了Python开发的核心知识.其中,第1-13章主要讲解的是Python的核心语法,包括基本概念和语句.风 ...

  5. 解析python网络爬虫pdf 黑马程序员_正版 解析Python网络爬虫 核心技术 Scrapy框架 分布式爬虫 黑马程序员 Python应用编程丛书 中国铁道出版社...

    商品参数 书名:Python应用编程丛书:解析Python网络爬虫:核心技术.Scrapy框架.分布式爬虫 定价:52.00元 作者:[中国]黑马程序员 出版社:中国铁道出版社 出版日期:2018-0 ...

  6. “中关村黑马程序员训练营”练习题(二)

    /* * 2. 编写一个程序,它先将键盘上输入的一个字符串转换成十进制整数,然后打印出这个十进制整数对应的二进制形式. * 这个程序要考虑输入的字符串不能转换成一个十进制整数的情况,并对转换失败的原因 ...

  7. 黑马程序员java笔记之二-----多线程

    java中实现多线程操作有两种手段,一种继承自Thread类,另一种是实现Runnable接口. 一.继承Thread Thread类是在java.lang包中定义的,一个类只要继承了Thread类, ...

  8. 黑马程序员_网络编程总结

    ------- android培训.java培训.期待与您交流! ---------- //以下笔记来自毕向东老师课程的总结 网络编程 网络编程的实现方式 网络编程有两种实现方式:即通过TCP协议和U ...

  9. 黑马程序员-JavaSE核心知识-03第一个Java程序

    3.3.第一个Java程序:永远的"Hello World !"(重点) 第一个要测试的程序永远从"Hello World !"开始,Java程序的文件后缀必须 ...

最新文章

  1. 算法训练 出现次数最多的整数
  2. 理解依赖注入(IOC)
  3. java liste_内功心法 -- java.util.ArrayListE (1)
  4. 信息学奥赛一本通(1195:判断整除)
  5. Silverlight Issue , Operation named 'CreateUser' does not conform to the required signature.
  6. Python 命令行非阻塞输入
  7. Redis 6.0 源码阅读笔记(7) -- ZSet 数据类型源码分析
  8. VML 编程之--------《VML极道教程》原著:沐缘华
  9. 6-汇编语言中段的使用+dw+start标号
  10. 矩阵卷积、矩阵相乘以及频域相乘之间的关系
  11. 20210108练习
  12. Ubuntu下vscode配置OpenGL(使用glfw+glad)
  13. 基于javaweb的文具学习用品商城系统(java+ssm+jsp+jquery+mysql)
  14. matlab计算复活节概率,复活节是几月几日_计算复活节日期_我爱历史网
  15. windows电脑防火墙关闭,一键系统防火墙关闭工具推荐
  16. 数据挖掘十大经典算法,你都知道哪些?
  17. Psoc Creator入门——EZI2C 通信
  18. 全栈工程师如何逆袭?
  19. 一篇好文之Android数据库 GreenDao的使用指南(源码+案列)
  20. Python中把字典dic转换为DataFrame

热门文章

  1. 2023华为od统一考试B卷【经典屏保】C语言 实现
  2. 5纳米芯片推力电池续航能力提升
  3. 非u盘怎么安装linux系统文件,如何不用U盘和光盘装linux?
  4. 小马bi-常用功能集合
  5. 【0基础学python必看】python行业调研分析——现状+优势+薪资+发展
  6. 安装websockets和pymysql
  7. 手写RPC框架(五)
  8. 【Navidrome 开源音乐服务器】手把手教你打造属于自己的音乐播放器随时随地想听就听
  9. UNITY 编辑器或游戏卡死
  10. Linux系统system call的源码以及init/main.c分析