C++中的虚拟继承机制探讨

STM32硬件SPI驱动ADS1255/1256避坑指南:从Cube MX配置到稳定采样实战 本文详细解析了使用STM32硬件SPI驱动高精度ADC芯片ADS1255/1256的完整流程与关键陷阱。重点阐述了Cube MX中SPI模式(必须为CPOL=1, CPHA=1)、数据帧格式(MSB First)及波特率(建议从256kHz起)的正确配置,并提供了分步读写时序的代码实现与逻辑分析仪调试方法,帮助开发者避开常见坑点,实现稳定可靠的高精度数据采样。 阅读详情

    在谈虚拟继承前让我们先了解一下为什么需要虚拟继承:
    多重继承
    在多重继承中,基类的构造函数的调用次序既不受派生类构造函数初始化列表中出现的基类构造函数的影响,也不受基类在构造函数初始化列表中的出现次序的影响,它按照基类在类派生列表中的出现次序依次调用相应的基类构造函数。析构顺序与构造顺序逆序进行。
    多重继承中,派生类的指针或引用可以转换为其任意基类的指针或引用。因此,这种转换更可能遇到二义性问题。
    在多重继承中,成员函数中使用的名字的查找首先在函数本身进行,如果不能在本地找到名字,就继续在成员的类中查找,然后同时(并行)查找所有基类继承子树。多重继承的派生类有可能从两个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。
    注意,多重继承中首先发生名字查找。你可能会感到吃惊的是,即使两个继承的同名函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公有或者受保护的,也是错误的。或者,在一个类给定义了函数,而在另一个类中没有定义,调用仍是错误的。例如:
    #include <iostream>
    class Base1 {
    public:
    void print() {}
    void display() {}
    void show() {}
    };
    class Base2 {
    public:
    void print(int) {}
    void show(int);
    private:
    void display(int) {}
    };
    class Derived: public Base1, public Base2 {
    };
    int main () {
    Derived d;
    d.print();
    d.display();
    d.show();
    return 0;
    }
    编译结果(MinGW2.05):
    Compiling source file(s)…
    main.cpp
    main.cpp: In function `int main()':
    main.cpp:24: error: request for member `print' is ambiguous
    main.cpp:13: error: candidates are: void Base2::print(int)
    main.cpp:6: error:                 void Base1::print()
    main.cpp:25: error: request for member `display' is ambiguous
    main.cpp:16: error: candidates are: void Base2::display(int)
    main.cpp:7: error:                 void Base1::display()
    main.cpp:26: error: request for member `show' is ambiguous
    main.cpp:14: error: candidates are: void Base2::show(int)
    main.cpp:8: error:                 void Base1::show()
    Test.exe - 9 error(s), 0 warning(s)
    解决这种二义性的方法可以是通过指定使用哪个类的版本(即带上类名前缀)来解决。但最好的方法是,在解决二义性的派生类中定义函数的一个版本。
    虚继承
    在标准I/O库中的类都继承了一个共同的抽象基类ios,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。istream和ostream类直接继承这个公共基类,库定义了另一个名为isotream的类,它同时继承istream和ostream,iostream类既可以对流进行读又可以对流进行写。如果I/O类型使用常规继承,则每个iostream对象可能包含两个ios子对象:一个包含在它的istream子对象中,另一个包含在它的ostream子对象中。从设计角度讲,这个实现是错误的:iostream类想要对单个缓冲区进行读和写,它希望跨越输入和输出操作符共享条件状态。
    在C++中,通过使用虚继承(virtual inheritance)解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类(virtual base class)。
    通过在派生类列表中包含关键字virtual设置虚基类,例如:
    class istream : public virtual ios {…};
    class ostream : virtual public ios {…};
    class iostream : public istream, public ostream {…};
    假定通过多个派生路径继承名为X的成员,有下面三种可能性:
    1)如果在每个路径中X表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例;
    2)如果在某个路径中X是虚基类的成员,而在另一路径中X是后代派生类的成员,也没有二义性--特定派生类实例的优先级高于共享虚基类实例。
    3)如果沿每个继承路径X表示后代派生类的不同成员,则该成员的直接访问是二义性的。
    例如:
    #include <iostream>
    class B {
    public:
    void print() {
    std::cout 《 "B" 《 std::endl;
    }
    };
    class D1: public virtual B {
    };
    class D2: public virtual B {
    public:
    void print() {
    std::cout 《 "D2" 《 std::endl;
    }
    };
    class DD: public D1, public D2 {
    };
    int main () {
    DD d;
    d.print();    // ok: call D2::print
    return 0;
    }

