【c++随笔12】继承

【c++随笔12】继承

  • 一、继承
    • 1、继承的概念
    • 2、3种继承方式
    • 3、父类和子类对象赋值转换
    • 4、继承中的作用域——隐藏
    • 5、继承与友元
    • 6、继承与静态成员
  • 二、继承和子类默认成员函数
    • 1、子类构造函数
  • 二、子类拷贝构造函数
    • 3、子类的赋值重载
    • 4、子类析构函数
  • 三、单继承、多继承、菱形继承
    • 1、单继承:一个子类只有一个直接父类,我们称这种继承关系为单继承。
    • 2、多继承:一个子类有两个或以上直接父类,我们称这种继承关系为多继承。
    • 3、菱形继承(Diamond Inheritance)是指在类继承关系中,存在一个派生类同时继承自两个直接或间接基类,并且这两个基类又共同继承自一个共同的基类,从而形成了菱形状的继承结构。
      • 3.1、菱形继承可能引发以下问题:
      • 3.2、为了解决菱形继承带来的问题,可以采用以下方法:
    • 4、继承和组合

原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/131795289
qq技术交流群:921273910

C++ 是基于面向对象的程序,面向对象有三大特性 —— 封装、继承、多态。

一、继承

1、继承的概念

  • 继承(inheritance)机制是面向对象程序设计,使代码可以复用的最重要的手段。
  • 它允许程序员在保持原有类特性的基础上进行扩展,以增加功能。这样产生新的类,称为派生类。
  • 继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
  • 以前我们接触的复用都是函数复用,而继承是类设计层次的复用。
#include <iostream>
#include<string>
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {string m_stuID;  // 学号
};/* Teacher 公有继承了 Person */
class Teacher : public Person {string m_employeeID;  // 工号
};int main() 
{Person p;p.print();Student su;su.print();Teacher t;t.print();return 0;
}

输出

在这里插入图片描述

  • 继承的定义格式
    Student 是 子类,我们也称之为派生类。Person 是父类,我们也称之为 基类。
class 派生类名:[继承方式] 基类名{派生类新增加的成员
};

2、3种继承方式

  • 访问限定符:public / protected / private

下表汇总了不同继承方式对不同属性的成员的影响结果

继承方式/基类成员public成员protected成员private成员
public继承publicprotected不可见
protected继承protectedprotected不可见
private继承privateprivate不可见

由于 private 和 protected 继承方式会改变基类成员在派生类中的访问权限,导致继承关系复杂,所以实际开发中我们一般使用 public

3、父类和子类对象赋值转换

  • 子类对象可以赋值给父类的对象、父类的指针、父类的引用:
#include <iostream>
#include<string>
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {string m_stuID;  // 学号
};int main() 
{Student s;Person p = s;Person* pointer_p = &s;Person& ref_p = s;p.print();pointer_p->print();ref_p.print();return 0;
}

输出

在这里插入图片描述

注意事项:

  • ① 父类对象不能赋值给子类对象
Student s;  // 子类
Person p;   // 父类s = p;
  • ② 父类的指针可以通过强转赋值给子类的指针,但是必须是父类的指针是指向子类对象时才是安全的。

这里父类如果是多态类型,可以使用 RTTI(Run-Time Type Information,即运行时类型识别)的 dynamic_cast 来进行识别后进行安全转换。

#include <iostream>
#include<string>
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {
public:string m_stuID;  // 学号
};int main() 
{Student s;// 父类的指针可以通过强制类型转换赋值给子类的指针Person* pointer_p = &s;Student* pointer_s = (Student*)pointer_p;pointer_s->m_stuID;Person p;// 这种情况虽然可以,但是会存在越界访问问题Person* pointer_p2 = &p;Student* pointer_s2 = (Student*)pointer_p2;pointer_s->m_stuID;return 0;
}

