【C++ 】面向对象三大特性之封装和继承 详解

news2025/4/4 15:45:45

目录

  • 0 引言
  • 1 封装
    • 1.1 封装是什么?
    • 1.2 封装的优缺点
  • 2 继承
    • 2.1 继承的定义
    • 2.2 继承方式
    • 2.3 继承时名字遮蔽
    • 2.6 遮蔽、函数重写、函数重载的区别
    • 2.5 继承时的对象模型
    • 2.6 派生类与基类互相赋值原则
    • 2.6 继承时的构造函数和析构函数

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:C++专栏
  • 📜 相关文章:C++多态详解
  • 💥 标题:【C++ 】面向对象三大特性:封装、继承、多态 详解
  • ❣️ 寄语:人生的意义或许是可以发挥自己全部的潜力,所以加油吧!
  • 🎈 最后:文章作者技术和水平有限,如果文中出现错误,希望大家能指正

0 引言

封装、继承、多态是面向对象的三大特性。接着本篇博客大家一起回顾一下

1 封装

1.1 封装是什么?

封装(Encapsulation)是面向对象编程中的一种重要概念,指将数据和操作封装在一个单元中,通过对外提供公共接口来访问和操作数据,同时隐藏内部的实现细节。

在C++中封装通过类来实现。类将数据成员和成员函数组合在一起,形成一个封闭的单元。数据成员可以是私有的(private),只能在类的内部访问;成员函数可以是公共的(public),被外部代码调用以访问和修改数据。这种封装机制提供了数据的安全性和灵活性。

1.2 封装的优缺点

其实封装最显著的特点就是将复杂的实现过程隐藏起来,不暴露给外界。(例如开汽车,我们不需要知道复杂的汽车是如何实现的,只需要通过一些简单的操作就能让汽车启动起来)

封装的优点

  • 数据隐藏和安全性:通过将数据成员设为私有,封装可以防止外部代码直接访问和修改对象的数据,只允许通过公共接口进行操作。这样可以有效地保护数据的完整性和安全性。
  • 接口统一和简化:封装可以将相关的操作封装在一起,形成一个简洁的公共接口。外部代码只需要调用接口函数,而无需了解具体的实现细节,使代码使用更加方便和易懂
  • 代码模块化和维护性:封装促进了代码的模块化,使得各个模块之间的耦合度降低。当需要修改实现时,只需修改封装类内部的代码,而不会影响到外部代码,提高了代码的维护性和可复用性。
  • 增强了代码的可靠性:封装可以通过公共接口对数据进行有效的验证和控制,避免了错误的数据访问和操作。这样可以减少bug的产生,提高代码的可靠性。

封装的缺点

  • 间接性和性能开销:封装导致代码的间接性增加,因为需要通过函数调用来访问和操作数据。这会引入一定的性能开销,特别是对于频繁调用的函数。
  • 不利于对数据的直接访问:封装限制了对数据的直接访问,可能导致某些特定场景下的效率问题。在某些情况下,直接访问数据可能比通过函数调用更有效。

综上所述,封装是面向对象编程的重要特征,它通过将数据和操作封装在一起,提供了数据的隐藏和安全性,简化了接口,增强了代码的可维护性和可靠性。尽管存在一些缺点,但在大多数情况下,封装的优点远远超过了其缺点,使得代码更加可靠、易用和可维护。

2 继承

它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程,从抽象到具体,例如从动物类到猫类。

2.1 继承的定义

C++ 继承是面向对象编程中的一种重要概念,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为。继承可以通过创建一个新的类,该类从一个或多个现有类派生而来,以实现代码的重用和扩展。

在C++中,继承通过关键字class后面的冒号来声明。派生类在其类定义中指定基类的名称,如下所示:

class DerivedClass : access-specifier BaseClass
{
    // class body
};

其中,DerivedClass 是派生类的名称,BaseClass 是基类的名称,access-specifier 可以是public、protected或private,用于指定派生类对基类成员的访问权限

