继承

一、继承概述

1、为什么需要继承

如下示例,Person 类、Student 类、Teacher 类有大量重复的代码,造成代码冗余,降低开发效率。

在这里插入图片描述

我们可以通过继承来解决这一问题。在面向对象的编程语言中,继承是一个核心概念。主要作用将重复的代码统一定义在父类中,子类从父类继承,同时继承也是实现多态的重要条件。

2、什么是继承

继承就是一个新类从现有类派生的过程。新类称之为派生类或子类,原有的类称之为基类或父类;子类可以继承父类中的成员,从而可以提高代码的可重用性。

在这里插入图片描述
继承关系下,子类和父类存在 is a 的 关系。例如,狗是动物,猫是动物,老虎是一个动物等等。那么可以说动物类是一个父类,老虎、猫、狗都是动物类的子类。

在这里插入图片描述
在继承关系下父类更通用,子类更具体。也就是说父类拥有子类的共同特性,子类可以具备独有的特性。
在这里插入图片描述

二、继承的实现

C++ 中类实现继承的形式如下:

class 派生类名:[继承方式]基类名 //默认是private继承方式
{
}

继承方式有 3 种类型,分别为共有型 (public),保护型 (protected) 和私有型 (private);: 表示基类和派生类之间的继承关系的符号。

示例:

Person 类

Person 类作为父类,其包含了 public 修饰的属性:

#pragma once
#include <string>
class Person
{
public:
std::string name;
int age;
};

Student 类

继承了 Person 类,子类从父类继承 public 成员

Student.h

#pragma once
#include "Person.h"
class Student: public Person
public:
void show();
};

Student.cpp

#include "Student.h"
#include <iostream>
using namespace std;
void Student::show()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}

Main.cpp

#include <iostream>
#include "Student.h"
int main()
Student s;
s.name = "张三"; //从父类继承的成员
s.age = 20;
s.show();
}

三、派生类的访问控制

在 C++ 中,类成员的访问权限分为 public (公共)、protected (受保护) 或 private (私有) 3 种。其中父类的 public 和 protected 成员允许子类继承,private 成员不能被继承。

以 public 继承模式为例,访问控制权限如下:

访问publicprotectedprivate
同一个类yesyesyes
派生类yesyesno
外部的类yesnono

类 A 的定义:

#pragma once
class A
{
public:
int num_public; //公有的成员任何类都可以访问
protected:
int num_protected; //受保护的成员可以在当前类和子类中访问
private:
int num_private; //私有的成员只能在当前类中访问
};

类 B 继承于类 A:

#pragma once
#include "A.h"
class B: public A
public:
B();
B(int a, int b);
void print();
};

类 B 中访问父类中的成员:

#include "B.h"
#include <iostream>
using namespace std;
B::B() {}
B::B(int a, int b)
{
this->num_public = a;
this->num_protected = b;
}
void B::print()
{
cout << "public:" << num_public << endl;
cout << "protected:" << num_protected << endl;
//cout << "private:" << num_private << endl; //编译错误,私有成员,不能被子类继承
}

四、继承类型

C++ 支持三种继承类型,分别是 public、protected 及 private 类型,这些继承类型影响着基类成员在派生类中的访问权限。

1、访问权限变化总览
基类成员权限            public继承         protected继承         private继承
---------------------------------------------------------------------------
public成员    ───►   public(外部可访问)  protected(外部不可)   private(外部不可)
protected成员 ───►   protected(外部不可) protected(外部不可)   private(外部不可)
private成员   ───►   不可访问               不可访问                不可访问
  • 继承方式只会影响基类 public / protected 成员在派生类中的可见性,不会影响派生类对自己新成员的访问控制。
  • private 成员无论哪种继承方式,子类都不能直接访问。

直观理解:

  • public继承:原汁原味 —— public 还是 public,protected 还是 protected。
  • protected继承:降一级 —— public 变 protected,protected 不变。
  • private继承:全收进屋 —— public 和 protected 全变 private。
2、基类定义
#pragma once
#include <iostream>
using namespace std;class Base
{
public:void func_public() { cout << "Base::func_public()" << endl; }protected:void func_protected() { cout << "Base::func_protected()" << endl; }private:void func_private() { cout << "Base::func_private()" << endl; }
};
3、公有继承 (Public Inheritance)
规则
  • 基类 public 成员 → 派生类 public
  • 基类 protected 成员 → 派生类 protected
  • 外部依然可以访问继承的 public 成员
代码示例

SubPublic.h

#pragma once
#include "Base.h"
class SubPublic : public Base
{
public:void func();
};

SubPublic.cpp

#include "SubPublic.h"
#include <iostream>
using namespace std;void SubPublic::func()
{cout << "[public继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public();    // ✅func_protected(); // ✅
}

测试

