(五)C++的类继承、多态、组合

在C++中,类继承(Class Inheritance)是面向对象编程(OOP)的核心概念之一。它允许一个类(称为派生类或子类)继承另一个类(称为基类或父类)的属性(数据成员)和行为(成员函数)。通过继承,可以实现代码的重用、建立类之间的层次结构以及实现多态性。以下是对C++类继承的详细介绍,包括其定义、语法、类型、实现方式、访问控制以及多态性。

一.继承

定义
类继承是指一个类(派生类)从另一个类(基类)派生出来,从而获得基类的成员(数据成员和成员函数)。派生类可以扩展或修改基类的功能。

语法

class BaseClass {// 基类的成员
};class DerivedClass : accessSpecifier BaseClass {// 派生类的成员
};BaseClass:基类的名称。
DerivedClass:派生类的名称。
accessSpecifier:继承的访问控制符,可以是publicprotectedprivate

继承的类型

a. 公有继承(Public Inheritance)

class Derived : public Base {// ...
};

访问权限:
public成员在派生类中仍然是public。
protected成员在派生类中仍然是protected。
private成员在派生类中不可访问

示例:

class Base {
public:void publicFunc() {}
protected:void protectedFunc() {}
private:void privateFunc() {}
};class Derived : public Base {
public:void test() {publicFunc();      // 合法protectedFunc();   // 合法// privateFunc();  // 非法,不可访问}
};

b. 受保护继承(Protected Inheritance)

class Derived : protected Base {// ...
};

访问权限:
public和protected成员在派生类中都是protected
private成员在派生类中不可访问
示例:

class Derived : protected Base {
public:void test() {publicFunc();      // 合法,变为 protectedprotectedFunc();   // 合法// privateFunc();  // 非法}
};

c. 私有继承(Private Inheritance)

class Derived : private Base {// ...
};

访问权限:
public和protected成员在派生类中都是private
private成员在派生类中不可访问

示例:

class Derived : private Base {
public:void test() {publicFunc();      // 合法,变为 privateprotectedFunc();   // 合法,变为 private// privateFunc();  // 非法}
};

实现方式
a. 单继承
一个派生类继承自一个基类。

示例:

class Animal {
public:void eat() {}
};class Dog : public Animal {
public:void bark() {}
};

b. 多继承
一个派生类继承自多个基类。
示例:

class Person {
public:void speak() {}
};class Employee {
public:void work() {}
};class Manager : public Person, public Employee {
public:void manage() {}
};

c. 虚继承(Virtual Inheritance)
用于解决多继承中的菱形继承问题,确保基类在派生类中只有一份实例。

示例:

class A {
public:void func() {}
};class B : virtual public A {// ...
};class C : virtual public A {// ...
};class D : public B, public C {// ...
};

访问控制
公有继承(public):基类的public和protected成员在派生类中保持原样,private成员不可访问。
受保护继承(protected):基类的public和protected成员在派生类中都变为protected,private成员不可访问。
私有继承(private):基类的public和protected成员在派生类中都变为private,private成员不可访问

多态性
通过继承和虚函数,可以实现多态性,即同一个函数调用可以根据对象的实际类型执行不同的实现

示例:

#include <iostream>class Shape {
public:virtual void draw() const {std::cout << "Drawing Shape" << std::endl;}
};class Circle : public Shape {
public:void draw() const override {//重写基类方法draw()std::cout << "Drawing Circle" << std::endl;}
};void render(const Shape& shape) {shape.draw();
}int main() {Circle circle;render(circle); // 输出: Drawing Circlereturn 0;
}

注意事项
构造函数和析构函数:派生类的构造函数会先调用基类的构造函数,析构函数则相反,先调用派生类的析构函数,再调用基类的析构函数。
重写(Override)在派生类中重写基类的虚函数时,使用override关键字可以提高代码的可读性和安全性。
防止继承:使用final关键字可以防止类被继承。
访问控制:合理使用访问控制符,确保类的封装性和安全性。

二.多态

多态(Polymorphism)是面向对象编程(OOP)的核心概念之一,它允许不同类的对象以统一的方式进行处理。多态性使得程序能够根据对象的实际类型,在运行时调用相应的方法,从而实现灵活和可扩展的代码。在C++中,多态主要通过虚函数(Virtual Functions)和基类指针或引用来实现。以下是对多态的详细介绍,包括其定义、类型、实现方式、注意事项以及示例。

多态是指同一个操作可以作用于不同类型的对象上,并产生不同的行为。换句话说,多态允许程序在运行时根据对象的实际类型调用相应的方法,而不是编译时绑定的类型。

类型
a. 编译时多态(静态多态)
定义:在编译时确定调用哪个函数。
实现方式:
函数重载:同一个函数名有不同的参数列表。
模板(Templates):使用泛型编程实现不同类型的操作。

示例:

#include <iostream>// 函数重载
void print(int x) {std::cout << "Integer: " << x << std::endl;
}void print(double x) {std::cout << "Double: " << x << std::endl;
}int main() {print(5);       // 调用 void print(int)print(3.14);    // 调用 void print(double)return 0;
}

b. 运行时多态(动态多态)
定义:在运行时确定调用哪个函数。
实现方式
*虚函数(Virtual Functions)
声明:在基类中使用virtual关键字声明函数。
重写:在派生类中使用override关键字重写虚函数。
动态绑定:通过基类指针或引用调用虚函数时,根据实际对象的类型执行相应的实现。

示例:

class Shape {
public:virtual void draw() const {std::cout << "Drawing Shape" << std::endl;}
};class Circle : public Shape {
public:void draw() const override {std::cout << "Drawing Circle" << std::endl;}
};

纯虚函数(Pure Virtual Functions)和抽象类(Abstract Classes)
纯虚函数:在基类中声明为virtual且没有实现的函数,通常用于定义接口。
抽象类:包含至少一个纯虚函数的类,不能实例化,只能被继承。

示例:

class Shape {
public:virtual void draw() const = 0; // 纯虚函数
};class Circle : public Shape {
public:void draw() const override {std::cout << "Drawing Circle" << std::endl;}
};

注意事项
虚函数表(vtable):C++通过虚函数表实现动态绑定,每个包含虚函数的类都有一个虚函数表,每个对象都有一个指向虚函数表的指针。这会增加一定的内存开销和函数调用开销。
性能影响:动态绑定相比于静态绑定有一定的性能开销,但在大多数情况下,这种开销是可以接受的。
避免过度使用:虽然多态性提供了强大的功能,但过度使用可能导致代码难以理解和维护。应根据实际需求合理使用多态性。
虚析构函数:如果类中包含虚函数,建议将析构函数也声明为virtual,以确保在删除基类指针指向的派生类对象时,能够正确调用派生类的析构函数。

示例:

class Base {
public:virtual ~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* obj = new Derived();delete obj; // 输出:// Derived destructor// Base destructorreturn 0;
}

三.菱形继承问题

在C++中,菱形继承问题(Diamond Problem) 是多重继承(Multiple Inheritance)带来的一种常见问题。
当一个类继承自两个或多个基类,而这些基类又共同继承自同一个基类时,就会出现菱形继承问题。以下是对菱形继承问题的详细解释:
1. 菱形继承问题的定义
在这种继承结构中,类D通过类B和类C间接继承了类A两次,这导致了菱形继承问题。

       A  基类/ \B   C  单继承\ /D   多重继承

菱形继承问题的影响
由于类D通过两条路径继承了类A的成员(通过类B和类C),这会导致以下问题
命名冲突:类D中可能存在来自类A的重复成员(变量或函数),导致命名冲突。
数据冗余:类D中可能存在多个来自类A的实例,导致数据冗余
二义性:在类D中访问类A的成员时,会产生二义性,因为编译器不知道应该使用哪条继承路径

示例:

#include <iostream>class A {
public:void show() {std::cout << "A::show()" << std::endl;}
};class B : public A {// B 继承自 A
};class C : public A {// C 继承自 A
};class D : public B, public C {// D 多重继承自 B 和 C
};int main() {D d;d.show(); // 编译错误:二义性return 0;
}d.show() 会导致编译错误,因为编译器不知道调用的是 B::A::show() 还是 C::A::show()

解决方案
a.使用虚拟继承(Virtual Inheritance)
虚拟继承可以解决菱形继承问题,使得最终派生类只继承基类的一份成员。

示例:

#include <iostream>class A {
public:void show() {std::cout << "A::show()" << std::endl;}
};class B : virtual public A {// B 虚拟继承自 A
};class C : virtual public A {// C 虚拟继承自 A
};class D : public B, public C {// D 多重继承自 B 和 C
};int main() {D d;d.show(); // 正确调用 A::show()return 0;
}

b.显式指定基类
如果不使用虚拟继承,可以通过显式指定基类来消除二义性。

示例:

d.B::show(); // 调用 B::A::show()
d.C::show(); // 调用 C::A::show()

注意事项
虚拟继承的开销:虚拟继承会增加一些额外的开销,因为需要维护共享基类的实例。
设计复杂性:虚拟继承增加了继承结构的复杂性,可能使代码难以理解和维护。
慎用多重继承:在设计类层次结构时,尽量避免不必要的多重继承,使用接口(抽象类)或组合(Composition)来替代多重继承,可以减少菱形继承问题的发生。

四.组合

在C++中,**组合(Composition)**是一种面向对象编程的设计原则,它通过在一个类中包含另一个类的对象来实现代码的复用和模块化。组合强调的是“有一个”的关系,即一个类由一个或多个其他类的对象组成。与继承相比,组合通常提供了更大的灵活性和更低的耦合度。

组合是指一个类包含一个或多个其他类的对象作为其成员。这种关系表示“整体-部分”的关系,即一个类(整体)由其他类(部分)组成。

组合与继承区别:

组合继承
关系类型 “有一个”关系类型 “是一个”
耦合度低耦合度高
灵活性高灵活性低
代码复用方式 通过包含对象代码复用方式通过扩展类
不直接支持多态性支持多态性
适用场景需要复用代码但不需要多态性时适用场景需要实现多态性或扩展类功能时

组合的实现
通过在类中声明其他类的对象作为成员变量来实现组合

#include <iostream>
#include <string>// 引擎类
class Engine {
public:void start() {std::cout << "Engine started." << std::endl;}void stop() {std::cout << "Engine stopped." << std::endl;}
};// 车轮类
class Wheel {
public:void inflate(int psi) {std::cout << "Wheel inflated to " << psi << " psi." << std::endl;}
};// 汽车类(组合)
class Car {
public:Car() : engine(), wheels() {std::cout << "Car created." << std::endl;}void start() {engine.start();}void stop() {engine.stop();}void inflateWheels(int psi) {for (int i = 0; i < 4; ++i) {wheels[i].inflate(psi);}}private:Engine engine;       // 汽车包含一个引擎Wheel wheels[4];    // 汽车包含四个车轮
};int main() {Car myCar;myCar.start();           // 输出: Engine started.myCar.inflateWheels(32); // 输出: Wheel inflated to 32 psi. (四次)myCar.stop();            // 输出: Engine stopped.return 0;
}

组合与继承的选择
使用组合
当你只需要复用功能而不需要多态性时。
当你希望降低类之间的耦合度时。
当你希望更灵活地改变组合关系时。

使用继承
当你需要实现多态性时。
当你希望扩展类的功能或行为时。
当“是一个”关系更合适时。

五.动态联编和静态联编

在C++中,**动态联编(Dynamic Binding)和静态联编(Static Binding)**是两种不同的函数调用机制,它们决定了在程序运行时如何解析函数调用。理解这两种联编方式对于掌握多态性、虚函数以及面向对象编程至关重要。

静态联编
静态联编是指在编译阶段就确定了函数调用的具体实现。这种绑定方式基于对象的静态类型(即声明类型),而不是对象的实际类型。

实现方式
普通函数调用:非虚函数的调用是静态联编的。
函数重载:编译器根据参数类型和数量在编译时确定调用哪个重载版本。
模板实例化:编译器在编译时生成具体的函数实例。

特点
效率高:由于在编译时已经确定了调用哪个函数,运行时没有额外的开销。
灵活性低:无法根据对象的实际类型动态改变函数的行为。

示例

#include <iostream>class Base {
public:void display() const {std::cout << "Base display" << std::endl;}
};class Derived : public Base {
public:void display() const {std::cout << "Derived display" << std::endl;}
};void show(const Base& obj) {obj.display(); // 静态联编,调用 Base::display()
}int main() {Derived d;show(d); // 输出: Base displayreturn 0;
}

