【讨论C++继承】

news2025/1/11 20:45:42

讨论C++继承

  • 继承
    • 定义
    • 继承方式和访问限定符
  • 基类和派生类的赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 继承和友元
  • 继承和静态成员
  • 菱形继承
    • 虚拟继承

继承是面向对象程序设计中,使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展。

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承

定义

class Person {
public:
    void print() {
        cout << "这是一个人类" << endl;
    }
protected:
    string name;
    int age;
};

class Student : public Person{
private:
    int stuId;
};

int main() {
    Student s;
    Person p;
    s.print();
    p.print();
    return 0;
}

继承打印结果

上述代码,Person是基类,Student是派生类。

使用:来实现继承。

继承定义

继承方式和访问限定符

继承方式

访问限定符

基类/继承方式public继承protected继承private继承
public成员派生类的public成员派生类的protected成员派生类的private成员
protected成员派生类的protected成员派生类的protected成员派生类的private成员
private成员在派生类中不可见在派生类中不可见在派生类中不可见
  1. 基类使用private修饰的成员在派生类中无论以什么方式继承,都不可见。不可见是指还是会继承,只是不能直接使用,因为private修饰的成员在类外不可使用。
  2. protected修饰的成员不可在类外使用,但是存在继承关系的,派生类可以直接访问基类的成员。
  3. 使用关键字class时,默认的继承方式是private,使用struct时,默认的继承方式是public

基类和派生类的赋值转换

派生类对象可以直接赋值给基类的对象、指针、引用。

基类的对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用,但必须是基类的指针指向派生类的对象时才安全。

赋值转换

class Person {
public:
    void print() {
        cout << "这是一个人类" << endl;
    }
protected:
    string name;
    int age;
};

class Student : public Person{
private:
    int stuId;
};

int main() {
    Student s;
    Person* ps = &s;
    Person& rps = s;
    Person p = s;
    return 0;
}
  • 派生类对象赋值给基类对象,基类对象只能访问基类拥有的成员变量,不能访问派生类特有的成员变量。
  • 派生类赋值给基类对象时,不产生临时变量。

继承中的作用域

  1. 在继承体系中,基类和派生类的作用域是独立的。
  2. 当基类和派生类中有同名成员时,派生类成员将屏蔽基类同名成员的直接访问,称为隐藏或重定义。
  3. 成员函数只需要函数名相同即可达成隐藏。
class Person {
protected:
    string name = "张三";
    int age = 10;
};

class Student : public Person{
public:
    void print() {
        cout << "姓名:" << name << endl;
        cout << "年龄:" << age << endl;
        cout << "学号:" << stuId << endl;
    }
private:
    int stuId;
    int age;
};

int main() {
    Student s;
    s.print();
    return 0;
}

作用域结果

  • 上述代码,Student类中的agePerson类中的age构成隐藏。Student对象使用自己类域中的age成员变量,因此是随机值。
  • 要想使用Person类中的age,需要Person::age
class Student : public Person{
public:
    void print() {
        cout << "姓名:" << name << endl;
        cout << "年龄:" << Person::age << endl;
        cout << "学号:" << stuId << endl;
    }
private:
    int stuId;
    int age;
};

作用域结果2

派生类的默认成员函数

  1. 派生类构造时必须调用基类的构造函数初始化基类的部分成员,如果基类没有默认构造函数,必须在派生类构造函数初始化列表中显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  3. 派生类的operator=必须调用基类的operator=完成基类的赋值。
  4. 派生类的析构函数会在调用完成后自动调用基类的析构函数清理基类成员。
  5. 派生类对象初始化先调用基类构造函数再调用派生类构造函数。
  6. 派生类对象析构时先调用派生类析构函数再调用基类析构函数。
class Person {
public:
    Person(const char *name = "张三")
            : _name(name) {
        cout << "Person(name)" << 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 stuId = 110)
            : Person(name), _stdId(stuId) {
        cout << "Student(name, stuId)" << endl;
    }