SubPublic pub;
pub.func();
pub.func_public(); // ✅ 外部可访问
4、私有继承 (Private Inheritance)
规则
  • 基类 public 成员 → 派生类 private
  • 基类 protected 成员 → 派生类 private
  • 外部无法访问这些继承的成员
代码示例

SubPrivate.h

#pragma once
#include "Base.h"class SubPrivate : private Base
{
public:void func();
};

SubPrivate.cpp

#include "SubPrivate.h"
#include <iostream>
using namespace std;void SubPrivate::func()
{cout << "[private继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public();    // ✅func_protected(); // ✅
}

测试

SubPrivate pri;
pri.func();
// pri.func_public(); // ❌ 外部不可访问
5、保护继承 (Protected Inheritance)
规则
  • 基类 public 成员 → 派生类 protected
  • 基类 protected 成员 → 派生类 protected
  • 外部无法直接访问,但派生类的子类可以访问
代码示例

SubProtected.h

pragma once
#include "Base.h"class SubProtected : protected Base
{
public:void func();
};

SubProtected.cpp

#include "SubProtected.h"
#include <iostream>
using namespace std;void SubProtected::func()
{cout << "[protected继承] 子类内部可以访问父类 public + protected 成员" << endl;func_public();    // ✅func_protected(); // ✅
}

6、保护继承的子类

Subclass.h

#pragma once
#include "SubProtected.h"class Subclass : public SubProtected
{
public:void test();
};

Subclass.cpp

#include "Subclass.h"
#include <iostream>
using namespace std;void Subclass::test()
{cout << "[保护继承的子类] 仍然可以访问父类的 public + protected 成员" << endl;func_public();    // ✅func_protected(); // ✅
}

测试

Subclass subc;
subc.test();
// subc.func_public(); // ❌ 外部不可访问

五、继承中的构造函数与析构函数

基类中的构造函数、析构函数和拷贝构造函数不能被派生类继承。

1、构造函数和析构函数的执行顺序

继承关系下:

  • 当派生类对象被创建时,先调用基类的构造函数,然后再调用派生类的构造函数。
  • 析构时顺序相反,先调用派生类的析构函数,再调用基类的析构函数。
  • 基类的构造函数、析构函数以及拷贝构造函数不会被继承到派生类。
#include <iostream>
using namespace std;class A
{
public:A(){cout << "A类构造函数" << endl;}~A(){cout << "A类析构函数" << endl;}
};class B : public A
{
public:B(){cout << "B类构造函数" << endl;}~B(){cout << "B类析构函数" << endl;}
};class C : public B
{
public:C(){cout << "C类构造函数" << endl;}~C(){cout << "C类析构函数" << endl;}
};int main()
{C c;  // 创建C类对象return 0;
}

程序输出

A类构造函数
B类构造函数
C类构造函数C类析构函数   
B类析构函数
A类析构函数
  • 当你定义(创建)一个对象时,系统会自动调用该对象所属类的构造函数,用来完成对象的初始化。
  • 当对象的生命周期结束时,系统会自动调用对应类的析构函数,用来完成清理工作(比如释放内存、关闭文件等)。
2、子类中调用父类构造
  • 当基类只提供带参数的构造函数且没有无参构造函数时,派生类必须在其初始化列表中显式调用基类的有参构造函数,否则编译会报错。
  • 如果基类有无参构造函数,则派生类会默认调用基类的无参构造函数。

基类 Person 示例

Person.h

#pragma once
#include <string>class Person
{
public:Person(std::string name);std::string getName();private:std::string name;
};

Person.cpp

#include "Person.h"Person::Person(std::string name) : name(name)
{
}std::string Person::getName()
{return name;
}

派生类 Student 示例

Student.h

#pragma once
#include "Person.h"class Student : public Person
{
public:Student();Student(std::string name);
};

Student.cpp

#include "Student.h"// 当基类无默认构造时,派生类必须显示调用基类有参构造函数
Student::Student() : Person("")
{
}Student::Student(std::string name) : Person(name)
{
}

测试 main.cpp

#include <iostream>
#include "Student.h"
using namespace std;int main()
{Student s("张三");cout << s.getName() << endl;  // 输出:张三return 0;
}

Student::Student() : Person("") { }
这是 Student 的无参构造函数,写法表示:

  • 当创建 Student 对象时,先调用基类 Person 的构造函数,传入空字符串 "" 初始化 Person 部分。
  • 然后执行 Student 自己的构造函数体(这里为空)。

Student::Student(std::string name) : Person(name) { }
这是带参数的构造函数,表示:

  • 创建 Student 对象时,先调用基类 Person 的构造函数,传入参数 name
  • 然后执行 Student 自己的构造函数体(这里为空)。

