【C++】继承(上)

发布于:2025-02-10 ⋅ 阅读:(57) ⋅ 点赞:(0)

大家好,我是苏貝,本篇博客带大家了解C++的继承,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 1.继承的概念及定义
    • 1.1 继承的概念
    • 1.2 继承定义
      • (A) 定义格式
      • (B) 继承的初尝试
      • (C) 继承关系和访问限定符
      • (D) 继承基类成员访问方式的变化
  • 2.基类和派生类对象赋值转换
  • 3.继承中的作用域
  • 4.派生类的默认成员函数

我们之前了解了面向对象的第一个特性:封装,我们遇到的封装有下面2种

  1. 数据和方法放到类中,把想让访问的定义为公有,不想被访问的定义为私有
  2. 一个类型放到另一个类型的里面,通过typedef或者(成员函数调整?)自定义类型或者运算符重载(迭代器就是一个经典的封装,每个容器的底层结构不同,但提供统一的迭代器的设计和行为)等,封装另一个全新的类型,如iterator和reverse_iterator,vector和stack/queue

1.继承的概念及定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类或子类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用

1.2 继承定义

(A) 定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

在这里插入图片描述

(B) 继承的初尝试

继承后父类Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了 Student复用了Person的成员

在这里插入图片描述
在这里插入图片描述

© 继承关系和访问限定符

在这里插入图片描述

(D) 继承基类成员访问方式的变化

在这里插入图片描述

上图的方块内表示基类(或父类)的public/private/protected成员在public/private/protected继承下在派生类(或子类)中的访问权限

下面来对上图做解释

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面,都不能去直接访问它,但可以间接访问

不能直接访问父类的私有成员
在这里插入图片描述

可以间接访问父类的私有成员
在这里插入图片描述

  1. 基类private成员在派生类中是不可见的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
    在这里插入图片描述

  2. 实际上总结一下上面的表格,会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。

  3. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强

在这里插入图片描述

  1. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
    在这里插入图片描述

2.基类和派生类对象赋值转换

  1. 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割,寓意把派生类中父类那部分切来赋值过去。但切片的前提是派生类public继承基类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

如果将派生类的对象s赋值给基类的对象p,那么就是将派生类中的基类的那部分切割拷贝给p,修改p不会导致s被修改。但如果将派生类的对象s赋值给基类的指针ptrp或者基类的引用refp,就是让它们指向s的基类的部分,因此用ptrp或refp就能修改s的基类部分

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 基类对象不能赋值给派生类对象

在这里插入图片描述

3.继承中的作用域

C++中有4个域:局部域,全局域,命名空间域,类域(局部域和全局域会影响生命周期,命名空间域和类域不影响)
同一个域里,变量不能同名,函数如果不是重载,也不能同名
不同域里的变量和函数可以同名

1、 在继承体系中基类和派生类都有独立的作用域。
2、 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

因为在继承体系中基类和派生类都有独立的作用域,所以基类和派生类可以有同名变量/函数。如果有同名变量/函数,那么子类成员将屏蔽父类对同名成员的直接访问

在这里插入图片描述
在这里插入图片描述

如果想在子类成员函数中访问父类的同名变量,可以使用 父类:: 父类成员 显示访问
在这里插入图片描述

3、 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

下图的fun函数构成隐藏而非重载
在这里插入图片描述

问:下面代码的结果是什么?
在这里插入图片描述

编译报错,为什么?
子类B和父类A都有函数fun,构成隐藏,因此在子类B中将父类A的fun函数隐藏,只能看见子类B的fun函数。B的对象b调用无参的fun函数,B类中的fun函数有参数,报错

那下图呢?
在这里插入图片描述
在这里插入图片描述

为什么它有不报错了?
子类B和父类A都有函数fun,构成隐藏,因此在子类B中将父类A的fun函数隐藏,只能看见子类B的fun函数。B的对象b调用类A 的无参的fun函数,成功

4、 注意在实际中在继承体系里面最好不要定义同名的成员

4.派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

在这里插入图片描述

派生类的成员变量可以分为下面2个部分:
1、基类的成员
2、子类的成员:自定义类型和内置类型

将基类的成员看成一个整体的自定义类型,子类的成员和我们在类和对象里说的一样(编译器生成的:1. 构造函数和析构函数:对内置类型不做处理,调用自定义类型的构造/析构函数。2. 拷贝构造和赋值运算符重载:对内置类型值拷贝,调用自定义类型的拷贝构造/赋值运算符重载)

1、 派生类的构造函数必须调用基类的构造函数来初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用,且派生类对象初始化先调用基类构造再调派生类构造。

基类有默认的构造函数,那么派生类就不用在构造函数的初始化列表里初始化基类的成员
在这里插入图片描述

基类没有默认构造函数,那么派生类就要在构造函数的初始化列表里,调用基类的构造函数来初始化基类的成员
在这里插入图片描述

2、 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、 派生类的operator=必须要调用基类的operator=完成基类的复制。

在这里插入图片描述

4、 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
在这里插入图片描述

问:为什么都是调用A的析构函数,在子类B的就报错,a不报错?
因为后续多态的需要,析构函数的名字会被统一处理成destructor,所以父类A和子类B的析构函数会构成隐藏,因此报错

那我们就在~A()前面加作用域A

在这里插入图片描述
在这里插入图片描述

为什么会调用2次A的析构函数?
因为子类的析构函数内可能也需要用到父类的成员,所以如果我们显示写调用父类的析构函数,那么可能在访问前就将父类释放了,导致访问时出错。因此析构函数的调用要保证先子后父(子类先调用析构函数,然后父类再调用)。编译器会在子类的析构函数结束后自动调用父类的析构函数

构造:先父后子
析构:先子后父


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️