课程《C++语言程序设计进阶》清华大学 郑莉老师)

基本概念

继承派生的区别:

  • 继承:保持已有类的特性而构造新类的过程称为继承。
  • 派生:在已有类的基础上新增自己的特性(函数方法、数据成员)而产生新类的过程称为派生

被继承的已有类称为基类派生出的新类称为派生类,直接参与派生出某类的基类称为直接基类,基类的基类甚至更高层的基类称为间接基类

继承与派生的目的

  • 继承的目的:实现设计与代码的重用。
  • 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对

    原有程序的基础上增加新的特性。

继承类的定义语法

单继承

单继承的直接基类只有一个,定义时需要指定基类的继承方式,继承方式包括:private、public、protected三种

  1. class 派生类名:继承方式1 基类名1,继承方式2 基类名2,...{
  2. 成员声明;
  3. }

多继承

多继承的直接基类有多个,定义时需要为每个直接基类指定继承方式。

  1. class 派生类名:继承方式 基类名{
  2. 成员声明;
  3. }

派生类的构成

派生类的类成员包括三个部分:

  1. 吸收基类成员

    默认情况下派生类包含了全部基类中除构造和析构函数之外的所有成员。

  2. 改造基类成员

    如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或

    覆盖了外层同名成员

  3. 添加新的成员

    派生类中可以增加新数据成员与函数方法。

不同继承方式的区别

三种继承方法(private,protected,public)不同继承方式的影响主要体现在:

  • 派生类成员基类成员的访问权限
  • 通过派生类对象基类成员的访问权限

公有继承(public)

  • 继承的访问控制

    • 基类的public和protected成员:访问属性在派生类中保持不变;
    • 基类的private成员:不可以直接访问。
  • 访问权限

    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:只能访问public成员.

例如:

  1. #include <iostream>
  2. using namespace std;
  3. class Point {
  4. private:
  5. float x, y;
  6. public:
  7. Point(float x = 0, float y = 0) :x(x), y(y) {}
  8. void move(float offX, float offY) { x += offX; y += offY; }
  9. float getX() const { return x; }
  10. float getY() const { return y; }
  11. };
  12. class Rectangle :public Point {
  13. private:
  14. float w, h; //新增数据成员
  15. public:
  16. //新增函数成员
  17. Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
  18. float getH() const { return h; }
  19. float getW() const { return w; }
  20. };
  21. int main() {
  22. Rectangle rect(2, 3, 20, 10);
  23. rect.move(-2, -3);
  24. cout << "The date of rect(x,y,w,h):" << endl;
  25. cout << rect.getX() << endl;
  26. cout << rect.getY() << endl;
  27. cout << rect.getW() << endl;
  28. cout << rect.getH() << endl;
  29. //cout << rect.x << endl; /*不可访问基类私有成员*/
  30. }

私有继承(private)与保护继承(protected)

三类继承方式中,public:派生类以及派生类对象都可以访问public成员,private:只有基类自身函数可以访问,派生类以及派生类对象都不可以访问private成员,protected:派生类成员可以访问,但派生类对象不可以访问protected成员。

基类 派生类 派生类对象
public
protected ×
private × ×

私有继承(private)

  • 继承的访问控制

    • 基类的public和protected成员:都以private身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限

    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员

例如:

  1. #include <iostream>
  2. using namespace std;
  3. class Point {
  4. private:
  5. float x, y;
  6. public:
  7. Point(float x = 0, float y = 0) :x(x), y(y) {}
  8. void move(float offX, float offY) { x += offX; y += offY; }
  9. float getX() const { return x; }
  10. float getY() const { return y; }
  11. };
  12. class Rectangle :private Point {
  13. private:
  14. float w, h;
  15. public:
  16. Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
  17. float getH() const { return h; }
  18. float getW() const { return w; }
  19. };
  20. int main() {
  21. Rectangle rect(2, 3, 20, 10);
  22. //rect.move(-2, -3); /*不可访问基类私有成员*/
  23. cout << "The date of rect(x,y,w,h):" << endl;
  24. //cout << rect.getX() << endl; /*不可访问基类私有成员*/
  25. //cout << rect.getY() << endl; /*不可访问基类私有成员*/
  26. cout << rect.getW() << endl;
  27. cout << rect.getH() << endl;
  28. /*不可访问基类私有成员*/
  29. }

