【C++】继承(详解)

前言:今天我们正式的步入C++进阶内容的学习了,当然了既然是进阶意味着学习难度的不断提升,各位一起努力呐。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


什么是继承

C++中的继承是一种面向对象编程的特性,它允许一个类(子类)继承另一个类(父类)的属性和方法,并且可以添加自己的属性和方法。通过继承,子类可以重用父类的代码,减少重复编写代码的工作量。在C++中,使用关键字"extends"可以声明一个类继承另一个类。子类将自动继承父类的非私有成员和方法,并可以通过重写(override)父类的方法或添加新方法来实现自己的行为。(光看文字大家肯定还是觉得晦涩难懂的,我们直接看代码)


继承的使用格式

在C++中,使用继承的格式如下:

class 子类名 : 访问权限 基类名
{// 子类的成员和函数声明
};

其中,子类名表示要定义的子类的名称,访问权限可以是public、protected或private,用于指定从基类继承成员的访问权限,基类名表示要继承的基类的名称。

例如,定义一个基类(父类)father和一个派生类(子类)son:

class father//父类
{
public:string name = "张三";int age = 20;
};class son: public father//public继承子类可以直接继承父类的公有对象
{
public :void print(){cout << name << endl << age << endl;}
};
int main()
{son s1;s1.print();return 0;
}

在上述例子中son是parent的子类,使用public权限继承了parent中name和age,然后用子类中的成员函数去调用print函数从而调用了父类中的成员。

结果如下:
在这里插入图片描述

如果还是不太能理解父类和子类的关系,看下图,通俗的理解就是儿子继承了父亲的遗产,并且儿子的财产依然存在。
在这里插入图片描述


继承后子类(派生类)的访问权限

在C++中,继承后的访问权限可以分为三种:public、protected和private。

公有继承(public inheritance):当通过公有继承派生一个子类时,基类的公有成员和保护成员在子类中仍然是公有的,可以直接访问。基类的私有成员在子类中是不可访问的。

保护继承(protected inheritance):当通过保护继承派生一个子类时,基类的公有成员和保护成员在子类中都变成了保护成员,它们通过子类只能被子类自身和子类的派生类访问,外部无法访问。基类的私有成员在子类中是不可访问的.。

私有继承(private inheritance):当通过私有继承派生一个子类时,基类的公有成员和保护成员在子类中都变成了私有成员,只能在子类内部访问,外部无法访问。基类的私有成员在子类中是不可访问的。

需要注意的是,继承的访问权限仅影响继承后的成员的访问权限,不会改变基类中成员本身的访问权限。
在这里插入图片描述

实例演示:

class Base {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class PublicDerived : public Base {
public:void example() {publicMember = 10; // 可以直接访问公有成员protectedMember = 20; // 可以直接访问保护成员// privateMember = 30; 无法访问私有成员}
};class ProtectedDerived : protected Base {
public:void example() {publicMember = 10; // 可以直接访问公有成员protectedMember = 20; // 可以直接访问保护成员// privateMember = 30; 无法访问私有成员}
};class PrivateDerived : private Base {
public:void example() {publicMember = 10; // 可以直接访问公有成员protectedMember = 20; // 可以直接访问保护成员// privateMember = 30; 无法访问私有成员}
};

总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他
    成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
  6. 很多人分不清保护继承和私有继承,其实区别就是保护继承他的儿子的儿子也能访问他的保护成员和公有成员,私有继承就是只有他儿子能访问。

子类与父类

子类与父类的相互赋值转换

  1. 符号赋值“=”

注:这里我们先暂时知道赋值的规则就是切片赋值,就是把子类中的父类那部分,赋值过去即可。

在C++中,子类对象可以被隐式地转换为父类对象,但是父类对象不能被隐式地转换为子类对象。这是因为子类对象继承了父类对象的成员和方法,但父类对象并不包含子类特有的成员和方法。

以下是一个示例说明子类对象转换为父类对象的情况:

class Parent {
public:void print() {std::cout << "Parent class" << std::endl;std::cout << age << std::endl;}int age = 30;
};class Child : public Parent {
public:void print() {std::cout << "Child class" << std::endl;}
};int main() {Child child;child.age = 50;Parent parent = child;  // 子类对象隐式转换为父类对象parent.print();  // 调用父类的print()方法return 0;
}
  1. 引用
Child s1;//子类Parent& t1=st;//父类,通过引用对其进行赋值
  1. 指针
Child s1;//子类Parent* t1 = &st;//父类,通过指针进行赋值

总结来说,C++中可以将子类对象隐式地转换为父类对象,但是父类对象无法隐式地转换为子类对象。如果需要使用子类特有的成员和方法,需要进行类型转换.


同名成员变量

在C++中,当子类继承了父类时,如果子类中出现了与父类同名的成员变量,那么子类的同名成员变量会隐藏父类的同名成员变量。这称为"隐藏"(hiding)。

具体来说,如果子类定义了与父类同名的成员变量,那么子类中的同名成员变量会隐藏父类中的同名成员变量。这意味着当通过子类对象访问同名成员变量时,会优先访问子类中的成员变量,而无法直接访问到父类中的同名成员变量。

#include <iostream>
class Parent {
public:int num = 10;
};class Child : public Parent {
public:int num = 20;
};int main() {Child child;std::cout << "Child num: " << child.num << std::endl;   // 编译器还是就近原则,优先访问子类中的num,输出: Child num: 20std::cout << "Parent num: " << child.Parent::num << std::endl; //想要访问父类中的num,需要指定类域,输出: Parent num: 10return 0;
}

在这里插入图片描述

在上面的示例中,Parent类中定义了一个名为num的成员变量,初始值为10。Child类继承了Parent类,并定义了一个同名成员变量num,初始值为20。

在main函数中,我们创建了Child类的对象child。当我们通过子类对象child访问num时,会优先访问子类的同名成员变量,因此输出结果为20。如果我们需要访问父类中的同名成员变量,可以使用作用域解析符"::"来指定父类,如child.Parent::num


同名成员函数

在C++中,当子类继承了父类时,如果子类中出现了与父类同名的成员函数,那么子类的同名成员函数将隐藏父类的同名成员函数。

具体来说,如果子类定义了与父类同名的成员函数,那么子类中的同名成员函数将隐藏父类中的同名成员函数。这意味着当通过子类对象调用同名成员函数时,会优先调用子类中的成员函数,而无法直接调用到父类中的同名成员函数。

然而,与成员变量的隐藏不同的是,对于成员函数的隐藏,如果需要在子类中使用父类的同名成员函数,可以使用作用域解析符"::"来指定父类.

class Parent {
public:void print() {std::cout << "Parent print" << std::endl;}
};class Child : public Parent {
public:void print() {std::cout << "Child print" << std::endl;Parent::print();//可以通过指定域来调用}
};int main() {Child child;child.print();                // 输出: Child printchild.Parent::print();        // 输出: Parent printreturn 0;
}

子类中的默认成员函数

构造函数

在C++中,子类可以通过构造函数来初始化继承的父类成员。子类的构造函数可以调用父类的构造函数来初始化父类的成员变量。子类的构造函数在构造子类对象时被调用,而且会在子类构造函数的初始化列表中调用父类的构造函数.

注: 编译器会默认先调用父类的构造函数,再调用子类的构造函数.

class Parent
{
public:Parent(string name = "Dad", int age = 30):_name(name),_age(age){cout << "这是父类:" << _name << " " << age << endl;}protected:string _name;int _age;
};class Child: public Parent
{
public:Child(string son):_son(son){cout << "这是子类:" << _son << endl;}protected:string _son;
};int main()
{Child s1("son");return 0;
}

在这里插入图片描述
这里我们可以明显的知道,编译器先调用父类的默认构造,在调用子类的构造,所以一定要保证这里的父类的构造是有效的,以防止构造失效.


析构函数

在C++中,子类可以定义自己的析构函数,用来释放子类对象在内存中分配的资源。子类的析构函数与基类的析构函数类似,通过在类的声明中使用特殊的名称~类名()来定义。

子类的析构函数会自动调用基类的析构函数,以确保基类对象中分配的资源被正确释放。

注: 析构函数和构造函数相反,编译器默认先调用子类的析构函数,再调用父类的析构函数.

实例演示:

class Parent
{
public:~Parent(){cout << "这是父类的析构" << endl;}
protected:string _name;int _age;
};class Child: public Parent
{
public:~Child(){cout << "这是子类的析构" << endl;}
protected:string _son;
};int main()
{Child s1;return 0;
}

在这里插入图片描述


拷贝构造

在C++中,子类可以定义自己的拷贝构造函数,用于创建一个新的子类对象,该对象与已存在的子类对象具有相同的值。

注: 子类的拷贝构造函数会自动调用基类的拷贝构造函数,以确保基类对象的成员变量得到正确的复制.

class Parent
{
public:Parent(string name = "Dad", int age = 30):_name(name), _age(age){cout << "这是父类:" << _name << " " << age << endl;}protected:string _name;int _age;
};class Child : public Parent
{
public:Child(string son):_son(son){cout << "这是子类:" << _son << endl;}Child(Child& child):Parent(child)//通过切片的方式直接对父类进行了初始化,_son(child._son){cout << "拷贝构造:" << _name << " " << _age << " " << _son << endl;}
protected:string _son;
};int main()
{Child s1("son");Child s2(s1);return 0;
}

在这里插入图片描述


赋值运算符重载

子类的赋值运算符重载函数可以用来实现对象之间的赋值操作,包括赋值基类对象的成员变量和子类对象的成员变量。在赋值过程中,可以先调用基类的赋值运算符重载函数来赋值基类对象的成员变量,然后再进行子类对象的成员变量的赋值操作.

class Parent
{
public:Parent(string name = "Dad", int age = 30):_name(name), _age(age){cout << "这是父类:" << _name << " " << age << endl;}Parent& operator=(const Parent& s1){if (this != &s1){cout << "调用父类" << endl;_name = s1._name;_age = s1._age;}}protected:string _name;int _age;
};class Child : public Parent
{
public:Child(string son):_son(son){cout << "这是子类:" << _son << endl;}Child(Child& child):Parent(child),_son(child._son){cout << "拷贝构造:" << _name << " " << _age << " " << _son << endl;}Child& operator=(const Child& s1){if (this != &s1){Parent::operator=(s1);//调用父类的运算符重载,以免调用自身造成栈溢出_son = s1._son;}}
protected:string _son;
};int main()
{Child s1("son");Child s2(s1);return 0;
}

继承中的单继承与多继承

在C++中,单继承和多继承是两种不同的继承方式。

  1. 单继承:
    单继承指的是一个派生类只能继承自一个基类(一个孩子一个父亲)。在单继承中,一个派生类可以继承基类的所有成员(包括成员函数和成员变量),并且可以通过派生类对象访问这些成员。单继承的语法如下:

    class Base {// base class members
    };class Derived : public Base {// derived class members
    };
    

    在这个例子中,Derived类从Base类单继承,Derived类可以访问Base类中的所有公有成员。

  2. 多继承:
    多继承指的是一个派生类能够从多个基类继承。在多继承中,一个派生类可以继承多个基类的成员,这些成员可以通过派生类对象访问(一个孩子有多个父亲)。多继承的语法如下:

    class Base1 {// base class 1 members
    };class Base2 {// base class 2 members
    };class Derived : public Base1, public Base2 {// derived class members
    };
    

    在这个例子中,Derived类从Base1类和Base2类多继承,Derived类可以访问Base1Base2类中的所有公有成员。

需要注意的是,多继承在设计上可能会引入复杂性和冲突,需要谨慎使用。在多继承中,如果多个基类具有相同的成员函数或成员变量,派生类必须显式指明使用哪个基类的成员。另外,多继承还可能导致菱形继承问题,即继承图中出现多个路径指向同一个基类。为了解决这个问题,可以使用虚继承或其他技术。

总结起来,单继承和多继承是C++中的两种继承方式。单继承指的是派生类只能继承自一个基类,而多继承则允许派生类从多个基类继承。使用继承时需要考虑设计的简洁性和灵活性,以及可能引入的复杂性和冲突。


菱形继承

C++中的菱形继承(diamond inheritance)是多继承的一个特殊情况。当一个派生类通过两个不同的路径继承自同一个基类时,就会形成菱形继承结构。这种继承结构的名称源于它的图形表示类似于菱形。

考虑以下示例代码:

class A {
public:void funcA() {cout << "A::funcA()" << endl;}
};class B : public A {
public:void funcB() {cout << "B::funcB()" << endl;}
};class C : public A {
public:void funcC() {cout << "C::funcC()" << endl;}
};class D : public B, public C {
public:void funcD() {cout << "D::funcD()" << endl;}
};

在这里插入图片描述

在这个例子中,类D通过BC两个路径分别继承了类A,形成了菱形继承结构。这意味着类D会有两份来自A类的成员函数和成员变量。例如,D类实例可以调用funcA()方法两次,一次是通过B的路径,另一次是通过C的路径。

菱形继承可能导致以下问题:

  1. 虚函数二义性(Virtual Function Ambiguity):如果在菱形继承结构中,派生类中存在同名的虚函数,那么在派生类中访问这个虚函数时将产生二义性,编译器不知道应该使用哪个父类的虚函数实现。

  2. 数据冗余(Data Redundancy):由于菱形继承结构中派生类从两个不同的路径继承了同一个基类,导致派生类中会存在两份相同的成员变量。

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

  1. 虚拟继承(Virtual Inheritance)是C++中一种特殊的继承方式,用于解决多继承中的菱形继承问题。通过使用虚拟继承,可以确保在派生类中只包含一个共享的基类子对象,避免了数据冗余和虚函数二义性的问题。

虚拟继承的特点有:

虚拟继承只发生在最顶层的派生类中,而不会在派生类的派生类中发生。也就是说,只有直接派生自基类的派生类才能使用虚拟继承。

虚拟继承会在内存布局中引入一个额外的指针(虚表指针)来维护虚拟继承的关系。这个指针指向了虚拟基类的子对象,用于访问虚基类的成员。

如果虚拟基类有自己的派生类,那么虚拟继承会优先选择这个派生类中的虚基类子对象作为共享对象

  1. 重写和调用具体的父类函数:通过在派生类中重写同名的虚函数,并在派生类中显式调用具体的父类函数,可以解决虚函数二义性的问题。

综上所述,菱形继承是C++多继承的一个特殊情况,可能会引发虚函数二义性和数据冗余的问题,可以通过虚继承和重写父类函数的方式来解决这些问题。


好啦,今天的内容就到这里啦,下期内容预告C++中的多态.


结语:进阶的内容有点繁杂,大家一起加油呐!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

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

相关文章

RabbitMQ安装部署

简介 RabbitMQ一款知名的开源消息队列系统&#xff0c;为企业提供消息的发布、订阅、点对点传输等消息服务。 RabbitMQ在企业开发中十分常见&#xff0c;课程为大家演示快速搭建RabbitMQ环境。 安装 rabbitmq在yum仓库中的版本比较老&#xff0c;所以我们需要手动构建yum仓库…

# Kafka_深入探秘者(8):kafka 高级应用

Kafka_深入探秘者&#xff08;8&#xff09;&#xff1a;kafka 高级应用 一、kafka 消费组管理 1、kafka 命令行工具 参考官网: http://kafka.apache.org/22/documentation.html 2、kafka 消费组管理&#xff1a;查看消费组 # 切换到 kafka 安装目录 cd /usr/local/kafka/…

leetCode.91. 解码方法

leetCode.91. 解码方法 题目思路 题解 class Solution { public:int numDecodings(string s) {int n s.size();// dp 中f[0]一般不做使用&#xff0c;只是存一个初值1&#xff0c;表示默认由一种方案s s;vector<int> f( n 1 );f[0] 1;for ( int i 1; i < n;…

【数学】100332. 包含所有 1 的最小矩形面积 II

本文涉及知识点 数学 LeetCode100332. 包含所有 1 的最小矩形面积 II 给你一个二维 二进制 数组 grid。你需要找到 3 个 不重叠、面积 非零 、边在水平方向和竖直方向上的矩形&#xff0c;并且满足 grid 中所有的 1 都在这些矩形的内部。 返回这些矩形面积之和的 最小 可能值…

vant4的组件气泡弹出框van-popover,在列表中遍历后点击一个全部/显示隐藏,解决办法

环境&#xff1a;vue3 vant-ui4 <div v-for"(info, index) in item.infoListVOs" :key"index"><van-popoverv-model:show"showPopover":actions"actions"overlayplacement"bottom-end"select"onSelect(info…

软件工程全套学习培训资料,实际优质项目编制及各类建设方案,信息安全,运维资料

目的&#xff1a;规范系统开发流程&#xff0c;提高系统开发效率。 立项申请需求分析方案设计方案评审开发调整测试阶段系统培训试运行测试验收投入使用 所有文档过去进主页获取。 获取方式&#xff1a;本文末个人名片直接获取。 软件资料清单列表部分文档清单&#xff1a;工作…

AGI 远不止 ChatGPT!一文入门 AGI 通识及应用开发_通向agi之路网站使用什么开发的网站

AI 大语言模型进入爆发阶段 2022 年 12 月 ChatGPT 突然爆火&#xff0c;原因是其表现出来的智能化已经远远突破了我们的常规认知。虽然其呈现在使用者面前仅仅只是一个简单的对话问答形式&#xff0c;但是它的内容化水平非常强大&#xff0c;甚至在某些方面已经超过人类了&am…

程序的调试技术,设置断点

断点&#xff08;break point&#xff09;是指在代码中指定位置&#xff0c;当程序运行到此位置时变中断下来&#xff0c;并让开发者可查看此时各变量的值。因断点中断的程序并没有结束&#xff0c;可以选择继续执行。 在程序的调试过程中&#xff0c;设置断点是一个很有用的分…

可用的搜索引擎

presearchhttps://presearch.com/yandexhttps://ya.ru

书归正传,说说颍川士族

我的非遗项目是《颍川士族传说》&#xff0c;此前做的视频只是触及了边缘&#xff0c;属于气氛的营造&#xff0c;今后就正式转入主题了。 首先说说什么是士族&#xff0c;它有两个同义词&#xff1a;世族和势族。“世”是一代又一代的意思&#xff0c;“势”是权势&#xff0…

python中类的继承详解

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力&#xff1a;它可以使用现有类的所有功能&#xff0c;并在无需重新编写原来的类的情况下对这些功能进行扩展 &#xff08;1&#xff09;在类的继承中&#xff0c;存在父类跟子类&#xff0c;子类可以继…

【项目实训】数据库内容丰富

经团队讨论&#xff0c;对前端页面展示数据进行了增加&#xff0c;于是相应的修改数据库 经团队成员使用大模型对各公司面试经验中问题的总结优化&#xff0c;我们打算将大模型的回答存储到数据库中&#xff0c;以显示在前端页面 于是在数据库中存储大模型的回答&#xff1a;…

三种三相交流电动机正反转互锁电路的分析

PLC和固态继电器应用都很普及了&#xff0c;常规电磁继电器还有用武之地吗?答案是&#xff1a;有用武之地的。因为微处理器的应用使逻辑控制发生了变革&#xff0c;极大地发挥了开关功能的特性&#xff0c;但在应用中&#xff0c;它还是无法承受较大的负载&#xff0c;因此还要…

Charles网络抓包工具安装和web抓包(一)

目录 概述 抓包工具对比 安装 下载 web抓包配置 按键说明 前言-与正文无关 ​ 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们往往容易陷入工作的漩涡&#xff0c;忘记了停下脚步&#…

Unity开发者转UE 新手必读

前言 本页面为熟悉Unity的用户概述了 虚幻引擎(UE)。如果你具备一些Unity知识&#xff0c;而且想学习如何运用自己所学的知识在虚幻引擎中工作&#xff0c;下面各小节将帮助你入门。 下面的截图并排显示了Unity和虚幻编辑器。各个区域采用相同的颜色来表示相同的功能。每个区…

用友 U8+ 控制金额、单价等字段权限设置

进入路径 系统服务——权限——数据权限控制设置 本功能是数据权限设置的前提&#xff0c;用户可以根据需要先在数据权限控制设置中选择需要进行权限控制的对象。 数据权限的控制分为记录级和字段级两个层次&#xff0c;对应系统中的两个页签"记录级"和"字段…

LabVIEW在光学与光子学实验室中的应用

光学与光子学实验室致力于光学和光子学前沿领域的研究&#xff0c;涉及超快光学、非线性光学、光纤通信、光子晶体等多个方向。实验室需要高精度的实验控制和数据采集系统&#xff0c;以进行复杂的光学实验&#xff0c;并对实验数据进行实时处理和分析。 项目需求 实时控制与监…

Vue + SpringBoot 实现文件的断点上传、秒传,存储到Minio

一、前端 1. 计算文件的md5值 前端页面使用的elment-plus的el-upload组件。 <el-upload action"#" :multiple"true" :auto-upload"false" :on-change"handleChange" :show-file-list"false"><FileButton content&…

telnet_h3c_ap

import telnetlib import time # 定义交换机参数 HOSTS [10.61.168.x, ]for HOST in HOSTS:tn telnetlib.Telnet(HOST,timeout22)tn.read_until(b"Password: ")tn.write("h3capadmin".encode(ascii) b"\n") ##h3c默认密码time.sleep(0.2)tn.…

JS对象、数组、字符串超详细方法

JavaScript 对象方法 对象创建的方式 对象字面量 var dog1 {name: "大黄",age: 2,speak: function () {console.log("汪汪");}, };使用Object构造函数 var dog2 new Object(); dog2.name "大黄"; dog2.age 2; dog2.speak function () …