11. 类的继承

news2024/12/28 13:59:06

一、为什么要用继承

一个简化的Student类

class Student {
private:
    string name;
    string studentID;
public:
    string getName(){ return name; }
    void setName(string newName) { name = newName; }
    string getStudentID(){ return studentID; }
    void setStudentID(string newID) { studentID = newID; }

};

例:

在建模的过程中需要产生新类:研究生类,这个类是学生类的一个特殊类型:

  • 一个研究生在进入研究生学习之前需要获得什么学位?
  • 这个学生是从什么学院获得的学士学位?

方案一:修改学生类

class Student {
private:
    string name;
    string studentID;
    
    // 新增
    string undergraduateDegree;
    string undergraduateInstitution;
    bool isGraduateStudent;
    
public:
    string getName(){ return name; }
    void setName(string newName) { name = newName; }
    string getStudentID(){ return studentID; }
    void setStudentID(string newID) { studentID = newID; }
    void displayAllAttributes();
};

void Student::displayAllAttributes(){
    cout << "姓名 = " << name << "学号 = " << studentID << endl;
    if(isGraduateStudent) {
        cout << "毕业院校 = " << undergraduateDegree;
        cout << "毕业院校 = " << undergraduateInstitution;
    }
}

问题:

  • 学生种类很多的时候,代码会变得冗长
  • 每新增一个学生类,就要修改添加很多代码
  • 难以编写和维护

方案二:克隆学生类产生新类

  • 将上述Student类克隆出一个GraduateStudent类
class GraduateStudent {
private:
    string name;
    string studentID;
    
    string undergraduateDegree;
    string undergraduateInstitution;
    ...

问题:

  • 这是一个非常不好的设计,因为在Student类和GraduateStudent类中包含了太多相同的代码。若将来想修改其中的一个类的属性,则必须两个类同时进行维护

正确的方案:利用继承

  • 在研究生类中不必复制学生类中任何属性,因为研究生类已经自动的继承学生类中的属性
class Student {
private:
    string name;
    string studentID;
public:
    string getName(){ return name; }
    void setName(string newName) { name = newName; }
    string getStudentID(){ return studentID; }
    void setStudentID(string newID) { studentID = newID; }
};

// 继承
class GraduateStudent:public Student {
private:
    string undergraduateDegree;
    string undergraduateInstitution;
...
}
image-20220607105950063

二、类的继承与派生

  1. 继承和派生是同一过程从不同的角度来看:

    • 继承保持已有类的特性而构造新类 的过程
    • 派生在已有类的基础上新增自己的特性而产生新类 的过程
  2. 基类(父类):被继承的已有类

    • 直接基类:直接参与派生出某类的基类
    • 间接基类:基类的基类甚至跟高层的基类
  3. 派生类(子类):派生出的新类

  4. 语法:

    class 派生类 :继承访问控制 基类 {
    public:
        公有成员列表
    protected:
        受保护成员列表
    private:
        私有成员列表    
    };
    

示例:

// 基类Point类的定义
class Point {
private:
    float x, y;
public:
    void initPoint (float = 0, float y = 0){
        this->x = x;
        this->y = y;
    }
    void move(float offX, float offY){
        x += offX;
        y += offY;
    }
    float getX() { return x; }
    float getY() { return y; }
};

// 派生类定义
class Rectangle: public Point {
private:
    float width, height;
public:
    void initRectangle(float x, float y, float w, float h) {
        initPoint(x, y);	// 调用基类公有成员函数
        this->width = w;
        this->height = h;
    }
    float getHeight() { return height; }
    float getWidth() { return width; }
};
image-20220607114312172

三、访问控制规则

image-20220607111057897

3.1 public继承

  • 定义一个父类:

    • 公共、保护、私有属性各有一个
class Base1 {
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};
  • 定义一个子类,继承上述父类
class Son1 : public Base1 {
public:
	void func() {
		m_A = 10;	// 父类的公共权限成员,子类中依旧是公共权限
		m_B = 10;	// 父类的保护权限成员,子类中依旧是保护权限
	}
};
  • 结论与分析

    • 父类中的公共权限成员,在子类中依旧是公共权限

      image-20221017011152278
      • 类外可以访问

        image-20221017011225376
    • 父类中的保护属性,在子类中依旧是保护属性