尽管show函数接收的是一个Derived对象的引用,但由于display函数不是虚函数,调用的是Base::display(),这是静态联编的结果。

动态联编
动态联编是指在运行时根据对象的实际类型确定函数调用的具体实现。这种绑定方式允许程序在运行时根据对象的实际类型动态改变函数的行为。

实现方式
虚函数(Virtual Functions):在基类中声明为virtual的函数,在派生类中重写。
基类指针或引用:通过基类指针或引用调用虚函数时,调用的是实际对象的重写版本。

特点
灵活性高:可以根据对象的实际类型动态改变函数的行为。
性能开销:由于需要在运行时查找虚函数表,存在一定的性能开销。
内存开销:每个包含虚函数的类都有一个虚函数表,每个对象都有一个指向虚函数表的指针。

示例

#include <iostream>class Base {
public:virtual void display() const {std::cout << "Base display" << std::endl;}
};class Derived : public Base {
public:void display() const override {std::cout << "Derived display" << std::endl;}
};void show(const Base& obj) {obj.display(); // 动态联编,调用实际对象的重写版本
}int main() {Derived d;show(d); // 输出: Derived displayreturn 0;
}

示例对比

#include <iostream>class Base {
public:void func() const {std::cout << "Base func (static)" << std::endl;}virtual void vfunc() const {std::cout << "Base vfunc (dynamic)" << std::endl;}
};class Derived : public Base {
public:void func() const {std::cout << "Derived func (static)" << std::endl;}void vfunc() const override {std::cout << "Derived vfunc (dynamic)" << std::endl;}
};void callFunc(const Base& obj) {obj.func();    // 静态联编,调用 Base::func()obj.vfunc();   // 动态联编,调用 Derived::vfunc()
}int main() {Derived d;callFunc(d);return 0;
}
Base func (static)
Derived vfunc (dynamic)

