C++----继承

一、继承的基本概念

本质:代码复用+类关系建模(是多态的基础)

class Person { /*...*/ };
class Student : public Person { /*...*/ }; // public继承
  • 派生类继承基类成员(数据+方法),可以通过监视窗口检验成员复用。

二、继承中的访问权限控制

访问权限变化表

基类成员访问限定符/继承方式public继承protected继承private继承
public成员->派生类public->派生类protected->派生类private
protected成员->派生类protected->派生类protected->派生类private
private成员不可见(但存在)不可见(但存在)不可见(但存在)

关键规则

  1. private成员:在派生类中始终不可访问(但存在于对象中)
  2. protected成员:专为继承设计,派生类可访问,外部不可访问。
  3. 访问权限计算:Min(成员在基类的权限,继承方式),权限等级:public>protected>private
  4. 默认继承方式:class默认private继承;struct默认public继承。
  5. 实际开发:优先使用public继承(占实际使用90%以上),慎用protected/private继承。

三、对象赋值转换规则

允许的操作

Student s;
Person p = s;         // 对象切片(调用拷贝构造)
Person& rp = s;       // 直接引用基类部分
Person* pp = &s;      // 直接指向基类部分

禁止的操作

// Person p;
// Student s = p;       // 错误!基类无法赋给派生类

关键注意

  • 对象切片:派生类->基类赋值时,丢失派生类特有成员。
  • 引用转换原理:派生类对象包含完整的基类子对象,无临时变量生成。

        类型系统对比:

  int i = 0;
  const double& rd = i;  // 需要const引用(临时变量具有常性)

四、继承中的作用域

核心规则

  1. 独立作用域:基类和派生类拥有独立的作用域。
  2. 隐藏/重定义:派生类成员与基类同名时,隐藏基类成员,包括成员变量和函数(无论参数是否一致)。
    class Base {
    public:
        void func(int) {}
    };
    
    class Derived : public Base {
    public:
        void func() { 
            Base::func(42); // 必须显式指定作用域
        }
    };

重要细节

  • 函数隐藏与重载:派生类会隐藏基类所有同名函数(即使参数不同)
  • 访问被隐藏成员:使用作用域解析符
    Base::member
  • 设计建议:避免定义同名非虚函数

五、派生类默认成员函数

1. 构造函数

  • 规则

1.当基类存在默认构造函数时:如果基类有隐式或显式的无参构造函数(即默认构造函数),派生     类的构造函数初始化列表不需要显式调用基类构造函数。编译器会自动隐式调用基类的默认构造     函数。示例:

class Person {
public:
    Person() {} // 默认构造函数(可以隐式生成)
};

class Student : public Person {
public:
    Student() {} // 隐式调用 Person::Person()
};

2.当基类没有默认构造函数时:如果基类的构造函数需要参数,且没有定义无参构造函数,则派生类必须在初始化列表中显示调用基类的某个构造函数,否则会编译报错。示例:

class Person {
public:
       Person(int x) {} // 没有默认构造函数
};

class Student : public Person {
public:
       Student() : Person(42) {} // 必须显式调用基类构造函数
};
  • 原理:基类成员初始化顺序优先于派生类成员

 2. 拷贝构造函数

  • 规则:需显式调用基类拷贝构造,完成基类部分的深拷贝

  • 代码示例

    Student(const Student& s)
        : Person(s)         // 切片调用基类拷贝构造
        , _id(s._id) {}

3. 赋值运算符重载 

  • 规则:需要显式调用基类赋值运算符,处理自赋值情况

  • 代码示例

    Student& operator=(const Student& s) {
        if (this != &s) {
            Person::operator=(s);  // 显式调用基类赋值
            _id = s._id;
        }
        return *this;
    }

4. 析构函数 

  • 规则

    • 析构顺序:派生类->基类(自动调用基类析构)

    • 禁止显式调用基类析构函数

  • 代码示例

    ~Student() {
        // 自动调用Person::~Person()
        delete _ptr;  // 先清理派生类资源
    }

