【C++ 学习 ⑯】- 继承(上)

news2025/3/9 23:07:49

目录

一、继承的概念和定义

1.1 - 概念

1.2 - 定义

二、继承时的对象内存模型

三、向上转型和向下转型

四、继承时的名字遮蔽问题

4.1 - 有成员变量遮蔽时的内存分布

4.2 - 重名的基类成员函数和派生类成员函数不构成重载


 


一、继承的概念和定义

1.1 - 概念

C++ 中的继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。

继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程,例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。

被继承的类称为父类或基类,继承的类称为子类或派生类。"父类" 和 "子类" 通常放在一起称呼,"基类" 和 "派生类" 通常放在一起称呼。

#include <iostream>
using namespace std;
​
// 基类 Person
class Person
{
public:
    void SetName(const char* name = "张三") { _name = name; }
​
    void SetAge(int age = 18) { _age = age; }
​
    const char* GetName() const { return _name.c_str(); }
​
    int GetAge() const { return _age; }
protected:
    string _name;  // 姓名
    int _age;  // 年龄
};
​
// 派生类 Student
class Student : public Person
{
public:
    void SetStuId(int stu_id = 0) { _stu_id = stu_id; }
​
    int GetStuId() const { return _stu_id; }
protected:
    int _stu_id;  // 学号
};
​
// 派生类 Teacher
class Teacher : public Person
{
public:
    void SetJobId(int job_id) { _job_id = job_id; }
​
    int GetJobId() const { return _job_id; }
protected:
    int _job_id;  // 工号
};
​
int main()
{
    Student s;
    s.SetName("李四");
    s.SetAge(19);
    s.SetStuId(1);
    cout << s.GetName() << "的年龄是" << s.GetAge() << 
        ",学号是" << s.GetStuId() << "。" << endl;
    // 李四的年龄是19,学号是1。
​
    Teacher t;
    t.SetName("王五");
    t.SetAge(30);
    t.SetJobId(10);
    cout << t.GetName() << "的年龄是" << t.GetAge() <<
        ",工号是" << t.GetJobId() << "。" << endl;
    // 王五的年龄是30, 工号是10。
    return 0;
}

通过以上的例子我们就可以明白:继承机制是面向对象程序中使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。继承呈现了面向对象呈现设计的层次结构,体现了由简单到复杂的认知过程,以前我们接触的复用是模板,继承则是类设计层次的复用

1.2 - 定义

继承的定义格式为

class 派生类名 : [继承方式] 基类名
{
    派生类新增加的成员
}

不同的继承方式会影响基类成员在派生类中的访问权限

基类成员/继承方式public 继承protected 继承private 继承
基类的 public 成员派生类的 public 成员派生类的 protected 成员派生类的 private 成员
基类的 protected 成员派生类的 protected 成员派生类的 protected 成员派生类的 private 成员
基类的 private 成员在派生类中不可见在派生类中不可见在派生类中不可见

总结

  1. 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问和调用)。注意:我们说的是基类的 private 成员不能在派生类中使用,并没有说基类的 private 成员不能被继承。实际上,基类的 private 成员是能够被继承的,并且成员变量会占用派生类对象的内存,它只是在派生类中无法使用罢了,即不可见

  2. 如果希望基类的成员既不向外暴露,即不能通过对象访问,还能在派生类中使用,那么只能声明为 protected,由此可以看出 protected 访问限定符是因为继承才出现的

  3. 通过上面的表格我们可以发现,基类的 private 成员在派生类中不可见,基类的其他成员在派生类中的访问权限 = min(成员在基类中的访问权限,继承方式),比较规则是:public > protected > private

  4. 使用 class 关键字时,默认是 private 继承,使用 struct 关键字时,默认是 public 继承,不过最好显示地写出继承方式

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


二、继承时的对象内存模型

没有继承时,对象内存模型很简单,对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象),成员函数与对象内存分离,存储在代码区。

当有继承关系时,派生类对象的内存模型可以看成是基类成员变量和新增成员变量的总和,而所有成员函数仍然存储在另外一个区域——代码区,由所有对象共享

