Day17 C++ 继承

news2025/1/14 1:08:24

C++ 继承

  • 继承
    • 继承的基本语法
          • 继承的语法
          • 示例
    • 继承方式
          • 公有继承(public inheritance)
          • 保护继承(protected inheritance)
          • 私有继承(private inheritance)
          • 总结
    • 继承中的对象模型
    • 继承中构造和析构顺序
          • 构造顺序
          • 析构顺序
          • 示例
          • 原理
          • 总结
    • 继承同名成员处理方式
        • 访问方式
            • 访问子类同名成员 **直接访问**即可
            • 访问**父类同名成员 需要加作用域**
            • 总结:
        • 隐藏成员
        • 覆盖成员函数
        • 使用using声明
        • 举例
        • 覆盖和隐藏的区别
    • 继承同名静态成员处理方式
    • 多继承语法
        • 定义
        • 语法
        • 注意
        • 示例
    • 菱形继承
        • 定义
        • 二义性(Ambiguity)
        • 问题
        • 解决方法
          • 加修饰限定:使用作用域解析符明确指定使用哪个基类的成员
            • 虚继承:在继承方式前加上virtual

继承

继承是面向对象三大特性之一

在C++中,继承是面向对象编程的重要特性之一。通过使用继承,一个类可以继承另一个类的属性和方法,从而构建出更加灵活和可重用的代码结构。



继承的基本语法

继承的语法

class 子类 : 继承方式 父类

示例
class BaseClass {
   // BaseClass的定义
};

class DerivedClass : public BaseClass {
   // DerivedClass从BaseClass继承而来
};


继承方式

也是访问修饰符,可以是public、protected或private,用于指定派生类对基类成员的访问权限。

继承方式一共有三种:

公有继承(public inheritance)

使用关键字 public
进行继承,基类中的公有成员在派生类中仍为公有成员,保护成员在派生类中变为保护成员,私有成员在派生类中不可访问

保护继承(protected inheritance)

使用关键字 protected 进行继承,基类中的公有和保护成员在派生类中变为保护成员私有成员在派生类中不可访问。

私有继承(private inheritance)

使用关键字 private
进行继承,基类中的所有成员(包括公有、保护和私有成员)都在派生类中变为私有成员,不可在派生类外部访问。

总结

public继承表示基类的公有成员在派生类中仍为公有成员。
protected继承表示基类的公有和保护成员在派生类中变为保护成员。
private继承表示基类的公有和保护成员在派生类中变为私有成员。
任何派生类都无法访问父类的私有属性

在这里插入图片描述

继承中的对象模型

父类中私有成员也被子类继承下去了,只是由编译器给隐藏后访问不到,由于私有成员在类内部是不可访问的,因此在子类中无法直接访问或继承私有成员。

子类只能通过继承来获取父类的接口和实现,但无法直接访问父类的私有成员。私有成员对于子类来说是不可见和不可访问的。

这种隐藏和无法访问的特性是C++封装机制的一部分,其目的是保护类的实现细节和数据的安全性。通过将成员设为私有,可以限制外部访问并控制数据的修改。子类只能通过公有和受保护的成员函数间接地访问和操作基类的私有成员

继承中构造和析构顺序

结:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

构造顺序

首先,基类的构造函数会被调用,在派生类的构造函数体之前执行。
如果存在多级继承,构造函数的调用顺序按照从上到下、从左到右的顺序进行。
派生类的构造函数体会在基类构造函数执行完成后执行。

析构顺序

析构顺序与构造顺序相反
首先,派生类的析构函数会被调用,在基类的析构函数之前执行。
如果存在多级继承,析构函数的调用顺序按照从下到上、从右到左的顺序进行。
基类的析构函数会在派生类析构函数执行完成后执行。

示例
#include <iostream>

class BaseClass {
public:
    BaseClass() {
        std::cout << "BaseClass 构造函数" << std::endl;
    }
    ~BaseClass() {
        std::cout << "BaseClass 析构函数" << std::endl;
    }
};

class DerivedClass : public BaseClass {
public:
    DerivedClass() {
        std::cout << "DerivedClass 构造函数" << std::endl;
    }
    ~DerivedClass() {
        std::cout << "DerivedClass 析构函数" << std::endl;
    }
};

