【C++ 学习 ⑰】- 继承(下)

news2024/11/16 1:55:35

目录

一、派生类的默认成员函数

二、继承与友元

三、继承与静态成员

四、复杂的菱形继承及菱形虚拟继承

五、继承和组合


 


一、派生类的默认成员函数

  1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数,那么必须在派生类的构造函数的初始化列表中显示调用基类的构造函数。

  2. 派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的那一部分成员的拷贝初始化。

  3. 派生类的 operator= 必须调用基类的 operator= 完成基类的那一部分成员的赋值。

  4. 派生类的析构函数在被调用完后,会自动调用基类的析构函数清理基类的那一部分成员,即在派生类的析构函数中不用显示地调用基类的析构函数。

  5. 在创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。

  6. 在销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数。

#include <iostream>
using namespace std;
​
class Person
{
public:
    Person(const char* name = "张三", int age = 18)
        : _name(name), _age(age)
    {
        cout << "Person::default constructor" << endl;
    }
​
    Person(const Person& p)
        : _name(p._name), _age(p._age)
    {
        cout << "Person(const Person& p)" << endl;
    }
​
    Person& operator=(const Person& p)
    {
        cout << "Person& operator=(const Person p)" << endl;
        if (this != &p)
        {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }
​
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name;  // 姓名
    int _age;  // 年龄
};
​
class Student : public Person
{
public:
    Student(const char* name = "张三", int age = 18, int stu_id = 0)
        : Person(name, age), _stu_id(stu_id)
    {
        cout << "Student::default constructor" << endl;
    }
​
    Student(const Student& s)
        : Person(s), _stu_id(s._stu_id)
    {
        cout << "Student(const Student& s)" << endl;
    }
​
    Student& operator=(const Student& s)
    {
        cout << "Student& operator=(const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s);  // operator=(s); 会陷入死循环
            _stu_id = s._stu_id;
        }
        return *this;
    }
​
    ~Student()
    {
        cout << "~Student()" << endl;
    }
protected:
    int _stu_id;  // 学号
};
​
int main()
{
    Student s1("李四", 20, 1);
    // Person::default constructor
    // Student::default constructor
​
    Student s2(s1);
    // Person(const Person& p)
    // Student(const Student & s)
​
    Student s3;
    // Person::default constructor
    // Student::default constructor
​
    s3 = s1;
    // Student& operator=(const Student& s)
    // Person& operator=(const Person p)
​
    // ~Student()
    // ~Person()
    // ~Student()
    // ~Person()
    // ~Student()
    // ~Person()
    return 0;
}


二、继承与友元

友元关系不能继承,即基类友元不能访问子类私有和保护成员

#include <iostream>
using namespace std;
​
class Student;
class Person
{
    friend void Print(const Person& p, const Student& s);
protected:
    string _name = "张三";
    int _age = 18;
};
​
class Student : public Person
{
    // 必须声明,否则会报错
    friend void Print(const Person& p, const Student& s);  
protected:
    int _stu_id = 0;
};
​
void Print(const Person& p, const Student& s)
{
    cout << p._name << " " << p._age << endl;
    cout << s._stu_id << endl;
}
​
int main()
{
    Person p;
    Student s;
    Print(p, s);
    return 0;
}


三、继承与静态成员

如果基类定义了 static 静态成员,无论派生出了多少个类,在整个继承体系中都只有一个 static 静态成员实例

#include <iostream>
using namespace std;
​
class Person
{
public:
    Person() { ++_count; }
protected:
    string _name;
    int _age;
public:
    static int _count;  // static 静态成员变量
};
​
int Person::_count = 0;
​
class Student : public Person
{
protected:
    int _stu_id;
};
​
class Graduate : public Student
{
protected:
    string _seminarCourse;  // 研究科目
};
​
int main()
{
    cout << &Person::_count << " " << &Student::_count
        << " " << &Graduate::_count << endl;
    // 输出的三个地址相同
    Person p1;
    Student s1;
    Student s2;
    Graduate g1;
    Graduate g2;
    Graduate g3;
    cout << Person::_count << endl;  
    // 6
    return 0;
}


四、复杂的菱形继承及菱形虚拟继承

单继承:一个派生类只有一个直接基类时,称这种继承关系为单继承。

多继承:一个派生类有两个或两个以上直接基类时,称这种继承关系为多继承。

菱形继承是多继承的一种特殊情况,它会造成数据冗余和二义性的问题,例如

#include <iostream>
using namespace std;
​
// 间接基类 A
class A
{
public:
    int _a;
};
​
// 直接基类 B
class B : public A
{
public:
    int _b;
};
​
// 直接基类 C
class C : public A
{
public:
    int _c;
};
​
// 派生类 D
class D : public B, public C
{
public:
    int _d;
};
​
int main()
{
    D d;
    // 为了消除歧义,必须在 _a 前面指明它具体来自哪个类
    d.B::_a = 0;
    d.C::_a = 1;
    d._b = 2;
    d._c = 3;
    d._d = 4;
    return 0;
}

