C++启蒙笔记(八)---类继承、动态内存分配

2023-11-20

一、基本概念

  • 代码复用:继承的基本功能,封装起来的基类稳定性、安全性和效率都较高

1.1派生类

  • 派生类:继承了基类的数据成员和成员函数,派生类中只需写新增的数据成员和新增的成员函数即可
  • 派生类构造函数:
    • a、先调用基类构造函数,然后调用派生类构造函数
    • b、若未显式调用基类构造函数,则隐式调用
  • 派生类析构函数:先调用派生类的析构函数,然后调用基类的析构函数

1.2 继承关系

  • is-a关系
    • 全称:is-a-kind-of
    • 单向性:person包含所有singer,但singer不包含所有person
    • 扩展性:派生类可在基类基础上添加属性,但不可删减
  • has-a关系
    • 例如:meal包含fruit,但是不能从meal派生出fruit
    • 处理:将fruit类作为meal的一个数据成员

二、常规写法

  • 合并写法:可以把以下三个放到一个文件里
  • 头文件写法:分成三个文件,头文件、类实现文件、主程序文件,三者找地方放

2.1 头文件

  • person.h
    // 这个PERSON_H_是自己命名的,不必跟文件名一致
    #ifndef PERSON_H_
    #define PERSON_H_
    // 以下内容会被原封不懂的插入#include "person.h"语句处
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类
    class Person
    {
    private:
        string _name;
        int _age;
        // 静态数据成员:见下注释,记录类创建了多少个
        static int count;
    
    public:
    	// const静态数据成员:见下注释,记录创建对象的基类名字
    	static const string  title;
    	// 常规构造函数,接收0、1、2个参数
        Person(const string &name = "未命名", int age = 0);
        ~Person();
        void show() const;
    };
    
    // 派生类:指明基类是Person,继承方式为public,
    //    可访问public,protected,不可访问private
    class Singer : public Person
    {
    private:
        string _style;
    
    public:
        Singer(const string &name = "未命名", int age = 0,
               const string &style = "无风格");
        void show() const;
    };
    #endif
    
    • 静态数据成员:
      • 功能:作为声明变量的类所创建的对象共享变量
      • 声明及初始化:类声明中声明,在类方法文件中初始化,防止多次引入头文件,导致的多次初始化
    • 静态const数据成员
      • 声明及初始化:同静态数据成员
      • 功能:创建的常量生命周期跟类同长,避免频繁创建销毁

2.2 类实现

  • person.cpp
    // 注意路径的写法,同级目录可只写文件名
    #include "./person.h"
    
    // 基类中静态成员初始化
    int Person::count = 0;
    // 基类中const静态成员初始化
    const string Person::title = "Person类";
    
    // 基类的成员函数定义
    // 类构造函数:分号后为成员初始化列表形式,只能用于构造函数
    //           分号前括号内为形参,分号后_name(name),意义同_name=name;
    Person::Person(const string &name, 
    			   int age) : _name(name), _age(age) { count += 1; }
    ~Person(){ count -= 1; };
    // 基类的常规成员函数
    void Person::show() const
    {
        cout << "姓名是:" << _name << endl;
        cout << "年龄是:" << _age << endl;
        cout << "当前Person个数:"<< count << endl;
    }
    
    // 派生类的成员函数定义
    // 派生类构造函数:成员初始化列表,且调用了基类的构造函数
    Singer::Singer(const string &name, int age,
                   const string &style) : Person(name, age), _style(style){};
    // 派生类的常规成员函数
    void Singer::show() const
    {	// 派生类中调用基类的成员函数方法
        Person::show();
        cout << "风格为:" << _style << endl;
        // 生成了15个*号
        string s(15, '*');
        cout << s << endl;
    }
    

