当前位置: 首页 > news >正文

【C++】从零认识C++的“继承”

在这里插入图片描述

文章目录

  • 一、继承的概念
  • 二、定义与使用
    • 1. 定义方式
    • 2. 继承方式
    • 3. 类模板的继承
  • 三、基类和派生类的转换
  • 四、基类和派生类的作用域
  • 五、派生类的默认成员函数
  • 六、继承与友元
  • 七、继承与静态成员
  • 八、不能继承的类
  • 九、多继承问题

一、继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,可以增加方法(成员函数)和属性(成员变量),这样产生的类,叫做派生类(或子类)。作为基础的原有类,称为基类(或父类)

二、定义与使用

1. 定义方式

继承的定义方式是:

class 派生类名 : 继承方式 基类名
{派生类内容};

假如定义一个Person类表示人,派生出一个Student类表示学生。Student包括Person的方法与属性,同时也有Student的独特方法与属性。那么就可以先定义出Person类,再继承出Student类:

class Person
{
public:Person(){cout << "Person()" << endl;}
private://名字string name;
};class Student : public Person
{
public:Student(){cout << "Student()" << endl;}
private://学号string id;
};

2. 继承方式

继承方式有三种:public、private、protected。就是三种访问限定符,它们之间有什么关系呢?

基类成员的属性\派生类的继承方式public继承后↓protected继承后↓private继承后↓
基类的public成员变成→派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员变成→派生类的protected成员派生类的protected成员派生类的private成员
基类的prvate成员变成→在派生类中不可见在派生类中不可见在派生类中不可见

哎,这里我们就看出来protected和private的区别了。

  • 总结一下上面的表格能发现,基类的private成员在派生类中都是不可见的。基类的其他成员在派生类中的访问方式为Min(在基类的访问限定符,继承方式),也就是两者里的较小者。其中public > protected > private。
  • 基类的private成员无论以什么方式继承,在派生类中都是不可见的。不可见指的是基类的private成员其实被继承到了派生类中,但是语法上限制派生类对象在类内或类外都不能去访问不可见成员。
  • 基类private成员在派生类中不能直接被访问。如果基类成员不想在类外被访问,但是想在派生类中能访问,就定义为protected,可以看出protected限定符是因继承才出现的。
  • 继承方式也可以不写,使用class关键字时默认的继承方式是private,使用struct时默认的继承方式是public。最好显式写出继承方式。
  • 在实际应用中我们一般都使用public继承,很少使用也不建议用protected和private继承,不利于代码维护。public继承能使基类和派生类中的接口访问方式保持一致。

3. 类模板的继承

类模板也是可以继承的。特殊的是,在定义派生类时,基类如果是类模板,必须指定类域,否则编译报错。

template<class T>
class stack : public std::vector<T> //或者前面using namespace std;
{
public:void push(const T& x){push_back(x);}
}; 

三、基类和派生类的转换

public继承的派生类对象可以赋值给基类的指针或基类的引用,叫做赋值兼容转换,形象的说法是切片,就像把派生类中的基类那部分切割出来。基类指针或引用指向的是派生类中切出来的基类那部分。
在这里插入图片描述
比如:

class Person
{
public:Person(){cout << "Person()" << endl;}
protected:string name = "zhangsan";
};class Student : public Person
{
public:Student(){cout << Person::name << endl;}
private:string id = "000001";
};int main()
{Student stu;Person* ptr = &stu;Person& ref = stu;//此时ptr和ref只指向和引用stu中属于基类的内容return 0;
}

注意:基类对象不能赋值给派生类对象。

四、基类和派生类的作用域

在继承体系中,基类和派生类都有各自独立的作用域,所以派生类和基类中可以有同名成员,但派生类中将屏蔽对基类的同名成员的直接访问,这种情况叫做隐藏。若想访问基类的这个成员,需要写成基类::基类成员显式访问。
需要知道的是,如果是基类和派生类的成员函数,只要函数名相同,就构成隐藏。
不过,在实际应用继承体系中,我们最好就不要定义同名的成员了。