    Student(const Student &s)
            : Person(s), _stdId(s._stdId) {
        cout << "Student(const Student &s)" << endl;
    }

    Student& operator=(const Student& s) {
        cout << "Person& operator=(const Person& p)" << endl;
        if(this != &s) {
            Person::operator=(s);
            _stdId = s._stdId;
        }
        return *this;
    }

    ~Student() {
        cout << "~Student()" << endl;
    }

private:
    int _stdId;
};

int main() {
    Student s1("李四", 111);
    cout << "====================" << endl;

    Student s2(s1);
    cout << "====================" << endl;

    Student s3("王五", 112);
    s2 = s3;
    cout << "====================" << endl;

    return 0;
}
  • 根据规则,派生类构造前应完成基类构造,因此在创建派生类对象,一定会初始化基类,析构时先析构派生类,再析构基类。

验证结果

继承和友元

友元关系不能被继承。

class Student;
class Person {
public:
    friend void print(const Person& p, const Student& s);
public:
    Person(const char *name = "张三")
            : _name(name) {
        cout << "Person(name)" << 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 stuId = 110)
            : Person(name), _stdId(stuId) {
        cout << "Student(name, stuId)" << endl;
    }

    Student(const Student &s)
            : Person(s), _stdId(s._stdId) {
        cout << "Student(const Student &s)" << endl;
    }

    Student& operator=(const Student& s) {
        cout << "Person& operator=(const Person& p)" << endl;
        if(this != &s) {
            Person::operator=(s);
            _stdId = s._stdId;
        }
        return *this;
    }

    ~Student() {
        cout << "~Student()" << endl;
    }

protected:
    int _stdId;
};

void print(const Person& p, const Student& s) {
    cout << p._name << endl;
    cout << s._name << s._stdId << endl;
}

int main() {
    Student s("李四", 111);
    print(s, s);
    return 0;
}
  • 上述代码会报错,error: '_stdId' is a protected member of 'Student'。证明友元函数没有被继承,因为Student类使用privateprotected修饰的成员变量不能在类外使用。

继承和静态成员

基类定义的static静态成员,整个继承体系中只有一个这样的成员。

class Person {
public:
    Person() {
        ++n;
    }
public:
    static int n;
};
int Person::n = 0;
class Student : public Person {
};


int main() {
    Student s;
    cout << Person::n << endl;
    s.n = 10;
    cout << Person::n << endl;

    return 0;
}

验证结果

菱形继承

  • 单继承:一个派生类只有一个直接基类。Java只支持单继承。

单继承

  • 多继承:一个派生类继承于多个基类。C++支持多继承。

多继承

  • 菱形继承:它是多继承的一个特殊情况。

菱形继承

菱形继承所带来的问题是,最初继承的类内成员会存在两份,即数据冗余和二义性问题。

class A {
public:
    int _a;
};

class B : public A{
public:
    int _b;
};

class C : public A {
public:
    int _c;
};

class D : public B, public C {
public:
    int _d;
};


int main() {
    D d;

    return 0;
}

调试结果

  • 想要直接通过d对象去修改_a是不被允许的,因为编译器不知道想要修改的是B类和C类中哪个类的_a
  • 只能通过d.B::a = 10这种指定类域的方式来修改或赋值。这种方式可以解决二义性问题,但无法解决数据冗余问题。

虚拟继承

  • 使用virtual关键字建立虚拟继承,可以解决数据冗余和二义性问题。
  • 当不使用虚拟继承时,内存空间是这样的。
class A {
public:
    int _a;
};

class B : public A{
public:
    int _b;
};

class C : public A {
public:
    int _c;
};

class D : public B, public C {
public:
    int _d;
};


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

    return 0;
}

内存图

  • 使用虚拟继承,_a就只存在一份,可以直接赋值。
class A {
public:
    int _a;
};

class B : virtual public A{
public:
    int _b;
};

class C : virtual public A {
public:
    int _c;
};

class D : public B, public C {
public:
    int _d;
};


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

    return 0;
}

内存结果