保护继承(protected)

  • 继承的访问控制

    • 基类的public和protected成员:都以protected身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限
    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员
  • protected 成员的特点与作用
    • 对建立其所在类对象的模块来说,它与private成员的性质相同**。
    • 对于其派生类来说,它与 public 成员的性质相同
    • 既实现了数据隐藏,又方便继承,实现代码重用。

基类与派生类之间的类型转换

公有派生类对象可以被当作基类的对象使用。

  • 派生类的对象可以隐含转换为基类对象;

  • 派生类的对象可以初始化基类的引用

  • 派生类的指针可以隐含转换为基类的指针。

通过基类对象名、指针只能使用从基类继承的成员。

  1. #include <iostream>
  2. using namespace std;
  3. class Base1 {
  4. public:
  5. void display() const { cout << "Base1::display()" << endl; }
  6. };
  7. class Base2 :public Base1 {
  8. public:
  9. void display() const { cout << "Base2::display()" << endl; }
  10. };
  11. class Derived :public Base2 {
  12. public:
  13. void display() const { cout << "Derived::display()" << endl; }
  14. };
  15. void fun(Base1* ptr) { ptr->display(); }
  16. int main() {
  17. Base1 base1;
  18. Base2 base2;
  19. Derived derived;
  20. fun(&base1);
  21. fun(&base2);
  22. fun(&derived);
  23. return 0;
  24. }
  25. /*
  26. Base1::display()
  27. Base1::display()
  28. Base1::display()
  29. */

void fun(Base1* ptr)函数中传入参数为基类指针,派生类的指针可以隐含转换为基类的指针。但是以上程序结果最终只调用基类display()函数。可以使用virtual定义虚函数,实现多态特性。

派生类的构造函数

继承时,默认规则

  • 基类的构造函数不被继承
  • 派生类需要定义自己的构造函数

如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数

派生类成员初始化:

派生类新增成员:派生类定义构造函数初始化

继承来的成员:自动调用基类构造函数进行初始化

派生类的构造函数需要给基类的构造函数传递参数

例如:

先前的Point、Rectangle例子中,Rectangle类的构造函数,传递参数至Point类构造函数Point(x, y),对基类成员进行初始化

  1. Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}

构造函数的执行顺序

  1. 调用基类构造函数。

    顺序按照它们被继承时声明的顺序(从左向右)。

  2. 对初始化列表中的成员进行初始化。

    顺序按照它们在类中定义的顺序

    对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。

  3. 执行派生类的构造函数体中的内容。

派生类复制构造函数

派生类未定义复制构造函数的情况

编译器会在需要时生成一个隐含的复制构造函数先调用基类的复制构造函数

再为派生类新增的成员执行复制。

例如:

以下例子中的复制构造函数传参

  1. Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }
  1. #include <iostream>
  2. using namespace std;
  3. class Point {
  4. private:
  5. float x, y;
  6. public:
  7. Point(float x = 0, float y = 0) :x(x), y(y) {}
  8. Point(const Point& p) { this->x = p.x; this->y = p.y; }//复制构造函数
  9. void move(float offX, float offY) { x += offX; y += offY; }
  10. float getX() const { return x; }
  11. float getY() const { return y; }
  12. };
  13. class Rectangle :public Point {
  14. private:
  15. float w, h;
  16. public:
  17. Rectangle(float x, float y, float w, float h) :Point(x, y), w(w), h(h) {}
  18. Rectangle(const Rectangle& rect) :Point(rect) { this->w = rect.w; this->h = rect.h; }//复制构造函数
  19. float getH() const { return h; }
  20. float getW() const { return w; }
  21. friend ostream& operator << (ostream& os, const Rectangle& rect) {
  22. os << "Center:(" << rect.getX() << "," << rect.getY() << ")" << endl;
  23. os << "h=" << rect.getH() << endl
  24. << "w=" << rect.getW() << endl;
  25. return os;
  26. }
  27. };
  28. int main() {
  29. Rectangle rect(2, 3, 20, 10);
  30. Rectangle rect1(rect);
  31. rect.move(-2, -3);
  32. cout << "rect" << endl << rect;
  33. cout << "rect1" << endl << rect1;
  34. }
  35. /*
  36. rect
  37. Center:(0,0)
  38. h=10
  39. w=20
  40. rect1
  41. Center:(2,3)
  42. h=10
  43. w=20
  44. */