特殊的初始化语义
    通常,每个类只初始化自己的直接基类。在应用于虚基类的时候,这个初始化策略会失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该虚基类的每个继承路径初始化。
    为了解决这个重复初始化问题,从具有虚基类的类继承的类对初始化进行特殊处理。在虚派生中,由最低层派生类的构造函数初始化虚基类。
    虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化式只在创建中间类型的对象时使用。
    例如,我们有四个类:ZooAnimal, Bear, Raccoon和Panda,它们之间构造一个继承层次:Bear和Raccoon继承ZooAnimal,Panda继承Bear和Raccoon.那么,它们的构造函数就形如:
    Bear::Bear(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Bear") {}
    Raccoon::Raccoon(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Raccoon") {}
    Panda::Panda(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Panda"), Bear(name, onExhibit), Raccoon(name, onExhibit) {}
    当创建Panda对象的时候,构造过程如下:
    1)首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分;
    2)接下来,构造Bear部分(在派生列表中Bear在前)。忽略Bear初始化列表中用于ZooAnimal构造函数的初始化式;
    3)然后,构造Raccoon部分,再次忽略ZooAnimal初始化式;
    4)最后,构造Panda部分。
    如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数;如果ZooAnimal没有默认构造函数,则代码出错。
    无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。
    例如,有下面的继承关系:
    class Character { };
    class BookCharater: public Character { };
    class ToyAnimal { };
    class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal { };
    直观继承图为:
 
    按声明次序检查直接基类,确定是否存在虚基类。上例中,首先检查BookCharacter的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。按从根类开始向下到最低层派生类的次序检查每个子树。在这里依次检查到ZooAnimal和ToyAnimal为虚基类。
    TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal(检查到的顺序)。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookCharacter,它导致调用Character构造函数,然后是Bear.在这里,由最低层派生类TeddyBear指定用于ZooAnimal和ToyAnimal的初始化式。

当然,对于析构函数的调用顺序与构造函数相反。
    示例代码如下:
    #include <iostream>
    class Character {
    public:
    Character() {
    std::cout 《 "Character Constructor" 《 std::endl;
    }
    ~Character() {
    std::cout 《 "Character Destructor" 《 std::endl;
    }
    };
    class BookCharacter: public Character {
    public:
    BookCharacter() {
    std::cout 《 "BookCharacter Constructor" 《 std::endl;
    }
    ~BookCharacter() {
    std::cout 《 "BookCharacter Destructor" 《 std::endl;
    }
    };
    class ZooAnimal {
    public:
    ZooAnimal() {
    std::cout 《 "ZooAnimal Constructor" 《 std::endl;
    }
    ~ZooAnimal() {
    std::cout 《 "ZooAnimal Destructor" 《 std::endl;
    }
    };
    class Bear: public virtual ZooAnimal {
    public:
    Bear() {
    std::cout 《 "Bear Constructor" 《 std::endl;
    }
    ~Bear() {
    std::cout 《 "Bear Destructor" 《 std::endl;
    }
    };
    class ToyAnimal {
    public:
    ToyAnimal() {
    std::cout 《 "ToyAnimal Constructor" 《 std::endl;
    }
    ~ToyAnimal() {
    std::cout 《 "ToyAnimal Destructor" 《 std::endl;
    }
    };
    class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal {
    public:
    TeddyBear() {
    std::cout 《 "TeddyBear Constructor" 《 std::endl;
    }
    ~TeddyBear() {
    std::cout 《 "TeddyBear Destructor" 《 std::endl;
    }
    };
    int main () {
    TeddyBear tb;
    return 0;
    }
    运行结果如下:
    ZooAnimal Constructor
    ToyAnimal Constructor
    Character Constructor
    BookCharacter Constructor
    Bear Constructor
    TeddyBear Constructor
    TeddyBear Destructor
    Bear Destructor
    BookCharacter Destructor
    Character Destructor
    ToyAnimal Destructor
    ZooAnimal Destructor
    Terminated with return code 0
    Press any key to continue …