为了解决菱形继承中的问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员

在继承方式前面加上 virtual 关键字就是虚继承,例如

#include <iostream>
using namespace std;
​
// 间接基类 A
class A
{
public:
    int _a;
};
​
// 直接基类 B
class B : virtual public A  // 虚继承
{
public:
    int _b;
};
​
// 直接基类 C
class C : virtual public A  // 虚继承
{
public:
    int _c;
};
​
// 派生类 D
class D : public B, public C
{
public:
    int _d;
};
​
int main()
{
    D d;
    d._a = 1;  // ok
    d._b = 2;
    d._c = 3;
    d._d = 4;
    return 0;
}

虚继承底层实现原理与编译器相关,一般是通过虚基类指针和虚基类类表实现的

每个虚继承的子类都有一个虚基类指针,该指针指向一个虚基类表,表中记录了虚基类和本类的偏移量,通过这个偏移量就可以找到虚基类成员

当虚继承的子类被当作父类继承时,虚基类指针也会被继承


五、继承和组合

面向对象系统中功能复用的两种最常用技术是类继承对象组合(object composition)

类继承允许你根据基类的实现定义派生类的实现,这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语 "白箱" 是相对可视性而言的:在继承方式中,基类的内部细节对派生类可见。继承一定程度上破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类的依赖关系很强,耦合度高

对象组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(white-box reuse),因为对象的内部细节是不可见的,对象只能以 "黑箱" 的形式出现。组合类之间没有很强的依赖关系,耦合度低

因此在实际中,尽量多使用组合。不过继承也有用武之地的:public 继承是一种 is-a 的关系,即每个派生类对象都是一个基类对象,组合是一种 has-a 的关系,即假设 B 组合了 A,那么每个 B 类对象都有一个 A 类对象,而有些关系就适合用继承;另外要实现多态,也必须要用继承

例一

class Car
{
protected:
    string _color;  // 颜色
    string _num;  // 车牌号
};
​
class AITO : public Car
{
public:
    void Describe() const { cout << "Intelligent" << endl; }
};
​
class AVATR : public Car
{
public:
    void Describe() const { cout << "luxurious" << endl; }
};

AITO、AVATR 和 Car 构成 is-a 的关系

例二

class Tire
{
protected:
    string _brand;  // 品牌
    size_t _size;  // 尺寸
};
​
class Car
{
protected:
    string _color;
    string _num;
    Tire _t;
};

Car 和 Tire 构成 has-a 的关系

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

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

相关文章

spring框架:简介+依赖注入

目录 一、spring简介 二、创建项目 三、spring创建对象 四、SpringBean管理 1.注入实现-XML 2.注入实现-注解 一、spring简介 spring诞生与2003年&#xff0c;是一个轻量级的、IOC( Inversion Of Control 控制反转)和AOP(Aspect Oriented Programming 面向切面编程)的jav…

linux挂载内网镜像源文件,支持yum安装

cd /etc/yum.repos.d/vim kylin_aarch64.repo 重建yum缓存 yum clean allyum makecacheyum repolist

【vue】实现高性能虚拟滚动的Vue代码解析

在前端开发中&#xff0c;当需要展示大量数据时&#xff0c;如何保持页面的流畅性是一个挑战。传统的滚动方式会将所有数据一次性渲染到页面&#xff0c;这可能导致页面加载缓慢甚至崩溃。而虚拟滚动技术能够解决这个问题&#xff0c;它只渲染可视区域内的数据&#xff0c;从而…

Vue2向Vue3过度Vuex核心概念actions

目录 1 核心概念 - actions1.定义actions2.组件中通过dispatch调用 2 辅助函数 -mapActions 1 核心概念 - actions state是存放数据的&#xff0c;mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化)&#xff0c; actions则负责进行异步操作 说…

用Python写一个武侠游戏

前言 在本教程中&#xff0c;我们将使用Python写一个武侠类的游戏&#xff0c;大的框架全部搭好了&#xff0c;很多元素都可以自己添加&#xff0c;让游戏更丰富 &#x1f4dd;个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列&#xff1a; ☄️爬虫JS逆向系列专栏 -…

PHP自己的框架cookie()使用(完善篇七)

1、PHP自己的框架cookie() 2、cookie类&#xff08;CookieBase.php&#xff09; <?php class CookieBase {/*** 设置cookie*/public static function set($name, $value, $expire 3600, $path , $domain , $secure false, $httponly false) {setcookie($name, $valu…

Verilog 实现超声波测距

Verilog 实现超声波测距 教学视频&#xff1a; https://www.bilibili.com/video/BV1Ve411x75W?p33&spm_id_frompageDriver&vd_source19ae31dff4056e52d2729a4ca212602b 超声波测距原理 参考资料&#xff1a;STM32的超声波测距程序_超声波测距stm32程序_VaderZhang的…