派生类析构函数

析构函数不被继承,派生类如果需要,要自行声明析构函数。

声明方法与无继承关系时类的析构函数相同。

不需要显式地调用基类的析构函数,系统会自动隐式调用。

先执行派生类析构函数的函数体,再调用基类的析构函数。

例如:

以下例子中,派生类Derived的对象obj,按照继承顺序,依次调用构造函数,最后是自己的构造函数,而销毁对象按照相反的顺序执行析构函数。

  1. #include <iostream>
  2. using namespace std;
  3. class Base1 {
  4. public:
  5. Base1(int i){cout << "Constructing Base1 " << endl;}
  6. ~Base1() { cout << "Destructing Base1" << endl; }
  7. };
  8. class Base2 {
  9. public:
  10. Base2(int j){cout << "Constructing Base2 " << endl;}
  11. ~Base2() { cout << "Destructing Base2" << endl; }
  12. };
  13. class Base3 {
  14. public:
  15. Base3() { cout << "Constructing Base3" << endl; }
  16. ~Base3() { cout << "Destructing Base3" << endl; }
  17. };
  18. class Derived : public Base2, public Base1, public Base3 {
  19. public:
  20. Derived(int a, int b, int c, int d) : Base1(a), member2(d), member1(c), Base2(b){ }
  21. private:
  22. Base1 member1;
  23. Base2 member2;
  24. Base3 member3;
  25. };
  26. int main() {
  27. Derived obj(1, 2, 3, 4);
  28. return 0;
  29. }
  30. /*
  31. Constructing Base2
  32. Constructing Base1
  33. Constructing Base3
  34. Constructing Base1
  35. Constructing Base2
  36. Constructing Base3
  37. Destructing Base3
  38. Destructing Base2
  39. Destructing Base1
  40. Destructing Base3
  41. Destructing Base1
  42. Destructing Base2
  43. */

派生类成员二义性问题

二义性指的是在派生类中存在与基类拥有相同名字的成员。可以通过使用类名限定的方式进行特定访问

例如:

  1. #include <iostream>
  2. using namespace std;
  3. class Base1 {
  4. public:
  5. int var;
  6. void fun() { cout << "Member of Base1" << endl; }
  7. };
  8. class Base2 {
  9. public:
  10. int var;
  11. void fun() { cout << "Member of Base2" << endl; }
  12. };
  13. class Derived : public Base1, public Base2 {
  14. public:
  15. int var;
  16. void fun() { cout << "Member of Derived" << endl; }
  17. };
  18. int main() {
  19. Derived d;
  20. Derived* p = &d;
  21. //访问Derived类成员
  22. d.var = 1;
  23. d.fun();
  24. //访问Base1基类成员
  25. d.Base1::var = 2;
  26. d.Base1::fun();
  27. //访问Base2基类成员
  28. p->Base2::var = 3;
  29. p->Base2::fun();
  30. return 0;
  31. }
  32. /*
  33. Member of Derived
  34. Member of Base1
  35. Member of Base2
  36. */

虚基类(个人一般不常用)

需要解决的问题

当派生类从多个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性。

虚基类声明

以virtual说明基类继承方式class B1:virtual public B

作用:主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题,为最远的派生类提供唯一的基类成员,而不重复产生多次复制

注意:在第一级继承时就要将共同基类设计为虚基类

类的存储

参考B站的一个视频:链接

  1. #include <iostream>
  2. using namespace std;
  3. class Base {
  4. public:
  5. //int var; //4字节
  6. float x; //4字节
  7. double y;//8字节
  8. static int a; //静态成员不占用类空间
  9. void fun() { cout << "Member of Base1" << endl; } //函数不占字节
  10. void virtual fun1() {};//虚函数,相当于一个指向虚表的指针 4字节
  11. };
  12. int main() {
  13. Base base;
  14. cout << sizeof(base);
  15. return 0;
  16. }
  17. /*
  18. 24
  19. */