class A
{
protected:int x;
};class B : public A
{
public:test(){//直接访问不到基类的同名成员cout << x << endl;//必须指定类域才能访问cout << A::x << endl;}
protected:int x;
};

再来看一个例子:

class A
{
public:void func(int x){cout << "func(int x)" << endl;}
};class B : public A
{
public:void func(){	cout << "func()" << endl;}
};

基类A和派生类B中有同名函数,虽然参数列表不同,但是还是构成隐藏关系。直接访问func只会调到派生类的func:

在这里插入图片描述

若想访问到基类的func,也要指定类域:

在这里插入图片描述

五、派生类的默认成员函数

类有默认成员函数的概念,派生类也有。最重要的是要理解:基类成员的初始化、清理等行为都是作为一个整体来完成的。

  • 派生类的构造函数必须要调用基类的构造函数,来初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类的构造函数的初始化列表阶段显式调用。而派生类对象初始化时,先调用基类构造,再调派生类构造。
  • 派生类的析构函数会在被调用完成后自动调用基类的析构函数,因为这样才能保证派生类对象先清理派生类成员,再清理基类成员的顺序。派生类析构函数和基类析构函数构成隐藏关系,这涉及到多态的知识。
  • 派生类的拷贝构造函数,必须调用基类的拷贝构造函数,完成基类成员的拷贝初始化。
  • 派生类的operator=必须要调用基类的operator=,完成基类的复制。需要注意的是,派生类的operator=和基类的operator=构成隐藏关系,所以显式调用基类的operator=时需要指定基类作用域。

以Person和Student为例:

class Person
{
public:Person(const string& name):_name(name){ cout << "基类构造" << endl;}~Person(){cout << "基类析构" << endl;}Person& operator=(const Person& p){if (this != &p){_name = p._name;}return *this;}Person(const Person& p):_name(p._name){}protected:string _name;
};class Student :public Person
{
public:Student(const string& name, const string& id):Person(name) //调用基类的构造函数,_id(id){ cout << "派生类构造" << endl;}~Student(){cout << "派生类析构" << endl;//结束后自动调用基类析构函数}Student(const Student& s)	: Person(s) //基类的引用变量可以引用派生类,引用的是基类的那部分切片, _id(s._id){ }Student& operator=(const Student& s){if (this != &s){//调用基类的operator=,而且要显示指明类域Person::operator=(s); //基类的引用变量可以引用派生类,引用的是基类的那部分切片_id = s._id;}return *this;}protected:string _id;
};

测试:

六、继承与友元

友元关系不能继承,基类的友元并不是其派生类的友元,也就是基类友元并不能访问派生类的私有和保护成员。需要在派生类中再进行友元声明。

七、继承与静态成员

基类的静态成员,在派生类中也被共享。换言之,所有基类的对象和他所有派生类的对象都共享同一份静态成员。无论派生出多少个派生类,都只有一个static成员实例。

八、不能继承的类

想实现出一个不能继承的类,有两种方法:

  • 将基类的构造函数私有化。派生类的构造必须调用基类的构造函数,但是基类的构造函数私有化以后,派生类就无法调用了,派生类也无法实例化出对象。
  • C++11新增了一个关键字:final。在类名后用final修饰,这个类就不能被继承了。
    在这里插入图片描述

九、多继承问题

继承也可以分为单继承、多继承。

  • 单继承:一个派生类只有一个直接基类时称这个继承关系为单继承
  • 多继承:一个派生类可能同时继承两个或以上的类,基类间用,分开,称这个关系为多继承。多继承对象在内存中的模型是:先继承的基类内容存在前面,后继承的基类内容存在后面,派生类成员放在最后面。
class A 
{
//...
};class B
{
//...
};// C同时继承A和B
class C : public A, public B
{
//...
};

除此之外,菱形继承是多继承的一种特殊情况,模型如下。
在这里插入图片描述
菱形继承会有数据冗余和二义性的问题。在Assistant的对象中,Person成员会有两份。支持多继承,就会出现菱形继承这样的问题,像Java的语法直接不支持多继承了,规避了这种问题。实践中我们非常不建议设计出菱形继承这样的模型的。初次之外,还有virtual虚继承、菱形虚拟继承这些东西,十分复杂,我们暂时不谈这些了。

相关文章:

  • Flink-Yarn运行模式
  • C++ 中,派生类什么时候可以不定义构造函数,什么时候必须定义构造函数?
  • Flink 核心概念解析:流数据、并行处理与状态
  • TDengine 运维—容量规划
  • leetcode 25. Reverse Nodes in k-Group
  • 鸿蒙HarmonyOS最新的组件间通信的装饰器与状态组件详解
  • SpringMVC 通过ajax 实现文件的上传
  • 关于光谱相机的灵敏度
  • naive-ui切换主题
  • 实验分享|基于千眼狼sCMOS科学相机的流式细胞仪细胞核成像实验
  • 【marked与katex结合】渲染公式
  • Vue3 Element Plus el-table-column Sortable 排序失效
  • 2025最新obs31.0.x版本编译办法,windows系统
  • 数据湖和数据仓库的区别
  • ES的倒排索引和正排索引的区别及适用场景?为什么倒排索引适合全文搜索?
  • vue3 threejs 物体发光描边
  • 电力设备制造企业数字化转型路径研究:从生产优化到生态重构
  • WordPress_Madara 本地文件包含漏洞复现(CVE-2025-4524)
  • k8s-ServiceAccount 配置
  • GPT 等decoder系列常见的下游任务
  • 排名优化推广/上海专业seo
  • 淄博市沂源县建设局网站/软文兼职
  • 做移动网站优化优/个人网页制作教程
  • 怎样建设网站客服服务器/学大教育培训机构电话
  • 网站开发语言为wap/网站seo技术教程
  • 如何做独立网站/网站收录查询方法