在上述示例中,func是普通函数,调用的是Base::func(),这是静态联编的结果。而vfunc是虚函数,调用的是Derived::vfunc(),这是动态联编的结果。

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

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

相关文章

算法学习笔记之贪心算法

导引&#xff08;硕鼠的交易&#xff09; 硕鼠准备了M磅猫粮与看守仓库的猫交易奶酪。 仓库有N个房间&#xff0c;第i个房间有 J[i] 磅奶酪并需要 F[i] 磅猫粮交换&#xff0c;硕鼠可以按比例来交换&#xff0c;不必交换所有的奶酪 计算硕鼠最多能得到多少磅奶酪。 输入M和…

oracle执行grant授权sql被阻塞问题处理

一 问题描述 执行普通的grant授权sql(grant select,update on 表名 to 用户名)好几分钟都没反应&#xff0c;跟被阻塞了似的。 二 问题排查 #排查是否有阻塞 用OEM可以看到阻塞信息&#xff1a; 点‘性能’-‘阻塞会话’&#xff1a; 下面那个会话2958是我执行grant sql的…

SSM仓库物品管理系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.用户登录代码&#xff1a;2.保存物品信息代码&#xff1a;3.删除仓库信息代码&#xff1a; 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SSM框架开发的仓库…

Deepseek 接入Word处理对话框(隐藏密钥)

