C++ 继承:面向对象编程的核心概念(一)
文章目录
- 引言
- 1. 继承的基本知识
- 1.1 继承的关键词的区别
- 1.2 继承类模版
- 2. 基类和派生类间的转换
- 3. 继承中的作用域
- 4. 派生类的默认成员函数
- 4.1 默认成员函数的规则
- 4.2 自己实现成员函数
- 4.3 实现一个不能被继承的基类(基本不用)
引言
在C++中,继承是面向对象编程的核心概念之一,它允许根据现有的类定义新类。这种机制不仅简化了应用程序的创建和维护,还实现了代码重用和执行效率的提升。当你创建一个新类时,无需重写数据成员和成员函数,只需指明新类继承了现有类的成员即可。现有的类称为基类,新创建的类称为派生类。继承与函数重载、模版相比,前者是类设计层次的复用,后者是函数层次的复用。
// 继承的实现意义
class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 学习void study(){// ...}
protected:string _name = "peter"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄int _stuid; // 学号
};class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 授课void teaching(){//...}
protected:string _name = "张三"; // 姓名int _age = 18; // 年龄string _address; // 地址string _tel; // 电话string _title; // 职称
};int main()
{return 0;
}
在上面的代码中,我们看到没有继承的类Student 和 Teacher,Student 和 Teacher都有一部分相同的成员和函数(_name、_address、_tel、_age、identity),这部分函数分别实现在两个类里面是冗余的。但是这两个类又有不同的部分(学生:study、_stuid 和 老师:teaching、_title)。这就导致我们没办法彻底把两个类合并。
这个时候就可以使用继承,把公共的部分分别继承给两个类,提高程序的实现效率:
// 继承的使用
// 基类
class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout << "void identity()" << _name << endl;}protected:string _name = "张三"; // 姓名string _address; // 地址string _tel; // 电话int _age = 18; // 年龄
};// 派生类1
class Student : public Person
{
public:// 学习void study(){// ...}protected:int _stuid; // 学号
};// 派生类2
class Teacher : public Person
{
public:// 授课void teaching(){//...}protected:string title; // 职称
};int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}
1. 继承的基本知识
1.1 继承的关键词的区别
首先,我们需要知道继承根据不同的关键词,基类成员在派生类中的访问权限不同。

