重学C++系列之虚继承

news2024/10/10 14:24:15

一、什么是虚继承

        虚继承是C++中一种继承的机制。继承有普通继承虚继承两种机制,默认是普通继承,如果要使用多继承,需要在继承方式里加上关键字virtual。

二、为什么要虚继承

         在C++中,为解决二义性问题,引入虚继承机制。

        注意:虚继承是在多继承中讲的,而虚函数是在多态中讲的。

三、虚继承的特性

        虚继承使得公共的基类在派生类中只有一份,虚继承在多重继承的基础上多了vtable表(虚表)来存储到公共基类的偏移,而在使用公共基类时,通过vptr指针(虚指针)查表得到,该虚指针和虚表的维护由系统来进行。

 四、虚继承的原理

         C++在设计虚继承机制中,提供一个vtable(虚表)和vptr(虚指针)。虚指针指向虚表,虚表就是用来存放虚基类(祖父类)成员的地址。

        基类在采用虚继承时,编译器就会给这个类分配一个vptr(虚指针,32位地址空间占4个字节,64位地址空间占8个字节),要访问虚基类的成员时,就通过查表,虚基类的成员只保留一份,不管谁来访问,就是通过查表,虚表和虚指针都是通过编译器自身维护。

        由于虚基类的成员只保留一份,从整体来说,虚继承比普通多继承更加节省空间。

 五、实现虚继承的语法

        在多继承时,基类去继承虚基类时,使用关键字(virtual),再加上继承方式 虚基类。语法如下:

class 父类:virtual 祖父类    // 虚继承
{

};

class 子类:继承方式 父类1, 继承方式 父类2...(普通多继承)
{

};

六、案例

        1、验证虚指针的存在

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A1
{
    int a;
};

// 基类B1,虚继承
class B1:virtual public A1
{
    int b;

};

// 基类C1,虚继承
class C1:virtual public A1
{
    int c;
};

class Test1:public B1, public C1
{
    int data;
};


// 基类B2, 普通继承
class B2:public A1
{
    int b;

};

// 基类C2,普通继承
class C2:virtual public A1
{
    int c;
};

class Test2:public B2, public C2
{
    int data;
};


