当前位置: 首页 > news >正文

【C++】继承详解

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 📢前言
  • 🏳️‍🌈一、C++ 继承的基本概念
  • 🏳️‍🌈二、继承中的访问控制
    • ❤️一)不同继承方式的影响
    • 🧡二)父类成员的设置与访问
  • 🏳️‍🌈三、继承中的构造和析构函数
    • ❤️一)父类构造和析构的调用
    • 🧡二)构造和析构的执行顺序
  • 🏳️‍🌈四、继承中的默认成员函数
    • ❤️一)子类构造函数的初始化
    • 🧡二)拷贝构造、赋值运算符重载与析构
  • 👥总结


📢前言

继承(inheritance) 机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量),这样产生新的类,称子类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,继承是类设计层次的复用。


🏳️‍🌈一、C++ 继承的基本概念

在这里插入图片描述
C++ 中的继承是一种强大的面向对象编程特性,它允许一个类(派生类)从另一个类(基类)获取成员变量和成员函数。继承的定义在于通过特定的语法建立类之间的层次关系,实现代码的复用和扩展。

其特点显著,一方面,它大大减少了重复代码的编写,提高了代码的重用性和可扩展性。另一方面,它也增加了类之间的耦合性,当基类的某些成员发生改变时,派生类可能需要相应的修改。

继承的语法通常如下:class 派生类名: 继承方式 基类名 { // 派生类的成员 }; 。继承方式主要包括公有继承(public)保护继承(protected)私有继承(private)

在公有继承中,基类的公有和保护成员在派生类中保持其原有访问属性;保护继承下,基类的公有和保护成员在派生类中变为保护成员;私有继承时,基类的所有成员在派生类中都变为私有成员。

例如,定义一个基类Person和派生类Student

class Person {
public:void showInfo() {std::cout << "Person Info" << std::endl;}
protected:int age;
};class Student: public Person {
public:void study() {std::cout << "Student is studying" << std::endl;}
};

在上述示例中,Student类通过公有继承获取了Person类的showInfo方法,并且可以在自身中定义新的方法study

总之,C++ 的继承机制为程序设计提供了极大的灵活性和可扩展性,但也需要谨慎使用,以避免不必要的复杂性和错误。

🏳️‍🌈二、继承中的访问控制

❤️一)不同继承方式的影响

在 C++ 的继承体系中,不同的继承方式会显著影响子类对父类成员的访问权限。

  • 公有继承(public):父类的公有成员和保护成员在子类中保持原有的访问级别。这意味着子类可以直接访问父类的公有成员和保护成员。例如,在上述Person和Student的示例中,由于Student类是公有继承自Person类,所以Student类的对象可以直接调用Person类的showInfo方法。

  • 私有继承(private):父类的公有成员和保护成员在子类中都变为私有成员。这使得子类对象在类内外都无法直接访问父类的这些成员,只有在子类内部的成员函数中可以通过特定方式访问。

  • 保护继承(protected):父类的公有成员和保护成员在子类中都变为保护成员。子类对象在类外无法访问这些成员,但子类内部的成员函数可以访问。

🧡二)父类成员的设置与访问

合理设置父类成员的访问权限对于实现特定的设计需求至关重要。

如果希望子类能够完全继承并自由使用父类的某些成员,应将其设置为公有成员或保护成员。例如,在一个图形库中,父类Shape的一些通用属性如颜色、线条宽度等可能被设置为保护成员,以便派生类Circle、Rectangle等能够在内部进行处理和扩展。

相反,如果某些成员仅希望在父类内部使用,应将其设置为私有成员,防止子类的误访问和修改。比如,父类中的一些内部计算逻辑或临时变量。

总之,通过合理设置父类成员的访问权限,可以更好地控制类的封装性和可扩展性,减少潜在的错误和混乱。

🏳️‍🌈三、继承中的构造和析构函数

❤️一)父类构造和析构的调用

在继承关系中,当创建派生类对象时,父类的构造函数会先被调用,用于初始化从父类继承的成员。这是因为在构建派生类对象时,需要先确保父类部分的成员得到正确初始化。而在销毁派生类对象时,析构函数的调用顺序则相反,即先调用派生类的析构函数,然后再调用父类的析构函数,以保证资源的正确释放和清理。

例如:

class Parent {
public:Parent() {std::cout << "Parent constructor" << std::endl;}~Parent() {std::cout << "Parent destructor" << std::endl;}
};class Child : public Parent {
public:Child() {std::cout << "Child constructor" << std::endl;}~Child() {std::cout << "Child destructor" << std::endl;}
};int main() {Child c;return 0;
}

输出结果为:

Parent constructor
Child constructor
Child destructor
Parent destructor

🧡二)构造和析构的执行顺序

在复杂的继承和组合情况下,构造函数的执行顺序通常是先调用最顶层父类的构造函数,然后按照继承层次依次向下调用各派生类的构造函数,最后调用成员对象的构造函数。而析构函数的执行顺序则完全相反,先调用自身的析构函数,然后依次向上调用各派生类和父类的析构函数,最后调用成员对象的析构函数。
例如,在多重继承的情况下:

class Base1 {
public:Base1() {std::cout << "Base1 constructor" << std::endl;}~Base1() {std::cout << "Base1 destructor" << std::endl;}
};class Base2 {
public:Base2() {std::cout << "Base2 constructor" << std::endl;}~Base2() {std::cout << "Base2 destructor" << std::endl;}
};class Derived : public Base1, public Base2 {
public:Derived() {std::cout << "Derived constructor" << std::endl;}~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Derived d;return 0;
}

