C++进阶-1-单继承、多继承、虚继承

C++单继承详解

1. 基础概念

继承是面向对象编程中的一个核心概念,允许一个类(子类或派生类)继承另一个类(父类或基类)的属性和方法。单继承意味着一个类只能直接继承一个父类。这种简单的结构在许多情况下是足够的,特别是在类层次结构较为简单的情况下。

2. 语法结构

在C++中,单继承的基本语法结构如下:

cpp

class Base {
    // 基类内容
};

class Derived : public Base {
    // 派生类内容
};
  • Base:基类,包含公共或保护成员。
  • Derived:派生类,继承自基类,可以访问基类的公共和保护成员。

3. 访问权限

C++提供了三种访问权限控制:

  • public:公共成员可以被任何地方访问,包括通过派生类访问。
  • protected:保护成员只能在基类及其派生类中访问,外部代码无法访问。
  • private:私有成员只能在基类内部访问,派生类无法直接访问。

示例代码

以下是一个详细的示例,展示了单继承的基本特性和用法。

cpp

#include <iostream>
using namespace std;

// 基类
class Animal {
protected:
    string name;

public:
    Animal(string n) : name(n) {} // 构造函数

    void speak() {
        cout << name << " says: Hello!" << endl;
    }
};

// 派生类
class Dog : public Animal {
public:
    Dog(string n) : Animal(n) {} // 调用基类构造函数

    void bark() {
        cout << name << " barks!" << endl;
    }
};

int main() {
    Dog myDog("Buddy");
    myDog.speak(); // 调用基类方法
    myDog.bark();  // 调用派生类方法
    return 0;
}

4. 构造与析构

在单继承中,子类的构造函数会调用父类的构造函数。可以使用初始化列表来实现这一点。析构函数也遵循相同的规则,派生类的析构函数会在对象销毁时自动调用基类的析构函数。

cpp

class Base {
public:
    Base() { cout << "Base Constructor" << endl; }
    ~Base() { cout << "Base Destructor" << endl; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived Constructor" << endl; }
    ~Derived() { cout << "Derived Destructor" << endl; }
};

int main() {
    Derived d; // 输出基类和派生类构造函数
    return 0;  // 输出派生类和基类析构函数
}

5. 多态性

单继承也可以实现多态性。通过基类指针或引用,可以调用派生类的重载方法。

cpp

class Base {
public:
    virtual void show() {
        cout << "Base class" << endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        cout << "Derived class" << endl;
    }
};

void display(Base* b) {
    b->show(); // 调用时根据对象类型决定调用哪个方法
}

int main() {
    Base b;
    Derived d;
    display(&b); // 输出 "Base class"
    display(&d); // 输出 "Derived class"
    return 0;
}

6. 优缺点

优点

  1. 简单性:单继承结构清晰,易于理解和使用。
  2. 易于维护:代码结构简单,便于维护和扩展。
  3. 减少复杂性:避免了多重继承带来的复杂性和潜在问题,如二义性。

缺点

  1. 灵活性不足:单继承限制了类的扩展性,无法利用多重继承的灵活性。
  2. 代码重复:如果多个类需要共享相同的功能,可能需要在多个类中重复代码。

7. 单继承的设计模式

单继承在许多设计模式中被广泛应用。例如:

  • 模板方法模式:基类定义一个算法的框架,子类可以实现具体的步骤。
  • 策略模式:基类定义一个接口,子类实现不同的策略。

8. 实际应用

在实际应用中,单继承被广泛用于类的层次结构设计。例如,在图形界面编程中,可以有一个基本的窗口类,所有特定类型的窗口(如对话框、主窗口等)都可以继承自这个基本窗口类。

9. 总结

C++中的单继承是一种重要的面向对象编程特性,使得代码的组织和结构变得更加清晰。在设计类结构时,应根据项目需求和复杂性,合理选择单继承或多重继承。尽管单继承有其局限性,但在许多情况下,它仍然是实现类之间关系的有效方式。

C++多继承详解

1. 基础概念

多继承是面向对象编程中的一个特性,允许一个类同时继承多个类的属性和方法。在C++中,类可以通过多继承来组合多个基类的功能,形成更复杂的类层次结构。这种灵活性使得代码复用变得更加高效,但也引入了一些复杂性。

2. 语法结构

在C++中,使用逗号分隔多个父类来实现多继承,基本语法如下:

cpp

class Base1 {
    // 基类1的内容
};

class Base2 {
    // 基类2的内容
};

class Derived : public Base1, public Base2 {
    // 派生类的内容
};

3. 访问权限

在多继承中,访问权限可以对每个基类单独设置。例如,派生类可以使用 publicprotectedprivate 来继承多个基类:

cpp

class Derived : public Base1, protected Base2 {
    // 访问权限根据需要设置
};

示例代码

以下是一个多继承的示例,展示了如何从多个基类继承功能。

cpp

#include <iostream>
using namespace std;

// 第一个基类
class Printer {
public:
    void print() {
        cout << "Printing..." << endl;
    }
};

// 第二个基类
class Scanner {
public:
    void scan() {
        cout << "Scanning..." << endl;
    }
};

// 派生类
class MultiFunctionPrinter : public Printer, public Scanner {
public:
    void copy() {
        cout << "Copying..." << endl;
    }
};

int main() {
    MultiFunctionPrinter mfp;
    mfp.print(); // 从Printer基类继承的方法
    mfp.scan();  // 从Scanner基类继承的方法
    mfp.copy();  // 自己定义的方法
    return 0;
}

4. 构造与析构

在多继承中,派生类的构造函数会依次调用每个基类的构造函数。析构函数的调用顺序是相反的,先调用派生类的析构函数,然后是基类的析构函数。

cpp

class Base1 {
public:
    Base1() { cout << "Base1 Constructor" << endl; }
    ~Base1() { cout << "Base1 Destructor" << endl; }
};

class Base2 {
public:
    Base2() { cout << "Base2 Constructor" << endl; }
    ~Base2() { cout << "Base2 Destructor" << endl; }
};

class Derived : public Base1, public Base2 {
public:
    Derived() { cout << "Derived Constructor" << endl; }
    ~Derived() { cout << "Derived Destructor" << endl; }
};

int main() {
    Derived d; // 构造顺序:Base1 -> Base2 -> Derived
    return 0;  // 析构顺序:Derived -> Base2 -> Base1
}

5. 二义性问题

多继承的一个主要问题是二义性,即如果多个基类有同名成员,派生类将无法决定使用哪个基类的成员。这种情况需要通过作用域解析运算符来解决。

cpp

class A {
public:
    void show() {
        cout << "A::show()" << endl;
    }
};

class B {
public:
    void show() {
        cout << "B::show()" << endl;
    }
};

class C : public A, public B {
public:
    void display() {
        A::show(); // 使用A的show
        B::show(); // 使用B的show
    }
};

int main() {
    C c;
    c.display();
    return 0;
}

6. 虚基类

为了解决多继承中的二义性问题,C++提供了虚基类的概念。虚基类确保在多重继承中只创建一个基类的实例,从而避免二义性。

cpp

class A {
public:
    void show() {
        cout << "A::show()" << endl;
    }
};

class B : virtual public A {
public:
    void show() {
        cout << "B::show()" << endl;
    }
};

class C : virtual public A {
public:
    void show() {
        cout << "C::show()" << endl;
    }
};

class D : public B, public C {
public:
    void display() {
        A::show(); // 确保只调用一个A的实例
    }
};

int main() {
    D d;
    d.display();
    return 0;
}

7. 优缺点

优点

  1. 灵活性:多继承允许组合多个类的功能,增加了类的灵活性和复用性。
  2. 功能扩展:可以通过继承多个基类来扩展功能,满足复杂的需求。
  3. 代码复用:通过多继承可以重用多个基类中的代码,减少代码重复。

缺点

  1. 复杂性:多继承可能导致类层次结构变得复杂,难以理解和维护。
  2. 二义性问题:当多个基类有同名成员时,会引起二义性,增加了使用难度。
  3. 内存开销:虚基类的使用可能导致内存开销增加。

8. 实际应用

多继承常用于需要组合多个功能的场景,例如:

  • 接口组合:在设计API时,可以通过多继承组合多个接口。
  • 设备驱动:在硬件驱动开发中,可能需要同时继承多个设备的功能。
  • 游戏开发:在游戏开发中,可以通过多继承创建具有多个特性的角色或对象。

9. 设计模式中的多继承

在一些设计模式中,多继承被广泛应用,例如:

  • 适配器模式:通过多继承实现不同接口的适配。
  • 桥接模式:将抽象部分与实现部分分离,可以通过多继承实现不同的实现。

注意事项

在使用多继承时,有几个注意事项:

  1. 清晰性:确保类的设计是清晰的,避免不必要的复杂性。
  2. 避免滥用:多继承虽然强大,但不应随意使用,应根据需要合理设计。
  3. 使用虚基类:在多重继承时,如果存在共同基类,考虑使用虚基类以避免二义性。

10. 总结

C++中的多继承是面向对象编程的重要特性,允许一个类同时继承多个基类的功能。尽管多继承提供了灵活性和代码复用,但也带来了复杂性和潜在的二义性问题。在设计类层次结构时,开发者应考虑是否真的需要多继承,权衡其优缺点,以实现更高效、更可维护的代码。

C++虚继承详解

1. 基础概念

虚继承是C++中处理多重继承的一种机制,旨在解决多继承时可能出现的二义性问题。二义性问题出现在两个或多个基类继承自同一个父类时,派生类会继承多个父类的实例,导致相同成员的冲突。虚继承确保在多重继承中只创建一个基类的实例,从而有效避免这种冲突。

2. 语法结构

在C++中,使用关键字 virtual 来声明虚基类。基本语法如下:

cpp

class Base {
    // 基类内容
};

class Derived1 : virtual public Base {
    // 派生类1
};

class Derived2 : virtual public Base {
    // 派生类2
};

class FinalDerived : public Derived1, public Derived2 {
    // 最终派生类
};

3. 访问权限

与普通继承相同,虚继承也支持访问权限控制。可以为虚基类指定 publicprotectedprivate,以控制派生类对基类成员的访问权限。

示例代码

以下是一个简单的虚继承示例,展示了如何使用虚继承来解决二义性问题。

cpp

#include <iostream>
using namespace std;

// 基类
class Base {
public:
    void show() {
        cout << "Base class show()" << endl;
    }
};

// 第一个派生类
class Derived1 : virtual public Base {
public:
    void show() {
        cout << "Derived1 class show()" << endl;
    }
};

// 第二个派生类
class Derived2 : virtual public Base {
public:
    void show() {
        cout << "Derived2 class show()" << endl;
    }
};

// 最终派生类
class FinalDerived : public Derived1, public Derived2 {
public:
    void display() {
        // 显示调用的基类方法
        Base::show(); // 调用Base类的方法
    }
};

int main() {
    FinalDerived fd;
    fd.display(); // 调用显示方法
    return 0;
}

5. 虚继承的工作机制

共享基类实例

虚继承的核心在于确保派生类只拥有一个基类的实例。对于上述示例中的 Base 类,Derived1Derived2 都虚继承自 Base,在 FinalDerived 中只会有一个 Base 的实例。这通过在内存中使用虚表(vtable)来实现。

构造顺序

在虚继承的情况下,构造函数的调用顺序如下:

  1. 先构造虚基类的实例(即 Base)。
  2. 然后构造派生类(如 Derived1Derived2)。
  3. 最后构造最终派生类(FinalDerived)。

6. 优缺点

优点

  1. 消除二义性:虚继承确保在多继承中只有一个基类实例,从而消除二义性。
  2. 代码复用:允许多个派生类共享父类的实现,减少代码重复。
  3. 清晰的层次结构:虚继承提供了更清晰的类层次结构,易于管理。

缺点

  1. 复杂性:引入虚继承可能使类的设计变得更加复杂,增加理解和维护的难度。
  2. 性能开销:使用虚继承会增加内存开销,因为需要维护虚表和指针。
  3. 构造与析构的复杂性:构造和析构顺序的特殊处理可能导致混淆。

7. 实际应用

虚继承多用于以下几种场景:

  1. 接口设计:在设计需要多个接口的类时,虚继承可以确保接口的唯一性。
  2. 多重继承:当多个类需要继承同一基类时,虚继承可以避免重复实例化,减少内存使用。
  3. 复杂系统:在复杂系统中,尤其是涉及多个模块和组件的系统,虚继承可以帮助管理类之间的关系。

注意事项

在使用虚继承时,需要注意以下几点:

  1. 合理使用:只有在确实需要解决二义性问题时,才应使用虚继承。
  2. 清晰设计:确保类的设计清晰,避免不必要的复杂性。
  3. 构造函数:理解虚基类的构造函数调用顺序,以避免潜在的错误。

示例扩展

以下是一个更复杂的示例,展示了虚继承的灵活性和强大功能。

cpp

#include <iostream>
using namespace std;

// 基类
class Vehicle {
public:
    Vehicle() { cout << "Vehicle constructed." << endl; }
    void drive() { cout << "Driving a vehicle." << endl; }
};

// 虚基类
class Car : virtual public Vehicle {
public:
    Car() { cout << "Car constructed." << endl; }
    void honk() { cout << "Car honking!" << endl; }
};

// 虚基类
class Truck : virtual public Vehicle {
public:
    Truck() { cout << "Truck constructed." << endl; }
    void load() { cout << "Loading truck." << endl; }
};

// 最终派生类
class Pickup : public Car, public Truck {
public:
    Pickup() { cout << "Pickup constructed." << endl; }
};

int main() {
    Pickup p;
    p.drive(); // 调用Vehicle类的方法
    p.honk();  // 调用Car类的方法
    p.load();  // 调用Truck类的方法
    return 0;
}

8. 设计模式中的虚继承

在一些设计模式中,虚继承的应用也相当普遍。例如:

  • 适配器模式:通过虚继承组合不同类型的接口,使得对象可以互相适配。
  • 桥接模式:将抽象部分与实现部分分离,使用虚继承可以实现不同的实现。

9. 进阶概念

虚析构函数

为了安全地删除通过基类指针指向的派生类对象,基类应使用虚析构函数。这确保在删除时会调用派生类的析构函数。

cpp

class Base {
public:
    virtual ~Base() { cout << "Base Destructor" << endl; }
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived Destructor" << endl; }
};

int main() {
    Base* b = new Derived();
    delete b; // 正确调用Derived和Base的析构函数
    return 0;
}

10. 总结

C++中的虚继承是解决多继承中二义性问题的重要机制。通过确保在多重继承中只创建一个基类的实例,虚继承提高了代码的可复用性和管理性。然而,虚继承引入的复杂性和性能开销也不容忽视。在设计类层次结构时,应合理考虑虚继承的使用,以实现更高效和可维护的代码结构。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是虚拟机?常用虚拟机软件有哪些?

目录 VMware Workstation Oracle VM VirtualBox Microsoft Hyper-V 虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;是一种通过软件模拟的具有完整硬件系统功能的、运行在计算机上的软件。它允许用户在单一物理机器上同时运行多个操作系统&#xff0c;每个…

git branch -r(--remotes )显示你本地仓库知道的所有 远程分支 的列表

好的&#xff0c;git branch -r 这个命令用于列出远程分支。让我详细解释一下&#xff1a; 命令&#xff1a; git branch -rdgqdgqdeMac-mini ProductAuthentication % git branch -rorigin/main作用&#xff1a; 这个命令会显示你本地仓库知道的所有 远程分支 的列表。它不…

Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)

01.生命周期 Vue生命周期&#xff1a;就是一个Vue实例从创建 到 销毁 的整个过程 生命周期四个阶段&#xff1a;① 创建 ② 挂载 ③ 更新 ④ 销毁 1.创建阶段&#xff1a;创建响应式数据 2.挂载阶段&#xff1a;渲染模板 3.更新阶段&#xff1a;修改数据&#xff0c;更新视图 4…

安装SQL Server2019 Developer版本时出现“服务没有及时响应启动或控制请求”的问题

1. 异常描述 2. 异常分析 应该是数据库服务所属账户的权限不够&#xff0c;可以设置为Administrator&#xff1b; 3. 异常解决 参考资料&#xff1a;https://blog.csdn.net/zi_longh/article/details/130293081 注意&#xff1a;SQL Server代理和SQL Server数据库引擎的账户…

【系统移植】制作SD卡启动——将uboot烧写到SD卡

在开发板上启动Linux内核&#xff0c;一般有两种方法&#xff0c;一种是从EMMC启动&#xff0c;还有一种就是从SD卡启动&#xff0c;不断哪种启动方法&#xff0c;当开发板上电之后&#xff0c;首先运行的是uboot。 制作SD卡启动&#xff0c;首先要将uboot烧写到SD卡&#xff…

2. FPGA基础了解--全局网络

前言 引入扇出的概念介绍FPGA中的全局网络为后续时序优化埋下伏笔 扇出 在FPGA设计中扇出是一个重要的概念&#xff0c;所谓的扇出就是一个控制信号所能控制的数据信号的总个数&#xff0c;比如ctrl信号的扇出就是16 reg ctrl 0; reg [15:0] out 0; always (posedge c…

RAGFlow(3):VScode端口转发在在本机浏览(比内网穿透好用)

docker会在内网服务器上的80端口部署&#xff0c;然而内网Ip是无法访问到的&#xff0c;所以无法看到页面。所以之前想到的解决方法是利用zerotier工具做内网穿透&#xff0c;将内网服务器的公网ip和本机ip组成一个局域网&#xff0c;把内网Ip变成了192.168xxx&#xff0c;这样…

生成式AI大模型未来发展趋势:开启创造力无限可能

随着人工智能技术的不断突破&#xff0c;生成式AI大模型正逐渐成为业界关注的焦点。从文本生成、图像创作到音乐创作&#xff0c;生成式AI大模型在多个领域展现出惊人的创造力。展望未来&#xff0c;生成式AI大模型的发展趋势将呈现以下特点&#xff1a; 一、模型规模持续扩大&…

Mybatis增删改查(配置文件版)