六、继承关系与友元 

  • 规则:基类友元不能访问派生类私有成员,需要额外声明

  • 代码示例

    class Student;  // 前向声明
    class Person {
        friend void Display(const Person&, const Student&);
    };
    class Student : public Person {
        friend void Display(const Person&, const Student&);
    };
    void Display(const Person& p, const Student& s) {
        cout << p._name << endl;    // 访问基类保护成员
        cout << s._stuNum << endl;  // 访问派生类保护成员
    }

七、静态成员与继承 

  • 特性

    • 基类静态成员被所有派生类共享

    • 继承的是访问权而非副本

    • 静态成员不被包含在对象中,它放在静态存储器。

  • 代码示例

    class Person
    {
    public :
        Person () {++ _count ;}
    protected :
        string _name ; // 姓名
    public :
        static int _count; // 统计人的个数。
    };
    int Person :: _count = 0;
    
    class Student : public Person
    {
    protected :
        int _stuNum ; // 学号
    };
    
    class Graduate : public Student
    {
    protected :
        string _seminarCourse ; // 研究科目
    };
    
    void TestPerson()
    {
        Student s1 ;
        Student s2 ;
        Student s3 ;
        Graduate s4 ;
        cout <<" 人数 :"<< Person ::_count << endl; //输出:人数 :4
        Student ::_count = 0;
        cout <<" 人数 :"<< Person ::_count << endl; //输出:人数 :0
    }

八、复杂继承模型 

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承

  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承:多继承的一种特殊情况

问题

数据冗余和二义性(从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。)

class Person
{
public :
    string _name ; // 姓名
};
class Student : public Person
{
protected :
    int _num ; //学号
};
class Teacher : public Person
{
protected :
    int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
    string _majorCourse ; // 主修课程
};

void Test ()
{
    // 这样会有二义性无法明确知道访问的是哪一个
    Assistant a ;    
    a._name = "peter";    
    // 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
    a.Student::_name = "xxx";
    a.Teacher::_name = "yyy";
}

解决方案

虚拟继承(在Assistant的对象中Person成员只有一份。)

class Person
{
public :
    string _name ; // 姓名
};
class Student : virtual public Person
{
protected :
    int _num ; //学号
};
class Teacher : virtual public Person
{
protected :
    int _id ; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected :
    string _majorCourse ; // 主修课程
};

void Test ()
{
    Assistant a ;
    a._name = "peter";
}

虚拟继承解决数据冗余和二义性的原理

讲解

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助内存窗口观察对象成员的模型。

class A { int _a; };
class B : public A { int _b; };
class C : public A { int _c; };
class D : public B, public C { int _d; };

int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢? 这里是通过了 B C 的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A
  • 疑问1:为什么D中B和C部分要去找属于自己的A?

        那么大家看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?

  • 疑问2: 为什么要在B和C中存指针,而不直接存距离A的偏移量呢?

        ①存储内容多样性:

            - 多偏移量:菱形继承中,虚基表除存当                   前类到虚基类偏移量,在多重继承、复                   杂模板实例化等场景,还需存不同条件                   下访问虚基类的其他偏移量,用于正确                   定位成员。
            - 辅助信息:虚基表存储虚基类构造、析                   构函数指针等辅助信息,确保在对象构                   构造、析构及函数调用时正确操作。仅                   用偏移量无法存储这些信息,易致错误。

        ②支持复杂偏移关系:

            - 适应结构变化:使用虚基表指针,面对                   复杂继承结构变化(如 B、C 继承路径                     新增虚继承层次)时,虚基表可添加新                   偏移量或信息,指针仍能正确指向,保                   证虚基类成员访问。直接用偏移量,结                   构变化时需多处修改,维护扩展困难。

下面是上面的Person关系菱形虚拟继承的原理图:

九、继承与组合的选择

特征继承组合
关系性质"is-a"关系"has-a"关系
可见性白箱复用(了解实现细节)黑箱复用(接口隔离)
耦合度高耦合低耦合
多态支持支持不支持
代码复用方式垂直复用(扩展功能)水平复用(功能组合)