2.2 继承方式

  • 继承方式限定了基类成员在派生类中的访问权限,包括public(公有的)、private(私有的)和protected(受保护的)。此项是可选项,如果不写,默认为private(成员变量和成员函数默认也是private)。
  • public成员可以通过对象来访问,private成员不能通过对象访问,protected成员和private成员类似,也不能通过对象访问。但是当存在继承关系时,protected和private就不一样了:基类中的protected成员可以在派生类中使用,而基类中的private成员不能在派生类中使用。(这里要注意一个混淆点:基类中的protected成员,假如被派生类以private的方式继承,那么其protected成员就变成private成员了,也就不能在派生类中直接访问了)

C++中的继承方式

  • 公有继承(public inheritance):使用public关键字声明派生类对基类的继承,基类中的公有成员在派生类中仍然为公有成员,保持其访问权限不变。
  • 保护继承(protected inheritance):使用protected关键字声明派生类对基类的继承,基类中的公有成员在派生类中变为保护成员,不能被外部访问,只能在派生类的成员函数中访问。
  • 私有继承(private inheritance):使用private关键字声明派生类对基类的继承,基类中的公有成员在派生类中变为私有成员,不能被外部访问,只能在派生类的成员函数中访问。

通过上述分析可以得到以下几点结论

  1. 基类成员在派生类中的访问权限不得高于继承方式中指定的权限。(例如当继承方式为protected时,那么基类成员在派生类中的访问权限最高也为protected,高于protected的会降级为protected,但低于protected不会升级。再如,当继承方式为public时,那么基类成员在派生类中的访问权限将保持不变。也就是说,继承方式中的public、protected、private是用来指明基类成员在派生类中的最高访问权限的。)
  2. 不管继承方式如何,基类中的private成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。
  3. 如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为public或protected;只有那些不希望在派生类中使用的成员才声明为private。
  4. 如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为protected

注意:我们这里说的是基类的private成员不能在派生类中使用,并没有说基类的private成员不能被继承。实际上,基类的private成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了。private成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。

2.3 继承时名字遮蔽

名字遮蔽(Name Hiding):当派生类中定义了和基类成员名称相同的成员时,派生类成员会遮蔽(隐藏)基类成员。这意味着在派生类中无法直接访问被遮蔽的基类成员。如果需要访问被遮蔽的基类成员,可以使用作用域解析运算符::来指定基类的名称

class BaseClass {
public:
    int x;
};

class DerivedClass : public BaseClass {
public:
    int x; // 遮蔽了基类的成员x

    void print() {
        cout << "DerivedClass x: " << x << endl; // 访问派生类的成员x
        cout << "BaseClass x: " << BaseClass::x << endl; // 访问基类的成员x
    }
};

在派生类的成员函数print()中,可以使用DerivedClass::x来访问派生类的成员x,使用BaseClass::x来访问基类的成员x。

2.6 遮蔽、函数重写、函数重载的区别

在继承中,我们经常会遇到名字遮蔽(Name Hiding)、函数重写(Function Overriding)和函数重载(Function Overloading)这三个概念很容易弄混淆。

(1)名字遮蔽(Name Hiding)
名字遮蔽指的是在派生类中定义了与基类中的成员名称相同的成员。当派生类中存在与基类相同名称的成员时,基类的成员会被遮蔽,即无法直接访问基类中被遮蔽的成员。

 class BaseClass {
 public:
     void method() {
         // base class method
     }
 };

 class DerivedClass : public BaseClass {
 public:
     void method() {
         // derived class method, hides the base class method
     }
 };
在上述例子中,派生类DerivedClass定义了一个名为method的成员函数,该函数与基类BaseClass的成员函数method名称相同。
由于名字遮蔽的存在,派生类的method会隐藏基类的method,在派生类中无法直接访问基类的method。

(2)函数重写(Function Overriding)
函数重写指的是在派生类中定义一个与基类中相同名称、参数列表和返回类型的成员函数,用来覆盖(override)基类中的同名函数。与名字遮蔽不同,基类中的同名函数是虚函数,被vitural关键字修饰

class BaseClass {
public:
    virtual void method() {
        // base class method
    }
};

