【C++】——继承【上】
博主主页:Yan. yan.
C语言专栏
数据结构专栏
力扣牛客经典题目专栏
C++专栏
文章目录
- 继承的概念与定义
- 1、继承的概念
- 2、继承的定义
- 2.1、继承的语法形式
- 2.2、继承中类的叫法
- 2.3、继承后的子类成员访问权限
- 基类与派生类的赋值转换
- 1、派生类对象赋值给基类对象
- 2、派生类对象的引用赋值给基类对象
- 3、派生类对象的指针赋值给基类对象
- 4、基类指针赋值给派生类指针
- 继承的作用域
- 1、同名变量
- 2、同名函数
- 派生类中的默认成员
- 1、对象的构造和析构遵循特定的顺序
- 2、派生类构造函数调用基类构造函数
- 3、析构函数的特殊处理
- 4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化
继承的概念与定义
1、继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
举一个简单的例子
一个人,他具有年龄,姓名等个人信息,然后我们可以将这些信息整合为一个Person类,如果说我们还想要定义一个Student的类,这些学生当然也是人,因此我们可以复用Person这个类,然后再添加一些其他的信息,例如学号之类的。
2、继承的定义
2.1、继承的语法形式
继承的定义时=是通过在子类的声明中使用基类, 并加上冒号“:” 和继承方式(public \ protected \ private)来实现的。
class Person
{//.....基类
};// 子 继承方式 父
class Student : public Person
{//....派生类
};
例:
class Person
{
public:void print(){cout << _name << endl;cout << _address << endl;cout << _age << endl;}
private:string _name = "张三 "; // 姓名string _address = "河北 ";// 地址int _age = 18 ;// 年龄
};// 子 继承方式 父
class Student : public Person
{
private:string _tel = "189 ";// 电话int _id = 123321;
};int main()
{Person p;Student s;return 0;
}
通过监视窗口可以看出,Student类继承了Person类的成员与函数。
2.2、继承中类的叫法
(1)子类(或派生类): 这是指继承其他类(即父类)的类。子类可以使用父类的所有非私有属性和方法,同时也可以添加自己的属性和方法或重写父类的方法。
(2)父类(或基类):这是指被其他类(即子类)继承的类。父类提供了通用的属性和方法,这些可以被子类继承和使用。
2.3、继承后的子类成员访问权限
不同的继承方式产生的继承效果自然也不一样。
如图:
- 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
基类与派生类的赋值转换
在面向对象编程中,基类和派生类之间的赋值转换涉及到对象的类型兼容性和多态性。
1、派生类对象赋值给基类对象
派生类对象可以赋值给基类的对象。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
相反,基类成员无法赋值给派生类成员,因为有些成员派生类有,而基类没有。
class Person
{
public:Person(string name = "Kana", int age = 18, double height = 1.50) :_name(name),_age(age),_height(height){// ...}void Print() const{cout << _name << endl;cout << _age << endl;cout << _height << endl;}private:string _name; // 姓名 int _age; // 年龄 double _height; // 身高
};class Student : public Person
{
public:Student(string name = "Kana", int age = 18, double height = 1.50, int id = 233333, int grade = 10): Person(name, age, height), _id(id), _grade(grade) {}void Print() const{// 首先调用基类的 Print 方法 Person::Print();// 然后打印学生特有的属性 cout << "ID: " << _id << endl;cout << "Grade: " << _grade << endl;}private:int _id; // 学号 int _grade; // 年级
};int main()
{Student s;s.Print();// 尝试将 Student 对象切片为 Person 对象(不推荐,因为会丢失信息) Person p = s;p.Print(); // 仅打印 Person 的信息(姓名、年龄、身高) return 0;
}
2、派生类对象的引用赋值给基类对象
我们可以将一个派生类对象的引用赋值给一个基类类型的引用,而不需要const修饰符。
class Person
{
public:void Print(){cout << "Person Print()" << endl;}
};class Student :public Person
{
public:void Print(){cout << "Student Print()" << endl;}
};int main()
{Student s;Person& p = s;p.Print();return 0;
}
3、派生类对象的指针赋值给基类对象
派生类对象的指针可以赋值给基类对象的指针。
class Person
{
public:void Print() const{cout << "Person: Name = " << _name << ", Age = " << _age << endl;}Person(string name, int age) : _name(name), _age(age) {}private:string _name;int _age;
};class Student : public Person
{
public:void Print() const{cout << "Student: ID = " << _id << ", Grade = " << _grade << endl;}Student(string name, int age, int id, int grade) : Person(name, age), _id(id), _grade(grade) {}private:int _id; // 学号 int _grade; // 年级
};int main()
{Student student("Kana", 18, 12345, 10);// 将Student对象的指针赋值给Person对象的指针 Person* p = &student;p->Print(); // 如果要访问Student特有的成员,需要使用Student类型的指针或引用 Student* s = &student;s->Print(); return 0;
}
4、基类指针赋值给派生类指针
在C++中,将基类指针直接强制转换为派生类指针是一种危险的做法,通常是不被推荐的,因为它违反了类型安全的原则,并且可能导致未定义行为,包括越界访问或访问无效内存。
Person p;Student *s = (Student*) & p; // right
1.派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
2.基类对象不能赋值给派生类对象。
3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。
继承的作用域
继承的作用域决定了从基类继承到派生类的成员(包括变量和方法)的访问权限。
在C++的继承体系中, 派生类和基类都各自拥有独立的作用域,当派生类和基类中定义了相同的变量(包括成员和函数)时,派生类的成员会“隐藏”或者“重定义”基类中的同名成员,这意味着在派生类的作用域内,直接访问该同名成员将引用派生类的成员,而不是基类的成员。
1、同名变量
class Person
{
protected:int _id = 123;int _age = 18;
};class Student : public Person
{
public:void print(){cout << _id << endl;cout << _age << endl;cout << _num << endl;}
private:int _age = 20;int _num = 3;
};int main()
{Student s;s.print();return 0;
}
派生类和基类中都含有名为_age的成员变量,打印结果如下:
如果想要打印基类中的_age,则需要使用 :: 限定符:
2、同名函数
class A
{
public:void fun(){cout << "fun()" << endl;}
};class B : public A
{
public:void fun(int i){A::fun();cout << "fun(int i)->" << i << endl;}
};int main()
{B b;b.fun(1);
};
B 中的 fun 和 A 中的 fun 构成隐藏,成员函数满足函数名相同就构成隐藏。
由于函数重载针对的是同一个作用域的函数,而基类与派生类直接作用域不同。因此不是函数重载。
同样的,如果需要访问其他作用域的函数,我们需要使用 :: 操作符:
派生类中的默认成员
我们知道:在类中有6个默认成员函数,如果不显示定义,编译会自动生成。
那么在派生类中,这些成员函数如何生成?
1、对象的构造和析构遵循特定的顺序
对象的构造和析构遵循特定的顺序,以确保对象的正确初始化和清理。
构造函数调用顺序:
(1)创建派生类对象时,从最顶层的基类开始,逐层向下调用构造函数,直到派生类。
(2)接着,按照派生类中成员变量的声明顺序初始化成员变量(若成员是对象,则调用其构造函数)。
(3)最后,执行派生类构造函数体中的代码。
析构函数调用顺序:
(1)销毁派生类对象时,首先调用派生类的析构函数。
(2)然后,按照成员变量声明的逆序调用成员变量的析构函数(若成员是对象)。
(3)最后,从最顶层的基类开始,逐层向上调用析构函数,直到派生类的基类。
class Person
{
public:Person(string name = "Kana"): _name(name){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
private:string _name; // 姓名
};
class Student : public Person
{
public:Student(){cout << "Student()" << endl;}~Student(){cout << "~Student()" << endl;}
private:int _id;
};int main()
{Student s;return 0;
}
2、派生类构造函数调用基类构造函数
(1)派生类的构造函数必须调用基类的构造函数来初始化基类的成员。
(2)如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表中显式调用基类的一个构造函数。
class Person
{
public:// Person类的构造函数,用于初始化名字Person(const char* name) : _name(name) {// ...}// Person类的拷贝构造函数Person(const Person& p) : _name(p._name) {}
private:string _name;
};class Student : public Person
{
public:// Student类的构造函数,接收学号和名字Student(int id, const char* name) : _id(id), Person(name) {// ...}// Student类的默认构造函数Student() : Person("Default Student Name"), _id(0) {// ...}
private:int _id;
};
3、析构函数的特殊处理
因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destructor()。
4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化
class Person
{
public:// 默认构造函数 Person(const string& name) : _name(name) {// ...}// 拷贝构造函数 Person(const Person& p) : _name(p._name) {cout << "Copy Person(" << _name << ")" << endl;}// 赋值操作符重载 Person& operator=(const Person& p) {if (this != &p) {_name = p._name;cout << "Assign Person(" << _name << ")" << endl;}return *this;}// 析构函数 ~Person() {cout << "~Person(" << _name << ")" << endl;}string _name;
};class Student : public Person
{
public:// 构造函数 Student(int num, const string& name) : Person(name), _num(num) {cout << "Student(" << _num << ", " << _name << ")" << endl;}// 拷贝构造函数 Student(const Student& s) : Person(s), _num(s._num) {cout << "Copy Student(" << _num << ", " << _name << ")" << endl;}// 赋值操作符重载 Student& operator=(const Student& s) {if (this != &s) {Person::operator=(s); // 调用基类的赋值操作符 _num = s._num;}return *this;}// 析构函数 ~Student() {cout << "~Student(" << _num << ", " << _name << ")" << endl;}int _num;
};
相关文章:

【C++】——继承【上】
P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。 P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。 博主主页:Yan. yan. …...

SpringBoot 整合 阿里云 OSS图片上传
一、OOS 简介 阿里云OSS(Object Storage Service)是一种基于云存储的产品,适用于存储和管理各种类型的文件,包括图片、视频、文档等。 阿里云OSS具有高可靠性、高可用性和低成本等优点,因此被广泛应用于各种场景&…...

内核编译 设备驱动 驱动程序
内核编译 一、内核编译的步骤 编译步骤: (linux 内核源码的顶层目录下操作 ) 1. 拷贝默认配置到 .config cp config_mini2440_td35 .config 2. make menuconfig 内核配置 make menuconfig 3. make uImage make u…...

自由学习记录
约束的泛型通配符? Java中的泛型 xiaomi和byd都继承了car,但是只是这两个类是car的子类而已,而arraylist<xiaomi> ,arraylist<byd> 两个没有半毛钱继承关系 所以传入的参数整体,是car的list变形,里面的确都能存car…...

在 C# 中使用 LINQ 查询文件列表并找出最大文件
文章目录 1. 环境准备2. 创建项目3. 引入命名空间4. 示例代码5. 运行代码6. 进阶:异常处理7. 总结 在现代 C# 开发中,LINQ (Language Integrated Query) 提供了一种强大而优雅的方式来处理集合数据。本文将详细介绍如何使用 LINQ 查询文件系统中的文件&a…...

数学建模算法与应用 第6章 微分方程建模及其求解方法
目录 6.1 微分方程建模概述 6.2 发射卫星与三阶火箭建模 Matlab代码示例:火箭发射模拟 6.3 微分方程数值解法 Matlab代码示例:欧拉法与龙格-库塔法 6.4 放射性废料的处理 Matlab代码示例:放射性衰变 6.5 初值问题的Matlab数值求解 习…...

数据库的相关知识
数据库的相关知识 1.数据库能够做什么? 存储大量数据,方便检索和访问保持数据信息的一致、完整共享和安全通过组合分析,产生新的有用信息 2.数据库作用? 存储数据、检索数据、生成新的数据 3.数据库要求? 统一、…...

Python cachetools常用缓存算法汇总
文章目录 cachetools介绍缓存操作设置数据生存时间(TTL)自定义缓存策略缓存装饰器缓存清理cachetools 超过缓存数量maxsize cachetools 使用示例 cachetools介绍 cachetools : 是一个Python第三方库,提供了多种缓存算法的实现。缓存是一种用于…...

java类和对象_成员变量方法修饰符局部变量this关键字-cnblog
java类和对象 成员变量 权限修饰符 变量类型 变量名; 成员变量可以是任意类型,整个类是成员变量的作用范围 成员变量 成员方法 权限修饰符 返回值类型 方法名() 成员方法可以有参数,也可以有返回值,用return声明 权限修饰符 private 只能在本类…...

海信和TCL雷鸟及各大品牌智能电视测评
买了型号为32E2F(9008)的海信智能的电视有一段时间了,要使用这个智能电视还真能考验你的智商。海信电视有很多优点,它的屏幕比较靓丽,色彩好看,遥控器不用对着屏幕就能操作。但也有不少缺点。 1. 海信智能电视会强迫自动更新操作…...

Linux 基本系统命令及其使用详解手册(六)
指令:mesg 使用权限:所有使用者 使用方式:mesg [y|n] 说明 : 决定是否允许其他人传讯息到自己的终端机介面 把计 : y:允许讯息传到终端机介面上。 n:不允许讯息传到终端机介面上 。 如果没有设定,则讯息传递与否则由终端机界…...

Oracle架构之段管理和区管理
文章目录 1 段1.1 简介1.1.1 定义1.1.2 分类 1.2 段空间的管理模式1.2.1 手工段空间管理(Manual Segment Space Management)1.2.2 自动段空间管理(Auto Segment Space Management) 1.3 段空间的手工管理(Manual Segmen…...

mybatis-plus转换数据库json类型数据为java对象
JacksonTypeHandler JacksonTypeHandler 可以实现把json字符串转换为java对象。同一类型的handler有: Fastjson2TypeHandlerFastjsonTypeHandlerGsonTypeHandlerJacksonTypeHandler 至于需要哪一个选一个用就好了 使用方式 在实体类中加入注解 TableName(value "table_…...

Java | Leetcode Java题解之第467题环绕字符串中唯一的子字符串
题目: 题解: class Solution {public int findSubstringInWraproundString(String p) {int[] dp new int[26];int k 0;for (int i 0; i < p.length(); i) {if (i > 0 && (p.charAt(i) - p.charAt(i - 1) 26) % 26 1) { // 字符之差为…...

诺贝尔物理奖与化学奖彰显AI力量,探索智能新边界
在今年的诺贝尔物理学奖和化学奖的颁奖典礼上,人工智能(AI)再次成为耀眼的明星。两位物理学奖得主约翰J霍普菲尔德和杰弗里E辛顿因在人工神经网络和机器学习领域的开创性工作而获奖,而化学奖则颁给了在蛋白质结构设计和预测方面做…...

基于京东:HotKey实现自动缓存热点Key!!!
一.引言 某些热点数据,我们提前如果能够预判到的话,可以提前人工给数据加缓存,也就是缓存预热,将其缓存在本地或者Redis中,提高访问性能同时,减低数据库压力,也减轻后端服务的压力。但是&#…...

★ 算法OJ题 ★ 二分查找算法
Ciallo~(∠・ω< )⌒☆ ~ 今天,塞尔达将和大家一起做几道二分查找算法算法题 ~ ❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️❄️ 澄岚主页:椎名澄嵐-CSDN博客 算法专栏:★ 优选算法100天 ★_椎名澄嵐的博客-CSDN博客…...

RTSP RTP RTCP SDP基础知识
理论 流(Streaming ) 是近年在 Internet 上出现的新概念,其定义非常广泛,主要是指通过网络传输多媒体数据的技术总称。 流式传输分为两种 顺序流式传输 (Progressive Streaming) 实时流式传输 (Real time Streaming) …...

静态变量、变量作用域、命名空间
静态变量 静态变量一般位于程序全局data区,只是编程语言根据它所在的scope做语言级别访问限制。 静态变量和全局变量 可以在C语言一个函数中定义static变量,并比较和全局变量的地址差异。 C系语言使用static关键字标示静态变量。 PHP使用大写的STATIC关键…...

Android笔记(二十四)基于Compose组件的MVVM模式和MVI模式的实现
仔细研究了一下MVI(Model-View-Intent)模式,发现它和MVVM模式非常的相识。在采用Android JetPack Compose组件下,MVI模式的实现和MVVM模式的实现非常的类似,都需要借助ViewModel实现业务逻辑和视图数据和状态的传递。在这篇文章中,…...

MySQL 是否支持 XML
MySQL 是否支持 XML:概述与应用 虽然 MySQL 主要以处理关系型数据为主,但它也提供了对 XML 数据的支持。XML(可扩展标记语言)是一种用于数据传输和存储的通用格式。在许多应用场景中,XML 被广泛用于数据交换、配置文件…...

pikachu靶场总结(四)
九、越权漏洞 1.概述 如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作。 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的。 一般越权漏洞容易出现在权限页面(需要登…...

24.3 基于文件的服务发现模式
本节重点介绍 : 基于文件的服务发现提供了一种配置静态目标的更通用的方法可以摆脱对特定服务发现源的依赖通常的做法是调用内部CMDB的接口获取target数据,打上标签,生成json文件发给prometheus采集 基于文件的服务发现模式 解决的问题 之前手动配置…...

【Java】面向UDP接口的网络编程
【Java】面向UDP接口的网络编程 一. 基本通信模型二. APIDatagramSocketDatagramPacket 三. 回显服务器/客户端示例服务器客户端总结 一. 基本通信模型 UDP协议是面向数据报的,因此此处要构建数据报(Datagram)在进行发送。 二. API DatagramSocket DatagramSocke…...

SRS服务器搭建
1、配置 listen 1935; max_connections 1000; #srs_log_tank file; #srs_log_file ./objs/srs.log; daemon on; http_api { enabled on; listen 1985; } http_server { enabled on; listen 808…...

iMazing只能苹果电脑吗 Win和Mac上的iMazing功能有区别吗
在当今数字时代,管理和备份手机数据变得越来越重要。无论是转移照片、备份短信,还是管理应用程序,一个强大的工具可以大大简化这些操作。iMazing作为一款备受好评的iOS设备管理软件,已经成为许多用户的选择。但是,许多…...

ChatGPT可以分析股票吗?
结合国庆前大A股市的小波牛市以及今天的股市表现,我从多个角度为你提供一些分析和建议: 一、国庆前的小波牛市分析 国庆前,大A股市出现了一波小幅上涨,市场呈现出一些积极的信号: 政策面利好:政府出台了…...

Dockerfile搭建镜像
Dockerfile搭建镜像的优势与区别 引言 在现代软件开发与运维中,容器化技术日益普及,而Docker作为最流行的容器化平台之一,通过Dockerfile提供了一种灵活、自动化的方式来构建Docker镜像。Dockerfile使得镜像的构建过程可重复、可版本化&…...

Kubernetes-Kind篇-01-kind搭建测试集群
1、Kind 介绍 官方文档地址:https://kind.sigs.k8s.io/ github仓库地址:https://github.com/kubernetes-sigs/kind 国内镜像仓库地址:https://gitcode.com/gh_mirrors/ki/kind/overview kind 是一种使用 Docker 容器 nodes 运行本地 Kubern…...

在UniApp中高效处理大量文件请求的策略
在开发跨平台应用时,尤其是在使用UniApp这样的框架时,我们可能会遇到需要同时请求多个文件的情况。然而,不加节制地同时发起大量请求可能会带来严重的性能问题,如界面卡顿、内存溢出、网络带宽饱和等。本文将探讨如何在UniApp中高…...