使用建议

  1. 优先使用对象组合

  2. 需要多态特性时使用继承

  3. 避免过度使用多继承

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DeepSeek】DeepSeek小模型蒸馏与本地部署深度解析DeepSeek小模型蒸馏与本地部署深度解析

一、引言与背景 在人工智能领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;如DeepSeek以其卓越的自然语言理解和生成能力&#xff0c;推动了众多应用场景的发展。然而&#xff0c;大型模型的高昂计算和存储成本&#xff0c;以及潜在的数据隐私风险&#xff0c;限制了…

ZZNUOJ(C/C++)基础练习1081——1090(详解版)

目录 1081 : n个数求和 &#xff08;多实例测试&#xff09; C C 1082 : 敲7&#xff08;多实例测试&#xff09; C C 1083 : 数值统计(多实例测试) C C 1084 : 计算两点间的距离&#xff08;多实例测试&#xff09; C C 1085 : 求奇数的乘积&#xff08;多实例测试…

axios 发起 post请求 json 需要传入数据格式

• 1. axios 发起 post请求 json 传入数据格式 • 2. axios get请求 1. axios 发起 post请求 json 传入数据格式 使用 axios 发起 POST 请求并以 JSON 格式传递数据是前端开发中常见的操作。 下面是一个简单的示例&#xff0c;展示如何使用 axios 向服务器发送包含 JSON 数…

硬盘接入电脑提示格式化?是什么原因?怎么解决?

有时候&#xff0c;当你将硬盘接入电脑时&#xff0c;看到系统弹出“使用驱动器中的光盘之前需要将其格式化”的提示&#xff0c;肯定会感到十分困惑和焦虑。这种情况不仅让人担心数据丢失&#xff0c;也可能影响正常使用。为什么硬盘会突然要求格式化&#xff1f;是硬盘出了问…

使用Python实现PDF与SVG相互转换

目录 使用工具 使用Python将SVG转换为PDF 使用Python将SVG添加到现有PDF中 使用Python将PDF转换为SVG 使用Python将PDF的特定页面转换为SVG SVG&#xff08;可缩放矢量图形&#xff09;和PDF&#xff08;便携式文档格式&#xff09;是两种常见且广泛使用的文件格式。SVG是…

【大数据技术】搭建完全分布式高可用大数据集群(Kafka)

搭建完全分布式高可用大数据集群(Kafka) kafka_2.13-3.9.0.tgz注:请在阅读本篇文章前,将以上资源下载下来。 写在前面 本文主要介绍搭建完全分布式高可用集群 Kafka 的详细步骤。 注意: 统一约定将软件安装包存放于虚拟机的/software目录下,软件安装至/opt目录下。 安…

【C++篇】C++11新特性总结1

目录 1&#xff0c;C11的发展历史 2&#xff0c;列表初始化 2.1C98传统的{} 2.2&#xff0c;C11中的{} 2.3&#xff0c;C11中的std::initializer_list 3&#xff0c;右值引用和移动语义 3.1&#xff0c;左值和右值 3.2&#xff0c;左值引用和右值引用 3.3&#xff0c;…

Redis --- 使用HyperLogLog实现UV(访客量)

UV 和 PV 是网站或应用数据分析中的常用指标&#xff0c;用于衡量用户活跃度和页面访问量。 UV (Unique Visitor 独立访客)&#xff1a; 指的是在一定时间内访问过网站或应用的独立用户数量。通常根据用户的 IP 地址、Cookies 或用户 ID 等来唯一标识一个用户。示例&#xff1…

【机器学习案列】糖尿病风险可视化及预测

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

单片机之基本元器件的工作原理

一、二极管 二极管的工作原理 二极管是一种由P型半导体和N型半导体结合形成的PN结器件&#xff0c;具有单向导电性。 1. PN结形成 P型半导体&#xff1a;掺入三价元素&#xff0c;形成空穴作为多数载流子。N型半导体&#xff1a;掺入五价元素&#xff0c;形成自由电子作为多…

