C++对象布局

C++对象布局

什么是多态

多态分为编译时多态和运行时多态

  • 编译时多态:基于模板和函数重载方式,在编译时就已经确定对象的行为,也称为静态绑定
  • 运行时多态:通过继承方式使得程序在运行时才会确定相应调用的方法,也称为动态绑定,它的实现主要依赖虚函数表

查看对象布局的方法

如果是g++编译器使用如下命令:

g++ -fdump-lang-class test.cpp

使用完这个命令后会在对应的文件夹下生成对应的.class文件,直接打开就可以看到类信息

如果使用的是clang编译器则使用如下命令:

clang -Xclang -fdump-record-layouts -stdlib=libc++ -c model.cc

// 查看对象布局
clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c model.cc

// 查看虚函数表布局

使用这个命令后就可以直接在终端显示布局结果

虚函数表(vtable

  • 核心目的:实现运行时多态,支持动态绑定
  • 关键指针:vptr(虚函数表指针),通常位于对象内存布局起始处
  • 引入方式:类中包含至少一个virtual函数
  • 访问开销:通过vptr间接调用,比普通函数调用稍慢,无法内联

虚函数表的结构

实际上虚函数表本质是由连续内存块构成的函数指针数组,这些函数指针按声明顺序指向虚函数的入口地址

虚基类表(vbtable)

  • 核心目的:解决菱形继承中的数据冗余和二义性问题,确保虚基类只有一份实例
  • 表内容:存储偏移量,用于定位派生类对象中的虚基类子对象
  • 关键指针:vbptr(虚基类表指针),存在于虚继承的派生类子对象中
  • 引入方式:使用virtual关键字继承基类
  • 通过vbptr查找偏移量再计算地址,存在性能损耗
虚基类表的存储结构

虚基类表中存放的是当前派生类相对于基类的偏移量,存储从当前子对象起始地址到共享基类子对象地址的偏移值,这样能够保证共享基类只有一份实例,是解决菱形继承的重要手段

普通类对象的布局

代码如下

c++ 复制代码
class Base
{
public:
    Base() = default;
    ~Base() = default;
    void Func() {}
    int a;
    int b;
};

int main()
{
    Base a;
    return 0;
}

使用clang编译器查看其编译结果可得如下输出

bash 复制代码
*** Dumping AST Record Layout
         0 | class Base
         0 |   int a
         4 |   int b
           | [sizeof=8, dsize=8, align=4,
           |  nvsize=8, nvalign=4]

*** Dumping IRgen Record Layout

由图我们不难推出结构体的空间分布如下

由此输出结果可知,这个结构体Base的大小为8字节,a占四个字节,b占四个字节

带虚函数的类对象的布局

代码如下

c++ 复制代码
#include <stdio.h>
class Base
{
public:
    virtual ~Base() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("FuncB\n");
    }

    int a, b;
};

int main()
{
    Base a;
    return 0;
}

使用命令clang -Xclang -fdump-record-layouts -stdlib=libc++ -c main.cpp后可以在终端中看到对象布局如下

bash 复制代码
*** Dumping AST Record Layout
         0 | class Base
         0 |   (Base vtable pointer)
         8 |   int a
        12 |   int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

由输出结果我们不难发现这个结构体的大小为16个字节,在对象头部的前8个字节中存放的是虚函数表的指针,指向虚函数相应的函数指针地址,因此a占4个字节,b占4个字节,总大小为16个字节,布局图如下:

根据布局图我们不难发现虚函数表指针位于派生类的起始地址,在当前的64位操作系统下占据8个字节,并指向虚函数表中对应虚函数的起始地址

单继承下不含有覆盖函数的类对象的布局

代码如下

c++ 复制代码
#include <stdio.h>
class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("Base FuncB\n");
    }
    int a;
    int b;
};

class Derive : public Base
{
};

int main()
{
    Base a;
    Derive d;
    return 0;
}

通过命令可以得到基类和派生类的对象布局,此处我们仅展示子类对象的布局