2.3 主程序

  • main.cpp
    // 注意路径的写法,同级目录可只写文件名
    #include "./person.h"
    
    int main()
    {	
    	// 类数组列表初始化
        Singer singer[3] = {Singer("小明", 15, "hip-hop"),
                            Singer("小李", 13, "blue")};
        singer[0].show();
        singer[1].show();
        // 这里调用的是基类Person的show函数
        singer[2].Person::show();
    
    	// 调用默认构造函数,生成一个对象,count增加1
    	Singer temp;
    	temp.show();
    }
    

2.4 编译及显示

  • 编译命令
    >> g++ main.cpp person.cpp person.h
    >> ./a.out
    
  • 显示
    姓名是:小明
    年龄是:15
    当前Person个数:3
    风格为:hip-hop
    ***************
    姓名是:小李
    年龄是:13
    当前Person个数:3
    风格为:blue
    ***************
    姓名是:未命名
    年龄是:0
    当前Person个数:3
    姓名是:未命名
    年龄是:0
    当前Person个数:4
    风格为:无风格
    

三、多态公有继承

  • 名词释义

    名称 释义
    多态 即具有多种形态,同一方法,在派生类和基类中的行为是不同的
    多态用途 函数虚实参传递时可用这一特性,实参为派生类对象(singer)或地址(&singer),虚参为基类绑定(&rp)或指针(*pp)
    重载问题 若有基类有重载声明,派生类不可单独重载其中一个,需全部重写
    派生类不可继承函数 构造、析构、=(用目标对象修改已初始化对象) 函数
    能默认生成函数 构造(空的)、析构(空的)、=(默认逐数据成员项复制,遇到指针浅浅复制)、& 函数
  • 多态调用指向(见2.3.1代码)

    • 合法指向:基类的指针或绑定可以指向派生类,通过等号右侧(指针或绑定指向的对象)的对象决定调用哪个类的方法
    • 限制指向:派生类指针或绑定不可以指向基类,除非显式的定义了构造函数派生类(&基类)

3.1 虚方法

  • 功能:若要在派生类中重新定义基类的方法,将它设置为虚方法,否则为非虚方法(效率高相对高)
  • 头文件person.h
    ...
    
    // 基类
    class Person
    {
    private:
        ...
    
    public:
    	...
    	// 只在基类增加virtual关键字即可
        virtual void show() const;
    	...
    	// 虚析构函数:基类内声明,规范析构函数调用顺序,
    	// 先调用派生类析构函数,再调用基类析构函数
    	// 若无此句,在main.cpp中,a、b完成后将只调用基类的析构函数
    	virtual ~Person(){}
    };
    
    class Singer : public Person
    {
    ...
    }
    ...
    #endif
    
  • 代码实现main.cpp
    #include "person.h"
    
    int main()
    {
        Singer singer("小明", 15, "hip-hop");
    
    	// 以下三个都是调用派生类的show函数,若person.h中无virtual
    	// 以下前两个调用的是基类的show函数,最后个还是派生类的show函数
    	// 注意体会将其应用在函数虚实参传递中的效果
    
        // a、基类引用可以绑定派生类
        Person &rp = singer;
        rp.show();
    
        // b、基类指针可以指向派生类
        Person *pp = &singer;
        pp->show();
    	
        singer.show();
    }
    

3.2 抽象基类

  • 定义:抽象基类(abstract base class,ABC),提取两个类的共性,形成抽象基类
  • 纯虚函数:只可在ABC中创建,且ABC中至少包含一个
  • 特点:不可创建ABC的对象,可应用虚函数技术指向派生类
  • 代码示例
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    // 基类:构造、析构、复制函数均采用默认
    class Person
    {
    public:
    	// 纯虚函数:基类中,函数后加=0即可生成纯虚函数,同时将基类转换成ABC
    	// 允许:纯虚函数是否定义都可,=0也可以应用在非虚函数上
        virtual void show() const = 0;
        // 虚析构函数:规范析构函数调用顺序
        virtual ~Person(){};
    };
    
    // 派生类Singer:构造、析构、复制函数均采用默认
    class Singer : public Person
    {
    public:
    	// 派生类必须实现纯虚函数,否则报错
        void show() const { cout << "这是singer类" << endl; };
    };
    
    // 派生类Actor:构造、析构、复制函数均采用默认
    class Actor : public Person
    {
    public:
    	// 派生类必须实现纯虚函数,否则报错
        void show() const { cout << "这是actor类" << endl; };
    };
    
    // 主函数
    int main()
    {
        // ABC类指针数组:后续用于指向派生类
        Person *p[3];
        Singer singer;
        Actor actor;
    
        p[0] = &singer;
        // 输出:这是singer类
        p[0]->show();
    
        p[1] = &actor;
        // 输出:这是actor类
        p[1]->show();
    
        // 编译错误:ABC不可创建对象
        // p[2]->show();
    }
    