如果基类没有无参构造函数,编译器就不知道用什么参数去初始化基类部分,编译会失败。 所以派生类构造函数中必须用初始化列表显示调用基类构造函数,告诉它该怎么初始化基类。

3、调用顺序原因
一、构造函数调用顺序:先基类后派生类
  • 当你创建一个派生类对象时,派生类通常会用到基类的成员(包括数据和方法)。
  • 如果基类还没初始化,派生类就无法安全使用基类的内容。
  • 所以必须先调用基类的构造函数,完成基类部分的初始化,再调用派生类构造函数来初始化派生类自己新增的成员。

这样做保证了派生类拥有一个“完整且有效”的基类部分,避免使用未初始化数据带来的错误。


二、析构函数调用顺序:先派生类后基类
  • 对象销毁时,派生类先清理自己新增的资源(比如动态申请的内存、打开的文件等)。
  • 清理完派生类资源后,再去销毁基类成员。
  • 如果先销毁基类,派生类成员还没清理完,就会出现访问已销毁资源的错误。

所以析构时先调用派生类析构函数释放派生类资源,再调用基类析构函数释放基类资源,符合“从内到外”的释放原则。

三、简单比喻 ---- 把对象想象成建房子

构造(建房子)
盖房子的时候,先打好地基(基类),确保基础稳固,
然后再盖楼层(派生类),一层一层往上建。
先有地基,楼层才能安全搭建。

析构(拆房子)
拆房子时,先拆楼层(派生类),再拆地基(基类),
这样避免楼层倒塌砸到地基,也保证拆除顺序安全有序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/92853.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/92853.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/92853.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模块 PCB 技术在未来通信领域的创新突破方向

未来通信领域对数据传输速率、信号稳定性及设备集成度的要求持续攀升&#xff0c;模块 PCB 作为通信设备的关键组件&#xff0c;其技术创新成为推动行业发展的核心动力。猎板 PCB 凭借深厚的技术积累与持续的研发投入&#xff0c;在模块 PCB 技术创新方面取得诸多突破&#xff…

mysql的InnoDB索引总结

MySQL InnoDB索引知识点总结 1. 索引类型 1.1 聚簇索引&#xff08;Clustered Index&#xff09; 定义与特性 定义&#xff1a;聚簇索引是InnoDB的默认存储方式&#xff0c;数据行按照主键的顺序物理存储在磁盘上特性&#xff1a; 每个InnoDB表只能有一个聚簇索引数据页中的记录…

C++模板的补充

