C++ 继承详解

2023-11-05

继承语法

继承的一般语法为:

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

继承方式

继承方式包括 public(公有的)、private(私有的)和 protected(受保护的),此项是可选的,如果不写,那么默认为 private。不同的继承方式会影响基类成员在派生类中的访问权限。

(1)public继承方式

  • 基类中所有 public 成员在派生类中为 public 属性;
  • 基类中所有 protected 成员在派生类中为 protected 属性;
  • 基类中所有 private 成员在派生类中不能使用。

(2)protected继承方式

  • 基类中的所有 public 成员在派生类中为 protected 属性;
  • 基类中的所有 protected 成员在派生类中为 protected 属性;
  • 基类中的所有 private 成员在派生类中不能使用。

(3)private继承方式

  • 基类中的所有 public 成员在派生类中均为 private 属性;
  • 基类中的所有 protected 成员在派生类中均为 private 属性;
  • 基类中的所有 private 成员在派生类中不能使用。

通过上面的分析可以发现:

  1. 基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为 protected 时,那么基类成员在派生类中的访问权限最高也为 protected,高于 protected 的会降级为 protected,但低于 protected 不会升级。再如,当继承方式为 public 时,那么基类成员在派生类中的访问权限将保持不变。也就是说,继承方式中的 public、protected、private 是用来指明基类成员在派生类中的最高访问权限的。
  2. 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。
  3. 如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为 public 或 protected;只有那些不希望在派生类中使用的成员才声明为 private。
  4. 如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected。

注意,我们这里说的是基类的 private 成员不能在派生类中使用,并没有说基类的 private 成员不能被继承。实际上,基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。

private继承特点

如果是private继承,则不会自动将派生类类型转换为基类类型(不会自动转换,但是可以手动显式进行转换),不能隐式转换。private继承就是一种纯粹的实现技术,意味着子类继承了父类,纯粹是看中了父类里面的某些函数实现罢了,这个新的类将不会与父类指针有关系(接口都变private了)。

改变访问权限

使用 using 关键字可以改变基类成员在派生类中的访问权限,例如将 public 改为 private、将 protected 改为 public。

注意:using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问。

using 关键字使用示例:

#include<iostream>
using namespace std;
//基类People
class People {
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show() {
    cout << m_name << "的年龄是" << m_age << endl;
}
//派生类Student
class Student : public People {
public:
    void learning();
public:
    using People::m_name;  //将protected改为public
    using People::m_age;  //将protected改为public
    float m_score;
private:
    using People::show;  //将public改为private
};
void Student::learning() {
    cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
}
int main() {
    Student stu;
    stu.m_name = "小明";
    stu.m_age = 16;
    stu.m_score = 99.5f;
    stu.show();  //compile error
    stu.learning();
    return 0;
}

代码中首先定义了基类 People,它包含两个 protected 属性的成员变量和一个 public 属性的成员函数。定义 Student 类时采用 public 继承方式,People 类中的成员在 Student 类中的访问权限默认是不变的。

不过,我们使用 using 改变了它们的默认访问权限,如代码第 21~25 行所示,将 show() 函数修改为 private 属性的,是降低访问权限,将 name、age 变量修改为 public 属性的,是提高访问权限。

名字遮蔽

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

下面是一个成员函数的名字遮蔽的例子:

#include<iostream>
using namespace std;
//基类People
class People{
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show(){
    cout<<"嗨,大家好,我叫"<<m_name<<",今年"<<m_age<<"岁"<<endl;
}
//派生类Student
class Student: public People{
public:
    Student(char *name, int age, float score);
public:
    void show();  //遮蔽基类的show()
private:
    float m_score;
};
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
int main(){
    Student stu("小明", 16, 90.5);
    //使用的是派生类新增的成员函数,而不是从基类继承的
    stu.show();
    //使用的是从基类继承来的成员函数
    stu.People::show();
    return 0;
}

运行结果:

小明的年龄是16,成绩是90.5
嗨,大家好,我叫小明,今年16

本例中,基类 People 和派生类 Student 都定义了成员函数 show(),它们的名字一样,会造成遮蔽。第 37 行代码中,stu 是 Student 类的对象,默认使用 Student 类的 show() 函数。

但是,基类 People 中的 show() 函数仍然可以访问,不过要加上类名和域解析符,如第 39 行代码所示。

基类成员函数和派生类成员函数不构成重载。基类成员和派生类成员的名字一样时会造成遮蔽,这句话对于成员变量很好理解,对于成员函数要引起注意,不管函数的参数如何,只要名字一样就会造成遮蔽。换句话说,基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样

下面的例子很好的说明了这一点:

#include<iostream>
using namespace std;
//基类Base
class Base{
public:
    void func();
    void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }
//派生类Derived
class Derived: public Base{
public:
    void func(char *);
    void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }
int main(){
    Derived d;
    d.func("c.biancheng.net");
    d.func(true);
    d.func();  //compile error
    d.func(10);  //compile error
    d.Base::func();
    d.Base::func(100);
    return 0;
}

本例中,Base 类的func()、func(int)和 Derived 类的func(char *)、func(bool)四个成员函数的名字相同,参数列表不同,它们看似构成了重载,能够通过对象 d 访问所有的函数,实则不然,Derive 类的 func 遮蔽了 Base 类的 func,导致第 26、27 行代码没有匹配的函数,所以调用失败。

如果说有重载关系,那么也是 Base 类的两个 func 构成重载,而 Derive 类的两个 func 构成另外的重载。

继承时的对象模型

无变量遮蔽

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

#include <cstdio>
using namespace std;
 
class A{
protected:
    char a;
    int b;
public:
    A(char a, int b): a(a), b(b){}
    void display(){
        printf("a=%c, b=%d\n", a, b);
    }
};
 
class B: public A{
private:
    int c;
public:
    B(char a, int b, int c): A(a,b), c(c){ }
    void display(){
        printf("a=%c, b=%d, c=%d\n", a, b, c);
    }
};
 
int main(){
    A obj_a('@', 10);
    B obj_b('@', 23, 95);
    return 0;
}

obj_a 是基类对象,obj_b 是派生类对象。假设obj_a 的起始地址为 0X1000,那么它的内存分布如下图所示:
在这里插入图片描述

虽然变量 a 仅占用一个字节的内存,但由于内存对齐的需要,编译器会添加 3 个无用的字节(图中灰色部分),保证地址是 4 的倍数。后面的讲解中将忽略内存对齐,假设 a 的长度为4个字节。

假设 obj_b 的起始地址为 0X1100,那么它的内存分布如下图所示:
在这里插入图片描述

可以发现,基类的成员变量排在前面,派生类的排在后面。

下面再由 B 类派生出一个 C 类:

class C: public B{
private:
    int d;
public:
    C(char a, int b, int c, int d): B(a,b,c), d(d){ }
};
 
C obj_c('@', 45, 1009, 39);

假设 obj_c 的起始地址为 0X1200,那么它的内存分布如下图所示:
在这里插入图片描述

成员变量按照派生的层级依次排列,新增成员变量始终在最后。

有变量遮蔽

更改上面的C类:

class C: public B{
private:
    int b;  //遮蔽A类的变量
    int c;  //遮蔽B类的变量
    int d;  //新增变量
public:
    C(char a, int b, int c, int d): B(a,b,c), b(b), c(c), d(d){ }
    void display(){
        printf("A::a=%c, A::b=%d, B::c=%d\n", a, A::b, B::c);
        printf("C::b=%d, C::c=%d, C::d=%d\n", b, c, d);
    }
};
 
C obj_c('@', 23, 95, 2000);

假设 obj_c 的起始地址为 0X1300,那么它的内存分布如下图所示:
在这里插入图片描述

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

总结:派生类的对象模型中,会包含所有基类的成员变量。这种设计方案的优点是访问效率高,能够在派生类对象中直接访问基类变量,无需经过好几层间接计算。

final关键字

C++中,final关键字用于修饰类时,有以下作用:

  1. 禁止继承:将类标记为final,意味着无法继承。
  2. 禁止重写方法:当方法被标记为final时,在子类中无法重写该方法。
class test final{
};

class test
{
  public:
    virtual void func() final;
};
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 继承详解 的相关文章

  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • Web 客户端和 Expect100Continue

    使用 WebClient C NET 时设置 Expect100Continue 的最佳方法是什么 我有下面的代码 我仍然在标题中看到 100 continue 愚蠢的 apache 仍然抱怨 505 错误 string url http
  • 用于检查类是否具有运算符/成员的 C++ 类型特征[重复]

    这个问题在这里已经有答案了 可能的重复 是否可以编写一个 C 模板来检查函数是否存在 https stackoverflow com questions 257288 is it possible to write a c template
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 为什么当实例化新的游戏对象时,它没有向它们添加标签? [复制]

    这个问题在这里已经有答案了 using System Collections using System Collections Generic using UnityEngine public class Test MonoBehaviou
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • while 循环中的 scanf

    在这段代码中 scanf只工作一次 我究竟做错了什么 include
  • 垃圾收集器是否在单独的进程中运行?

    垃圾收集器是否在单独的进程中启动 例如 如果我们尝试测量某段代码所花费的进程时间 并且在此期间垃圾收集器开始收集 它会在新进程上启动还是在同一进程中启动 它的工作原理如下吗 Code Process 1 gt Garbage Collect
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 使用 x509 证书签署 json 文档或字符串

    如何使用 x509 证书签署 json 文档或字符串 public static void fund string filePath C Users VIKAS Desktop Data xml Read the file XmlDocum
  • 覆盖子类中的字段或属性

    我有一个抽象基类 我想声明一个字段或属性 该字段或属性在从该父类继承的每个类中具有不同的值 我想在基类中定义它 以便我可以在基类方法中引用它 例如覆盖 ToString 来表示 此对象的类型为 property field 我有三种方法可以
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 测试用例执行完成后,无论是否通过,如何将测试用例结果保存在变量中?

    我正在使用 NUNIT 在 Visual Studio 中使用 Selenium WebDriver 测试用例的代码是 我想在执行测试用例后立即在变量中记录测试用例通过或失败的情况 我怎样才能实现这一点 NUnit 假设您使用 NUnit
  • 是否可以在 .NET Core 中将 gRPC 与 HTTP/1.1 结合使用?

    我有两个网络服务 gRPC 客户端和 gRPC 服务器 服务器是用 NET Core编写的 然而 客户端是托管在 IIS 8 5 上的 NET Framework 4 7 2 Web 应用程序 所以它只支持HTTP 1 1 https le
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • 哪种 C 数据类型可以表示 40 位二进制数?

    我需要表示一个40位的二进制数 应该使用哪种 C 数据类型来处理这个问题 如果您使用的是 C99 或 C11 兼容编译器 则使用int least64 t以获得最大的兼容性 或者 如果您想要无符号类型 uint least64 t 这些都定

随机推荐

  • EM算法及其改进算法

    EM算法及其改进算法 搬运 EM算法笔记一 讲了基础的EM算法 EM算法笔记二 讲述了EM的改进算法 讲得很清晰 EM算法笔记三 不同应用场景的阐述
  • JavaScript实现跳跃游戏的贪婪方法的算法

    JavaScript实现跳跃游戏的贪婪方法的算法 跳跃游戏是一种常见的游戏类型 其中玩家需要控制角色进行跳跃操作以避开障碍物并达到目标 在这篇文章中 我们将使用JavaScript实现跳跃游戏的贪婪算法 该算法能够根据当前情况做出最优的跳跃
  • SpringFramework核心技术一(IOC:使用ICO容器)

    使用容器 这ApplicationContext是高级工厂的接口 能够维护不同bean及其依赖项的注册表 使用该方法T getBean String name Class requiredType 可以检索bean的实例 一 在Applic
  • 稀疏贝叶斯学习【Sparse bayesian learning】

    参考文献 An Empirical Bayesian Strategy for Solving the Simultaneous Sparse Approximation Problem 传统图像恢复 例如用Gaussian 噪声模型 TV
  • 基于ROS环境的相机标定教程