输出结果为:

Base1 constructor
Base2 constructor
Derived constructor
Derived destructor
Base2 destructor
Base1 destructor

在继承和组合情况混搭的情况下,如:

class Object {
public:Object() {std::cout << "Object constructor" << std::endl;}~Object() {std::cout << "Object destructor" << std::endl;}
};class Parent {
public:Parent() {std::cout << "Parent constructor" << std::endl;}~Parent() {std::cout << "Parent destructor" << std::endl;}
};class Child : public Parent {
private:Object obj;
public:Child() {std::cout << "Child constructor" << std::endl;}~Child() {std::cout << "Child destructor" << std::endl;}
};int main() {Child c;return 0;
}

输出结果为:

Parent constructor
Object constructor
Child constructor
Child destructor
Object destructor
Parent destructor

🏳️‍🌈四、继承中的默认成员函数

❤️一)子类构造函数的初始化

在继承关系中,子类的构造函数通常需要借助父类的构造函数来完成对继承成员的初始化。当子类对象被创建时,如果父类的构造函数不是默认的无参构造函数,那么子类的构造函数就需要在初始化列表中明确调用父类的特定构造函数。例如,如果父类有一个带参数的构造函数,子类在构造自己的对象时,就需要在初始化列表中调用父类的这个带参构造函数,以确保从父类继承的成员得到正确的初始化。如果父类的构造函数存在多个参数,子类在初始化列表中也需要按照相应的顺序和类型提供这些参数。

class Parent {
public:Parent(int num) {std::cout << "Parent constructor with num: " << num << std::endl;}
};class Child : public Parent {
public:Child(int num) : Parent(num) {std::cout << "Child constructor" << std::endl;}
};int main() {Child c(10);return 0;
}

🧡二)拷贝构造、赋值运算符重载与析构

在继承场景中,拷贝构造函数、赋值运算符重载和析构函数具有独特的特性。拷贝构造函数承担着创建并复制对象的责任,无论在何种情况下,都需要构造基类部分。赋值运算符重载则主要针对已经构造好的对象进行赋值操作。
对于析构函数,当子类对象被销毁时,先执行子类的析构函数,然后再执行父类的析构函数,以确保资源的正确释放。在子类的拷贝构造函数和赋值运算符重载函数中,如果没有显式地处理父类的相关操作,可能会导致父类部分的成员没有被正确拷贝或赋值。

例如,在下面的代码中:

class Parent {
public:Parent() {std::cout << "Parent constructor" << std::endl;}~Parent() {std::cout << "Parent destructor" << std::endl;}
};class Child : public Parent {
public:Child() {std::cout << "Child constructor" << std::endl;}Child(const Child& c) {std::cout << "Child copy constructor" << std::endl;}Child& operator=(const Child& c) {std::cout << "Child assignment operator" << std::endl;return *this;}~Child() {std::cout << "Child destructor" << std::endl;}
};int main() {Child c1;Child c2 = c1;c2 = c1;return 0;
}

输出结果展示了拷贝构造函数、赋值运算符重载函数和析构函数的执行顺序和效果。


👥总结

本篇博文对 C++ 继承 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Swift性能优化:掌握Swift性能分析工具的实用指南
  • C++基础面试题 | 什么是C++的列表初始化?
  • 大模型介绍
  • 趣味算法------拯救阿拉德大陆
  • 岩土工程中的渗流问题:有限单元法的理论与实践
  • 音频播放+音频采样(绘制音波)
  • 如何利用 Go 语言开发高性能服务
  • 银行卡三要素验证如何用PHP进行调用
  • 基于GPT回答:结合不同专业,论述GIS应用的关键技术问题
  • 零工市场Java源码,支持私有化部署?
  • devops学习思路
  • AI写小说第九天
  • 【mysql集群之组复制】
  • CUDA编程之CUDA Sample-5_Domain_Specific-simpleGL
  • html+css+js网页设计 个人简历1个页面
  • ECS应用管理最佳实践
  • Javascripit类型转换比较那点事儿,双等号(==)
  • JavaScript 奇技淫巧
  • java多线程
  • Java深入 - 深入理解Java集合
  • Markdown 语法简单说明
  • MySQL用户中的%到底包不包括localhost?
  • nodejs:开发并发布一个nodejs包
  • quasar-framework cnodejs社区
  • Yeoman_Bower_Grunt
  • 产品三维模型在线预览
  • 多线程 start 和 run 方法到底有什么区别?
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 小程序上传图片到七牛云(支持多张上传,预览,删除)
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • - 语言经验 - 《c++的高性能内存管理库tcmalloc和jemalloc》
  • 在Mac OS X上安装 Ruby运行环境
  • 阿里云ACE认证学习知识点梳理
  • ​iOS安全加固方法及实现
  • ​ssh免密码登录设置及问题总结
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • # Panda3d 碰撞检测系统介绍
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #FPGA(基础知识)
  • #我与Java虚拟机的故事#连载08:书读百遍其义自见
  • $(function(){})与(function($){....})(jQuery)的区别
  • (4)(4.6) Triducer
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (十八)三元表达式和列表解析
  • (限时免费)震惊!流落人间的haproxy宝典被找到了!一切玄妙尽在此处!
  • (转)视频码率,帧率和分辨率的联系与区别