int main() {
    DerivedClass obj;
    return 0;
}

输出结果:

BaseClass 构造函数
DerivedClass 构造函数
DerivedClass 析构函数
BaseClass 析构函数

原理

这是因为在对象创建时,需要先构造基类部分,然后才能构造派生类部分。而在对象销毁时,先析构派生类部分,然后才能析构基类部分。这种顺序保证了继承关系中对象的正确构造和析构。

总结

继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反





继承同名成员处理方式

访问方式

访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域

当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域

总结:
  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

隐藏成员

如果派生类定义了与基类同名的成员函数或成员变量在派生类内部该成员将会隐藏基类的同名成员。在派生类内使用成员名称时,默认情况下将引用派生类的成员,而不会访问基类的同名成员。如果需要访问基类的同名成员,可以使用作用域解析运算符::来指定基类的成员。

覆盖成员函数

如果派生类定义了与基类同名的成员函数,并且它们具有相同的参数列表,那么派生类的成员函数将覆盖(override)基类的同名成员函数。在调用同名函数时,将会调用派生类的函数而不是基类的函数。

使用using声明

在派生类中可以通过using声明来引入基类的同名成员,使其在派生类中可见。这样可以实现对基类成员的重载和扩展,而不是简单地隐藏基类的同名成员。例如,可以使用using声明来将基类的同名成员引入派生类的作用域,然后在派生类中对该成员进行重载。

举例

#include <iostream>

class BaseClass {
public:
    void myFunction() {
        std::cout << "BaseClass 的函数" << std::endl;
    }
};

class DerivedClass : public BaseClass {
public:
    void myFunction() {
        std::cout << "DerivedClass 的函数" << std::endl;
    }
};

int main() {
    DerivedClass obj;
    
    // 1. 隐藏成员
    obj.myFunction();  // 输出: "DerivedClass 的函数"
    
    // 2. 覆盖成员函数
    obj.BaseClass::myFunction();  // 输出: "BaseClass 的函数"
    
    // 3. 使用using声明
    using BaseClass::myFunction;
    obj.myFunction();  // 输出: "BaseClass 的函数"
    
    return 0;
}

在上面的示例中,派生类DerivedClass继承了基类BaseClass的成员函数myFunction()。我们通过三种不同的方式处理同名成员:

隐藏成员:派生类DerivedClass定义了与基类BaseClass同名的成员函数myFunction()。在派生类内部,默认情况下引用的是派生类的同名成员函数。因此,在调用obj.myFunction()时,会输出"DerivedClass 的函数"。

覆盖成员函数:如果我们想要调用基类BaseClass中的同名成员函数myFunction(),可以使用作用域解析运算符::来指定基类的成员。例如,obj.BaseClass::myFunction()将会调用基类的函数,输出"BaseClass 的函数"。

使用using声明:通过使用using BaseClass::myFunction;的声明,我们将基类中的同名成员引入了派生类的作用域。这样,可以在派生类中对该成员进行重载和扩展。在这种情况下,obj.myFunction()将会调用基类的函数,输出"BaseClass 的函数"。

覆盖和隐藏的区别

覆盖(override):当派生类定义了一个与基类同名成员函数时,且它们具有相同的参数列表,派生类的成员函数将覆盖(override)基类的同名成员函数。在调用同名函数时,将会调用派生类的函数而不是基类的函数。这种行为也被称为动态多态性(dynamic polymorphism),因为在运行时决定调用哪个函数。

隐藏(hide):当派生类定义了一个与基类同名的成员函数或成员变量时,在派生类内部该成员将会隐藏基类的同名成员。在派生类内使用成员名称时,默认情况下将引用派生类的成员,而不会访问基类的同名成员。如果需要访问基类的同名成员,可以使用作用域解析运算符::来指定基类的成员。隐藏可以看作是一种静态的行为,因为在编译时已经确定了成员的访问。

区别如下:

覆盖是在运行时决定调用哪个函数,是动态的行为;而隐藏是在编译时确定成员的访问,是静态的行为。
覆盖是指派生类重写了基类的同名成员函数,重新定义了其行为;而隐藏是指派生类定义了一个与基类同名的成员,将基类的同名成员隐藏起来。
在覆盖中,通过基类指针或引用调用同名函数时,根据实际指向的对象类型决定调用的函数;而在隐藏中,不论使用什么方式进行调用,都会引用派生类的同名成员。
需要注意的是,覆盖只适用于虚函数(在基类中使用virtual关键字声明的函数),而隐藏适用于任何成员函数或成员变量。
(虚函数相关知识请看 Day18 多态)


继承同名静态成员处理方式

静态成员和非静态成员出现同名,处理方式一致,
只不过有两种访问的方式(通过对象 和 通过类名)
(俩种方式见 Day 14 对象的初始化和清理)

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

多继承语法

定义

多继承是一种C++中的面向对象编程特性,允许一个派生类从多个基类派生而来。

语法

class 子类 :继承方式 父类1 , 继承方式 父类2...

注意

多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
多继承也可能引发菱形继承问题和命名冲突问题,需要注意和处理。

示例

class BaseClass1 {
    // BaseClass1 的成员声明和定义
};

class BaseClass2 {
    // BaseClass2 的成员声明和定义
};

class DerivedClass : public BaseClass1, public BaseClass2 {
    // DerivedClass 的成员声明和定义
};

在上面的示例中,DerivedClass 是从 BaseClass1 和 BaseClass2 两个基类派生而来的派生类。

多继承的语法使用逗号分隔了多个基类,每个基类的访问权限可以通过 public、protected 或 private 关键字来声明,默认情况下是 private 访问权限。这里的示例中使用了 public 访问权限,这表示派生类可以访问基类的公有成员。

在派生类中,可以直接访问基类的成员变量和成员函数,例如:

DerivedClass obj;
obj.baseVariable1 = 10; // 访问 BaseClass1 的成员变量
obj.baseFunction1(); // 调用 BaseClass1 的成员函数

obj.baseVariable2 = 20; // 访问 BaseClass2 的成员变量
obj.baseFunction2(); // 调用 BaseClass2 的成员函数

菱形继承

定义

菱形继承(Diamond Inheritance,也称为钻石继承)是多继承中可能遇到的一个问题,指的是某个派生类同时继承了两个基类,而这两个基类又共同继承自同一个基类,形成了一个菱形的继承结构。这种继承结构可能导致一些问题,其中最主要的问题是二义性

二义性(Ambiguity)

通常指的是在派生类中访问基类成员时,存在多个可能的匹配,导致编译器无法确定使用哪个成员的情况。

出现二义性的主要原因是多重继承,即一个派生类同时继承自多个基类,并且这些基类中存在相同名称的成员。当派生类通过对象或指针访问这个名称时,编译器无法确定应该使用哪个基类的成员,从而导致二义性。为了解决这个问题,我们可以使用作用域解析符明确指定使用哪个基类的成员,如A::foo()B::foo()

问题

class Grandparent {
public:
    void greet() {
        std::cout << "Hello from Grandparent!" << std::endl;
    }
};

class Parent1 : public Grandparent { };

class Parent2 : public Grandparent { };

class Child : public Parent1, public Parent2 { };

在上面的示例中,Child 类从 Parent1 和 Parent2 两个基类派生而来,而 Parent1 和 Parent2 都继承自 Grandparent 基类,形成了一个菱形继承结构。

问题出现在当我们尝试在 Child 类中调用 greet() 函数时,编译器无法确定应该使用哪个 greet() 函数,因为 greet() 函数在 Parent1 和 Parent2 中都存在,而且它们都继承自 Grandparent。这导致了二义性问题。

解决方法

加修饰限定:使用作用域解析符明确指定使用哪个基类的成员
Child child;
child.Parent1::greet(); // 显式调用 Parent1 的 greet() 函数
child.Parent2::greet(); // 显式调用 Parent2 的 greet() 函数

虚继承:在继承方式前加上virtual
class Parent1 : virtual public Grandparent { };

class Parent2 : virtual public Grandparent { };

虚继承(virtual inheritance)是C++中用于解决菱形继承问题的技术。虚继承的原理是通过在派生类中使用虚基类来共享基类的成员。当一个派生类通过多条路径间接继承同一个基类时,如果不使用虚继承,可能会导致基类在派生类中存在多个实例,从而引发二义性和资源浪费的问题。通过使用虚继承,我们可以避免菱形继承带来的二义性和资源浪费问题,确保只有一个实例存在。但是需要注意,虚继承会带来一些额外的开销,包括内存空间和访问成员时的间接性。因此,在使用虚继承时需要权衡利弊,并根据具体情况进行选择。

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

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