3.3 多重继承MI

  • 定义:能够使用两个或多个基类派生出新的类,组合功能

  • 虚基类:使得从多个类(他们基类相同)派生出的对象只继承一个基类对象,防止二义性

  • 构造函数规则:对虚基类,除默认构造函数,否则必须显式调用虚基类的某个构造函数


  • 继承关系示例

  • 代码示例

    #include <iostream>
    
    using namespace std;
    
    class B{
    public:
        void show(){cout<<"B"<<endl;};
    };
    
    // 指定B为虚基类
    class C:virtual public B{
    public:
        void show(){cout<<"C"<<endl;};
        void cal(){cout<<"C"<<endl;};
    };
    
    class D:public C{};
    
    // 指定B为虚基类
    class E:virtual public B{
    public:
        void cal(){cout<<"E"<<endl;};
    };
    
    class F:public D,public E{};
    
    int main() {
        F m;
        // 用的是类C的
        m.show();
        // m.cal()会产生二义性,
        // 最好在类F中重写此方法
        m.D::cal();
        return 0;
    }
    

四、动态内存分配

  • 静态内存分配:程序启动瞬间,变量即被分配了内存空间,直到程序退出销毁
  • 动态内存分配:程序运行期间,根据需要,动态分配内存空间,期间动态分配、销毁

4.1 头文件

  • person.h
    #ifndef PERSON_H_
    #define PERSON_H_
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Person
    {
    private:
        // 动态内存分配的核心:此处只声明指针,未分配内存
        char *_name;
        // 用于存储字符串长度,不包含结尾\0
        int len;
    
    public:
        // 默认构造函数
        Person();
        // 重载默认构造函数
        Person(const char *name);
        // 复制构造函数
        Person(const Person &p);
        // 析构函数
        ~Person();
    
        // 常规函数:显示对象名
        void show() const;
    
        // 重载赋值运算符=
        Person &operator=(const Person &p);
    };
    #endif
    

4.2 类实现

  • person.cpp
    #include "./person.h"
    
    Person::Person()
    {
        // 赋值静态数据成员,见2.1节
        static const char *s = "未命名";
        // 字符串长度:传入字符串指针
        len = strlen(s);
        // new分配内存空间,此处用[],对应于析构函数
        _name = new char[len + 1];
        // 字符串深拷贝
        strcpy(_name, s);
        cout << "默认构造函数被调用" << endl;
    };
    
    Person::Person(const char *name)
    {
        // 同构造函数
        len = strlen(name);
        _name = new char[len + 1];
        strcpy(_name, name);
        cout << _name << "的构造函数被调用" << endl;
    };
    
    Person::Person(const Person &p)
    {
        this->len = p.len;
        _name = new char[len + 1];
        strcpy(_name, p._name);
        cout << _name << "的复制构造函数被调用" << endl;
    };
    
    Person::~Person()
    {
        // 析构函数对应与构造函数
        delete[] _name;
        cout << _name << "的析构函数被调用" << endl;
    };
    
    Person &Person::operator=(const Person &p)
    {
        // 检查是否为自我复制,若是且无此判断语句
        // 则delete语句会先清空内存导致无值可赋
        if (this == &p)
            return *this;
        // 若非自我复制,清空_name指向的内存
        delete[] _name;
        // 以下同构造函数写法
        len = strlen(p._name);
        _name = new char[len + 1];
        strcpy(_name, p._name);
        cout << _name << "赋值运算符被调用" << endl;
        return *this;
    };
    
    void Person::show() const
    {
        cout << "姓名是:" << _name << endl;
    };
    
    