      image-20221017011255544
    • 父类中的私有属性,在子类中不可访问

      image-20221017011319503

3.2 protected继承

class Son2 : protected Base2 {
public:
	void func() {
		m_A = 100;	// 父类的公共权限成员,子类中变成保护权限
		m_B = 100;	// 父类的保护权限成员,子类中依旧是保护权限
		//m_C = 100;	// 父类的私有成员,子类访问不到
	}
};
  • m_A在父类中是公共权限成员,类外可以访问,但是子类通过保护继承的方式进行继承,其在子类中变成了保护权限,在类外就无法对其进行访问

    image-20221017011518557
  • m_B在父类中是保护权限成员,子类通过保护继承的方式进行继承,其在子类中依旧是保护权限,在类外就无法对其进行访问

    image-20221017011535168

3.3 private继承

class Son3 : private Base3 {
public:
	void func() {
		m_A = 100;	// 父类的公共权限成员,子类中变成私有成员
		m_B = 100;	// 父类的保护权限成员,子类中变成私有成员
		//m_C = 100;	// 父类的私有成员,子类访问不到
	}
};
  • 通过私有继承方式继承,父类中的三种属性成员均会在子类中变成私有属性

    image-20221017011732155
image-20221017011748475

四、构造函数和析构函数的继承

  1. 基类:
    • 基类中的数据成员所构成的部分——基类子对象
    • 基类子对象由基类中声明的构造函数初始化
  2. 派生类:
    • 派生类的对象的数据结构由基类中声明的数据成员在派生类中声明的数据成员构成
    • 派生类的构造函数只负责初始化在派生类中声明的数据成员
    • 派生类的构造函数必须通过调用基类的某个构造函数来初始化基类子对象
    • 不管派生类在类层次上有多少个祖先类,其构造函数只能用其直接基类的构造函数
  3. 构造函数不被继承,但可以被调用
  4. 子类继承父类后,当创建子类对象时,也会调用父类的构造函数
    • 先调用父类的构造函数,再调用子类的构造函数
    • 先调用子类的析构函数,再调用父类的析构函数
#include <iostream>
using namespace std;

// 父类Base
class Base {
public:
    Base() { cout << "constructing Base object.\n"; }
    ~Base() { cout << "constructing Base object.\n"; }
};

// 子类Derived
class Derived:public Base{
public:
    Derived(){ cout << "constructing Derived object.\n"; }
    ~Derived(){ cout << "Destructing Derived object.\n"; }
};

int main(){
    // 创建一个Derived类对象,与此同时自动调用Derived的无参构造函数
    // 先调用父类的构造函数,再调用子类的构造函数
    Derived obj;
    
    // 程序结束,自动调用Derived的析构函数,然后再调用父类的析构函数
    // 先调用父类的析构函数,再调用子类的析构函数
    return 0;
}
image-20220607165852039

五、派生类构造函数

  • 派生类构造函数应包括初始化本身数据成员和基类子对象的形式参数

  • 派生类构造函数实现时使用初始化列表将基类子对象的参数传递给基类构造函数

  • 实现带初始化列表的派生类构造函数的形式如下:

    派生类名(参数表):基类名(基类构造函数参数表){
        派生类构造函数体
    }
    

示例:带参数构造函数

#include <iostream>
using namespace std;

// 父类Base
class Base {
private:
    int base;
public:
    Base();			// 无参构造函数Base
    Base(int base);  // 有参构造函数Base
    ~Base();		// 无参析构函数Base
    
    void print();	// 打印函数
};

Base::Base() {
    base = 0;	// 无参构造函数将私有属性base初始化为0
    cout << "Base's default constructor called." << endl;
}
Base::Base(int base) {
    this->base = base;	// 有参构造函数将传入的base值赋给私有属性base
    cout << "Base's constructor called." << endl;
}
Base::~Base() {
    cout << "Base's destructor called." << endl;
}

void Base::print() {
    cout << "base = " << base << endl;
}

// 子类Derived,继承父类Base
class Derived: public Base {
private:
    int derived;
public:
    Derived();
    Derived(int base, int derived);
    ~Derived();
    
