首页 > 系统相关 >c++菱形继承、多态与类内存模型

c++菱形继承、多态与类内存模型

时间:2024-05-20 23:18:36浏览次数:26  
标签:函数 多态 c++ class 内存 Animal public mAge

目录

1.菱形继承

先看下面的例子,SheepTuo同时继承了SheepTuo,而他们同时继承Animal
image

#include <iostream>
using namespace std;

class Animal
{
    int mAge;
};

class Sheep : public Animal {};
class Tuo : public Animal {};
class SheepTuo : public Sheep, public Tuo {};

int main()
{
    SheepTuo st;
    ////// 1.报错,"SheepTuo::mAge" is ambiguous,mAge成员在两个子类都存在,二义性
    // st.mAge = 18; 

    ////// 2.可以声明作用域,避免成员的二义性
    st.Sheep::mAge = 18;
    st.Tuo::mAge = 100;

    ////// 3.但是在SheepTuo类中mAge成员会复制两份,造成内存浪费
    return 0;
}

1.1.菱形继承的问题

  1. 共享成员二义性——增加作用域可以解决
  2. 内存复制两份-浪费——虚继承解决
    image

1.2.解决办法

#include <iostream>
using namespace std;

// 虚基类
class Animal
{
public:
    int mAge;
};

// virtual --> 虚继承
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

int main()
{
    SheepTuo st;
    ////// 1.不会报错了
    // st.mAge = 18;

    ////// 2.这样写,下面的mAge都会是100,因此虚继承后,成员不会被复制,只在基类中有一份,子类中维护vbptr(管理不同的偏移量)指向它
    st.Sheep::mAge = 18;
    st.Tuo::mAge = 100;
    
    return 0;
}

SheepTuo继承了SheepTuo的虚基类指针vbptr,这俩的指针会指向他们的虚基类表vbtable,虚基类表中存着其vbptr的偏移量,通过偏移量可以帮助子类正确找到从虚基类继承来的数据
image

2.虚函数与多态

2.1.普通函数不能实现多态

#include <iostream>
using namespace std;

class Animal
{
public:
    void Speak()
    {
        cout << "动物 在说话" << endl;
    }
};

class Cat : public Animal 
{
    void Speak()
    {
        cout << "小猫 在说话" << endl;
    }
};
class Dog : public Animal 
{
    void Speak()
    {
        cout << "小狗 在说话" << endl;
    }
};

// 常对象只能调用常函数
// void DoSpeak(const Animal &animal)
void DoSpeak(Animal &animal)
{
    animal.Speak();
}

int main()
{
    Cat cat;
    DoSpeak(cat);
    
    return 0;
}

输出:动物 在说话
上面的AnimalSpeak是一个普通成员函数,虽然有继承的条件,但是编译器在编译阶段,会DoSpeak函数中把animal.Speak();animal绑定为Animal的地址,无法实现多态

2.2.虚函数(子类重写)+ 父类指向子类——实现多态

#include <iostream>
using namespace std;

class Animal
{
public:
    virtual void Speak()
    {
        cout << "动物 在说话" << endl;
    }
};

class Cat : public Animal 
{
    void Speak()
    {
        cout << "小猫 在说话" << endl;
    }
};
class Dog : public Animal 
{
    void Speak()
    {
        cout << "小狗 在说话" << endl;
    }
};

void DoSpeak(Animal &animal)
{
    animal.Speak();
}

int main()
{
    Cat cat;
    DoSpeak(cat);
    
    return 0;
}

输出:小猫 在说话
和2.1.节相比,只增加了virtual关键字,使得父类普通函数变为虚函数,使得DoSpeak函数中animal.Speak()的animal在运行阶段才会指向真正调用的对象,实现了多态
其运行流程先说明:对象指针->取其虚表指针->取其虚表中函数->call调用

2.3.多态原理

  • case1.普通函数的基类所占内存大小, sizeof(Animal) = 1
class Animal
{
public:
    void Speak()
    {
        cout << "动物 在说话" << endl;
    }
};

image

  • case2.虚函数的基类所占内存大小, sizeof(Animal) = 4
class Animal
{
public:
    virtual void Speak()
    {
        cout << "动物 在说话" << endl;
    }
};

image

case1和case2说明了增加virtual会在类中多用内存,增加的是虚函数指针和虚函数表

  • case3.子类继承虚基类,并且子类重写虚函数
class Animal
{
public:
    virtual void Speak()
    {
        cout << "动物 在说话" << endl;
    }
};