类模板(上一篇没讲到类模板C/C内存管理&函数模板-CSDN博客&#xff09; 类模板的定义&#xff1a; template<class T1, class T2, ..., class Tn> class 类模板名 {// 类内成员定义 }; 用一个简单的栈例子讲类模板 #define _CRT_SECURE_NO_WARNINGS #include &l…

用JOIN替代子查询的查询性能优化

一、子查询的性能瓶颈分析‌重复执行成本‌关联子查询会导致外层每行数据触发一次子查询&#xff0c;时间复杂度为O(M*N)sql-- 典型低效案例 SELECT e.employee_id, (SELECT d.department_name FROM departments d WHERE d.department_id e.department_id) FROM employees e; …

【设计模式】访问者模式模式

访问者模式&#xff08;Visitor Pattern&#xff09;详解一、访问者模式简介 访问者模式&#xff08;Visitor Pattern&#xff09; 是一种 行为型设计模式&#xff08;对象行为型模式&#xff09;&#xff0c;它允许你在不修改对象结构的前提下&#xff0c;为对象结构中的元素添…

比特币现货和比特币合约的区别与联系

一、基本定义项目现货&#xff08;Spot&#xff09;合约&#xff08;Futures / Perpetual&#xff09;本质直接买卖比特币本身买卖比特币价格的衍生品合约所得资产真实的 BTC合约头寸&#xff08;没有直接持有 BTC&#xff09;结算方式交割比特币现金结算&#xff08;多数平台&…

Qt/C++开发监控GB28181系统/实时监测设备在线离线/视频预览自动重连/重新点播取流/低延迟

一、前言说明 一个好的视频监控系统&#xff0c;设备掉线后能够自动重连&#xff0c;也是一个重要的功能指标&#xff0c;如果监控系统只是个rtsp流地址&#xff0c;那非常好办&#xff0c;只需要重新打开流地址即可&#xff0c;而gb28181中就变得复杂了很多&#xff0c;需要多…

此芯p1开发板使用OpenHarmony时llama.cpp不同优化速度对比(GPU vs CPU)

硬件环境 Cix P1 SoC 瑞莎星睿 O6 开发板 rx580显卡 产品介绍&#xff1a; https://docs.radxa.com/orion/o6/getting-started/introduction OpenHarmony 5.0.0 使用vulkan后端的llama.cpp &#xff08;GPU&#xff09; # ./llama-bench -m /data/qwen1_5-0_5b-chat-q2_k.…

Android 四大布局:使用方式与性能优化原理

一、四大布局基本用法与特点1. LinearLayout&#xff08;线性布局&#xff09;使用方式&#xff1a;<LinearLayoutandroid:orientation"vertical" <!-- 排列方向&#xff1a;vertical/horizontal -->android:layout_width"match_parent"android:…

Redis的BigKey问题

Redis的BigKey问题 什么是大Key问题&#xff1f; 大key问题其实可以说是大value问题&#xff0c;就是某个key对应的value所占据的存储空间太大了&#xff0c;所以导致我们在操作这个key的时候花费的时间过长&#xff08;序列化\反序列化&#xff09;&#xff0c;从而降低了redi…

TDengine IDMP 产品基本概念

基本概念 元素 (Element) IDMP 通过树状层次结构来组织数据&#xff0c;树状结构里的每个节点被称之为元素 (Element)。元素是一个物理的或逻辑的实体。它可以是具体的物理设备&#xff08;比如一台汽车&#xff09;&#xff0c;物理设备的一个子系统&#xff08;比如一台汽车的…

专题二_滑动窗口_将x减到0的最小操作数

一&#xff1a;题目解释&#xff1a;每次只能移除数组的边界&#xff0c;移除的边界的总和为x&#xff0c;要求返回你移除边界的最小操作数&#xff01;也就是说你最少花几次移除边界&#xff0c;就能够让这些移除的边界的和为x&#xff0c;则返回这个次数&#xff01;所以这个…

CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南

CentOS 7 下通过 Anaconda3 运行llm大模型、deepseek大模型的完整指南A1 CentOS 7 下通过 Anaconda3 运行大模型的完整指南一、环境准备二、创建专用环境三、模型部署与运行四、优化配置常见问题解决B1 CentOS 7 下通过 Anaconda3 使用 CPU 运行 DeepSeek 大模型的完整方案一、…

Flutter应用在Windows 8上正常运行

要让Flutter应用在Windows 8上正常运行,需满足以下前提条件,涵盖系统环境、依赖配置、编译设置等关键环节: 一、系统环境基础要求 Windows 8版本 必须是 Windows 8.1(核心支持),不支持早期Windows 8(需升级到8.1,微软已停止对原版Windows 8的支持)。 确认系统版本:右…

Redis实现消息队列三种方式

参考 Redis队列详解&#xff08;springboot实战&#xff09;_redis 队列-CSDN博客 前言 MQ消息队列有很多种&#xff0c;比如RabbitMQ,RocketMQ,Kafka等&#xff0c;但是也可以基于redis来实现&#xff0c;可以降低系统的维护成本和实现复杂度&#xff0c;本篇介绍redis中实现…

【C++动态版本号生成方案:实现类似C# 1.0.* 的自动构建号】

C动态版本号生成方案&#xff1a;实现类似C# 1.0.* 的自动构建号 在C#中&#xff0c;1.0.*版本号格式会在编译时自动生成构建号和修订号。本文将介绍如何在C项目中实现类似功能&#xff0c;通过MSBuild自动化生成基于编译时间的版本号。 实现原理 版本号构成&#xff1a;主版本…

【算法题】:斐波那契数列

用 JavaScript 实现一个 fibonacci 函数&#xff0c;满足&#xff1a; 输入 n&#xff08;从0开始计数&#xff09;输出第 n 个斐波那契数&#xff08;斐波那契数列从 1 开始&#xff1a;1,1,2,3,5,8,13,21…&#xff09; 示例&#xff1a; fibonacci(0) > 1fibonacci(4) &g…

【YOLOv13[基础]】热力图可视化实践 | 脚本升级 | 优化可视化效果 | 论文必备 | GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM等

本文将进行添加YOLOv13版本的升级版热力图可视化功能的实践,支持图像热力图可视化、优化可视化效果、 可以选择使用GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM, HiResCAM, LayerCAM, RandomCAM, EigenGradCAM。一个参数即可设置是否显示检测框等。 原图 结果图

ElasticSearch相关术语介绍

1.RESTful风格程序REST(英文全称为:"Representational State Transfer")指的是一组架构约束条件和原则。它是一种软件架构风格&#xff08;约束条件和原则的集合&#xff0c;但并不是标准&#xff09;。 REST通过资源的角度观察网络&#xff0c;以URI对网络资源进行…

《从零构建大语言模型》学习笔记4,注意力机制1

《从零构建大语言模型》学习笔记4&#xff0c;自注意力机制1 文章目录《从零构建大语言模型》学习笔记4&#xff0c;自注意力机制1前言一、实现一个简单的无训练权重的自注意力机制二、实现具有可训练权重的自注意力机制1. 分步计算注意力权重2.实现自注意力Python类三、将单头…