class DerivedClass : public BaseClass {
public:
    void method() {
        // derived class method, overrides the base class method
    }
};
在上述例子中,DerivedClass重写了BaseClass中的方法method。
通过在基类函数声明中添加virtual关键字,可以实现动态多态性,确保在运行时根据对象的实际类型调用适当的函数版本。

(3)函数重载(Function Overloading)
函数重载指的是在同一个作用域中定义多个具有相同名称但不同参数列表的函数。函数重载允许使用相同的函数名称来执行不同的操作,提供了更加灵活的函数调用方式。(同一作用域是重载,不同作用域是遮蔽

    class MyClass {
    public:
        void method(int x) {
            // method with int parameter
        }

        void method(double x) {
            // method with double parameter
        }
    };
在上述例子中,MyClass定义了两个名字相同但参数类型不同的成员函数method,分别接受intdouble类型的参数。
这样,根据所传递的参数类型,编译器可以选择调用合适的函数版本。

总结

  • 名字遮蔽(Name Hiding)发生在派生类中定义了与基类成员名称相同的成员,并导致基类成员在派生类中无法直接访问。
  • 函数重写(Function Overriding)是指在派生类中定义与基类中函数名称、参数列表和返回类型相同的成员函数,用于覆盖基类函数并实现对基类行为的修改或扩展。
  • 函数重载(Function Overloading)允许在同一作用域中定义具有相同名称但参数列表不同的多个函数,通过不同的参数类型或参数个数来实现不同的操作。

这些概念在C++继承中具有不同的作用和行为,可以根据需要灵活地使用。

2.5 继承时的对象模型

  1. 无遮蔽时的对象模型
    派生类的内存模型可以看成是基类成员变量新增成员变量总和所有成员函数仍在另一个区域(代码区),由所有对象共享
  2. 有遮蔽时的对象模型
    基类中被遮蔽的成员变量,仍然会在派生类对象的内存中。

在C++对象模型中,一个类的对象通常由两部分组成:对象成员虚函数表(对于含有虚函数的类)。
以下是C++对象模型的几个重要要素:

  1. 非静态数据成员
    类中的非静态数据成员(包括基类的成员和派生类自己的成员)被继承时,会按照它们声明的顺序依次排列在对象中。这样就实现了对成员的内存布局。
  2. 虚函数表(vtable)
    虚函数表是C++实现多态性的关键机制。当一个类声明了虚函数,编译器会为该类创建一个虚函数表。虚函数表是一个函数指针数组,包含了该类所有虚函数的地址。每个对象都有一个指向其类的虚函数表的指针(通常被称为虚指针),用于调用适当的虚函数。
  3. 虚指针(vptr)
    虚指针是一个指向虚函数表的指针,位于对象或类的开头。虚指针的存在使得C++的运行时多态性成为可能。通过虚指针,编译器可以在运行时根据对象的实际类型来调用正确的虚函数。
  4. 虚基类
    当一个类被多个派生类继承时,可能会出现多个派生类共享同一个基类的实例(称为共享对象)。为了避免创建多个共享对象,C++引入了虚基类的概念。在对象模型中,虚基类的子对象只会在派生类的继承层次结构中存在一次,并且被所有派生类共享。
  5. 对象大小和内存对齐
    C++对象的大小由其成员变量的总大小决定。为了高效访问对象成员,编译器通常会对对象进行内存对齐。对齐规则要么由编译器的默认规则决定,要么可以通过对齐属性指定。

2.6 派生类与基类互相赋值原则

  1. 可以使用派生类对象给基类对象赋值,但是不能使用基类对象给派生类赋值。(原因:切片原则:因为基类对象中不包含派生类新增的成员,所以在使用基类对象给派生类赋值的时候缺少参数,就会报错。但是派生类对象给基类对象赋值时,多余的参数就不管了)
    在这里插入图片描述

  2. 可以使用父类指针指向子类对象,但是不能使用子类指针指向父类对象。如果一定要指向,进行强制类型转换后可以,但是会有指针越界访问的问题。(原因:父类的指针可以指向子类中继承自父类的部分,但是子类指针如果指向父类中没有的成员时,访问会有问题,也就是超出父类对象的范围)

  3. 可以使用父类的引用去引用子类,不能使用子类的引用引用父类,与指针原理相同

2.6 继承时的构造函数和析构函数

  1. 构造函数:创建子类对象时,编译器会默认先调用父类的构造函数,再调用子类的构造函数。
  2. 析构函数:刚好和构造函数顺序相反,子类对象被销毁时,编译器默认先调用子类的析构函数,再调用父类的析构函数。(不能在子类析构中调用父类析构,因为如果是指针类型的话,同一块区域就会被释放两次,第二次释放的时候已经不存在那个地址了,就会造成野指针问题)

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

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

相关文章

LeetCode二叉树OJ

目录 剑指 Offer 55 - I. 二叉树的深度 - 力扣&#xff08;LeetCode&#xff09; 965. 单值二叉树 - 力扣&#xff08;LeetCode&#xff09; 100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 101. 对称二叉树 - 力扣&#xff08;LeetCode&#xff09; 二叉树遍历_牛客题…

【网络编程】套接字编程——TCP通信

文章目录 一、简单的TCP网络程序1. 单进程版2. 多进程版3. 多线程版 二、线程池版TCP网络程序三、日志与守护进程1. 日志2. 守护进程进程组和会话的引出守护进程的创建 一、简单的TCP网络程序 1. 单进程版 &#x1f495; tcpServer.hpp #pragma once #include "err.hpp…

iPhone手机记笔记工具选择用哪个

iPhone手机大家应该都比较熟悉&#xff0c;其使用性能是比较流畅的&#xff0c;在iPhone手机上记录笔记可以帮助大家快速地进行总结工作、记录工作内容等&#xff0c;在iPhone手机上记笔记工具选择用哪个呢&#xff1f; 可以在iPhone手机上使用的笔记工具是比较多的&#xff0…

OPC UA客户端工具Softing OPC Client使用图文教程

简介 Softing OPC Client 是一个用于与 OPC (OLE for Process Control) 服务器进行通信的工具。下面是一个详细的图文教程&#xff0c;以帮助您使用 Softing OPC Client 进行 OPC 通信。 1. 下载和安装 Softing OPC Client 首先&#xff0c;您需要从 Softing 官方网站下载 S…

csgo饰品市场会崩盘吗?如何评价现在的csgo饰品市场?

如何评价现在的csgo市场&#xff1f; csgo饰品市场会崩盘吗&#xff1f;如何评价现在的csgo饰品市场&#xff1f; 如何来评价现在CSGO市场&#xff1f;哈喽&#xff0c;大家好&#xff0c;我是童话姐姐&#xff0c;那么从长远来看&#xff0c;我觉得这个CSGO市场它一定是呈现一…

渗透测试KAILI系统的安装环境(第八课)

KAILI系统的安装环境(第八课) Kaili是一款基于PHP7的高性能微服务框架&#xff0c;其核心思想是面向服务的架构&#xff08;SOA&#xff09;&#xff0c;支持http、websocket、tcp等多种通信协议&#xff0c;同时还提供了RPC、Service Mesh、OAuth2等功能。Kaili框架非常适合构…

c语言练习85:通讯录的实现(基于顺序表实现)

通讯录的实现(基于顺序表实现&#xff09; 基于动态顺序表实现通讯录 C语⾔基础要求&#xff1a;结构体、动态内存管理、顺序表、⽂件操作 1、功能要求 1&#xff09;⾄少能够存储100个⼈的通讯信息 2&#xff09;能够保存⽤⼾信息&#xff1a;名字、性别、年龄、电话、地址…

Android性能优化,可以从那些方面解决?方案一览

说到Android性能优化大家都很熟悉&#xff0c;这是一个老生畅谈的话题与技术。本篇讲讲Android性能优化需要学习那些&#xff0c;让这些技术做到极致。虽然老生常谈但是一直是一个合格的Android开发人员需要掌握的重点。要想进入大厂也是重要的敲门砖。 Android性能优化重要性…

【FreeRTOS】【STM32】02 FreeRTOS 移植

基于 [野火]《FreeRTOS%20内核实现与应用开发实战—基于STM32》 正点原子《STM32F429FreeRTOS开发手册_V1.2》 准备 基础工程&#xff0c;例如点灯 FreeRTOS 系统源码 FreeRTOS 移植 上一章节已经说明了Free RTOS的源码文件在移植时所需要的&#xff0c;FreeRTOS 为我们提供…

leetcode:1967. 作为子字符串出现在单词中的字符串数目(python3解法)

难度&#xff1a;简单 给你一个字符串数组 patterns 和一个字符串 word &#xff0c;统计 patterns 中有多少个字符串是 word 的子字符串。返回字符串数目。 子字符串 是字符串中的一个连续字符序列。 示例 1&#xff1a; 输入&#xff1a;patterns ["a","abc&…

AlGaN/GaN结构的氧基数字蚀刻

引言 宽带隙GaN基高电子迁移率晶体管(HEMTs)和场效应晶体管(fet)能够提供比传统Si基高功率器件更高的击穿电压和电子迁移率。常关GaN非常需要HEMT来降低功率并简化电路和系统架构&#xff0c;这是GaN HEMT技术的主要挑战之一。凹进的AlGaN/GaN结构是实现常关操作的有用选择之一…

使用IDEA自带功能将WSDL转java

好像IDEA2018版本之后不再支持webservice转java&#xff0c;可以下载2018.3.6版本的IDEA&#xff08;直接IDEA官网下载即可&#xff09;&#xff0c;然后打开一个项目&#xff0c;在根目录处单击右键 选择Generate Java Code From Wsdl...&#xff0c; 选择OK&#xff0c;即可…

二、DMSP/OLS夜光数据校正之饱和校正

一、前言 首先需要将DMSP/OLS夜光数据下载,那么这里方便大家,可以直接私信我获得DMPS/OLS和NPP/VIIRS夜光原始数据,以百度云网盘形式分享给大家。 当把34期DMSP/OLS夜光数数据下载至电脑之后,解压后可以看到如下图的数据。 选择稳定平均灯光数据作为我们研究数据,也就是F…

MYSQL的事务原理

事务基础 事务概念 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务特性 原子性&#xff08;Atomicity&#xff09…

中兴通讯加入 “数字孪生网络基础框架”开源合作计划

在近日举行的“预见未来——数字孪生网络&#xff08;DTN&#xff09;”分论坛上&#xff0c;中国移动研究院不仅发布了“数字孪生网络基础框架”成果&#xff0c;同时与中兴通讯等合作伙伴正式启动了“数字孪生网络基础框架”开源&#xff08;Open-DTN&#xff09;合作计划。 …

基于SSM的国学文化网站设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

Lenovo联想笔记本IdeaPad YOGA 710-11IKB(80V6)原装出厂Win10系统镜像

下载链接&#xff1a;https://pan.baidu.com/s/1qAJ6QSQ0NV1Lmwv3YTqwHw?pwdrqxa 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大小&#xff1a;9.62GB 注&#xff1a;…

API攻防-接口安全SOAPOpenAPIRESTful分类特征导入项目联动检测

文章目录 概述什么是接口&#xff1f; 1、API分类特征SOAP - WSDLWeb services 三种基本元素&#xff1a; OpenApi - Swagger UISpringboot Actuator 2、API检测流程Method&#xff1a;请求方法URL&#xff1a;唯一资源定位符Params&#xff1a;请求参数Authorization&#xff…

关键词搜索1688商品列表数据接口,1688商品列表数据接口

在网页抓取方面&#xff0c;可以使用 Python、Java 等编程语言编写程序&#xff0c;通过模拟 HTTP 请求&#xff0c;获取1688网站上的商品页面。在数据提取方面&#xff0c;可以使用正则表达式、XPath 等方式从 HTML 代码中提取出有用的信息。值得注意的是&#xff0c;1688网站…

docker--使用docker login 报错解决方案

我们在本地使用 docker login 命令登录时报错&#xff0c;可以尝试一下先 docker logout 命令退出登录后&#xff0c;在使用 docker login命令进行登录操作&#xff1b; docker logout