cs231n assignment3 q5 Self-Supervised Learning for Image Classification

文章目录 嫌墨迹直接看代码Q5 Self-Supervised Learning for Image Classificationcompute_train_transform CIFAR10Pair.__getitem__()题面解析代码输出 simclr_loss_naive题面解析代码输出 sim_positive_pairs题面解析代码输出 compute_sim_matrix题面解析代码输出 simclr_lo…

37、springboot 为 spring mvc 提供的自动配置及对自动配置的一些自定义定制(大体思路)

springboot 为 spring mvc 提供的自动配置及对自动配置的一些自定义定制&#xff08;大体思路&#xff09; ★ Spring Boot主流支持两个MVC框架&#xff1a; Spring MVC&#xff08;基于Servlet&#xff09; Spring WebFlux&#xff08;基于Reactive&#xff0c;属于响应式AP…

开源双语对话语言模型 ChatGLM-6B 本地私有化部署

本文首发于&#xff1a;https://www.licorne.ink/2023/08/llm-chatglm-6b-local-deploy/ ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#xff0c;用户可以在消费级…

人员跌倒检测识别预警

人员跌倒检测识别预警系统通过pythonopencv深度学习网络模型架构&#xff0c;人员跌倒检测识别预警系统实时监测老人的活动状态&#xff0c;通过图像识别和行为分析算法&#xff0c;对老人的姿态、步态等进行检测和识别&#xff0c;一旦系统检测到跌倒事件&#xff0c;立即发出…

Vue2向Vue3过度Vuex核心概念state状态

目录 1 核心概念 - state 状态1.目标2.提供数据3.访问Vuex中的数据4.通过$store访问的语法5.代码实现5.1模板中使用5.2组件逻辑中使用5.3 js文件中使用 2 通过辅助函数 - mapState获取 state中的数据1.第一步&#xff1a;导入mapState (mapState是vuex中的一个函数)2.第二步&am…

Dubbo—流量管控

此任务基于一个简单的线上商城微服务系统演示了 Dubbo 的流量管控能力。 线上商城的架构图如下&#xff1a; 系统由 5 个微服务应用组成&#xff1a; Frontend 商城主页&#xff0c;作为与用户交互的 web 界面&#xff0c;通过调用 User、Detail、Order 等提供用户登录、商品…

为什么使用Nacos而不是Eureka(Nacos和Eureka的区别)

文章目录 前言一、Eureka是什么&#xff1f;二、Nacos是什么&#xff1f;三、Nacos和Eureka的区别3.1 支持的CAP3.2连接方式3.3 服务异常剔除3.4 操作实例方式 总结 前言 为什么如今微服务注册中心用Nacos相对比用Eureka的多了&#xff1f;本文章将介绍他们之间的区别和优缺点…

推荐前 6 名 JavaScript 和 HTML5 游戏引擎

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建3D应用场景 事实是&#xff0c;自从引入JavaScript WebGL API以来&#xff0c;现代浏览器具有直观的功能&#xff0c;使它们能够渲染更复杂和复杂的2D和3D图形&#xff0c;而无需依赖第三方插件。 你可以用纯粹的JavaScript开…

Nuxt3打包部署到Linux(node+pm2安装和运行步骤+nginx代理)

最近&#xff0c;我们项目组的工作接近尾声&#xff0c;需要把项目部署上线。由于前端第一次使用Nuxt3框架&#xff0c;后端也是第一次部署Nuxt3项目&#xff0c;所以刚开始出现了很多问题。在我上网搜索很多教程后&#xff0c;得到了基本的流程。 1.服务器安装node.js环境 N…

Linux常用命令_文件搜索命令

文章目录 1. 文件搜索命令find2. 其他搜索命令2.1 文件搜索命令&#xff1a;locate2.2 文件搜索命令&#xff1a;which2.3 文件搜索命令&#xff1a;whereis2.4 文件搜索命令&#xff1a;grep 1. 文件搜索命令find 2. 其他搜索命令 2.1 文件搜索命令&#xff1a;locate 作为f…

c语言练习题30:判断一个数是否为2^n

判断一个数是否为2^n 思路&#xff1a;2^n中只有一个1故可以通过n&(n-1)是否为0来判断。 代码&#xff1a;

Android 之 WindowManager (窗口管理服务)

本节引言&#xff1a; 本节给大家带来的Android给我们提供的系统服务中的——WindowManager(窗口管理服务)&#xff0c; 它是显示View的最底层&#xff0c;Toast&#xff0c;Activity&#xff0c;Dialog的底层都用到了这个WindowManager&#xff0c; 他是全局的&#xff01;该类…

用MFC打开外部程序

在MFC&#xff08;Microsoft Foundation Classes&#xff09;中&#xff0c;你可以使用ShellExecute函数来打开Notepad并加载指定的文件。ShellExecute函数是Windows API的一部分&#xff0c;它可以执行与操作系统相关的操作&#xff0c;例如打开文件、运行程序等。 以下是在M…