偏移

  • clang编译器通过BC的两个指针,指向一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存在偏移量。通过偏移量可以找到A

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

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

相关文章

CVPR 最佳学生论文,一键启动「BioCLIP 生物分类的层次预测 Demo」,帮你快速识别生物种类

很多生物由于外形的相似程度较高&#xff0c;难以使用肉眼进行区分。美国俄亥俄州立大学、微软研究院、加州大学欧文分校、伦斯勒理工学院共同发布了「BioCLlP: A Vision Foundation Model for the Tree of Life」&#xff0c; 基于 TREEOFLIFE-10M 这样的大规模标记数据集&…

海参海胆数据集:探索现实世界水下图像增强的创新之旅(目标检测)

亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 在当今…

【触想智能】工业平板电脑在新能源领域上的应用分析

工业平板电脑是一种具有高性能和稳定性的计算机设备&#xff0c;适用于在恶劣环境下进行数据采集、运营管理和现场操作。 随着新能源技术的快速发展&#xff0c;工业平板电脑不断地得到应用&#xff0c;并且已成为新能源领域中的重要工具之一。本文将从四个方面探讨工业平板电脑…

springboot项目jar包修改数据库配置运行时异常

一、背景 我将软件成功打好jar包了&#xff0c;到部署的时候发现jar包中数据库配置写的有问题&#xff0c;不想再重新打包了&#xff0c;打算直接修改配置文件&#xff0c;结果修改配置后&#xff0c;再通过java -jar运行时就报错了。 二、问题描述 本地项目是springBoot项目…

短视频预算表:成都柏煜文化传媒有限公司

短视频预算表&#xff1a;精打细算&#xff0c;打造高质量视觉盛宴 在数字时代&#xff0c;短视频以其独特的魅力迅速占领了互联网内容的半壁江山&#xff0c;成为品牌宣传、文化传播乃至个人表达的重要载体。然而&#xff0c;每一个成功的短视频背后&#xff0c;都离不开一份…

试用笔记之-汇通来电显示软件

首先汇通来电显示软件下载 http://www.htsoft.com.cn/download/httelephone.rar

武汉星起航:引领跨境电商风潮,铸就繁荣新篇章

在风起云涌的跨境电商领域&#xff0c;武汉星起航凭借其深厚的行业经验和创新的运营模式&#xff0c;自2017年起便开始在亚马逊平台上崭露头角。这家拥有多家亚马逊自营店铺的企业&#xff0c;不仅积累了大量的实战经验&#xff0c;更为合作伙伴提供了宝贵的市场洞察和运营指导…

专题七:Spring源码之BeanDefinition

上一篇我们通过refresh方法中的第二个核心方法obtainBeanFactory&#xff0c;通过createBeanFacotry创建容Spring的初级容器&#xff0c;并定义了容器的两个核心参数是否允许循环引用和覆盖。现在容器有了&#xff0c;我们来看看容器里的第一个重要成员BeanDefinition。 进入lo…

基于星火大模型的群聊对话分角色要素提取挑战赛Task1笔记

基于星火大模型的群聊对话分角色要素提取挑战赛Task1笔记 跑通baseline 1、安装依赖 下载相应的数据库 !pip install --upgrade -q spark_ai_python2、配置导入 导入必要的包。 from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler from sparkai.core.messages…

EEPROM内部原理

A2, A1, A0是EEPROM的地址引脚&#xff0c;用于设置设备地址。它们的作用如下&#xff1a; 设备寻址&#xff1a; 这三个引脚允许在I2C总线上唯一地标识EEPROM芯片。通过不同的连接方式&#xff08;接高、接低或悬空&#xff09;&#xff0c;可以为同一类型的EEPROM芯片设置不同…

通过docker overlay2 目录名查找占用磁盘空间最大的容器名和容器ID

有时候经常会有个别容器占用磁盘空间特别大&#xff0c; 这个时候就需要通过docker overlay2 目录名查找占用磁盘空间最大的容器名和容器ID&#xff1a; 1、 首先进入到 /var/lib/docker/overlay2 目录下,查看谁占用的较多 [rootPPS-97-8-ALI-HD1H overlay2]# cd /var/lib/doc…