4、继承中的作用域——隐藏

  • 继承体系中的父类和子类都有独立的作用域,如果子类和父类有同名成员,
  • 此时子类成员会屏蔽父类对同名成员的直接访问,这种情况叫做 “隐藏” (有文章把它叫重定义,其实我不建议这种叫法,因为重定义指的是同一个作用域重复定义)。

在子类成员函数中,可以使用如下方式进行显式访问:
基类::基类成员

例如:在Student类中
Person::print()

#include <iostream>
#include<string>
using namespace std;
class Person {/* 共有的信息 */
public:void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {
public:void print(){cout << "name = " << m_name << "\nage = " << m_age <<"\nstuID = "<< m_stuID << endl;}string m_stuID;  // 学号
};int main() 
{Student s;s.print();return 0;
}

5、继承与友元

  • 友元关系不能继承,也就是说父类友元不能访问子类私有和保护成员!
#include <iostream>
#include<string>
using namespace std;class Student;
class Person {
public:friend void Display(const Person& p, const Student& s);/* 共有的信息 */protected:void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}string m_name;int m_age;
};/* Student 公有继承了 Person */
class Student : public Person {protected:void print(){cout << "name = " << m_name << "\nage = " << m_age <<"\nstuID = "<< m_stuID << endl;}string m_stuID = "110";  // 学号
};void Display(const Person& p, const Student& s)
{cout << p.m_name << endl;cout << s.m_stuID << endl;
}int main() 
{Person p;Student s;Display(p, s);return 0;
}

报错
“Student::m_stuID”: 无法访问 protected 成员(在“Student”类中声明)

6、继承与静态成员

  • 父类定义了 static 静态成员,则整个继承体系里面中有一个这样的成员。

可以理解为共享,父类的静态成员可以在子类共享,父类和子类都能去访问它。
无论派生出多少个子类,都只有一个 static 成员实例:

#include <iostream>
#include<string>
using namespace std;class Person {
public:Person() {++m_count;}void print(){cout << "name = "<<m_name << "\nage = "<<m_age << endl;}
protected:string m_name;int m_age;
public:static int m_count;
};int Person::m_count = 0;
/* Student 公有继承了 Person */
class Student : public Person 
{
protected:string m_stuID = "110";  // 学号
};int main() 
{Student s1;Student s2;Student s3;Person s;cout << "大家都可以访问" << endl;cout << "人数 : " << Person::m_count << endl;cout << "人数 : " << Student::m_count << endl;cout << "大家也都可以变动" << endl;s3.m_count = 0;cout << "人数 : " << Person::m_count << endl;cout << "并且他们的地址也都是一样的,因为所有继承体系中只有一个" << endl;cout << "人数 : " << &Person::m_count << endl;cout << "人数 : " << &Student::m_count << endl;return 0;
}

输出

在这里插入图片描述

二、继承和子类默认成员函数

1、子类构造函数

  • 子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。

如果 父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。(如下面的demo)

  • 子类对象初始化先调用父类构造再调子类构造。
#include <iostream>
#include<string>
using namespace std;class Person {
public:Person(const char* m_name = "hello") {cout<<"构造 Person \n";}
protected:string m_name;int m_age;
};class Student : public Person 
{
public:Student(const char* name, int stuID) :Person(name), //如果 父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout << "构造 Student \n";}
protected:int m_stuID;  // 学号
};int main() 
{Student s1("",18);return 0;
}

思考:如何设计一个不能被继承的类?

  • 将父类的构造函数私有化:
class A 
{
private:A() {}
};

父类的构造函数私有化后,在父类的外部,父类自己也没法初始化了?

(单例模式可以这么做,如下)


class A {
public:static A CreateObject() {  // 提供一个获取对象的方式return A();}
private:A() {}
};class B : public A {};int main(void) 
{A a = A::CreateObject();return 0;
}

二、子类拷贝构造函数