告别云端依赖:手把手教你在Windows 10/11上离线部署WhisperDesktop(含模型下载避坑指南) 本文详细介绍了如何在Windows 10/11上离线部署WhisperDesktop,包括硬件要求、模型下载、环境配置和实战应用。通过本地部署,用户可以实现高效的语音转文字功能,无需依赖云端服务,特别适合注重数据隐私和需要离线工作的场景。 阅读详情

相关推荐

C++虚拟函数继承与多态性的深入探讨

本文详细解析了C++中的虚拟函数继承和多态性概念,通过分析具体示例,阐述了虚拟属性的继承原理及其在类层次结构中的应用。文章还探讨了纯虚拟函数的使用及其对基类派生类行为的影响

weixin_42601702的博客 342

C++笔记:虚继承

Animal下派生出Sheep和Tuo类。动物基类下,有两个派生类,一个是羊类,一个是驼类。这两个类都继承了Animal的一个属性,比如m_age。这样就导致资源的浪费,同时sheepTuo二义性。通过VS开发者工具中的工具,我们可以查看类的结构。解决方案:虚继承virtual。

xiaoyaolangwj的博客 212

C++中的继承与多重继承机制探究

本文通过C++编程语言中类的继承和多重继承机制,详细介绍了如何在面向对象编程中构建层次化的类结构。通过对劳动者的基类继承自劳动者的经理、科学家、工头等派生类的示例代码分析,展示了如何通过继承来复用代码和添加特定功能。同时,文章也探讨了类层次结构与组织结构图的不同,以及多重继承中的成员函数如何工作。

weixin_28949937的博客 391

虚继承 构造顺序

如下的菱形结构,四个字母分别表示一个类名       A   B    C       D 说明:B和C均继承A, D继承B和C 下面是我调试的5种情况的结果,请大家帮忙分析一下: 1. 若所有继承均为普通继承,则构造函数的调用顺序如下:A B A C D 2. 若仅仅A->B为虚继承,则构造函数的调用顺序如下:A B A C D 3. 若仅仅A->C为虚继承,则构造函数的调用顺序如下:A A B

suyksuyk的专栏 2038

虚继承中对象的构造顺序

虚继承中对象的构造顺序 虚基类的构造 首先虚基类的构造是通过最底层的派生类初始化,并且继承体系的每个类都可能在某个时刻成为最低层的派生类 class A{ public: A(){} } class B:virtual public A{ public: B(){} } class C:virtual public A{ public: C(){} } class D:public B,public C{ public: D(){} } 就比如说上面这一个例子, B b; //b是最底层

qq_45732995的博客 1283

c++虚拟继承

虚拟继承 虚拟继承引入的原因:在多继承方式下,派生类继承多个基类,当两个不同的基类拥有同名成员时,产生名字冲突。 看一个例子: #include<iostream> using namespace std; class A{ public: void f(){ cout<<"A中的f调用" << endl; } }; class B:public A{ }; class C :public A{ }; class D: public B, p

weixin_42326997的博客 1028

Cpp 对象模型探索 / 带有虚继承类的构造函数的调用顺序

栗子 #include <iostream> class A { public: A() { std::cout << "A" << std::endl; } }; class B : public A { public: B() { std::cout << "B" << std::endl; } }; class ...

布袋和尚 819

C++深度探索】继承机制详解(二)

菱形继承是指在C++中使用多重继承时,出现了多个派生类继承同一个基类,而最终有一个类同时继承了这些派生类,形成了一个菱形的继承结构。

Renswc的博客 3478

为什么C++中的继承比Java设计的要复杂,原因竟出在这儿?

我们知道面向对象的三大特性有封装、继承和多态。可能有铁子会误以为面向对象只有这三大特性,其实不然,还有其他的特性,比如反射、抽象等。那今天我们就好好来说说C++是如何设计继承的,为什么说比Java的继承要复杂呢?下面我们看到Person类是父类,也叫基类;Student类是子类,也叫派生类。1、我们都说C++语法复杂,其实多继承就是一个体现。有了多继承,就会存在菱形继承,有了菱形继承就会有菱形虚拟继承,它们的底层实现相当复杂。...

安然无虞的博客 2304

C++ 继承 详解