c++ 复制代码
*** Dumping AST Record Layout
         0 | class Derive
         0 |   class Base (primary base)
         0 |     (Base vtable pointer)
         8 |     int a
        12 |     int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

内存布局和上一种情况相同,含有虚函数的类大小为16,在对象头部,前8个字节是虚函数表的指针,指向虚函数的相应函数指针地址,a占四个字节,b占四个字节,总大小为16个字节,由此可以得出派生类会继承父类的函数表指针

在子类没有重写这个函数的前提下内存布局如下

通过命令clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -c main.cpp查看派生类的虚函数表布局可得

c++ 复制代码
Vtable for 'Base' (5 entries).
   0 | offset_to_top (0)
   1 | Base RTTI
       -- (Base, 0) vtable address --
   2 | Base::~Base() [complete]
   3 | Base::~Base() [deleting]
   4 | void Base::FuncB()

VTable indices for 'Base' (3 entries).
   0 | Base::~Base() [complete]
   1 | Base::~Base() [deleting]
   2 | void Base::FuncB()

Vtable for 'Derive' (5 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (Base, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Base::FuncB()

VTable indices for 'Derive' (2 entries).
   0 | Derive::~Derive() [complete]
   1 | Derive::~Derive() [deleting]

此时我们根据上述输出再次分析一下虚函数表的内存布局

  • 0 | offset_to_top (0) //此处存放对象到顶部的偏移量(单继承时为0)
  • 1 | RTTI指针 //此指针指向运行时类型信息(包含基类和派生类类型)
  • 2 | Derive::~Derive() [complete] //这代表完整的析构函数(覆盖基类)
  • 3 | Derive::~Derive() [deleting] //这代表删除析构函数(覆盖)
  • 4 | void Base::FuncB() //这代表虚函数的实现

此时我们可以将注意力放到虚表索引的内容,也就是VTable indices for 'Derive' (2 entries).下面的内容;

在此先介绍一下虚表索引的作用,虚表索引的作用为说明派生类中虚函数在虚表中的位置

  • 编译器为每个需要动态绑定的虚函数分配唯一索引值
  • 索引值标记该虚函数在虚表中的位置偏移量
  • 通过索引实现运行时多态调用

当前类中只有2个需要索引的虚函数:

0 | Derive::~Derive() [complete] ->完全析构函数

​ 1 | Derive::~Derive() [deleting] ->删除析构函数

此时基类的FuncB()未出现在索引中,因为派生类未覆盖该函数,因此当通过派生类调用FuncB()时则会直接使用Base类虚表中的索引,这是继承关系下的典型优化作用为避免重复存储。

单继承下含有覆盖函数的类对象的布局

代码如下

c++ 复制代码
#include <stdio.h>
class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("Base FuncB\n");
    }
    int a;
    int b;
};

struct Derive : public Base
{
public:
    void FuncB() override
    {
        printf("Derive FuncB \n");
    }
};

int main()
{
    Base a;
    Derive d;
    return 0;
}

使用命令我们可以得到子类对象布局:

b 复制代码
*** Dumping AST Record Layout
         0 | struct Derive
         0 |   class Base (primary base)
         0 |     (Base vtable pointer)
         8 |     int a
        12 |     int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout

子类中重写了父类的虚函数之后子类的空间布局并没有发生改变,依旧是头部的前8个字节存储的是虚函数表的指针,指向虚函数的相应函数指针地址,a占四个字节,b占4个字节,总大小为16个字节

内存布局如下

看似派生类中重写了虚函数后的空间布局与之前重写没有什么太大变化,这里可以发现的一点是,虚函数表中的FuncB变成了派生类自己内部的函数,而不是没有重写之前父类里面的函数

使用命令查看派生类的虚函数表布局可得