相关文章

【WiFi】Hostapd工作流程分析

目录 1.Hostapd概述 2.Hostapd代码框架 3.Hostapd各种命令配置工具 4.hostaod的主函数 5.hostaod代码分析 1.Hostapd概述 Hostapd是一个运行在用户态的守护进程,可以通过Hostapd来读取配置文件,通过nl802.11来控制底层的状态如RTS/CTS beacon帧间隔等等信息;也可以读取相…

录音怎么转换成文字?这几种简单转换方法学起来

将录音转换成文字可以方便查找和引用重要信息&#xff0c;可以使用搜索功能快速找到关键词和短语&#xff0c;而无需听整个录音。其次&#xff0c;将录音转换成文字可以提高准确性和完整性&#xff0c;因为可以在记录文本中添加遗漏的细节和备注。此外&#xff0c;将录音转换成…

【cluster_block_exception】写操作elasticsearch索引报错

【cluster_block_exception】操作elasticsearch索引b报错 背景导致原因&#xff1a;解决方法&#xff1a; 背景 今天线上elk的数据太多&#xff0c;服务器的空间不足了。所以打算删除一些没用用的数据。我是用下面的request&#xff1a; POST /{index_name}/_delete_by_query…

Java基础面试题3

Java基础面试题 1&#xff1a;https://cloud.fynote.com/share/d/qPGzAVr5 2&#xff1a;https://cloud.fynote.com/share/d/MPG9AVsAG 3&#xff1a;https://cloud.fynote.com/share/d/qPGHKVsM 一、JavaWeb专题 1.HTTP响应码有哪些 1、1xx&#xff08;临时响应&#xf…

调整心态,走出焦虑,追求更高的自我!

每个人都希望能够成为最好的自己&#xff0c;追求自我提升和进步。然而&#xff0c;在这个竞争激烈的社会中&#xff0c;我们常常会感到焦虑和迷茫。看着周围的人在学习、工作、生活中取得成功&#xff0c;我们不禁会问自己&#xff0c;为什么他们能够做到&#xff0c;而我却做…

识别万物扫一扫,遇到不认识的物品扫就完事

随着科技的不断发展&#xff0c;移动设备已经成为人们日常生活中必不可少的工具。移动设备上的扫一扫功能&#xff0c;可以通过摄像头扫描物品&#xff0c;识别并获取相关信息&#xff0c;为人们的生活带来了很大的便利。本文将探讨识别万物扫一扫的使用及原理。 识别万物的使用…

opencv-32 图像平滑处理-高斯滤波cv2.GaussianBlur()

在进行均值滤波和方框滤波时&#xff0c;其邻域内每个像素的权重是相等的。在高斯滤波中&#xff0c;会将中心点的权重值加大&#xff0c;远离中心点的权重值减小&#xff0c;在此基础上计算邻域内各个像素值不同权重 的和。 基本原理 在高斯滤波中&#xff0c;卷积核中的值不…

【TIZI】霆智服务器登录控制太后,显示no VNC

【问题描述】 登录霆智服务器管理平台后&#xff0c; 点击【控制台】出现no VNC界面&#xff0c; 以及可能会显示 Error 500: closing file "var/tmp/pve-reserved-ports.tmp.1514’ failed - No space left on device。 最终该界面停留在Loading状态。 【解决方法】 方法…

性能测试工具在提升软件质量和用户体验方面的关键作用

在当今的数字时代&#xff0c;软件应用的性能和响应速度对于用户体验和企业的成功至关重要。为了满足用户对高性能和卓越体验的期望&#xff0c;开发团队需要使用专业的性能测试工具来检测和改进应用程序的性能。本文将讨论性能测试工具在提升软件质量和用户体验方面的关键作用…

养鸡场损失背后,是通讯故障还是数据大危机