  • 子类的拷贝构造函数必须调用父类的拷贝构造完成拷贝初始化。
#include <iostream>
#include<string>
using namespace std;class Person {
public:Person(const char* m_name = "hello") {cout<<"构造 Person \n";}Person(const Person& p):m_name(p.m_name){cout << "拷贝构造 Person \n";}
protected:string m_name;int m_age;
};class Student : public Person 
{
public:Student(const char* name, int stuID) :Person(name), //如果 父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout << "构造 Student \n";}Student(const Student& s):Person(s), //子类的拷贝构造函数必须调用父类的拷贝构造完成拷贝初始化。m_stuID(s.m_stuID){cout << "拷贝构造 Student \n";}
protected:int m_stuID;  // 学号
};int main() 
{Student s1("haha",18);Student s2(s1);return 0;
}

输出

在这里插入图片描述

3、子类的赋值重载

  • 子类的 operator= 必须要调用父类的 operator= 完成父类的复制。
#include <iostream>
#include<string>
using namespace std;class Person {
public:Person(const char* m_name = "hello") {cout<<"构造 Person \n";}Person& operator=(const Person& p){cout << "赋值重载 Person \n";if(this != &p){m_name = p.m_name;}return *this;}
protected:string m_name;int m_age;
};class Student : public Person 
{
public:Student(const char* name, int stuID):Person(name), //如果 父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout << "构造 Student \n";}Student& operator=(const Student& s){cout << "赋值重载 Student \n";if (this != &s){Person::operator=(s); //子类的 operator= 必须要调用父类的 operator= 完成父类的复制。m_stuID = s.m_stuID;}return *this;}
protected:int m_stuID;  // 学号
};int main() 
{Student s1("小白",18);Student s2("小黑", 18);s1 = s2;return 0;

输出

在这里插入图片描述

4、子类析构函数

  • 为了保证子类对象先清理子类成员再清理父类成员的顺序,先子后父。

子类析构先子后父,子类对象的析构清理是先调用子类析构再调父类析构。

  • 子类析构函数完成后会自动调用父亲的析构函数,所以不需要我们显式调用。
#include <iostream>
#include<string>
using namespace std;class Person {
public:Person(const char* m_name = "hello") {cout<<"构造 Person \n";}~Person(){cout << "析构 Person \n";}
protected:string m_name;int m_age;
};class Student : public Person 
{
public:Student(const char* name, int stuID):Person(name), //如果 父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显式调用。m_stuID(stuID){cout << "构造 Student \n";}~Student(){cout << "析构 Student \n";}
protected:int m_stuID;  // 学号
};int main() 
{Student s1("小白",18);return 0;
}

输出

在这里插入图片描述

三、单继承、多继承、菱形继承

1、单继承:一个子类只有一个直接父类,我们称这种继承关系为单继承。

2、多继承:一个子类有两个或以上直接父类,我们称这种继承关系为多继承。

3、菱形继承(Diamond Inheritance)是指在类继承关系中,存在一个派生类同时继承自两个直接或间接基类,并且这两个基类又共同继承自一个共同的基类,从而形成了菱形状的继承结构。

下面是一个示例代码来说明菱形继承的概念:

class Animal {
public:void eat() {cout << "Animal eats." << endl;}
};class Mammal : public Animal {
public:void run() {cout << "Mammal runs." << endl;}
};class Bird : public Animal {
public:void fly() {cout << "Bird flies." << endl;}
};class Bat : public Mammal, public Bird {
public:void sleep() {cout << "Bat sleeps." << endl;}
};

在上述代码中,Animal 是基类,Mammal 和 Bird 是直接派生类,而 Bat 是通过多重继承同时派生自 Mammal 和 Bird 的派生类。注意到 Mammal 和 Bird 都继承自 Animal,这就形成了菱形继承结构。

3.1、菱形继承可能引发以下问题:

  • 二义性(Ambiguity):由于 Bat 同时继承自 Mammal 和 Bird,如果两个基类都定义了相同的成员函数或变量,编译器就无法确定该使用哪个版本,从而导致二义性错误。
  • 冗余数据:由于两个基类都继承自同一个基类 Animal,当 Bat 对象被创建时,会在内存中存在两份相同的 Animal 的数据。

3.2、为了解决菱形继承带来的问题,可以采用以下方法:

  • 使用虚拟继承(Virtual Inheritance):在 Mammal 和 Bird 继承 Animal 时,使用 virtual 关键字表示虚拟继承,这样就可以消除冗余数据和二义性问题。
class Mammal : virtual public Animal {// ...
};class Bird : virtual public Animal {// ...
};
  • 使用间接继承:在 Bat 类中只直接继承 Mammal 或 Bird 的一个,而间接继承另一个基类的成员函数或变量。
class Bat : public Mammal {
private:Bird bird;
public:// 使用 bird 对象来访问 Bird 类中的成员
};

菱形继承是多重继承中的一种特殊情况,需要谨慎使用,并采取适当的解决方案来避免引发问题。

4、继承和组合

  • 继承和组合 public继承是一种 is-a 的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种 has-a 的关系。假设B组合了A,每个B对象中都有一个A对象。
  • 优先使用对象组合,而不是类继承 。
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用 (white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。 继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关 系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对 象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse), 因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系, 耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适 合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

class A {// ...
};// 继承
class B : public A {};class C {// ...
};// 组合
class D {C _c;
};

继承就是团体出行,A 任何成员的修改都有可能影响 B 的实现。
组合就是自由出行,C 只要不修改公有,就不会对 D 有影响。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视觉大模型DINOv2:自我监督学习的新领域

1 DINOv2 1.1 DINOv2特点 前段时间&#xff0c;Meta AI 高调发布了 Segment Anything&#xff08;SAM&#xff09;&#xff0c;SAM 以交互式方式快速生成 Mask&#xff0c;并可以对从未训练过的图片进行精准分割&#xff0c;可以根据文字提示或使用者点击进而圈出图像中的特定…

Flink SQL 表值聚合函数(Table Aggregate Function)详解

使用场景&#xff1a; 表值聚合函数即 UDTAF&#xff0c;这个函数⽬前只能在 Table API 中使⽤&#xff0c;不能在 SQL API 中使⽤。 函数功能&#xff1a; 在 SQL 表达式中&#xff0c;如果想对数据先分组再进⾏聚合取值&#xff1a; select max(xxx) from source_table gr…

第24章_mysql性能分析工具的使用

文章目录 1. 数据库服务器的优化步骤2.查看系统性能参数3. 统计SQL的查询成本&#xff1a;last_query_cost4. 定位执行慢的 SQL&#xff1a;慢查询日志4.1 开启慢查询日志参数4.2 查看慢查询数目4.3 测试慢sql语句&#xff0c;查看慢日志4.4 系统变量 log_output&#xff0c; l…

用excel计算一个矩阵的转置矩阵

假设我们的原矩阵是一个3*3的矩阵&#xff1a; 125346789 现在求它的转置矩阵&#xff1a; 鼠标点到一个空白的地方&#xff0c;用来存放结果&#xff1a; 插入-》函数&#xff1a; 选择TRANSPOSE&#xff0c;这个就是求转置矩阵的函数&#xff1a; 点击“继续”&#xff1a…

Windows查看端口占用情况

Windows如何查看端口占用情况 方法1. cmd命令行执行netstat命令&#xff0c;查看端口占用情况 netstat -ano 以上命令输出太多信息&#xff0c;不方便查看&#xff0c;通过如下命令搜索具体端口占用情况&#xff0c;例如&#xff1a;8080端口 netstat -ano | findstr "…

初阶JavaEE(17)Linux 基本使用和 web 程序部署

接上次博客&#xff1a;初阶JavaEE&#xff08;16&#xff09;博客系统&#xff08;Markdown编辑器介绍、博客系统功能、博客系统编写&#xff1a;博客列表页 、博客详情页、实现登录、实现强制登录、显示用户信息、退出登录、发布博客&#xff09;-CSDN博客 目录 Linux 基本…

std::any

一、简介 std::any 可以储存任何可拷贝构造和可销毁的类型的对象。 struct test {test(int a,int b){} };int main(int argc, char *argv[]) {std::any a 1;qDebug() << a.type().name();a 3.14;qDebug() << a.type().name();a true;qDebug() << a.type…

从0到0.01入门React | 010.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

开发者测试2023省赛--UnrolledLinkedList测试用例

测试结果 官方提交结果 EclEmma PITest 被测文件UnrolledLinkedList.java /** This source code is placed in the public domain. This means you can use it* without any restrictions.*/package net.mooctest;import java.util.AbstractList; import java.util.Collectio…

Postman模拟上传文件

如图&#xff0c;在F12抓到的上传文件的请求 那要在postman上模拟这种上传&#xff0c;怎么操作呢&#xff0c;如图&#xff0c;选中【Select File】选取文件上传即可

C++进阶篇4---番外-红黑树

一、红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0…

19. 深度学习 - 用函数解决问题

文章目录 Hi&#xff0c; 你好。我是茶桁。 上一节课&#xff0c;我们从一个波士顿房价的预测开始写代码&#xff0c;写到了KNN。 之前咱们机器学习课程中有讲到KNN这个算法&#xff0c;分析过其优点和缺点&#xff0c;说起来&#xff0c;KNN这种方法比较低效&#xff0c;在数…

Leetcode刷题详解—— 有效的数独

1. 题目链接&#xff1a;36. 有效的数独 2. 题目描述&#xff1a; 请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的…

U-Mail邮件中继,让海外邮件沟通更顺畅

在海外&#xff0c;电子邮件是人们主要的通信工具&#xff0c;尤其是商务往来沟通&#xff0c;企业邮箱是标配。这主要是因为西方国家互联网发展较早&#xff0c;在互联网早期&#xff0c;电子邮件技术较为成熟&#xff0c;大家都用电子邮件交流&#xff0c;于是这成了一种潮流…

Jenkins简介及Docker Compose部署

Jenkins是一个开源的自动化服务器&#xff0c;用于自动化构建、测试和部署软件项目。它提供了丰富的插件生态系统&#xff0c;支持各种编程语言和工具&#xff0c;使得软件开发流程更加高效和可靠。在本文中&#xff0c;我们将介绍Jenkins的基本概念&#xff0c;并展示如何使用…

2023年第十六届山东省职业院校技能大赛中职组“网络安全”赛项规程

第十六届山东省职业院校技能大赛 中职组“网络安全”赛项规程 一、赛项名称 赛项名称&#xff1a;网络安全 英文名称&#xff1a;Cyber Security 赛项组别&#xff1a;中职组 专业大类&#xff1a;电子与信息大类 二、竞赛目的 网络空间已经成为陆、海、空、天之后的第…

C/C++满足条件的数累加 2021年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C满足条件的数累加 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C满足条件的数累加 2021年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 现有n个整数&#xff0c;将其中个位数…

linux安装并配置git连接github

git安装 sudo apt-get install git git信息配置 git config --global uer.name "yourname" git config --global user.email "youremail" 其中&#xff0c;yourname是你在github上配置的用户名&#xff0c;youremail是你在github上设置的邮箱 查看git…

吃透 Spring 系列—MVC部分

目录 ◆ SpringMVC简介 - SpringMVC概述 - SpringMVC快速入门 - Controller中访问容器中的Bean - SpringMVC关键组件浅析 ◆ SpringMVC的请求处理 - 请求映射路径的配置 - 请求数据的接收 - Javaweb常用对象获取 - 请求静态资源 - 注解驱动 标签 ◆ SpringMV…

STL简介+浅浅了解string——“C++”

各位CSDN的uu们好呀&#xff0c;终于到小雅兰的STL的学习了&#xff0c;下面&#xff0c;让我们进入CSTL的世界吧&#xff01;&#xff01;&#xff01; 1. 什么是STL 2. STL的版本 3. STL的六大组件 4. STL的重要性 5. 如何学习STL 6.STL的缺陷 7.为什么要学习string类 …