c++ 复制代码
Vtable for 'Derive' (5 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (Base, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Derive::FuncB()

VTable indices for 'Derive' (3 entries).
   0 | Derive::~Derive() [complete]
   1 | Derive::~Derive() [deleting]
   2 | void Derive::FuncB()

此时不难发现,与之前的只继承不重载相比虚函数索引中出现了FuncB函数,这表明如果此时再次通过派生类去调用基类中的FuncB函数,那么程序将会直接使用派生类中的函数,而不是去调用基类中的函数

多继承下不含有覆盖函数的类对象的布局

代码如下

c++ 复制代码
#include <stdio.h>
class BaseA
{
public:
    BaseA() = default;
    virtual ~BaseA() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("BaseA FuncB\n");
    }
    int a;
    int b;
};

class BaseB
{
public:
    BaseB() = default;
    virtual ~BaseB() = default;
    void FuncA() {}
    virtual void FuncC()
    {
        printf("BaseB FuncC\n");
    }
    int a;
    int b;
};

class Derive : public BaseA, public BaseB
{
};

int main()
{
    BaseA a;
    Derive d;
    return 0;
}

派生类对象布局

bash 复制代码
*** Dumping AST Record Layout
         0 | class Derive
         0 |   class BaseA (primary base)
         0 |     (BaseA vtable pointer)
         8 |     int a
        12 |     int b
        16 |   class BaseB (base)
        16 |     (BaseB vtable pointer)
        24 |     int a
        28 |     int b
           | [sizeof=32, dsize=32, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

在派生类的结构中我们不难发现其内部有两个虚表指针,因为派生类是多继承,一般情况下继承了几个带有虚函数的类,对象布局中就有几个虚表指针,并且子类也会继承基类的数据,一般来说,不考虑内存对齐的化,子类(继承父亲)的大小=子类(不继承父类)的大小+所有父类的大小

类对象虚函数表布局

c++ 复制代码
Vtable for 'BaseA' (5 entries).
   0 | offset_to_top (0)
   1 | BaseA RTTI
       -- (BaseA, 0) vtable address --
   2 | BaseA::~BaseA() [complete]
   3 | BaseA::~BaseA() [deleting]
   4 | void BaseA::FuncB()

VTable indices for 'BaseA' (3 entries).
   0 | BaseA::~BaseA() [complete]
   1 | BaseA::~BaseA() [deleting]
   2 | void BaseA::FuncB()

Vtable for 'Derive' (10 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (BaseA, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void BaseA::FuncB()
   5 | offset_to_top (-16)
   6 | Derive RTTI
       -- (BaseB, 16) vtable address --
   7 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
   8 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
   9 | void BaseB::FuncC()

Thunks for 'Derive::~Derive()' (1 entry).
   0 | this adjustment: -16 non-virtual

VTable indices for 'Derive' (2 entries).
   0 | Derive::~Derive() [complete]
   1 | Derive::~Derive() [deleting]

Vtable for 'BaseB' (5 entries).
   0 | offset_to_top (0)
   1 | BaseB RTTI
       -- (BaseB, 0) vtable address --
   2 | BaseB::~BaseB() [complete]
   3 | BaseB::~BaseB() [deleting]
   4 | void BaseB::FuncC()

VTable indices for 'BaseB' (3 entries).
   0 | BaseB::~BaseB() [complete]
   1 | BaseB::~BaseB() [deleting]
   2 | void BaseB::FuncC()

虚函数表的内容和之前相比并没有太大变化,依旧是每个类的虚函数索引中会出现本类中的成员函数,由于派生类中并没有复写基类的虚函数,因此派生类的虚函数表中只有析构函数的函数索引

对象的空间布局如下

其中offset 0表示当前这个虚函数表地址距离对象顶部地址的偏移量,因为对象的头部就是虚函数表的指针,所以偏移量为0

RTTI指针:此指针指向存储运行时类型信息(type_info)的地址,用于运行时类型识别,用于typeiddynamic_cast,RTTI下面就是虚函数表指针真正指向的地址,存储了类里面所有的虚函数

此次RTTI中有了两项,表示BaseADerived的虚表地址是相同的,BaseA类里的虚函数和Derived类里的虚函数都是在这个链条下,截止到offset(-16)之前都是BaseADerived的虚函数表

多继承下含有覆盖函数的类对象的布局

示例代码如下

c++ 复制代码
#include <stdio.h>
class BaseA
{
public:
    BaseA() = default;
    virtual ~BaseA() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("BaseA FuncB\n");
    }
    int a,b;
};

class BaseB
{
public:
    BaseB() = default;
    virtual ~BaseB() = default;
    void FuncA() {}
    virtual void FuncC()
    {
        printf("BaseB FuncC\n");
    }
    int a, b;
};

class Derive : public BaseA, public BaseB
{
public:
    void FuncB() override
    {
        printf("Derive FuncB \n");
    }
    void FuncC() override
    {
        printf("Derive FuncC \n");
    }
};

int main()
{
    BaseA a;
    Derive d;
    return 0;
}

使用相应命令后可以查看派生类布局如下

bash 复制代码
*** Dumping AST Record Layout
         0 | class Derive
         0 |   class BaseA (primary base)
         0 |     (BaseA vtable pointer)
         8 |     int a
        12 |     int b
        16 |   class BaseB (base)
        16 |     (BaseB vtable pointer)
        24 |     int a
        28 |     int b
           | [sizeof=32, dsize=32, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

此时派生类的内存布局与多继承不覆盖的情况一样,依旧是含有两个虚函数表指针

虚函数表布局如下

c++ 复制代码
Vtable for 'BaseA' (5 entries).
   0 | offset_to_top (0)
   1 | BaseA RTTI
       -- (BaseA, 0) vtable address --
   2 | BaseA::~BaseA() [complete]
   3 | BaseA::~BaseA() [deleting]
   4 | void BaseA::FuncB()

VTable indices for 'BaseA' (3 entries).
   0 | BaseA::~BaseA() [complete]
   1 | BaseA::~BaseA() [deleting]
   2 | void BaseA::FuncB()

Vtable for 'Derive' (11 entries).
   0 | offset_to_top (0)
   1 | Derive RTTI
       -- (BaseA, 0) vtable address --
       -- (Derive, 0) vtable address --
   2 | Derive::~Derive() [complete]
   3 | Derive::~Derive() [deleting]
   4 | void Derive::FuncB()
   5 | void Derive::FuncC()
   6 | offset_to_top (-16)
   7 | Derive RTTI
       -- (BaseB, 16) vtable address --
   8 | Derive::~Derive() [complete]
       [this adjustment: -16 non-virtual]
   9 | Derive::~Derive() [deleting]
       [this adjustment: -16 non-virtual]
  10 | void Derive::FuncC()
       [this adjustment: -16 non-virtual]

Thunks for 'Derive::~Derive()' (1 entry).
   0 | this adjustment: -16 non-virtual

Thunks for 'void Derive::FuncC()' (1 entry).
   0 | this adjustment: -16 non-virtual

VTable indices for 'Derive' (4 entries).
   0 | Derive::~Derive() [complete]
   1 | Derive::~Derive() [deleting]
   2 | void Derive::FuncB()
   3 | void Derive::FuncC()

Vtable for 'BaseB' (5 entries).
   0 | offset_to_top (0)
   1 | BaseB RTTI
       -- (BaseB, 0) vtable address --
   2 | BaseB::~BaseB() [complete]
   3 | BaseB::~BaseB() [deleting]
   4 | void BaseB::FuncC()

VTable indices for 'BaseB' (3 entries).
   0 | BaseB::~BaseB() [complete]
   1 | BaseB::~BaseB() [deleting]
   2 | void BaseB::FuncC()

此时再观察类的虚函数表的布局我们不难发现派生类的虚函数索引中出现了基类中的虚函数,因此当出现多继承和虚函数覆盖时派生类和基类的内存布局并不会发生改变,发生改变的是派生类的虚函数表的内容

对象布局图如下

虚继承的布局

示例代码如下

c++ 复制代码
#include <stdio.h>

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("BaseA FuncB\n");
    }
    int a, b;
};

class Derive : virtual public Base
{
public:
    void FuncB() override
    {
        printf("Derive FuncB \n");
    }
};

int main()
{
    Base a;
    Derive d;
    return 0;
}

使用命令查看类对象布局可得

bash 复制代码
*** Dumping AST Record Layout
         0 | class Base
         0 |   (Base vtable pointer)
         8 |   int a
        12 |   int b
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping AST Record Layout
         0 | class Derive
         0 |   (Derive vtable pointer)
         8 |   class Base (virtual base)
         8 |     (Base vtable pointer)
        16 |     int a
        20 |     int b
           | [sizeof=24, dsize=24, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout

此时我们发现基类的类对象布局并没有发生变化,但是派生类中相较于普通继承多了一个指针,这个指针指向的是基类的虚函数表

此时通过命令我们可以查看到程序中的虚函数表的布局

bash 复制代码
Vtable for 'Base' (5 entries).
   0 | offset_to_top (0)
   1 | Base RTTI
       -- (Base, 0) vtable address --
   2 | Base::~Base() [complete]
   3 | Base::~Base() [deleting]
   4 | void Base::FuncB()

VTable indices for 'Base' (3 entries).
   0 | Base::~Base() [complete]
   1 | Base::~Base() [deleting]
   2 | void Base::FuncB()

Vtable for 'Derive' (13 entries).
   0 | vbase_offset (8)
   1 | offset_to_top (0)
   2 | Derive RTTI
       -- (Derive, 0) vtable address --
   3 | void Derive::FuncB()
   4 | Derive::~Derive() [complete]
   5 | Derive::~Derive() [deleting]
   6 | vcall_offset (-8)
   7 | vcall_offset (-8)
   8 | offset_to_top (-8)
   9 | Derive RTTI
       -- (Base, 8) vtable address --
  10 | Derive::~Derive() [complete]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  11 | Derive::~Derive() [deleting]
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  12 | void Derive::FuncB()
       [this adjustment: 0 non-virtual, -32 vcall offset offset]

Virtual base offset offsets for 'Derive' (1 entry).
   Base | -24

Thunks for 'Derive::~Derive()' (1 entry).
   0 | this adjustment: 0 non-virtual, -24 vcall offset offset

Thunks for 'void Derive::FuncB()' (1 entry).
   0 | this adjustment: 0 non-virtual, -32 vcall offset offset

VTable indices for 'Derive' (3 entries).
   0 | void Derive::FuncB()
   1 | Derive::~Derive() [complete]
   2 | Derive::~Derive() [deleting]

布局图大致如下

菱形继承时的情况

代码如下

c++ 复制代码
#include <stdio.h>

class Base
{
public:
    Base() = default;
    virtual ~Base() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("BaseA FuncB\n");
    }
    int a, b;
};

class BaseA : virtual public Base
{
public:
    BaseA() = default;
    virtual ~BaseA() = default;
    void FuncA() {}
    virtual void FuncB()
    {
        printf("BaseA FuncB\n");
    }
    int a, b;
};

class BaseB : virtual public Base
{
public:
    BaseB() = default;
    virtual ~BaseB() = default;
    void FuncA() {}
    virtual void FuncC()
    {
        printf("BaseB FuncC\n");
    }
    int a, b;
};

class Derive : public BaseB, public BaseA
{
public:
    void FuncB() override
    {
        printf("Derive FuncB \n");
    }
    void FuncC() override
    {
        printf("Derive FuncC \n");
    }
};

int main()
{
    BaseA a;
    Derive d;
    return 0;
}

根据相关命令查询到派生类的内存构造如下

c++ 复制代码
*** Dumping AST Record Layout
         0 | class Derive
         0 |   class BaseB (primary base)
         0 |     (BaseB vtable pointer)
         8 |     int a
        12 |     int b
        16 |   class BaseA (base)
        16 |     (BaseA vtable pointer)
        24 |     int a
        28 |     int b
        32 |   class Base (virtual base)
        32 |     (Base vtable pointer)
        40 |     int a
        44 |     int b
           | [sizeof=48, dsize=48, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout

根据输出不难看出当前派生类中含有三个虚函数表指针分别指向派生类继承的三个基类

结合虚函数表我们不难推出其布局大致如下

C++虚继承中基类的析构函数为什么需要被声明为虚函数

总所周知当我们想要实现多态操作时,就需要让基类指针指向子类对象或者让基类引用绑定子类对象,假设我们现在让一个基类的指针指向了一个派生类的对象,此时派生类中还复写了基类的虚函数,此时就可以用这个指针直接调用派生类的虚函数,当我们调用结束后使用delete进行内存释放时如果此时基类的析构函数不是虚函数,那么程序就会直接调用基类的析构函数,派生类中的成员变量所占用的内存空间就无法释放,这回造成内存泄漏问题,下面是一个小示例

c++ 复制代码
#include <iostream>
class Base {
public:
    ~Base() //非虚析构
    { 
        std::cout << "Base destroyed\n"; 
    }  
};
class Derived : public Base 
{
    int* data;  // 子类成员变量
public:
    Derived() : data(new int[100]) {}  
    ~Derived() // 子类析构
    {  
        delete[] data;  
        std::cout << "Derived destroyed\n"; 
    }
};

int main() {
    Base* ptr = new Derived();  // 基类指针指向子类对象
    delete ptr;  // 仅调用 Base::~Base()
    // 输出:"Base destroyed"
    // 结果:Derived::~Derived() 未被调用 → data 内存泄漏!
}

为什么构造函数不能是虚函数

虚函数通过虚函数表实现动态绑定,每个对象通过虚函数指针访问虚函数表,但是虚函数指针的初始化发生在构造函数中,如果构造函数是虚函数,调用它需要通过虚函数指针来查找虚函数表,但是此时对象构造尚未完成,且虚函数表指针尚未被初始化,这就形成了自相矛盾的情况:未初始化的虚函数指针无法定位虚函数,但是构造函数又需要通过虚函数表指针进行调用;这就陷入了先有鸡还是先有蛋的问题中了

菱形继承是如何解决的

菱形继承主要依赖虚函数表,虚函数表是多态的基础,虚函数表中存储虚基类的偏移量,确保最终派生类中只包含一份基类的实例,在运行时可以通过偏移量计算基类子对象的位置,虚函数表还包含了虚基类的偏移条目,可以用于定位共享基类的子对象,例如D对象包含B和C的虚函数,各自携带A的偏移信息,当通过虚基类指针调用重写的虚函数时编译器会通过虚基类表获取A的偏移量,从而调整this指针,调用正确的函数

相关推荐
Beginner x_u2 小时前
如何解释JavaScript 中 this 的值?
开发语言·前端·javascript·this 指针
java1234_小锋3 小时前
Java线程之间是如何通信的?
java·开发语言
张张努力变强3 小时前
C++ Date日期类的设计与实现全解析
java·开发语言·c++·算法
沉默-_-3 小时前
力扣hot100滑动窗口(C++)
数据结构·c++·学习·算法·滑动窗口
feifeigo1233 小时前
基于EM算法的混合Copula MATLAB实现
开发语言·算法·matlab
LYS_06183 小时前
RM赛事C型板九轴IMU解算(4)(卡尔曼滤波)
c语言·开发语言·前端·卡尔曼滤波
盛世宏博北京4 小时前
高效环境管控:楼宇机房以太网温湿度精准监测系统方案
开发语言·数据库·php·以太网温湿度变送器
斐夷所非4 小时前
C++ 继承、多态与类型转换 | 函数重载 / 隐藏 / 覆盖实现与基派生类指针转换
c++
IT猿手4 小时前
六种智能优化算法(NOA、MA、PSO、GA、ZOA、SWO)求解23个基准测试函数(含参考文献及MATLAB代码)
开发语言·算法·matlab·无人机·无人机路径规划·最新多目标优化算法