    一 参考资料 ROS学习 利用电脑相机标定 二 安装usb cam驱动包 usb cam ROS Wiki GitHub ros drivers usb cam A ROS Driver for V4L USB Cameras usb ca
  • 五、Android开发基础知识

    android系统一共分为四层 application java应用程序 Framework java框架或系统服务 Library 本地框架或本地服务又称为Native Android Runtime java运行环境 Linux Ker
  • app逆向篇之常用命令及刷机

    前言 之前刷机的时候记录的 刚好今天发一下 这篇仅用来记录学习及实践过程中的一些知识点 如有错误或不足之处 望大佬们不吝指教 ADB命令 1 连接设备 adb tcpip 5555 指定连接端口 adb connect 192 168 12
  • Eclipse下载与安装

    一 下载Eclipse 1 下载链接 https www eclipse org downloads 2 点击Download Packages 不要点击Download 64 bit 因为我没点这个 所以不知道 点击进去之后会有很多版本的
  • 基于深度学习的IQA论文整理

    文章目录 经典的CORNIA 经典的BRISQUE 用CNN进行质量评价的典型文章 最近的一些新颖的方法 经典的CORNIA Unsupervised Feature Learning Framework for No reference
  • 微软 Windows Server 2016 简体中文 MSDN 官方原版 ISO 镜像下载

    Windows Server 2016 它可以理解为服务器版的 Windows 10 宣告整个核心架构定型稳定 Windows Server 2016 是微软推出的第六个 Windows server 版本 也是 Windows 10 的服
  • 完美解决微信小程序使用复选框van-checkbox无法选中

    由于小程序使用了vant ui框架 导致checkbox点击无法选中问题
  • Elasticsearch之相关性评分

    一 概念 1 相关性 确切地说 应该加限定词 应该称作 已匹配到的内容的相关性 通俗地讲 就是已匹配到的内容跟要搜索的词 或句子 像不像 其中 已匹配到的内容 是指那些匹配了部分的搜索词的内容 或者完全和搜索词一模一样 这样就算匹配 而这个
  • 浅析DNS-RCODE(针对DNS应答码/状态码进行分析)

    1 前言 本文针对Header section format的RCODE进行分析 此部分对应Wireshark中打开的DNS数据包Domian Name System部分Flags的RCODE 1 1 参考资料 1 1 1 RFC1035
  • OA 权限树搭建时的技巧

    1
  • CGAL模运算在C/C++中的应用

    CGAL模运算在C C 中的应用 CGAL Computational Geometry Algorithms Library 是一个用于计算几何算法的强大C 库 其中包含了许多模块 包括模运算 Modular Arithmetic 模块
  • Linux 系统修改时区,nodejs 进行时间戳转换时,相差8小时

    做一个简单记录 1 查看系统时间 date date Y m d H M S 按照格式显示当前日期 结果如下 图中是修改后的时间 CST 表示的是美国 澳大利亚 古巴或中国的标准时间 2 查看时区 date Z 我系统之前查出来的是WAT
  • c语言用递归求整数阶乘n!

    include
  • CB Loss:基于有效样本的类别不平衡损失

    点击上方 AI公园 关注公众号 选择加 星标 或 置顶 因公众号更改了推送规则 记得读完点 在看 下次AI公园的新文章就能及时出现在您的订阅列表中 作者 Sik Ho Tsang 编译 ronghuaiyang 导读 使用每个类的有效样本数
  • react入坑学习(一)const 的用法

    解构赋值 const 的用法 const 概念 const 几种用法 用法一 用法二 const 的用法 在公司初次学习react 记录一点学习成果 const 概念 const 用于声明一个或多个常量 声明时必须进行初始化 且初始化后值不
  • C++ 继承详解

    C 继承 继承语法 继承方式 private继承特点 改变访问权限 名字遮蔽 继承时的对象模型 无变量遮蔽 有变量遮蔽 final关键字 继承语法 继承的一般语法为 class 派生类名 继承方式 基类名 派生类新增加的成员 继承方式 继承