    void print();
};

Derived::~Derived() {
    cout << "Derived's destructor called." << endl;
}
Derived::Derived() {
    derived = 0;
    cout << "Derived's default constructor called." << endl;
}

/* 
“:Base(base)” 表明在准备执行Derived构造函数的时候要先调用Derived的父类Base的有参构造函数Base(int base),将传入的base值给父类的有参构造函数,然后“derived(derived)”再将传入的参数derived传给子类Derived的私有属性derived 
*/
Derived::Derived(int base, int derived): Base(base), derived(derived) {
    cout << "Derived's constructor called." << endl;
}
void Derived::print() {
    Base::print();
    cout << "derived = " << derived << endl;
}

int main() {
    // 定义子类的对象obj,同时自动调用Derived的有参构造函数,并将参数(5,6)传入
    Derived obj(5, 6);
    obj.print();
    
    // 先调用子类析构函数,再调用父类析构函数
    return 0;
}
image-20220608151706989

六、派生类析构函数

  • 析构函数不被继承,派生类要自行声明
    • 声明方法与一般类的析构函数相同
  • 不需要显式地调用基类地析构函数,系统会自动隐式调用
  • 析构函数的调用顺序与构造函数相反
#include <iostream>
using namespace std;

class Point {
private:
    float x, y;
public:
    Point() { x = 1; y = 1;}							// 缺省构造函数
    Point(float x, float y) {this->x = x; this->y = y;}	   // 有参构造函数
    ~Point() {}										   // 析构函数
    
    void move(float offX, float offY) { x += offX; y += offY; }
    float getX() { return x; }
    float getY() { return y; }    
    void display() { cout << "x=" << x << "y=" << y; }
};

class Rectangle: public Point {
private:
    float width, height;
public:
    Rectangle();
    rectangle(float x, float y, float w, float h); // 从父类继承了x和y属性,因此要初始化四个参数
    ~Rectangle(){}
    
    float getHeight() { return height; }
    float getWidth() { return width; }
    void display();
};

// 如果省略Point(1,1),则自动调用无参构造函数
Rectangle::Rectangle():Point(1,1){
    wigth = 1;
    height = 1;
}
// Rectangle类的有参构造函数
Rectangle::Rectangle(float x, float y, float w, float h):Point(x, y){
    width = w;
    height = h;
}

void Rectangle::display() {
    Point::display(); 
    cout << "wigth = " << width << "height = " << height << endl;
}

int main(){
    Rectangle r1;
    r1.display();
    Rectangle r2(3, 3, 5, 6);
    r2.display();
    return 0;
}
image-20230511113236764

七、继承同名成员处理方式

当子类和父类出现同名成员,如何通过子类对象,访问到子类或父类中同名成员?

  • 访问子类同名成员,直接访问即可
  • 访问父类同名成员,需要加作用域
  1. 访问同名成员变量

    父类和子类中有一个同名的m_A成员,父类中m_A为100,子类中m_A为200

    class Base {
    public:
    	Base() { m_A = 100; }
    	int m_A;
    };
    
    class Son : public Base {
    public:
    	Son() { m_A = 200; }
    	int m_A;
    };
    

    访问子类Son中的m_A:

    void test() {
    	Son s;
    	cout << "m_A = " << s.m_A << endl;
    }
    

    通过子类Son对象访问父类Base中的m_A:

    void test() {
    	Son s;
    	cout << "m_A = " << s.Base::m_A << endl;
    }
    
    image-20230511153912627
  2. 访问同名成员函数

    若子类中出现和父类同名的成员函数,子类的同名函数会隐藏掉父类中所有同名成员函数

    • 若想访问到父类中被隐藏的同名成员函数,需要加作用域
    image-20230511154322411
  3. 访问同名静态成员

    • 静态成员变量的属性

      • 所有对象共享同一份数据
      • 编译阶段分配内存
      • 类内声明,类外初始化
    • 静态成员函数的属性

      • 所有对象共享同一个函数
      • 只能访问静态的成员变量
    • 静态成员和非静态成员出现同名,处理方式一致

      • 静态成员变量

        静态成员类内声明,类外初始化

        class Base {
        public:
        	static int m_A;
        };
        
        int Base::m_A = 100;	// 类内声明,类外初始化
        
        class Son :public Base {
        public:
        	static int m_A;
        };
        
        int Son::m_A = 200;        // 类内声明,类外初始化
        
        image-20230511160823479
      • 静态成员函数