C++学习笔记:07 类的继承与派生的更多相关文章

  1. Java学习笔记 07 接口、继承与多态

    一.类的继承 继承的好处 >>使整个程序架构具有一定的弹性,在程序中复用一些已经定义完善的类不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性 继承的基本思想 >>基 ...

  2. C++学习笔记5——类的继承

    简介: 通过继承联系在以前的类构成一种层次关系.通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承,这些继承得到的类称为类的派生类. 作用: 1.子类拥有父类的所有成员函数和成员变量. 2 ...

  3. 学习笔记 07 --- JUC集合

    学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...

  4. python学习笔记4_类和更抽象

    python学习笔记4_类和更抽象 一.对象 class 对象主要有三个特性,继承.封装.多态.python的核心. 1.多态.封装.继承 多态,就算不知道变量所引用的类型,还是可以操作对象,根据类型 ...

  5. C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景

    在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...

  6. C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量

    学习了类的继承,今天说一下当父类与子类中有同名函数和变量时那么程序将怎么执行.首先明确当基类和子类有同名函数或者变量时,子类依然从父类继承. 举例说明: 例程说明: 父类和子类有同名的成员 data: ...

  7. UML学习笔记:类图

    UML学习笔记:类图 有些问题,不去解决,就永远都是问题! 类图 类图(Class Diagrame)是描述类.接口以及它们之间关系的图,用来显示系统中各个类的静态结构. 类图包含2种元素:类.接口, ...

  8. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  9. Java学习笔记——File类之文件管理和读写操作、下载图片

    Java学习笔记——File类之文件管理和读写操作.下载图片 File类的总结: 1.文件和文件夹的创建 2.文件的读取 3.文件的写入 4.文件的复制(字符流.字节流.处理流) 5.以图片地址下载图 ...

随机推荐

  1. long ? 的使用和理解

    Dictionary<string, object> dic = new Dictionary<string, object>(); long lg = 12345; dic[ ...

  2. Hibernate的一级缓存和二级缓存

    Fist level cache: This is enabled by default and works in session scope. Read more about hibernate f ...

  3. 一招解决下载或下拉GitHub项目速度太慢的问题

    相信很多朋友都有过这样的体验,就是从Github上下载或clone别人的项目时特别慢,甚至还会出现链接意外终止的情况,那么今天就来给大家分享一个提速的方法,步骤也非常简单,亲测有效! 首先进入你的目标 ...

  4. 刷题-力扣-1738. 找出第 K 大的异或坐标值

    1738. 找出第 K 大的异或坐标值 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-kth-largest-xor-co ...

  5. [题解] P4556 [Vani有约会]雨天的尾巴

    [题解] P4556 [Vani有约会]雨天的尾巴 ·题目大意 给定一棵树,有m次修改操作,每次修改 \(( x\) \(y\) \(z )\) 表示 \((x,y)\) 之间的路径上数值 \(z\) ...

  6. 管理 Python 多版本,pyenv 用起来

    介绍 学习使用pyenv在本地安装多个 Python 版本,这样既不影响工作,也不影响生活~ pyenv 可让你轻松地在多个 Python 版本之间切换.它简单.不引人注目,并且遵循 UNIX 的单一 ...

  7. python 实用技巧:几十行代码将照片转换成素描图、随后打包成可执行文件(源码分享)

    效果展示 原始效果图 素描效果图 相关依赖包 # 超美观的打印库 from pprint import pprint # 图像处理库 from PIL import Image # 科学计算库 imp ...

  8. MySQL存储结构及SQL分类

    MySQL目录结构 bin -- mysql执行程序 docs -- 文档 share - 各国编码信息 data -- 存放mysql 数据文件 * 每个数据库 创建一个同名文件夹,.frm 存放t ...

  9. 安装或更新时,pip出错,“No module named ‘pip’”

    解决办法: 在pycharm终端(Terminal)中 首先执行 :python -m ensurepip 然后执行 :python -m pip install --upgrade pip

  10. Identity基于角色的访问授权

    详情访问官方文档 例如,以下代码将访问权限限制为属于角色成员的用户的任何操作 AdministrationController Administrator : [Authorize(Roles = & ...