硅基流动邀请码&#xff1a;1zNe93Cp 邀请链接&#xff1a;网页链接 亲测deepseek接入word&#xff0c;自由调用对话&#xff0c;看截图有兴趣的复用代码&#xff08;当然也可以自己向deepseek提问&#xff0c;帮助你完成接入&#xff0c;但是提问逻辑不一样给出的答案是千差万…

Docker Compose介绍及安装使用MongoDB数据库详解

在现代容器化应用部署中&#xff0c;Docker Compose是一种非常实用的工具&#xff0c;它允许我们通过一个docker-compose.yml文件来定义和运行多容器应用程序。然而&#xff0c;除了Docker之外&#xff0c;Podman也提供了类似的工具——Podman Compose&#xff0c;它允许我们在…

IntelliJ IDEA Console控制台输出成json的配置方式

【IntelliJ IDEA Console控制台输出成json的配置方式】 1.帮助->查找操作 2.搜索注册表 3.ctrlf 搜索pty 控制台右键 结果

基础入门-HTTP数据包红蓝队研判自定义构造请求方法请求头修改状态码判断

知识点&#xff1a; 1、请求头&返回包-方法&头修改&状态码等 2、数据包分析-红队攻击工具&蓝队流量研判 3、数据包构造-Reqable自定义添加修改请求 一、演示案例-请求头&返回包-方法&头修改&状态码等 数据包 客户端请求Request 请求方法 …

react redux用法学习

参考资料&#xff1a; https://www.bilibili.com/video/BV1ZB4y1Z7o8 https://cn.redux.js.org/tutorials/essentials/part-5-async-logic AI工具&#xff1a;deepseek&#xff0c;通义灵码 第一天 安装相关依赖&#xff1a; 使用redux的中间件&#xff1a; npm i react-redu…

机器学习 - 线性回归(最大后验估计)