#include <iostream>
using namespace std;
​
class A
{
public:
    int _i;
    int _j;
};
​
class B : public A
{
public:
    int _k;
};
​
class C : public B
{
public:
    int _l;
};
​
int main()
{
    A a;
    cout << &a << endl;
    cout << &a._i << " " << &a._j << endl;
    cout << sizeof(A) << endl; 
​
    B b;
    cout << &b << endl;
    cout << &b._i << " " << &b._j << " " << &b._k << endl;
    cout << sizeof(B) << endl;  
​
    C c;
    cout << &c << endl;
    cout << &c._i << " " << &c._j << " " << &c._k << " " << &c._l << endl;
    cout << sizeof(C) << endl;
    return 0;
}

即:

可以发现,基类的成员变量排在前面,新增的成员变量排在后面


三、向上转型和向下转型

可以用 "派生类对象/派生类指针/派生类引用" 给 "基类对象/基类指针/基类引用" 初始化或赋值,这在 C++ 中称为向上转型(Upcasting),还有一个形象的说法叫切片或切割。向上转型非常安全,可以由编译器自动完成

相应地,用基类给派生类初始化或赋值称为向下转型(Downcasting)。向下转型则有风险,需要程序员手动干预

  1. 不能用 "基类对象" 给 "派生类对象" 初始化或赋值,因为基类不包含派生类的成员变量,所以无法对派生类的成员变量赋值

  2. "基类指针或者引用" 可以通过强制类型转换的方式给 "派生类指针或引用" 初始化或赋值,但是必须是 "基类指针" 指向 "派生类对象" 时才是安全的。这里基类如果是多态类型,可以使用 RTTI(Run-Time Type Information)的 dynamic_cast 识别后进行安全转换(后面再详细讲解)。

#include <iostream>
using namespace std;
​
class Person
{
protected:
    string _name;
    int _age;
};
​
class Student : public Person
{
public:
    int _stu_id;
};
​
int main()
{
    Student s;
    Person p = s;
    Person* pp = &s;
    Person& rp = s;  // 由此可以发现,向上转型的过程中没有发生隐式类型转换
​
    // s = p;  // error
​
    Student* ps = (Student*)pp;  
    ps->_stu_id = 1; 
    // 基类指针 pp 指向的是派生类对象 s,所以转换是安全的
​
    // pp = &p;
    // ps = (Student*)pp;
    // ps->_stu_id = 0;  // 越界访问
    // 这种情况下,强制类型转换是可行的,但不安全
    return 0;
}


四、继承时的名字遮蔽问题

如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用,以及通过派生对象访问该成员时,实际上使用的是派生类新增的成员,而不是从基类继承来的

#include <iostream>
using namespace std;
​
class A
{
public:
    void Print() { cout << _i << " " << _j << endl; }
public:
    int _i = 1;
    int _j = 2;
};
​
class B : public A
{
public:
    // 遮蔽基类的成员函数
    void Print() { cout << _i << " " << _j << " " << _k << endl; }
public:
    int _j = 3;  // 遮蔽基类的成员变量
    int _k = 4; 
};
​
int main()
{
    B b;
    // 使用的是派生类新增的成员,而不是从基类继承来的
    cout << b._j << endl;  // 3
    b.Print();  // 1 3 4
​
    // 使用的是基类继承来的成员
    cout << b.A::_j << endl;  // 2
    b.A::Print();  // 1 2
    return 0;
}

4.1 - 有成员变量遮蔽时的内存分布

cout << &b._i << " " << &b.A::_j << " " << &b._j << " " << &b._k << endl;
cout << sizeof(B) << endl;

即:

当基类 A 的成员变量被遮蔽时,仍然会留在派生类对象 b 的内存中,B 类新增的成员变量也始终排在基类 A 的成员变量的后面

4.2 - 重名的基类成员函数和派生类成员函数不构成重载

类其实是一种作用域,每个类都会定义它自己的作用域,在这个作用域类我们再定义类的成员。当存在继承关系时,派生类的作用域嵌套在基类的作用域之内,如果一个名字在派生类的作用域内无法找到,编译器会继续到外层的基类作用域中查找该名字的定义

在上面的例子中,B 继承自 A,它们作用域嵌套的嵌套关系如下:

函数重载指的是在同一个作用域内有多个名称相同但参数列表不同的函数,所以重名的基类成员函数和派生类成员函数不会构成重载,即便参数一样,也不会报错

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java八股文面试[java基础]——浅拷贝和深拷贝

自验证&#xff1a;创建Class Student两个类&#xff0c; Student中含有Class对象 public class Class implements Cloneable {public String getName() {return name;}public void setName(String name) {this.name name;}private String name;public Class(String name) {t…

无涯教程-PHP - IntlChar类

在PHP7中&#xff0c;添加了一个新的 IntlChar 类&#xff0c;该类试图公开其他ICU函数。此类定义了许多静态方法和常量&#xff0c;可用于操作unicode字符。使用此类之前&#xff0c;您需要先安装 Intl 扩展名。 <?phpprintf(%x, IntlChar::CODEPOINT_MAX);print (IntlCh…

构建智慧停车场:4G DTU实现无线数据高速传输

物联网技术的快速发展使得各种设备能够实现互联互通&#xff0c;无线网络技术给我们的日常生活带来了极大的便利。其中的网络技术如无线WiFi及4G网络已经成为了物联网应用中不可或缺的组成部分。而在工业领域中对4G无线路由器的应用是非常广泛的&#xff0c;人们通过4G工业路由…

python中 * 的用法,超详细教程

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 python中 * 是非常常见的一个运算符&#xff0c;它主要有以下几个功能&#xff1a; 乘法运算符&#xff1b; 函数形参表示可变参数&#xff1b; 函数实参代表tuple&#xff1b; 序列解包为tuple&#xff1b; zip解包运算&…

知识蒸馏Demo,非常详细,适合入门

文章来自&#xff1a;Ai浩的“知识蒸馏实战&#xff1a;使用CoatNet蒸馏ResNet”&#xff0c;文章地址为&#xff1a;知识蒸馏实战&#xff1a;使用CoatNet蒸馏ResNet_知识蒸馏实例_AI浩的博客-CSDN博客 感谢作者&#xff01;&#xff01;&#xff01; 摘要 知识蒸馏&#xf…

nvm安装使用教程

文章目录 下载配置安装最新稳定版 node安装指定版本查看版本切换版本删除版本 常见问题安装node后 显示拒绝访问的问题使用cnpm会报错的问题降低cnpm版本npm镜像 下载 NVM for Windows 下载地址&#xff1a;https://link.juejin.cn/?targethttps%3A%2F%2Fgithub.com%2Fcoreyb…

《动手学深度学习》-19卷积层

沐神版《动手学深度学习》学习笔记&#xff0c;记录学习过程&#xff0c;详细的内容请大家购买书籍查阅。 b站视频链接 开源教程链接 卷积 使用一个12M像素的相机采集图片&#xff0c;因为是RGB图片所以有36M元素。 使用MLP来做分类会遇到的问题&#xff1a; 参数太大&#…

goland 中的调试器 -- Evaluate

今天一个好朋友 找到我&#xff0c;问我关于goland中Evaluate 小计算器的使用方式&#xff0c;说实话&#xff0c;我在此之前也没用过这个东西&#xff0c;然后我就找一些相关文档&#xff0c;但是这类文档少的可怜&#xff0c;所以我就稍微研究一下&#xff0c;找找材料&#…

【附安装包】Vero visi2021安装教程

软件介绍 Vero visi是世界领先的CAD/CAM解决方案&#xff0c;又简称为visi&#xff0c;由多个模块组成&#xff0c;包括VISI Modelling、VISI Analysis、VISI Mould、VISI Flow、VISI Electrode、VISI Progress、VISI Multi-Slides、VISI Machining 2D、VISI PEPS-Wire、WorkX…

《操作系统真象还原》学习笔记:第七章 中断

由于 CPU 获知了计算机中发生的某些事&#xff0c;CPU 暂停正在执行的程序&#xff0c;转而去执行处理该事件的程序&#xff0c;当这段程序执行完毕后&#xff0c;CPU 继续执行刚才的程序。整个过程称为中断处理&#xff0c;也称为中断。 把中断按事件来源分类&#xff0c;来自…

nginx:正向代理与反向代理