4.3 主程序

  • main.cpp
    // 注意路径的写法,同级目录可只写文件名
    #include "./person.h"
    
    int main()
    {
        // 复习下:在堆中创建对象,per?是指向对象Person的指针
    
        // a、调用默认构造函数
        Person *per1 = new Person;
        // b、调用重载的默认构造函数
        Person *per2 = new Person("per2");
        // c、调用复制构造函数:注意传入的是*per2即Person对象
        Person *per3 = new Person(*per2);
    
        // 指针调用类方法的写法
        per1->show();
        per2->show();
        per3->show();
    
        // 此处调用重载的赋值运算符=
        *per3 = *per1;
    
        // 销毁类对象指针指向的内存空间,
        // 即调用Person对象的析构函数
        delete per1;
        delete per2;
        delete per3;
    }
    

4.4 显示

  • 显示结果
    默认构造函数被调用
    per2的构造函数被调用
    per2的复制构造函数被调用
    姓名是:未命名
    姓名是:per2
    姓名是:per2
    未命名赋值运算符被调用
    未命名的析构函数被调用
    per2的析构函数被调用
    未命名的析构函数被调用
    

上一篇:C++启蒙笔记(七)—类运算符重载
综合专题列表

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

C++启蒙笔记(八)---类继承、动态内存分配 的相关文章