        class Base {
        public:
        	static int m_A;
            // 静态成员函数
        	static void func() {
        		cout << "Base - static void func()" << endl;
        	}
        
        };
        int Base::m_A = 100;	// 类内声明,类外初始化
        
        class Son :public Base {
        public:
        	static int m_A;
           // 静态成员函数
        	static void func() {
        		cout << "Son - static void func()" << endl;
        	}
        };
        int Son::m_A = 200;
        
        image-20230511165305724

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

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

相关文章

操作系统基础知识介绍之内存技术和优化(一)(包含SRAM和DRAM、SDRAM、GDRAMs)

使用 SRAM 可以满足最小化高速缓存访​​问时间的需要。 然而&#xff0c;当缓存未命中时&#xff0c;我们需要尽快将数据从主存中移出&#xff0c;这就需要高带宽内存。 这种高内存带宽可以通过将构成主内存的许多 DRAM 芯片组织成多个内存条并使内存总线更宽来实现&#xff0…

数字孪生可视化开发工具在各行业中的应用

数字孪生就是指在信息化平台内模拟物理实体、流程或者系统&#xff0c;即打造一个现实场景的数字化孪生双胞胎。出于成本和周期考虑&#xff0c;快速低成本搭建数字孪生系统成为中小型企业的期望&#xff0c;深圳华锐视点研发的UE4数字孪生编辑器&#xff0c;是一种能够帮助用户…

【Shell脚本】Linux安装Nginx以及开机自启

目录 一、Linux安装Nginx脚本1、把编写好的安装Nginx脚本放置到nginx.sh文件中2、在检查网络的时候&#xff0c;这里的IP地址&#xff0c;填写的需要安装Nginx服务器的IP地址3、这里的端口号可按照自己的需要进行修改4、安装Nginx脚本 二、Nginx开机自启 一、Linux安装Nginx脚本…

API网关|JD|pinduoduoAPI接入

API网关是什么 在日常工作中&#xff0c;不同的场合下&#xff0c;我们可能听说过很多次网关这个名称&#xff0c;这里说的网关特指API网关&#xff08;API Gataway&#xff09;。字面意思是指将所有API的调用统一接入API网关层&#xff0c;由网关层负责接入和输出。 那么在什…

相遇于此,“相交链表”问题的两种思路

本篇博客会讲解力扣“160. 相交链表”的解题思路&#xff0c;这是题目链接。 老规矩&#xff0c;先来审题。这道题的题干有点长&#xff0c;简而言之&#xff0c;就是判断2个链表是否相交&#xff0c;如果相交就返回第一个相交结点&#xff0c;不相交就返回NULL。看看题目原文…

轻松打造完美客户服务系统,这4个关键点不容错过

客户服务对于一个企业来说非常重要&#xff0c;有以下几个原因&#xff1a; 1、建立客户忠诚度&#xff1a;通过提供高质量的客户服务&#xff0c;可以增加客户满意度和忠诚度。这将有助于企业保持竞争优势并吸引新客户。 2、提高客户满意度&#xff1a;客户对企业的服务感到…

MySQL 升级到 8.0 变慢问题分析

1. 背景介绍 前段时间&#xff0c;客户线上 MySQL 版本从 5.7.29 升级到 8.0.25。 升级完成之后&#xff0c;放业务请求进来&#xff0c;没到一分钟就开始出现慢查询&#xff0c;然后&#xff0c;慢查询越来越多&#xff0c;业务 SQL 出现堆积。 整个过程持续了大概一个小时&…

Java学习笔记 --- Stream流

一、体验Stream流【理解】 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素 把集合中所有以"张"开头的元素存储到一个新的集合 把"张"开头的集合中的长度为3的元素存储到一个新的集合 遍历上一步得到的集合 …

使用群晖NAS Mail Server搭建个性化邮件系统

准备工作 一个顶级域名可以获取公网IP的宽带路由使用DDNS功能&#xff0c;或使用群晖自带DDNS&#xff0c;解析顶级域名可以做“端口映射”的路由器 搭建开始 step1&#xff1a;安装套件 登录群晖NAS&#xff0c;打开[套件中心]&#xff0c;搜索“mail”&#xff0c;安装如…

idea-easyYapi的使用

链接: EasyYapi官方文档. 网上搜到的easyYapi基本上都是千篇一律&#xff0c;比较浅&#xff0c;稍微有点定制的东西都搜不到&#xff0c;帮此把自己的一些心得写出来&#xff0c;后续有新发现也会继续更新 第一步&#xff1a;安装插件 第二步&#xff1a;配置数据 yapi的t…

运营商大数据助力贷款行业快速精准获取意向客户

流量&#xff0c;是企业发展的一大痛点。随着市场格局不断变化&#xff0c;获取流量越来越成为企业摆脱发展困局的一种重要途径&#xff0c;如何在庞大的市场竞争中&#xff0c;实现自身的流量突破&#xff0c;也成为企业所要解决的首要问题。 贷款行业的竞争也很强烈&#xf…

一文总结MySQL面试知识点

文章目录 知识点1 定位慢查询2 存储引擎3 索引4 SQL优化5 事务6 主从同步7 分库分表 问答题1 如何定位慢查询2 那这个SQL语句执行很慢, 如何分析呢&#xff1f;3 MYSQL支持的存储引擎有哪些, 有什么区别 ?4 了解过索引吗&#xff1f;&#xff08;什么是索引&#xff09;5 索引…

录取分数爆降102分,只招一个人也敢报考的狠人!

本期为大家整理热门院校-“华南理工大学”的择校分析&#xff0c;这个择校分析专题会为大家结合&#xff1a;初试复试占比、复试录取规则&#xff08;是否公平&#xff09;、往年录取录取名单、招生人数、分数线、专业课难度等进行分析。希望能够帮到大家! –所有数据来源于研…

排班工具小程序开源版开发

排班工具小程序开源版开发 以下是排班工具小程序可能包含的功能列表&#xff1a; 用户注册和登录功能&#xff0c;支持微信登录和手机号登录。排班管理功能&#xff0c;包括创建、编辑、删除和查询排班表。排班表展示功能&#xff0c;支持按天、周、月等不同时间维度展示排班…

Apache DolphinScheduler 开源之夏学生项目申请开启,6 大课题等你来拿万元奖金!

开源之夏 2023 学生报名已经正式开启&#xff01;Apache DolphinScheduler 今年继续参与开源之夏的活动&#xff0c;2023 年 4 月 29 日-6 月 3 日 15:00 UTC8&#xff0c;同学们可以在开源之夏官网 https://summer-ospp.ac.cn/ 找到 Apache DolphinScheduler 下的项目&#xf…

i春秋 Misc Web 爆破-2

审计一下代码&#xff0c;和爆破-1的区别是&#xff0c;没有了正则匹配&#xff0c;且可变变量$$a变成了普通变量$a&#xff1b; 尝试像爆破-1那样传入超全局变量$GLOBALS 根据回显&#xff0c;我们发现flag不在变量中&#xff08;它还嘲笑我们“too young too simple”太年轻…

后端注册表单验证器实现

视图函数在去注册用户之前需要进行验证&#xff0c;表单验证需要先下载 flask-wtf 在终端执行&#xff1a; pip install flask-wtf新建forms.py import wtforms from wtforms.validators import Email,Length,EqualTo from models import UserModel,EmailCaptchaModel# Form…

详细的步骤在VirtualBox 上安装 CentOS 7

下面是详细的步骤来安装 CentOS 7 在 VirtualBox 上&#xff1a; 下载 CentOS 7 ISO 镜像文件&#xff1a; 前往 CentOS 官方网站的镜像下载页面&#xff1a;Download在页面上找到适合你系统架构的 CentOS 7 ISO 镜像文件&#xff0c;并下载到本地。 安装 VirtualBox&#x…

为什么大部分企业都选择加密软件来防止数据泄露?

加密软件是使用加密算法对数据或信息进行编码转换的软件&#xff0c;目的是防止未授权访问与保护敏感内容。它是实现加密技术的重要手段&#xff0c;为用户提供了简单易用的加解密功能&#xff0c;无需深入了解复杂的数学原理。 加密软件使用的加密算法通常采用对称与非对称算法…

16 KVM虚拟机配置-其他常见配置项

文章目录 16 KVM虚拟机配置-其他常见配置项16.1 概述16.2 元素介绍16.3 配置示例 16 KVM虚拟机配置-其他常见配置项 16.1 概述 除系统资源和虚拟设备外&#xff0c;XML配置文件还需要配置一些其他元素&#xff0c;本节介绍这些元素的配置方法。 16.2 元素介绍 iothreads&…