MySQL的Geometry数据处理之WKT方案

WKT全称是Well-Known Text。它是一种表达几何信息的字符串内容。 比如&#xff1a;点可以用WKT表示为POINT (3 3)&#xff1b;线可以用WKT表示为LINESTRING (1 1, 2 2)。 Mysql数据库可以存储一些几何类型数据&#xff0c;比如点、线、多边形等。这在一些基于地理信息的服务上…

【unity实战】使用旧输入系统Input Manager 写一个 2D 平台游戏玩家控制器——包括移动、跳跃、滑墙、蹬墙跳

最终效果 文章目录 最终效果素材下载人物环境 简单绘制环境角色移动跳跃视差和摄像机跟随效果奔跑动画切换跳跃动画&#xff0c;跳跃次数限制角色添加2d物理材质&#xff0c;防止角色粘在墙上如果角色移动时背景出现黑线条方法一方法二 墙壁滑行实现角色滑墙不可以通过移动离开…

opencascade AIS_InteractiveContext源码学习7 debug visualization

AIS_InteractiveContext 前言 交互上下文&#xff08;Interactive Context&#xff09;允许您在一个或多个视图器中管理交互对象的图形行为和选择。类方法使这一操作非常透明。需要记住的是&#xff0c;对于已经被交互上下文识别的交互对象&#xff0c;必须使用上下文方法进行…

PIRANA: Faster Multi-query PIR via Constant-weight Codes(论文解析)

一、介绍 匿踪查询&#xff1a;一个客户从服务器查询数据&#xff0c;并且服务器无法知晓查询内容。注意这里是保护查询安全&#xff0c;并不保护服务器数据安全。 主要贡献&#xff1a; 1.设计了一款更高速度的匿踪查询方案。 2.设计一款支持批量查询的匿踪查询方案。 3.匿踪…

分享一款Type C接口USB转2路485模块【带完整原理图】

大家好&#xff0c;我是『芯知识学堂』的SingleYork&#xff0c;今天给大家分享一款很实用的工具–基于Type C接口的USB转2路485模块。 这款模块主芯片采用南京沁恒的CH342F这款芯片&#xff0c;芯片特性如下&#xff1a; 该系列芯片有QFN24和ESSOP10 这2种封装&#xff0c;…

快速申请IP地址SSL证书指南

在当今数字化时代&#xff0c;网络安全已成为每个互联网用户和企业的首要关注点。为了保护在线数据传输的安全和隐私&#xff0c;使用SSL证书对网站进行加密变得至关重要。 IP地址SSL证书是一种用于专门保护IP地址的SSL证书。与域名SSL证书不同&#xff0c;IP地址SSL证书可以直…

TensorRT学习(二)TensorRT使用教程(Python版)

本文适合快速了解TensorRT使用的整体流程,具体细节还是建议参考TensorRT的官方文档。 加速原理: 加速原理比较复杂,它将会根据显卡来优化算子,以起到加速作用(如下图所示)。简单的来说,就是类似于你出一个公式1+1+1,而你的显卡支持乘法,直接给你把这个公式优化成了1*…

免费可视化工具如何提升智慧物流管理效率

在现代智慧物流中&#xff0c;免费可视化工具正扮演着越来越重要的角色。这些工具通过数据的可视化展示&#xff0c;使物流管理更加高效、透明和智能化。免费可视化工具可以将复杂的物流数据转换为直观的图表和图形&#xff0c;帮助管理者实时监控和分析物流运作情况&#xff0…

文件销毁是一件非常重要的事情分享一下我是如何安全、环保地处理

如何安全有效地销毁文件&#xff1a;一份详尽指南 在信息爆炸的时代&#xff0c;文件的生成、存储与处理已成为日常生活和工作中不可或缺的一部分。然而&#xff0c;随着数据量的激增&#xff0c;如何妥善管理并最终安全销毁不再需要的文件&#xff0c;成为了一个日益重要的议…