所谓代理服务器&#xff0c;就是位于发起请求的客户端与原始服务器端之间的一台跳板服务器&#xff0c; 正向代理可以隐藏客户端&#xff1a;想要实现正向代理&#xff0c;得配置一台转发请求的跳板服务器&#xff0c;同时客户端还得配置跳板服务器的代理地址。 我的电脑访问这…

5大轻量开源的项目管理软件推荐,更适合中小团队!

随着互联网的发展&#xff0c;项目管理软件越来越受到企业和团队的重视。不仅可以提高工作效率&#xff0c;还可以帮助团队协作、进度跟踪和资源管理等方面&#xff0c;简化复杂的项目管理流程。那么&#xff0c;对于中小团队来说&#xff0c;有没有一款轻量易上手的适合他们的…

网络防御与蓝队实践:探讨网络防御策略、入侵检测系统、安全事件响应等蓝队方面的实际案例和方法

第一章&#xff1a;引言 网络安全一直是当今信息社会中至关重要的话题。随着技术的不断发展&#xff0c;网络威胁也愈发复杂和隐匿。在这样的背景下&#xff0c;网络防御变得尤为重要&#xff0c;蓝队作为网络防御的重要一环&#xff0c;起着至关重要的作用。本文将深入探讨网…

libdrm全解析一 —— 总述

本文参考以下博文&#xff1a; Linux libdrm代码完全解析 LIBDRM使用 最简单的DRM应用程序 &#xff08;single-buffer&#xff09; Linux libdrm库入门教程 10. DRM图形显示框架 LIBDRM 特此致谢&#xff01; 一、介绍 BLFS中给出的介绍 libdrm提供了一个用户空间库&…

2023年湖北中级工程师职称申报专业有哪些?甘建二告诉你

中级职称职称申报专业&#xff1a;环境工程、 土木建筑、土建结构、土建监理、土木工程、岩石工程、岩土、土岩方、风景园林、园艺、园林、园林建筑、园林工程、园林绿化、古建筑园林、工民建、工民建安装、建筑、建筑管理、建筑工程、建筑工程管理、建筑施工、建筑设计、建筑装…

36k字从Attention解读Transformer及其在Vision中的应用(附pytorch代码)

文章目录 0.卷积操作1.注意力1.1 注意力概述&#xff08;Attention&#xff09;1.1.1 Encoder-Decoder1.1.2 查询、键和值1.1.3 注意力汇聚&#xff1a; Nadaraya-Watson 核回归 1.2 注意力评分函数1.2.1 加性注意力1.2.2 缩放点积注意力 1.3 自注意力&#xff08;Self-Attenti…

舍不得跳过的广告?900万播放冲上B站热门

8月17日&#xff0c;B站官方公布了2023年第二季度财报。 B站在用户增长方面又创新高&#xff0c;本季度财报显示&#xff0c;B站日均活跃用户达9650万&#xff0c;同比增长15%。用户日均使用时长94分钟&#xff0c;创同期历史新高成为一大亮点。 除此之外&#xff0c;B站还放…

Lnton羚通视频算法算力云平台如何快速了解pandas(上)

创建对象 创建 Series s pd.Series([1, 2, 3, 6, np.nan, 6, 8, 10]) s0 1.0 1 2.0 2 3.0 3 6.0 4 NaN 5 6.0 6 8.0 7 10.0 dtype: float64创建 DataFrame 利用 date_range 创建时间索引&#xff0c;并用 numpy 创建一个随机数组&#xff0c;…

Jmeter常用线程组设置策略

一、前言 ​ 在JMeter压力测试中&#xff0c;我们时常见到的几个场景有&#xff1a;单场景基准测试、单场景并发测试、单场景容量测试、混合场景容量测试、混合场景并发测试以及混合场景稳定性测试 在本篇文章中&#xff0c;我们会用到一些插件&#xff0c;在这边先给大家列出&…

wazuh配置和案例复现

wazuh配置 安装wazuh有二种方法 第一种 在官网下载ova文件 打开VMware进行虚拟机安装 账号:wazuh-user 密码:wazuh 将网络设置为net模式 重启网卡 systemctl network restart查看ip ip add启动小皮 远程连接 默认账号和密码admin admin 第二种 yum安装根据官方文件的步骤…