- 基类private成员虽然成功继承在派生类中。但是派生类无论以什么方式都是不可访问private成员的。
- 基类protected成员可以在派生类中被访问,但是无法在类外被访问。
- 使用class定义的类,在不显示写继承方式时,默认的继承方式是private;使用struct定义的类,默认的继承方式是public。一般都需要显示写继承方式。
- 一般继承的方式都是public继承,不建议使用其他继承。
// 实例演⽰三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public:void Print(){cout << _name << endl;}
protected:string _name; // 姓名
private:int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stunum; // 学号
};
1.2 继承类模版
template<class T>
class stack : public std::vector<T>
{
public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,std::// 否则编译报错:error C3861: “push_back”: 找不到标识符 // 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到vector<T>::push_back(x);// push_back(x);}void pop(){vector<T>::pop_back();}const T& top(){return vector<T>::back();}bool empty(){return vector<T>::empty();}
};
2. 基类和派生类间的转换
- public继承的派生类对象,可以定义指向基类的指针。可以理解成切片:指针指向的不是原本的基类,而是从派生类中切片出的基类部分。
- 基类对象不能定义指向派生类的指针。
- 基类的指针或引用可以通过强制类型转换赋值给派生类的指针或引用。但是必须是基类的指针是指向派生类对象的时候才是安全的。
class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{public :int _No; // 学号
};int main()
{Student sobj;// 1.派⽣类对象可以赋值给基类的指针/引⽤Person* pp = &sobj;Person& rp = sobj;// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的Person pobj = sobj;//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错sobj = pobj;return 0;
}
3. 继承中的作用域
在继承中基类和派生类都有其独立的作用域。就算指明作用域也无法给相关成员赋值。派生类和基类中有同名成员时,派生类将屏蔽基类对派生类同名成员的访问,这种情况叫隐藏。
如果是成员函数的话,只需要同名就构成隐藏。所以在继承中注意尽量不定义同名的成员/函数。
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是⾮常容易混淆
class Person
{
protected :string _name = "⼩李⼦"; // 姓名int _num = 111; // ⾝份证号
};class Student : public Person
{
public :void Print(){cout << " 姓名:" << _name << endl;// 没有指明类域默认访问派生类中的_numcout << " ⾝份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;}protected:int _num = 999; // 学号
};int main()
{// 主函数中指明作用域也无法使用//Person::_name;//Student::_num;Student s1;s1.Print();return 0;
}
4. 派生类的默认成员函数
4.1 默认成员函数的规则
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。需要注意的是派生类的operator=隐藏了基类的operator=,所以显示调用基类的operator=,需要指定基类作用域。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造,再调用派生类构造。
- 派生类对象析构先调用派生类析构,再调用基类析构。
- 基类析构函数不加virtual的情况下,编译器会对析构函数名进行特殊处理,处理成destructor(),派生类析构函数也会在运行时重命名为destructor(),所以会和基类析构函数构成隐藏关系。
class Person
{
public :Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public :Student(const char* name, int num): Person(name), _num(num){cout << "Student()" << endl;} Student(const Student& s): Person(s), _num(s._num){cout << "Student(const Student& s)" << endl;} Student& operator= (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){// 构成隐藏,所以需要显⽰调⽤Person::operator =(s);_num = s._num;}return* this;} ~Student(){cout << "~Student()" << endl;}protected:int _num; //学号
};int main()
{// 构造函数// 先调用基类的构造函数,再调用派生类的Student s1("jack", 18);// 拷贝构造// 先调用基类的拷贝构造,再调用派生类的Student s2(s1);// 拷贝构造Student s3("rose", 17);// operator=// 先调用派生类的=,再调用基类的=s1 = s3;// 析构函数先调用派生类的,再调用基类的return 0;
}
4.2 自己实现成员函数
当默认成员函数不能满足我们的需求时,可以自己实现成员函数。
// 自己实现成员函数的要点:子类的成员函数需要带上父类的成员函数
// 特点:子类中继承下来的父类成员当做一个整体对象
// 构造:子类成员 内置类型(有缺省值就用,没有不确定)和自定义类型(默认构造) + 父类成员(必须调用父类默认构造)
//
// 拷贝构造:子类成员 内置类型(值拷贝)和自定义类型(这个类型拷贝构造) + 父类成员(必须调用父类拷贝构造)
//
// 赋值重载:类似拷贝构造
//
// 析构:子类成员 内置类型(不处理)和自定义类型(调用他的析构) + 父类成员(调用他的析构)
// 自己实现的话,注意不需要显示调用父类析构,子类析构函数结束后,会自动调用父类析构class Person
{
public:// 基类构造函数Person(const char* name): _name(name){cout << "Person()" << endl;}// 基类const构造函数Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}// operator=重置Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}// 编译时重写为destructor()~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:// 派生类构造函数Student(int num, const char* address, const char* name):_num(num),_address(address),Person(name){cout << "Student()" << endl;}// 派生类拷贝构造函数。初始化列表调用基类的拷贝构造函数Student(const Student& s): Person(s), _num(s._num),_address(s._address){cout << "Student(const Student& s)" << endl;}// operator=重置Student& operator = (const Student& s){cout << "Student& operator= (const Student& s)" << endl;if (this != &s){_num = s._num;_address = s._address;// 子类赋值重载调用父类赋值重载Person::operator=(s);}return *this;}// 编译器在编译的时候,会将析构函数的名字重写为destructor()。// 这样析构函数的名字就会重复~Student(){// 不需要写,子类析构函数结束后,会自动调用父类析构//Person::~Person();cout << "~Student()" << endl;// delete[] _ptr;}protected:int _num; //学号string _address;//int* _ptr = new int[10];
};int main()
{Student s1(18, "张三", "西安");Student s2(s1);Student s3(19, "张四", "西安");s1 = s3;return 0;
}
4.3 实现一个不能被继承的基类(基本不用)
- 方法1:基类的构造函数私有,派生类的构成必须调用基类的构造函数,但是基类的构成函数私有化以后,派生类看不见就不能调用了,那么派生类就无法实例化出对象。
- 方法2:C++11新增了⼀个final关键字,final修改基类,派生类就不能继承了。
// C++11的⽅法。方法2
class Base final
{
public :void func5() { cout << "Base::func5" << endl; }protected:int a = 1;private:// C++98的⽅法。方法1/*Base(){}*/
}class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};int main()
{Base b;Derive d;return 0;
}
未完待续。
剩下的内容可以跳转到:C++ 继承:面向对象编程的核心概念(二)
相关文章:
C++ 继承:面向对象编程的核心概念(一)
文章目录 引言1. 继承的基本知识1.1 继承的关键词的区别1.2 继承类模版 2. 基类和派生类间的转换3. 继承中的作用域4. 派生类的默认成员函数4.1 默认成员函数的规则4.2 自己实现成员函数4.3 实现一个不能被继承的基类(基本不用) 引言 在C中,…...
蓝桥杯 临时抱佛脚 之 二分答案法与相关题目
二分答案法(利用二分法查找区间的左右端点) (1)估计 最终答案可能得范围 是什么 (2)分析 问题的答案 和 给定条件 之间的单调性,大部分时候只需要用到 自然智慧 (3)建…...
【图论】网络流算法入门
(决定狠狠加训图论了,从一直想学但没启动的网络流算法开始。) 网络流问题 • 问题定义:在带权有向图 G ( V , E ) G(V, E) G(V,E) 中,每条边 e ( u , v ) e(u, v) e(u,v) 有容量 c ( u , v ) c(u, v) c(u,v)&am…...
【算法day22】两数相除——给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。
29. 两数相除 给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 &#x…...
《TypeScript 7天速成系列》第4天:TypeScript模块与命名空间:大型项目组织之道
在大型TypeScript项目中,良好的代码组织架构是保证项目可维护性的关键。本文将深入探讨TypeScript的模块系统和命名空间,为企业级项目提供最佳实践方案。 一、模块化开发:现代前端工程的基石 1.1 ES模块基础语法 TypeScript全面支持ES6模块…...
AutoCAD C#二次开发中WinForm与WPF的对比
在AutoCAD .NET二次开发中,选择WinForm还是WPF作为用户界面技术,需要根据项目需求、团队技能和AutoCAD版本等因素综合考虑。以下是详细对比: ## 1. 基础特性对比 | 特性 | WinForm | WPF | |------------|…...
关于服务器只能访问localhost:8111地址,局域网不能访问的问题
一、问题来源: 服务器是使用的阿里云的服务器,服务器端的8111端口没有设置任何别的限制,但是在阿里云服务器端并没有设置相应的tcp连接8111端口。 二、解决办法: 1、使用阿里云初始化好的端口;2、配置新的阿里云端口…...
基于ADMM无穷范数检测算法的MIMO通信系统信号检测MATLAB仿真,对比ML,MMSE,ZF以及LAMA
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 ADMM算法 4.2 最大似然ML检测算法 4.3 最小均方误差(MMSE)检测算法 4.4 迫零(ZF)检测算法 4.5 OCD_MMSE 检测算法 4.6 LAMA检测算法 …...
Linux 配置时间服务器
一、同步阿里云服务器时间 服务端设置 1.检查chrony服务是否安装,设置chrony开机自启,查看chrony服务状态 [rootnode1-server ~]# rpm -q chrony # rpm -q 用于查看包是否安装 chrony-4.3-1.el9.x86_64 [rootnode1-server ~]# systemctl enable --n…...
可视化web组态开发工具
BY组态是一款功能强大的基于Web的可视化组态编辑器,采用标准HTML5技术,基于B/S架构进行开发,支持WEB端呈现,支持在浏览器端完成便捷的人机交互,简单的拖拽即可完成可视化页面的设计。可快速构建和部署可扩展的SCADA、H…...
深度学习驱动的车牌识别:技术演进与未来挑战
一、引言 1.1 研究背景 在当今社会,智能交通系统的发展日益重要,而车牌识别作为其关键组成部分,发挥着至关重要的作用。车牌识别技术广泛应用于交通管理、停车场管理、安防监控等领域。在交通管理中,它可以用于车辆识别、交通违…...
C++笔记-模板初阶,string(上)
一.模板初阶 1.泛型编程 以往我们要交换不同类型的两个数据就要写不同类型的交换函数,这是使用函数重载虽然可以实现,但是有以下几个不好的地方: 1.重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时&a…...
关于cmd中出现无法识别某某指令的问题
今天来解决以下这个比较常见的问题,安装各种软件都可能会发生,一般是安装时没勾选注册环境变量,导致cmd无法识别该指令。例如mysql,git等,一般初学者可能不太清楚。 解决这类问题最主要的是了解环境变量的概念&#x…...
绿联NAS安装内网穿透实现无公网IP也能用手机平板远程访问经验分享
文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 大家好,今天给大家带来一个超级炫酷的技能——如何在绿联NAS上快速安装cpolar内网穿透工具。想象一下,即使没有公网IP,你也能随时随地远程访问自己…...
d9-326
目录 一、添加逗号 二、爬楼梯 三、扑克牌顺子 添加逗号_牛客题霸_牛客网 (nowcoder.com) 一、添加逗号 没啥注意读题就是 注意逗号是从后往前加,第一位如果是3的倍数不需要加逗号,备注里面才是需要看的 count计数 是三的倍数就加逗号,…...
汇编(六)——汇编语言程序格式及MASM
汇编语言的实现也是先利用某种编辑器编写汇编语言源程序(*.ASM),然后经过汇编得到目标模块文件(*.OBJ)、连接后形成可执行文件(*.EXE)。 1、汇编语言程序的语句格式 汇编语源程序由语句序列构成…...
Win11+VS2022+CGAL5.6配置
1. CGAL库简介 CGAL(Computational Geometry Algorithms Library)是一个开源的计算几何算法库,主要用于处理几何问题和相关算法的实现。它提供了丰富的几何数据结构和高效算法,覆盖点、线、多边形、曲面等基本几何对象的表示与操…...
【Linux】MAC帧
目录 一、MAC帧 (一)IP地址和MAC地址 (二)MAC帧格式 (三)MTU对IP协议的影响、 (四)MTU对UDP协议的影响 (五)MTU对TCP协议的影响 二、以太网协议 &…...
Codeforces Round 1013 (Div. 3)(A-F)
题目链接:Dashboard - Codeforces Round 1013 (Div. 3) - Codeforces A. Olympiad Date 思路 找到第一个位置能凑齐01032025的位置 代码 void solve(){int n;cin>>n;vi a(n10);int id0;map<int,int> mp;for(int i1;i<n;i){cin>>a[i];mp[a…...
Flink 常用及优化参数
流批模式 SET execution.runtime-mode streaming; // or batch基础 Checkpoint 配置 -- 启用 Checkpoint,间隔 5 分钟 SET execution.checkpointing.interval 5min; -- Checkpoint 超时时间(10 分钟) SET execution.checkpointing.timeou…...
Vite 与 Nuxt 深度对比分析
一、核心定位差异 二、核心功能对比 渲染能力 Vite:默认仅支持客户端渲染(CSR),需通过插件(如vite-plugin-ssr)实现 SSR/SSG,但配置灵活 Nuxt:原生支持 SSR(服务端渲…...
Linux内核 内存管理 物理内存初始化流程
1.ARM64页表初始化流程图 start_kernel()│▼ setup_arch() // 架构相关初始化│▼ early_fixmap_init() // 初始化Fixmap(临时映射设备树等)│▼ arm64_memblock_init() // 从设备树解析内存布局│▼ arm…...
PyBluez2 的详细介绍、安装指南、使用方法及配置说明
PyBluez2:Python 蓝牙开发的核心库 一、PyBluez2 简介 PyBluez2 是 Python 的开源蓝牙编程库,支持蓝牙 2.0、BLE(低功耗蓝牙)和传统蓝牙协议栈的开发。它提供了对蓝牙硬件适配器的底层控制,适用于设备发现、配对、数…...
通过一个led点灯的demo来熟悉openharmony驱动编写的过程(附带hdf详细调用过程)
概述 本应用程序(led_rgb)是在上实现直接通过消息机制与内核驱动进行交互,设置RGB三色灯的亮灯行为。我从网上随便找了个demo测试了一下,坑了三天…,整个状态如下图,同时也迫使我深度梳理了一下整个流程框架。直到绝望的时候&…...
pycharm2024.1.1版本_jihuo
目录 前置: 步骤: step one 下载软件 step two 卸载旧版本 1 卸载软件 2 清除残余 step three 下载补丁 step four 安装2024.1.1版本软件 step five 安装补丁 1 找位置放补丁 2 自动设置环境变量 step six 输入jihuo码 前置: 之…...
目标检测20年(四)——最终章
欢迎各位读者尽情阅读前三篇文献解读。这一篇将会介绍文献的第五部分:目标检测近些年的新技术发展以及第六部分:总结与未来展望。这也是本篇论文解读的最后一篇文章。 目录 五、目标检测最新进展 5.1 不采用滑动窗口的检测 5.2 旋转和尺度变化的鲁棒性…...
PyTorch处理数据--Dataset和DataLoader
在 PyTorch 中,Dataset 和 DataLoader 是处理数据的核心工具。它们的作用是将数据高效地加载到模型中,支持批量处理、多线程加速和数据增强等功能。 一、Dataset:数据集的抽象 Dataset 是一个抽象类,用于表示数据集的接口。你…...
【Linux】POSIX信号量与基于环形队列的生产消费者模型
目录 一、POSIX信号量: 接口: 二、基于环形队列的生产消费者模型 环形队列: 单生产单消费实现代码: RingQueue.hpp: main.cc: 多生产多消费实现代码: RingQueue.hpp: main.…...
Spring Boot 连接 MySQL 配置参数详解
Spring Boot 连接 MySQL 配置参数详解 前言参数及含义常用参数及讲解和示例useUnicode 参数说明: 完整配置示例注意事项 前言 在 Spring Boot 中使用 Druid 连接池配置 MySQL 数据库连接时,URL 中 ? 后面的参数用于指定连接的各种属性。以下是常见参数…...
[linux] linux基本指令 + shell + 文件权限
目录 1. Linux的认识 1.1. Linux的应用场景 1.2. Linux的版本问题 1.3. 操作系统的认识 1.4. 常用快捷键 2. 常用指令介绍 2.1. ADD 2.1.1. touch [file] 2.1.1.1. 文件的属性信息 2.1.2. mkdir [directory] 2.1.3. cp [file/directory] 2.1.4. echo [file] 2.1.4.…...