class Cat : public Animal 
{
    void Speak()
    {
        cout << "小猫 在说话" << endl;
    }
};
  • Cat中的虚函数表发生覆盖
    image
  • 总体来看看
    image

3.c++内存模型

C++类中内存存储情况比较复杂,涉及成员数据、函数、静态成员、虚函数等情况
记录结论,详情参考C++类的内存布局
image
image

4.参考

C++类的内存布局
C++多态剖析

标签:函数,多态,c++,class,内存,Animal,public,mAge
From: https://www.cnblogs.com/kongweisi/p/18202906

相关文章

  • C++基础知识学习笔记(1)
    资料来源https://www.bilibili.com/video/BV1et411b73Z/?spm_id_from=333.337.search-card.all.click&vd_source=cc561849591f6a210152150b2493f6f3简单知识点创建项目用VS创建了一个C++的空项目。在【源文件】中创建一个cpp文件书写以下代码并运行#include<iostr......
  • C++常用模板
    常用模板:1.组合数(注意\(N\)与\(mod\))点击查看代码#include<bits/stdc++.h>usingnamespacestd;#definelllonglongconstllN=1e3+10,mod=998244353;lln,a[N],jc[N],dp[N],ans;voidinit(){ jc[0]=1; for(inti=1;i<N;i++)jc[i]=jc[i-1]*i%mod;}llksm......
  • 逆向 | 驱动挂靠进程直接读内存
    逆向|驱动挂靠进程直接读内存参考:https://cloud.tencent.com/developer/article/2358904https://github.com/Whitebird0/driver_read_and_write/blob/main/04-读写内存/ReadMemory.c代码如下:代码不长但是有坑,比如说ExAllocatePool2的参数就跟之前不一样了,这个点我调试了好......
  • [转帖]Linux内存管理基本概念
    最近在学习Linux系统的内存管理,小白一枚,零散从网上收集的一些笔记如下:/proc目录提供了很多工具给我们查看当前内存情况1./proc/meminfo是什么$cat/proc/meminfoMemTotal:2052440kB//总内存MemFree:50004kB//空闲内存Buffers:19976kB/......
  • C++的四种智能指针及简单实现
    C++的四种智能指针及简单实现参考:C++智能指针讲解及模拟实现一、auto_ptrauto_ptr有拷贝语义,拷贝后源对象变为空指针,可能引发严重问题template<classT>classmy_unique_ptr{private:T*_ptr;public://普通构造函数my_unique_ptr(T*ptr):_ptr(ptr){}......
  • C++算法刷题基础
    1.main函数的返回类型一定是int2.C++语言为我们准备了一组内置库,包含了很多常用的功能,并且这些内置库可以直接使用,而其中的内置库:iostream,就提供了输入和输出的功能,允许开发者从键盘读取输入并在屏幕上输出结果。3.在iostream库中,我们有两个对象可以使用,分别是cin和cout。......
  • 侯捷C++上期笔记
    1.头文件和类、构造函数c++和c最大的不同在于C++会把数据以及处理数据的函数放到一个对象objects(class)里,不同类之间不可见,类似C中结构体struct防止头文件重复声明ifndefcomplex//当之前没有声明过这个头文件时,才进行后续的声明definecomplex(2)补充定义(1)类定义(3)类功能解释......
  • C++身份证二要素实名认证api、实名认证接口
    在数字化时代背景下,个人信息安全成为了社会关注的焦点。为进一步加强网络空间的安全管理,提升服务效率,身份证实名认证接口的出现为各行业提供了更为安全、高效的身份验证解决方案。随着互联网+政务服务、金融科技、电子商务等领域的快速发展,实名认证需求日益增长。翔云身......
  • C++ 异常处理注意事项总结
    C++异常处理注意事项总结:异常安全代码:编写异常安全的代码是至关重要的。这意味着你的代码应该在面对异常时能够正确地清理资源并维持程序状态。使用RAII(ResourceAcquisitionIsInitialization)技术可以帮助自动管理资源,减少内存泄漏的风险。使用noexcept:对于不会抛出异常......
  • C++ 多线程编程要点总结
    C++多线程编程要点总结:选择合适的线程库:C++11引入了 <thread> 头文件,提供了对线程的原生支持。也可以使用第三方库,如Boost.Thread,它提供了更多高级功能和更好的跨平台兼容性。线程创建与管理:使用 std::thread 类创建新线程,并传入函数或可调用对象作为线程的入口......