【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.mzph.cn/news/140127.shtml

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

相关文章

设计模式-工厂方法

工厂方法是一种创建型设计模式&#xff0c;其在父类中提供一个创建对象的方法&#xff0c;允许子类决定实例化对象的类型。 问题 假设你开设了一个汽车工厂。创业初期工厂只能生产宝马这一款车&#xff0c;因此大部分代码都位于名为宝马的类中。 工厂效益非常好&#xff0c;为…

IDEA搭建ssm项目

此前&#xff0c;我一直在用eclipse编辑器做java项目&#xff0c;现在初次使用IDEA编辑器&#xff0c;在这里&#xff0c;我记录了使用IDEA环境下搭建ssm项目的过程。 创建Maven项目&#xff0c;如下 右击TEST4项目&#xff0c;在弹出的菜单中选择Add Framework Support 在弹出…

屏幕提词软件Presentation Prompter mac中文版使用方法

Presentation Prompter for mac是一款屏幕提词器软件&#xff0c;它可以将您的Mac电脑快速变成提词器&#xff0c;支持编写或导入&#xff0c;可以在一个或多个屏幕上平滑地滚动&#xff0c;Presentation Prompter 下载是为适用于现场表演者&#xff0c;新闻广播员&#xff0c;…

计算机网络——b站王道考研笔记

第一章 计算机网络体系结构 1.计算机网络概述 &#xff08;1&#xff09;概念 计算机网络是一个将分散的&#xff0c;具有独立功能的计算机系统&#xff0c;通过通信设备与线路连接起来&#xff0c;由功能完善的软件实现资源共享和信息传递的系统&#xff1b; 是互连的&#…

数据分析面试题1

1.右表为一组数据&#xff0c;尝试进行简单分析&#xff0c;并给出结论&#xff08;使用公式和图表辅助&#xff09; ①理解数据 userid&#xff1a;用户id神兽印记消耗数量 ②数据清洗 冻结首行&#xff0c;将列标题的英文字段转换成汉字字段检查是否有重复项&#xff1a;…

Leetcode—20.有效的括号【简单】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—20.有效的括号 C实现代码 class Solution { public:bool isValid(string s) {stack<char> arr;int len s.size();if(len 1) {return false;}for(int i 0; i < len; i) {if(s[i] ( || s[i] [ || s[i] {)…

基于springboot实现沁园健身房预约管理系统【项目源码】计算机毕业设计

基于springboot实现沁园健身房预约管理系统演示 B/S架构 B/S结构是目前使用最多的结构模式&#xff0c;它可以使得系统的开发更加的简单&#xff0c;好操作&#xff0c;而且还可以对其进行维护。使用该结构时只需要在计算机中安装数据库&#xff0c;和一些很常用的浏览器就可以…

Flink

1. Flink简介 1.1 初识Flink Flink项目的理念是&#xff1a;“Apache Flink是为分布式、高性能、随时可用以及准确的流处理应用程序打造的开源的有状态的流处理框架”。 Apache Flink是一个框架和分布式处理引擎&#xff0c;用于对无界和有界数据流进行有状态计算。Fl…

进亦忧,退亦忧,Github Copilot 集成进入 Visual Studio 带来的思考

开篇想到《岳阳楼记》的结尾&#xff1a; 不以物喜&#xff0c;不以己悲&#xff1b;居庙堂之高则忧其民&#xff1b;处江湖之远则忧其君。是进亦忧&#xff0c;退亦忧。然则何时而乐耶&#xff1f;其必曰&#xff1a;“先天下之忧而忧&#xff0c;后天下之乐而乐”乎。未来30…

HarmonyOS 高级特性

引言 本章将探讨 HarmonyOS 的高级特性&#xff0c;包括分布式能力、安全机制和性能优化。这些特性可以帮助你构建更强大、更安全、更高效的应用。 目录 HarmonyOS 的分布式能力HarmonyOS 的安全机制HarmonyOS 的性能优化总结 1. HarmonyOS 的分布式能力 HarmonyOS 的分布…

Python 使用tkinter复刻Windows记事本UI和菜单功能(一)

下一篇&#xff1a;Python 使用tkinter复刻Windows记事本UI和菜单&#xff08;二&#xff09;-CSDN博客 介绍&#xff1a; Windows操作系统中自带了一款记事本应用程序&#xff0c;通常用于记录文字信息&#xff0c;具有简单文本编辑功能。Windows的记事本可以新建、打开、保…

html菜单的基本制作

前面写过一点网页菜单的博文&#xff1b;下面再复习一些技术要点&#xff1b; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.…

Python---元组的相关操作方法

由于元组中的数据不允许直接修改&#xff0c;所以其操作方法大部分为查询方法。 编号函数作用1元组[索引]根据索引下标查找元素2index()查找某个数据&#xff0c;如果数据存在返回对应的下标&#xff0c;否则报错&#xff0c;语法和列表、字符串的index方法相同3count()统计某…

基于GPTs个性化定制SCI论文专业翻译器

1. 什么是GPTs GPTs是OpenAI在2023年11月6日开发者大会上发布的重要功能更新&#xff0c;允许用户根据特定需求定制自己的ChatGPT模型。 Introducing GPTs 官方介绍页面https://openai.com/blog/introducing-gpts 在原有自定义ChatGPT的流程中&#xff0c;首先需要自己编制p…

SOME/IP 协议介绍(四)RPC协议规范

RPC协议规范 本章描述了SOME/IP的RPC协议。 传输协议绑定 为了传输不同传输协议的SOME/IP消息&#xff0c;可以使用多种传输协议。SOME/IP目前支持UDP和TCP。它们的绑定在以下章节中进行了解释&#xff0c;而第[SIP_RPC_450页&#xff0c;第36页]节讨论了选择哪种传输协议。…

消息中心常见解决方案分享

解决方案 1、问题2、设计3、流程 看了大部分的消息中心解决方案&#xff0c;发现大家的中心思想都大差不差&#xff0c;区别基本都是在符合自身业务场景的做了一些定制化处理。本文为我对消息中心基本骨架的知识梳理&#xff0c;亦在帮助大家对消息中心设计有一个基本的理解。 …

Spring 常见面试题

1、Spring概述 1.1、Spring是什么? Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。这些功能的底层都依赖于它的两个核心特性&#xff0c;也就是…

vscode 访问本地或者远程docker环境

1、vscode 访问docker本地环境 直接点击左下角连接图标&#xff0c;弹出选项可以选择容器&#xff0c;只要容器在本地运行者&#xff0c;选择attach可以看到运行中的容器可以选择&#xff0c;选择其中需要选择的就行 ## 运行容器&#xff0c;可以-d后台运行都可以 docker run…

原型模式 rust和java的实现

文章目录 原型模式介绍优点缺点使用场景 实现java 实现rust 实现 rust代码仓库 原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能。 这种模式是实现了一个原型接口&#xff0c;该接口用于创建当前对象的克隆。当…

POJ 3254 Corn Fields 状态压缩DP(铺砖问题)

一、题目大意 我们要在N * M的田地里种植玉米&#xff0c;有如下限制条件&#xff1a; 1、对已经种植了玉米的位置&#xff0c;它的四个相邻位置都无法继续种植玉米。 2、题目中有说一些块无论如何&#xff0c;都无法种植玉米。 求所有种植玉米的方案数&#xff08;不种植也…