准备环境 1、数据库表tb_brand 2、实体类Brand 3、测试用例 3、1在test包中的java包中创建测试类com.xyy.test.MybatisTest.java 4、安装MyBatisX插件 添加插件后&#xff0c;因为在Mapper代理开发时&#xff0c;Mapper接口要和Mapper.xml映射文件放在同一个报下&#xff0…

Activiti开启流程实例

开始绘流程图&#xff0c;首先右击鼠标可以看到一下图标&#xff0c;都有相对应的意思 画好一个简易的流程过后&#xff0c;可以看到xml文件中已经有了 右击生成png格式的图片 图片点击后就是一个视图的效果 将流程文件部署 Test public void testDeploy() {//1.创建流程引擎P…

12.19问答解析

概述 某中小型企业有四个部门&#xff0c;分别是市场部、行政部、研发部和工程部&#xff0c;请合理规划IP地址和VLAN&#xff0c;实现企业内部能够互联互通&#xff0c;同时要求市场部、行政部和工程部能够访问外网环境(要求使用OSPF协议)&#xff0c;研发部不能访问外网环境…

完全离线使用,效率直接拉满

现在越来越多的人使用OCR软件来提高自己的工作效率&#xff0c;今天给大家推荐一款电脑端的文字识别工具&#xff0c;对比以往的软件来说&#xff0c;功能更加丰富全面。 Umi-OCR 美术、舞蹈、音乐 打开软件之后需要安装一下。 软件主要有截图OCR识别、批量OCR识别、批量文档识…

UITableView实现通讯录效果

// // TableViewIndexViewController.m // study2024 // // Created by figo zhu on 2024/12/22. //#import "TableViewIndexViewController.h" //实现协议UITableViewDelegate,UITableViewDataSource interface TableViewIndexViewController ()<UITableView…

【HarmonyOs学习日志(14)】计算机网络之域名系统DNS

域名系统DNS 域名系统DNS——从域名解析出IP地址 文章目录 域名系统DNS概述域名到IP地址的解析 互联网的域名结构命名标准 域名服务器域名的解析过程 概述 域名系统DNS&#xff08;Domain Name System&#xff09;是互联网使用的命名系统&#xff0c;用来把便于人们使用的机器…

贪心算法【Lecode_HOT100】

文章目录 1.买卖股票的最佳时机No.1212.跳跃游戏No.553.跳跃游戏IINo.454.划分字母区间No.763 1.买卖股票的最佳时机No.121 class Solution {public int maxProfit(int[] prices) {if (prices null || prices.length 0) {return 0;}// 初始化买入价格为最大值&#xff0c;最大…

【PCIe 总线及设备入门学习专栏 4 -- PCIe四种地址空间介绍】

文章目录 Overview1. 配置空间2. IO 空间3. Memory 空间BAR空间示例 -- 32bit内存地址空间请求BAR空间示例 -- 64bit内存地址空间请求 4. message空间 转自&#xff1a;cpu_arch 芯片架构笔记 2024年08月03日 22:32 上海 Overview PCIe架构定义了4种地址空间&#xff1a;配置…

SAP HCM 成本分配涉及的表

导读 成本分配:在HCM模块在SAP模块中&#xff0c;核心就是成分如何与财务无缝衔接&#xff0c;今天介绍的是关于成本中心在HR模块中涉及的几张表&#xff0c;对精细化管理有相应的帮助。涉及的信息类型有0014、0015、0267等 作者&#xff1a;vivi&#xff0c;来源&#xff1a;o…

Java模拟Mqtt客户端连接Mqtt Broker

Java模拟Mqtt客户端基本流程 引入Paho MQTT客户端库 <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.mqttv5.client</artifactId><version>1.2.5</version> </dependency>设置mqtt配置数据 …

Day13 用Excel表体验梯度下降法

Day13 用Excel表体验梯度下降法 用所学公式创建Excel表 用Excel表体验梯度下降法 详见本Day文章顶部附带资源里的Excel表《梯度下降法》&#xff0c;可以对照表里的单元格公式进行理解&#xff0c;还可以多尝试几次不同的学习率 η \eta η来感受&#xff0c;只需要更改学习率…

rk3568之mpp开发笔记怎么实现mpp编码摄像头实时码流?

前言&#xff1a; 大家好&#xff0c;今天给大家分享的内容是在rk3568上&#xff0c;通过mpp来进行实时对摄像头imx415采集的码流数据进行编码成h264或者h265,后期文章&#xff0c;会开发测试一下通过rtsp推流出去&#xff0c;然后电脑端拉流&#xff0c;看一下整个环路的延迟多…