近日&#xff0c;一场特殊的案件完成了判决&#xff0c;由于设备发生通信故障导致风机停止工作&#xff0c;进而导致1466只养殖的蛋鸡死亡&#xff0c;造成了巨额财产损失。最终&#xff0c;提供物联网终端设备的神桥公司被判处承担农场的70%的赔偿责任。 虽然被答辩人未公开表…

【漏洞复现】Metabase 远程命令执行漏洞(CVE-2023-38646)

文章目录 前言声明一、漏洞介绍二、影响版本三、漏洞原理四、漏洞复现五、修复建议 前言 Metabase 0.46.6.1之前版本和Metabase Enterprise 1.46.6.1之前版本存在安全漏洞&#xff0c;未经身份认证的远程攻击者利用该漏洞可以在服务器上以运行 Metabase 服务器的权限执行任意命…

光谱通用积分球的定义和原理

积分球是一个中空的、内壁涂以理想漫反射材料的球体&#xff0c;外部材质一般为金属材料&#xff0c;球壁上开有若干个窗孔&#xff0c;用以放置光接收器或作为球体的进光孔。进入积分球内部的光经内壁漫反射层多次反射以后&#xff0c;在整个内壁上都能得到均匀的照度&#xf…

大数据技术之Hadoop(二)

目录 一、Hadoop的诞生 二、大数据概述 三、大数据软件生态 3.1 数据存储相关技术 3.2 数据计算相关技术 3.3 数据传输相关技术 四、什么是Hadoop 本篇主要讲解大数据的核心概念以及Hadoop的基本介绍。 一、Hadoop的诞生 大数据的发展与日益庞大的数据量是密不可分的。从…

2023 7.31~8.6 周报 (多尺度的DL-FWI + 自然图像的风格迁移速度模型)

->目录<- 0 上周回顾1 本周论文背景简述2 模型架构3 风格化速度模型4 训练与实际数据的测试5 存在的一些问题6 总结和下一步工作 0 上周回顾 上周完成了VelocityGAN的重现和学习. 认识到了利用判别器网络对于常规网络进行约束是很一种很高效的设计思路. 1 本周论文背景…

恒运资本:股票总市值是什么意思?

职业新手可能会疑惑地问&#xff0c;股票总市值到底是什么意思&#xff1f;究竟&#xff0c;这是普通出资者常常看到的词汇&#xff0c;要了解股票总市值的含义&#xff0c;是需求了解金融商场的基本概念的。 股票总市值简介 股票的总市值是由公司一切的股票的数量乘以现在的价…

PREEvision Client 10.6.0

PREEvision Client 10.6.0 2692407267qq.com&#xff0c;更多内容请见http://user.qzone.qq.com/2692407267/

cookie的secure属性详解

cookie的secure属性详解 今天做项目的时候涉及到了cookie跨域传递的问题&#xff0c;也因此了解了cookie的一个属性——secure。 顾名思义&#xff0c;这个属性就是用来保证cookie的安全的。 当secure属性设置为true时&#xff0c;cookie只有在https协议下才能上传到服务器&a…

认识Webpack插件Plugin;CleanWebpackPlugin插件;HtmlWebpackPlugin;DefinePlugin;Mode模式

目录 1_认识插件Plugin2_CleanWebpackPlugin3_HtmlWebpackPlugin4_DefinePlugin4.1_介绍4.2_DefinePlugin的使用 5_Mode模式 1_认识插件Plugin Webpack的另一个核心是Plugin&#xff0c;官方有这样一段对Plugin的描述&#xff1a; While loaders are used to transform certai…

Linux - 进程控制(进程替换)

0.引入 创建子进程的目的是什么&#xff1f; 就是为了让子进程帮我执行特定的任务 让子进程执行父进程的一部分代码 如果子进程想执行一个全新的程序代码呢&#xff1f; 那么就要使用 进程的程序替换 为什么要有程序替换&#xff1f; 也就是说子进程想执行一个全新的程序代码&a…

P3957 [NOIP2017 普及组] 跳房子 (动态规划)(内附封面)

[NOIP2017 普及组] 跳房子 题目背景 NOIP2017 普及组 T4 题目描述 跳房子&#xff0c;也叫跳飞机&#xff0c;是一种世界性的儿童游戏&#xff0c;也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下&#xff1a; 在地面上确定一个起点&#xff0c;然后在起点右侧画…