文章目录一、什么是继承?1.1、概念1.2、定义1.2.1、继承关系和访问限定符1.2.2、继承基类成员访问方式的变化注意:二、基类派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七、菱形继承八、继承和组合 一、什么是继承? 1.1、概念 **继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。 1.2、定义 下面我们看到Person是父类,也称作

黑桃K 1万+

C++ 继承详解:从基础到深入

继承是面向对象编程中最强大的功能之一,它不仅促进了代码的重用,还帮助我们构建复杂的系统。在C++中,通过继承,我们可以创建一个新的类(称为派生类)来扩展现有类(基类)的功能。本文将全面探讨C++中的继承机制,从基础定义到复杂的菱形继承问题。

2301_77754590的博客 1830

C++继承机制深度剖析

继承(Inheritance)是面向对象程序设计中的一种代码复用方式,它允许程序员在保持原有类特性的基础上进行扩展,增加新的功能,从而创建出新的类(派生类)。继承机制体现了面向对象程序设计的层次结构,从简单到复杂的认知过程。在C++中,继承的定义通过使用特定的语法结构来实现。class 子类名 : 访问权限 基类名 {// 子类的成员和函数声明其中,子类名是要定义的派生类的名称,访问权限可以是publicprotected或private,用于指定从基类继承成员的访问权限,基类名。

jun778895的博客 1200

C++继承深度解析:继承方式和菱形虚拟继承的详解

继承的使用方法是在子类的位置对多个父类使用逗号,进行间隔,其余方式public形式不变,进行继承代码语言:javascriptAI代码解释。

2509_93923299的博客 1122

虚拟继承

Nov. 23. 2015 2015年11月23日 In this post, after introducing two preliminary concepts, I explain what virtual inheritance is, and how it is implemented, then introduce two applications of virtual inheri...

weixin_26755331的博客 406

C++中的继承

c++中的继承

. 1524

C++入门11——详解C++继承(菱形继承虚拟继承

万字长文详解C++中的继承

weixin_73629886的博客 1134

初识C++ · 继承(2)

继上文将介绍了继承的概念和定义,以及基类子类的互相赋值,作用域,和默认成员函数的使用,本文介绍最后一点内容:友元,静态成员,以及菱形继承和菱形虚拟继承。也就是成员变量是其他自定义类型的。这方面可以概括为,必要继承继承,必要组合就组合,组合和继承都可以就组合。这是因为组合的低耦合高内聚,继承的高耦合低内聚。

更新各种计算机技能~ 1332

关于多重继承虚拟继承

这两个概念在C++的书里面似乎也很少提及,所以看到这两个名词的时候我也往往自动忽略,今天有时间上网百度了一下,找到三篇写得不错的文章: 《详解多重继承》 《关于C++中的虚拟继承的一些总结》 《详解C++虚拟继承》 多重继承很好理解,后两篇是讲虚拟继承的,前一篇写的比较浅,看完基本能够理解这个概念,后一篇写的很详细,读起来不太好理解,自己动手写了代码在VC上跑了一下。 总的来说就是:

huang1433的博客 782

C++ 继承

继承

finish_speech的博客 701

C++继承问题

C++的三大特性:继承, 多态

u012956263的专栏 313

基于Python实现图像分类项目源码+文档说明.zip

基于Python实现图像分类项目源码+文档说明.zip使用方式:1.往 /data/train 和 /data/val 两个文件夹里从0开始创建并命名文件夹名,有多少个分类就建多少个文件夹,文件夹名即分类名序号.2.往各个文件夹内放对应类的训练图片和测试数据图片.3.确定好序号对应的类名,预测脚本内需要配置.4.根目录下运行 pip3 install -r requirements.txt 安装依赖项5.运行train.py 训练模型.6.命令行运行val.py 并指定要预测的图片路径,以验证模型精度。7.网络调参基于Python实现图像分类项目源码+文档说明.zip使用方式:1.往 /data/train 和 /data/val 两个文件夹里从0开始创建并命名文件夹名,有多少个分类就建多少个文件夹,文件夹名即分类名序号.2.往各个文件夹内放对应类的训练图片和测试数据图片.3.确定好序号对应的类名,预测脚本内需要配置.4.根目录下运行 pip3 install -r requirements.txt 安装依赖项5.运行train.py 训练模型.6.命令行运行val.py 并指定

上一篇: 防止程序多开方法
下一篇: const int*和int const*
Canvas
博客等级 码龄16年 4粉丝 1原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值