最大似然估计的一个缺点是当训练数据比较少时会发生过拟合&#xff0c;估计的参数可能不准确.为了避免过拟合&#xff0c;我们可以给参数加上一些先验知识. 一、先从最大似然估计的一个缺点入手 最大似然估计&#xff08;MLE&#xff09;在处理小样本数据时&#xff0c;容易发…

2025.2.8——二、Confusion1 SSTI模板注入|Jinja2模板

题目来源&#xff1a;攻防世界 Confusion1 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;查看网页源码信息 step 2&#xff1a;模板注入 step 3&#xff1a;构造payload&#xff0c;验证漏洞 step 4&#xff1a;已确认为SSTI漏洞中的Jinjia2…

Moretl 增量文件采集工具

永久免费: <下载> <使用说明> 用途 定时全量或增量采集工控机,电脑文件或日志. 优势 开箱即用: 解压直接运行.不需额外下载.管理设备: 后台统一管理客户端.无人值守: 客户端自启动,自更新.稳定安全: 架构简单,兼容性好,通过授权控制访问. 架构 技术架构: Asp…

基于STM32的ADS1230驱动例程

自己在练手项目中用到了ADS1230&#xff0c;根据芯片手册自写的驱动代码&#xff0c;已测可用&#xff0c;希望对将要用到ADS1230芯片的人有所帮助。 芯片&#xff1a;STM32系列任意芯片、ADS1230 环境&#xff1a;使用STM32CubeMX配置引脚、KEIL 部分电路&#xff1a; 代码…

HarmonyOS 5.0应用开发——NodeContainer自定义占位节点

【高心星出品】 文章目录 NodeContainer自定义占位节点案例开发步骤全部代码 NodeContainer自定义占位节点 NodeContainer是用来占位的系统组件&#xff0c;主要用于自定义节点以及自定义节点树的显示&#xff0c;支持组件的通用属性&#xff0c;对通用属性的处理请参考默认左…

26~31.ppt

目录 26.北京主要的景点 题目 解析 27.创新产品展示及说明会 题目​ 解析 28.《小企业会计准则》 题目​ 解析 29.学习型社会的学习理念 题目​ 解析 30.小王-产品展示信息 题目​ 解析 31.小王-办公理念-信息工作者的每一天 题目​ 解析 26.北京主要的景点…

单张照片可生成写实3D头部模型!Adobe提出FaceLift,从单一的人脸图像中重建出360度的头部模型。

FaceLift是Adobe和加州大学默塞德分校推出的单图像到3D头部模型的转换技术,能从单一的人脸图像中重建出360度的头部模型。FaceLift基于两阶段的流程实现:基于扩散的多视图生成模型从单张人脸图像生成一致的侧面和背面视图;生成的视图被输入到GS-LRM重建器中,产出详细的3D高斯表…

在Uniapp中使用阿里云OSS插件实现文件上传

在开发小程序时&#xff0c;文件上传是一个常见的需求。阿里云OSS&#xff08;Object Storage Service&#xff09;是一个强大的云存储服务&#xff0c;可以帮助我们高效地存储和管理文件。本文将介绍如何在Uniapp小程序中使用阿里云OSS插件实现文件上传功能。 1. 准备工作 首…

Tomcat添加到Windows系统服务中,服务名称带空格

要将Tomcat添加到Windows系统服务中&#xff0c;可以通过Tomcat安装目录中“\bin\service.bat”来完成&#xff0c;如果目录中没有service.bat&#xff0c;则需要使用其它方法。 打到CMD命令行窗口&#xff0c;通过cd命令跳转到Tomcat安装目录的“\bin\”目录&#xff0c;然后执…

Android Studio集成讯飞SDK过程中在配置Project的时候有感

在配置讯飞的语音识别SDK&#xff08;流式版&#xff09;时候&#xff0c;跟着写了两个Demo&#xff0c;一个是YuYinTestDemo01&#xff0c;另一个是02&#xff0c;demo01比较简单&#xff0c;实现功能图象也比较简陋&#xff0c;没用讯飞SDK提供的图片&#xff0c;也就是没用到…

DeepSeek 助力 Vue 开发:打造丝滑的进度条

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

NLP Word Embeddings

Word representation One-hot形式 在上一周介绍RNN类模型时&#xff0c;使用了One-hot向量来表示单词的方式。它的缺点是将每个单词视为独立的&#xff0c;算法很难学习到单词之间的关系。 比如下面的例子&#xff0c;即使语言模型已经知道orange juice是常用组合词&#xf…