int main()
{
    Test1 tmp1;
    Test2 tmp2;
   
    // 虚继承后,多一个虚指针
    cout << "sizeof(tmp1): " << sizeof(tmp1) << endl;  

    // 虚基类只有单个成员变量时,比普通多继承占用内存空间少一个虚指针的字节
    cout << "sizeof(tmp2): " << sizeof(tmp2) << endl;   

    // 64位地址空间,指针占8个字节; 32位地址空间,指针占4个字节
    cout << "sizeof(int*): " << sizeof(int*) << endl;   
    
    return 0;
}

        2、初始化虚基类成员时,默认调用无参的构造函数

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A
{
private:
    int a;
public:
    A(int a = 0)
    {
        cout << "A()" << endl;
        this->a = a;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void show()
    {
        cout << "a = " << a << endl;
    }
};

// 基类B,虚继承
class B:virtual public A
{
private:
    int b;
public:
    B(int a = 0, int b = 0):A(a)
    {
        cout << "B()" << endl;
        this->b = b;
    }
    ~B()
    {
        cout << "~B()" << endl;
    }
    void show()
    {
        cout << "b = " << b << endl;
    }
};

// 基类C,虚继承
class C:virtual public A
{
private:
    int c;
public:
    C(int a = 0, int c = 0):A(a)
    {
        cout << "C()" << endl;
        this->c = c;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    void show()
    {
        cout << "c = " << c << endl;
    }
};

// 派生类,普通多继承
// B类和C类都虚继承于虚基类A,但是在Test类中只有一个虚基类A的成员变量
// 如果不是虚继承,就会有两个A类的成员变量,造成影子现象
class Test:public B, public C
{
private:
    int data;
public:
    // 这里采用构造函数列表初始化方式,但是后面的初始化并不能初始化虚基类成员变量,
    // 如果没有显式声明虚继类的构造函数,默认调用无参的构造函数
    // 可以改为如下
    // Test(int a = 0, int b = 0, int c = 0, int data = 0):A(a), B(a, b), C(a, c)
    Test(int a = 0, int b = 0, int c = 0, int data = 0):B(a, b), C(a, c)
    {
        cout << "Test()" << endl;
        this->data = data;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
    void show()
    {
        A::show();
        B::show();
        C::show();
        cout << "data = " << data << endl;
    }
};


int main()
{
    Test tmp(1, 2, 3, 4);
    tmp.show();


    return 0;
}

        3、显式调用虚基类的有参构造函数才能正确初始化虚基类成员 

#include <iostream>

using namespace std;

// 祖父类(虚基类)
class A
{
private:
    int a;
public:
    A(int a = 0)
    {
        cout << "A()" << endl;
        this->a = a;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
    void show()
    {
        cout << "a = " << a << endl;
    }
};

// 基类B,虚继承
class B:virtual public A
{
private:
    int b;
public:
    B(int a = 0, int b = 0):A(a)
    {
        cout << "B()" << endl;
        this->b = b;
    }
    ~B()
    {
        cout << "~B()" << endl;
    }
    void show()
    {
        cout << "b = " << b << endl;
    }
};

// 基类C,虚继承
class C:virtual public A
{
private:
    int c;
public:
    C(int a = 0, int c = 0):A(a)
    {
        cout << "C()" << endl;
        this->c = c;
    }
    ~C()
    {
        cout << "~C()" << endl;
    }
    void show()
    {
        cout << "c = " << c << endl;
    }
};

// 派生类,普通多继承
// B类和C类都虚继承于虚基类A,但是在Test类中只有一个虚基类A的成员变量
// 如果不是虚继承,就会有两个A类的成员变量,造成影子现象
class Test:public B, public C
{
private:
    int data;
public:
    // 这里采用构造函数列表初始化方式,但是后面的初始化并不能初始化虚基类成员变量,
    // 如果没有显式声明虚继类的构造函数,默认调用无参的构造函数
    // 现在改为如下,显示调用
    Test(int a = 0, int b = 0, int c = 0, int data = 0):A(a), B(a, b), C(a, c)
    // Test(int a = 0, int b = 0, int c = 0, int data = 0):B(a, b), C(a, c)
    {
        cout << "Test()" << endl;
        this->data = data;
    }
    ~Test()
    {
        cout << "~Test()" << endl;
    }
    void show()
    {
        A::show();
        B::show();
        C::show();
        cout << "data = " << data << endl;
    }
};


int main()
{
    Test tmp(1, 2, 3, 4);
    tmp.show();


    return 0;
}

七、总结 

        虚继承的并不一定比多继承好,要具体情况具体分析,而且由于虚继承使用了虚指针和虚表,效率上比多继承的慢,当基类中出现多个重名的成员时,建议采用虚继承。

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

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

相关文章

tinkerCAD案例:9. Saw Shaped Wrench 锯形扳手

tinkerCAD案例&#xff1a;9. Saw Shaped Wrench 锯形扳手 ln this lesson you will learn how to create a cool saw shaped wrench. 在本课中&#xff0c;您将学习如何制作一个很酷的锯形扳手。 Start the lesson by dragging a polygon to the workplane. 通过将多边形拖动…

从模型坐标到屏幕坐标

在 3D 引擎中&#xff0c;场景通常被描述为三维空间中的模型或对象&#xff0c;每个模型对象由许多三维顶点组成。最终&#xff0c;这些模型对象将在平面屏幕上呈现和显示。 渲染场景始终相对于摄像机&#xff0c;因此&#xff0c;还必须相对于摄像机的视图定义场景的顶点。了…

zeppelin spark kerberos 使用过程遇到的问题

参考配置教程: Configure Zeppelin for a Kerberos-Enabled Cluster zeppelin spark kerberos 使用过程遇到的问题 ambari创建zeppelin时,会创建Kerberos account and keytab /etc/security/keytabs/zeppelin.server.kerberos.keytab interpreter配置 keytab Interpreter Ke…

新来个技术总监,禁止我们用Git的rebase!?

△Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 429 篇原创分享 作者 l Hollis 来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09; 在Git中&#xff0c;merge和rebase是两种不同的代码合并策略&#xff0c;它们用于将一个分支的更改合并到另一个分支…

动态内存常见的问题

对空指针的解引用 改正后的代码&#xff1a; 返回栈&#xff08;临时变量&#xff09;空间地址的问题 释放空间后及时把指针设为空 void Test(void) {char* str (char*)malloc(100);strcpy(str, "hello");free(str);str NULL;//释放空间后及时把指针设为空if (s…

嵌入式学习入门和求职经验

入门阶段&#xff1a;&#xff08;不要只看书&#xff0c;要多动手&#xff0c;但千万不是直接动手&#xff0c;不去看书&#xff09; C语言&#xff1a;嵌入式编程大多用C语言、少量汇编&#xff0c;先学习C语言&#xff0c;汇编用到的时候再上网查询。教材&#xff1a;随便一…

unity调用c++dll时变量处理问题

在C中为了处理方便可以将有些局部变量变为全局变量。 以下面三个变量为例 如果还有新全局变量需要这其来赋值&#xff0c;就会有问题。 如下面的body1_ptr为了dll生成处理方便&#xff0c;变为全局变量后&#xff0c;这个指针变赋值就会有问题。 因为geometry_path这个只在调…

上分秘籍 :如何用大模型跨模态能力核查网络谣言?

也许&#xff0c;你也曾听说过&#xff1a;&#x1f449;眼见未必为实&#x1f449;假新闻可比真新闻好传太多啦&#xff01;&#x1f449;谣言一张嘴&#xff0c;辟谣跑断腿大模型时代来临&#xff0c;除了肝&#xff0c;对付网络谣言&#xff0c;是不是有了新出路&#xff1f…

Java_23_并发包

并发包 并发包的来历&#xff1a; 在实际开发中如果不需要考虑线程安全问题&#xff0c;大家不需要做线程安全&#xff0c;因为如果做了反而性能不好&#xff01; 但是开发中有很多业务是需要考虑线程安全问题的&#xff0c;此时就必须考虑了。否则业务出现问题。 Java为很多业…

考公笔记题

一、考公报名网址 国家公务员局&#xff1a;国家公务员局 二、历年题库 华图在线&#xff1a;国省考公务员题库_公职教育类在线题库-华图在线 公务员&#xff1a;《行测》与《申论》 重点学习&#xff1a; 判断推理&#xff08;图形推理、定义判断&#xff08;影响不大&am…

vue中的数据代理

vue数据代理 Vue实现数据代理的核心----Object.defineProperty(); 数据代理 数据代理的定义是&#xff1a;一个对象操作(读\写)另一个对象中的属性和方法。 // 数据代理&#xff1a;通过一个对象代理对另一个对象中属性的操作&#xff08;读/写&#xff09;let obj { x: 100…

STM32之智能小车,手把手从0到1,模块化编程

小车介绍 本博文将会从0到1实现一个智能小车&#xff0c;该小车实现功能&#xff1a;1. 摇头避障模式、2. 跟随模式、3. 循迹模式、4. 小车测速并显示在OLED屏幕、5. 语音控制小车等等。 硬件组成 STM32F103开发板、小车套件、L9110S电机模块、超声波模块&#xff08;HC-SR04&a…

代码随想录算法训练营第二十五天 | 读PDF复习环节3

读PDF复习环节3 本博客的内容只是做一个大概的记录&#xff0c;整个PDF看下来&#xff0c;内容上是不如代码随想录网站上的文章全面的&#xff0c;并且PDF中有些地方的描述&#xff0c;是很让我疑惑的&#xff0c;在困扰我很久后&#xff0c;无意间发现&#xff0c;其网站上的讲…

Ubuntu-解决包依赖关系

Ubuntu-解决包依赖关系的办法 安装软件包的时候&#xff0c;有时会遇到类似下图的依赖问题&#xff0c;无法正常安装&#xff0c;下面提供三种方法解决依赖问题。 1.可以尝试用下面方法处理依赖问题&#xff0c;紧跟前一条安装命令后面输入下面命令&#xff0c;然后再执行安装…

第一次使用easyExcel报错信息记录 NullPointerException + MultipartException

第一次使用easyExcel报错信息记录 文章目录 第一次使用easyExcel报错信息记录NullPointerExceptionMultipartException NullPointerException 使用easyExcel报的错误 显示我的easyExcel监听器中出现了空指针异常 //报错信息 Creating a new SqlSessionClosing non transacti…

【Ajax】笔记-同源策略

同源策略(Same-Origin Policy)&#xff0c;是浏览器的一种安全策略 同源&#xff08;即url相同&#xff09;&#xff1a;协议、域名、端口号 必须完全相同。&#xff08;请求是来自同一个服务&#xff09; 跨域&#xff1a;违背了同源策略&#xff0c;即跨域。 ajax请求是遵循…

手把手教-gd32f450基于rt-thread发布的bsp包手动添加以太网外设

一、开发环境 rt-thread发布版本4.1.0&#xff1b; bsp包选用的是gd32目录下的gd32450z-eval rt-thread-v4.1.0\bsp\gd32\gd32450z-eval\ 开发板gd32f450z系列开发板。 二、手动添加以太网ETH外设 先看下初始结构 可以看到&#xff0c;配置中什么也没有。 手动添加步骤如下&…

27.提示卡片

提示卡片 html部分 <div class"msg-list"></div><button class"btn">点我看提示</button>css部分 *{margin: 0;padding: 0; }body{background-color: rebeccapurple;height: 100vh;overflow: hidden;display: flex;justify-con…

机器学习入门之 特征工程

数据集 数据集划分 ,训练集与 测试集合 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.feature_extraction import DictVectorizer from sklearn.feature_extraction.text import CountVectorizer, TfidfVecto…

【FPGA/D6】

2023年7月25日 VGA控制器 视频23notecodetb 条件编译error时序图保存与读取&#xff1f;&#xff1f;RGBTFT显示屏 视频24PPI未分配的引脚或电平的解决方法 VGA控制器 视频23 note MCU单片机 VGA显示实时采集图像 行消隐/行同步/场同步/场消隐 CRT&#xff1a;阴极射线管 640…