随机推荐

  • 使用命令启动默认程序(例如启动系统默认浏览器打开指定网址)

    文章目录 目的 基础说明 代码示例 Golang 总结 目的 通过命令调用系统默认应用程序打开对应格式的文件是比较常用的功能 这篇文章将介绍下相关内容 基础说明 Windows windows下可以使用 start 指令来启动默认程序打开对
  • 数据结构——广度优先遍历(BFS)无向连通图

    以下是数据结构中关于广度优先遍历无向连通图的操作 编程风格参考严蔚敏版数据结构 其实深度优先遍历就是二叉树的层次遍历的推广 头文件及宏 include
  • Python----利用Threading和Queue实现多线程

    用来学习Threading Queue的组合使用 实现多线程编程 实现功能 利用 ping 来检测主机是否存活 代码如下 coding utf 8 from IPy import IP from subprocess import Pope
  • 2022年 大学生工程训练比赛[物料搬运]

    本人和团结参加了2022年大学生工程训练 简称工训赛 校赛选拔 准备了几个月的时间和花费了较多的资金 由于疫情等多种情况 很遗憾未能参加湖南省省赛 过了这么久还是写个博客记录参赛准备和调试过程 目录 一 比赛要求 二 整体思路 三 硬件模块
  • 所有OLE接口

    比较有用 记录下来供查阅 常规 函数 lUnknown 目的 控制的接口协商的对象生存期 普遍存在的任何组件 而不考虑实现 QueryInterface 公开传入的接口 函数 IEnum 目的 枚举的各种类型的列表 在许多情况下 整个 OL
  • 计算机扫描的文件保存在哪,电脑教程:文件扫描后自动保存哪里去了

    科技本身 支配宇宙的自然规律是充满魅力的 因此越来越多的人开始关注科技的相关动态 近来文件扫描后自动保存哪里去了的消息也是引起了很多人的关注 那么既然现在大家都想要知道文件扫描后自动保存哪里去了 小编今天就来给大家针对文件扫描后自动保存哪里
  • 关于 运算符号 &(与运算)、

    1 与运算 在二进制中 运算规则 0 0 0 0 1 0 1 0 0 1 1 1 类比到十进制 例如 3和4 首先化成二进制 就是 011 和 100 再进行相同位上的与运算 就是 000 最后就是0 因为是 运算符号 所以返回的是int
  • Ffmpeg视频开发教程(七)——基于ffmpeg4.0生成模拟yuv数据和模拟音频数据再合成为mp4文件

    本文主要实现使用最新版的ffmpeg生成模拟yuv数据和模拟音频数据再合成为mp4文件 重要代码都是来自官方 稳定性可靠 本文程序的环境搭建参考我的第一篇FFMPEG教程 https blog csdn net zhangamxqun ar
  • ENSP—NAT综合实验

    实验要求 1 IP地址的规划和拓扑搭建 2 IP地址的配置 AR1的代码如下 r1 interface g0 0 1 r1 GigabitEthernet0 0 1 ip add 12 1 1 1 24 r1 GigabitEthernet
  • 服务器虚拟化的优势

    1 提高硬件资源使用效率 一个服务器上可以开多个虚拟机 给不同应用使用 打破一个应用一台服务器的限制 因为某具体用户使用的时间 资源有限 多个用户 应用 就可以大大提高服务器的使用效率 减少服务器数量 可以 降低购买服务器的投资 降低服务器
  • C++(四)——C++标准模板库

    文章目录 1 STL组件 Component 2 容器 Container 2 1 序列式容器 Sequence Container 2 2 关联式容器 Associative Container 2 3 无序容器 Unordered Co
  • 用matlab绘制系统函数的DTFT

    freqz函数 frequency response of digital filter 对于一个输入离散序列 输出离散序列的离散时间系统 我们可以用它的系统函数H Z 来描述这个系统 求这个系统函数的DTFT 可以得到这个系统的幅频响应和
  • logback-spring.xml中三种相对路径生成的日志文件的位置

    logback spring xml中关于路径配置的三种写法 写法1
  • 大屏图表,ECharts 从“熟练”到入门

    阅读本文 你将 了解 配置驱动 的思想 理解 Echarts 基本概念 了解 graphic 和 动画基本玩法 了解 Echarts 基底组件的封装的思路 一 不是标题党 Echarts 简历上人均 熟练 公司最近在招外包 而因为目前大屏的
  • java自动识别文件编码格式UTF-8,UTF-8无BOM,GBK

    背景 在解读properties配置文件时windows操作系统编辑过的内容上传后总是无法通过键获取文件中内容 讲过分析是文件的编码格式为UTF 8带BOM的 因此通过该程序获取文件编码格式 import java io BufferedI
  • ES6阮一峰入门教程

    地址为 https es6 ruanyifeng com
  • visual studio 一直显示正在准备解决方案

    首先重启电脑 无法解决的情况下执行以下步骤 Kill Visual Studio Open Visual Studio without loading a solution Disable AnkhSvn as Source Control
  • vue动态绑定video视频src问题解决

    做个项目 视频部分需要先后台上传 然后前端页面显示 然后就遇到了视频动态获取地址的问题 一开始想着很简单 使用v model双向绑定就行了 结果试了下并不行 后面开始度娘 尝试过很多人说的 refs解决 结果并不行 虽然浏览器中看地址确实绑
  • 设计模式(2)

    2 2 结构型模式 结构型模式一共有七种 其中 适配器模式和装饰模式统称为包装模式 装饰模式和代理模式的类图基本相同 但目的不同 这些有相似目的或者有相似结构的模式需要对其概念辨析清楚 才能较好地掌握 下面将对结构型模式分别进行介绍 2 2
  • C++启蒙笔记(八)---类继承、动态内存分配

    目录 一 基本概念 1 1派生类 1 2 继承关系 二 常规写法 2 1 头文件 2 2 类实现 2 3 主程序 2 4 编译及显示 三 多态公有继承 3 1 虚方法 3 2 抽象基类 3 3 多重继承MI 四 动态内存分配 4 1 头文件