llama.cpp GGUF 模型格式

llama.cpp GGUF 模型格式 1. Specification1.1. GGUF Naming Convention (命名规则)1.1.1. Validating Above Naming Convention 1.2. File Structure 2. Standardized key-value pairs2.1. General2.1.1. Required2.1.2. General metadata2.1.3. Source metadata 2.2. LLM2.2.…

Conmi的正确答案——Rider中添加icon作为exe的图标

C#版本&#xff1a;.net 8.0 Rider版本&#xff1a;#RD-243.22562.250&#xff08;非商业使用版&#xff09; 1、添加图标到解决方案下&#xff1a; 2、打开“App.xaml”配置文件&#xff0c;添加配置&#xff1a; <Applicationx:Class"ComTransmit.App"xmlns&q…

告别手动操作!用Ansible user模块高效管理 Linux账户

在企业运维环境中&#xff0c;服务器的用户管理是一项基础但非常重要的任务。比如&#xff0c;当有新员工加入时&#xff0c;我们需要在多台服务器上为他们创建账户并分配合适的权限。而当员工离职或岗位发生变化时&#xff0c;我们也需要迅速禁用或删除他们的账户&#xff0c;…

C++小等于的所有奇数和=最大奇数除2加1的平方。

缘由 三种思路解题&#xff1a;依据算术推导得到一个规律&#xff1a;小等于的所有奇数和等于最大奇数除以2加1的平方。将在后续发布&#xff0c;总计有十种推导出来的实现代码。 int a 0,aa 1,aaa 0;cin >> a; while (aa<a) aaa aa, aa 2;cout << aaa;i…

【CPP】CPP经典面试题

文章目录 引言1. C 基础1.1 C 中的 const 关键字1.2 C 中的 static 关键字 2. 内存管理2.1 C 中的 new 和 delete2.2 内存泄漏 3. 面向对象编程3.1 继承和多态3.2 多重继承 4. 模板和泛型编程4.1 函数模板4.2 类模板 5. STL 和标准库5.1 容器5.2 迭代器 6. 高级特性6.1 移动语义…

深入浅出谈VR(虚拟现实、VR镜头)

1、VR是什么鬼&#xff1f; 近两年VR这次词火遍网上网下&#xff0c;到底什么是VR&#xff1f;VR是“Virtual Reality”&#xff0c;中文名字是虚拟现实&#xff0c;是指采用计算机技术为核心的现代高科技手段生成一种虚拟环境&#xff0c;用户借助特殊的输入/输出设备&#x…

【Redis】安装配置Redis超详细教程 / Linux版

Linux安装配置Redis超详细教程 安装redis依赖安装redis启动redis停止redisredis.conf常见配置设置redis为后台启动修改redis监听地址设置工作目录修改密码监听的端口号数据库数量设置redis最大内存设置日志文件设置redis开机自动启动 学习视频&#xff1a;黑马程序员Redis入门到…

[LeetCode]day16 242.有效的字母异位词

242. 有效的字母异位词 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的 字母异位词 示例 1: 输入: s "anagram", t "nagaram" 输出: true示例 2: 输入: s "rat"…

[MoeCTF 2022]baby_file

题目 <html> <title>Heres a secret. Can you find it?</title> <?phpif(isset($_GET[file])){$file $_GET[file];include($file); }else{highlight_file(__FILE__); } ?> </html> 读取flag /?filephp://filter/readconvert.base64-encode…

Centos挂载镜像制作本地yum源,并补装图形界面

内网环境centos7.9安装图形页面内网环境制作本地yum源 上传镜像到服务器目录 创建目录并挂载镜像 #创建目录 cd /mnt/ mkdir iso#挂载 mount -o loop ./CentOS-7-x86_64-DVD-2009.iso ./iso #前面镜像所在目录&#xff0c;后面所挂载得目录#检查 [rootlocalhost mnt]# df -h…