深入探索 C++ 多态 ② - 继承关系

前言

上一章 简述了虚函数的调用链路,本章主要探索 C++ 各种继承关系的类对象的多态特性。

  • 深入探索 C++ 多态 ① - 虚函数调用链路
  • 深入探索 C++ 多态 ② - 继承关系
  • 深入探索 C++ 多态 ③ - 虚析构

1. 概述

封装,继承,多态是 C++ 的三大特性,其中多态与继承有密切关系。C++ 语言支持三种继承关系:单一继承,多重继承,虚拟继承:

在这里插入图片描述

图片来源:《多型与虚拟》

在这里插入图片描述


2. 继承关系

2.1. 单一继承

C++ 的单一继承是指一个类只能从一个父类继承属性和方法。

文字来源:ChatGPT

动态多态的单一继承对象类层次结构相对简单:

  1. 对象内存只有一个虚指针,并且在其首位。
  2. 虚表上的虚函数,通过层层覆盖,最终得出对象对应的虚函数表,详看下图。
  • 测试代码。
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include <iostream>class Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}virtual void vBaseFunc3() {}long long m_base_data;long long m_base_data2;
};class Base2 : public Base {public:virtual void vBaseFunc() {}virtual void vBase2Func() { std::cout << "Base2::vBase2Func" << std::endl; }virtual void vBase2Func2() {}long long m_base2_data;long long m_base2_data2;
};class Derived : public Base2 {public:virtual void vBaseFunc2() {}virtual void vBase2Func() { std::cout << "Derived::vBase2Func" << std::endl; }virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data;long long m_derived_data2;
};
  • 类布局层次。
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 5u entries
0     (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8     (int (*)(...))(& _ZTI4Base)
16    (int (*)(...))Base::vBaseFunc
24    (int (*)(...))Base::vBaseFunc2
32    (int (*)(...))Base::vBaseFunc3Vtable for Base2
Base2::_ZTV5Base2: 7u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI5Base2)
16    (int (*)(...))Base2::vBaseFunc
24    (int (*)(...))Base::vBaseFunc2
32    (int (*)(...))Base::vBaseFunc3
40    (int (*)(...))Base2::vBase2Func
48    (int (*)(...))Base2::vBase2Func2# Derived 虚表。
Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 9u entries
0     (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8     (int (*)(...))(& _ZTI7Derived) 
16    (int (*)(...))Base2::vBaseFunc
24    (int (*)(...))Derived::vBaseFunc2
32    (int (*)(...))Base::vBaseFunc3
40    (int (*)(...))Derived::vBase2Func
48    (int (*)(...))Base2::vBase2Func2
56    (int (*)(...))Derived::vDerivedFunc
64    (int (*)(...))Derived::vDerivedFunc2# 类的继承关系
Class Derivedsize=56 align=8base size=56 base align=8
Derived (0x0x7fb058fa8478) 0# 虚指针指向虚表的位置。vptr=((& Derived::_ZTV7Derived) + 16u)Base2 (0x0x7fb058fa8a28) 0primary-for Derived (0x0x7fb058fa8478)Base (0x0x7fb058ee87e0) 0primary-for Base2 (0x0x7fb058fa8a28)
  • 虚表整合。

在这里插入图片描述

  • 对象整体布局。

在这里插入图片描述

  • 虚函数调用。

    1. 对象首位保存的是虚指针 vptr,虚指针指向虚表。
    2. 虚指针指向的虚表地址向高地址偏移 0x18 个字节,这样可以获取 Derived::vBase2Func 虚函数地址,然后进行调用。
int main() {auto d = new Derived;std::cout << d << std::endl;auto b = static_cast<Base2 *>(d);std::cout << b << std::endl;b->vBase2Func();return 0;
}// 输出:
// 0x13a0010
// 0x13a0010
// Derived::vBase2Func

在这里插入图片描述


2.2. 多重继承

C++ 支持多重继承,这意味着一个类可以从多个父类继承属性和方法,在 C++ 中,可以使用逗号分隔的方式来指定多个父类。

文字来源:ChatGPT。

  • 测试代码。
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include <iostream>class Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}long long m_base_data;long long m_base_data2;
};class Base2 {public:virtual void vBase2Func() {}virtual void vBase2Func2() { std::cout << "Base2::vBase2Func2" << std::endl; }long long m_base2_data;long long m_base2_data2;
};class Base3 {public:virtual void vBase3Func() {}virtual void vBase3Func2() {}long long m_base3_data;long long m_base3_data2;
};class Derived : public Base, public Base2, public Base3 {public:virtual void vBaseFunc() {}virtual void vBase2Func2() { std::cout << "Derived::vBase2Func2" << std::endl; }virtual void vBase3Func2() {}virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data;long long m_derived_data2;
};
  • 类布局层次。
Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0     (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8     (int (*)(...))(& _ZTI4Base)
16    (int (*)(...))Base::vBaseFunc
24    (int (*)(...))Base::vBaseFunc2Vtable for Base2
# _ZTV5Base2: vtable for Base2
Base2::_ZTV5Base2: 4u entries
0     (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
8     (int (*)(...))(& _ZTI5Base2)
16    (int (*)(...))Base2::vBase2Func
24    (int (*)(...))Base2::vBase2Func2Vtable for Base3
# _ZTV5Base3: vtable for Base3
Base3::_ZTV5Base3: 4u entries
0     (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
8     (int (*)(...))(& _ZTI5Base3)
16    (int (*)(...))Base3::vBase3Func
24    (int (*)(...))Base3::vBase3Func2Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 16u entries
0     (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
8     (int (*)(...))(& _ZTI7Derived)
16    (int (*)(...))Derived::vBaseFunc
24    (int (*)(...))Base::vBaseFunc2
32    (int (*)(...))Derived::vBase2Func2
40    (int (*)(...))Derived::vBase3Func2
48    (int (*)(...))Derived::vDerivedFunc
56    (int (*)(...))Derived::vDerivedFunc2
64    (int (*)(...))-24
72    (int (*)(...))(& _ZTI7Derived)
80    (int (*)(...))Base2::vBase2Func
# _ZThn24_N7Derived11vBase2Func2Ev: non-virtual thunk to Derived::vBase2Func2()
88    (int (*)(...))Derived::_ZThn24_N7Derived11vBase2Func2Ev
96    (int (*)(...))-48
104   (int (*)(...))(& _ZTI7Derived)
112   (int (*)(...))Base3::vBase3Func
# _ZThn48_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
120   (int (*)(...))Derived::_ZThn48_N7Derived11vBase3Func2EvClass Derivedsize=88 align=8base size=88 base align=8
Derived (0x0x7f4196042348) 0vptr=((& Derived::_ZTV7Derived) + 16u)Base (0x0x7f4195f3e840) 0primary-for Derived (0x0x7f4196042348)Base2 (0x0x7f4195f3e8a0) 24vptr=((& Derived::_ZTV7Derived) + 80u)Base3 (0x0x7f4195f3e900) 48vptr=((& Derived::_ZTV7Derived) + 112u)
  • 虚表整合。

    1. 首先派生类的虚表与第一个基类的虚表结合成一个虚表单元,并覆盖基类的虚函数。
    2. 其它的基类,作为一个独立虚表单元。当派生类虚函数有重写基类的虚函数时,基类对应虚函数,通过 thunk 技术跳转到第一个虚表单元的对应虚函数。

在这里插入图片描述

  • 对象整体布局。由下图可见:

    1. 多重继承有多个虚指针,并指向对应的虚表单元。
    2. 如果派生类有 N 个多重继承单一基类,那么它的对象有 N 个虚指针和虚表单元。

在这里插入图片描述

  • 虚函数调用。有了上面内存布局的理解,我们应该不难理解下面这个基类指针是怎么调用派生类虚函数的:
int main() {auto d = new Derived;std::cout << d << std::endl;auto b = static_cast<Base2 *>(d);std::cout << b << std::endl;b->vBase2Func2();return 0;
}// 输出:
// 0x13db010
// 0x13db028
// Derived::vBase2Func2
  1. Base2 指针指向存储 vptr2 的地址:从对象内存顶部向高地址偏移 0x18 个字节,获得 vptr2 虚指针。
  2. vptr2 指针指向的虚表地址向高地址偏移 0x8 个字节,获得 non-virtual thunk to Derived::vBase2Func2() 地址。
  3. 通过 non-virtual thunk to Derived::vBase2Func2() 地址跳转到 Derived::vBase2Func2 虚函数,获取虚表上对应的虚函数地址进行调用。

在这里插入图片描述

  • 通过汇编理解函数 thunk to 跳转的工作原理。
# thunk to 跳转原理(汇编)。
0000000000400aba <non-virtual thunk to Derived::vBase2Func2()>:# rdi 寄存器保存的是 b 指针指向的地址,该地址向低地址偏移 0x18 个字节,# 也就是 rdi 寄存器保存的是 Derived 内存首位地址,# 换句话说,将 Derived 的 this 指针作为参数传入 Derived::vBase2Func2 函数。400aba:    48 83 ef 18      sub    $0x18,%rdi# 调用 Derived::vBase2Func2() 函数。400abe:    eb d0            jmp    400a90 <Derived::vBase2Func2()>
  • 思考,上面多重继承的多态实例对象,下面这样释放是否正确?!(详情请参考:虚析构)。
int main() {Base2* b = new Derived;delete b;return 0;
}

2.3. 虚拟继承

多重继承可以让一个类具有多个不同父类的特性,但也可能引发一些问题,比如菱形继承问题。为了解决这个问题,C++ 提供了虚继承和虚基类的概念。虚继承可以解决菱形继承问题,确保只有一个实例的共享基类。

在 C++ 中,虚拟继承(virtual inheritance)是一种特殊的继承方式。它用于解决多重继承中的菱形继承问题。当一个类通过虚拟继承从多个基类继承时,只会保留一个基类的实例,而不会重复继承。这样可以避免菱形继承带来的二义性和冗余。在虚拟继承中,派生类需要使用关键字 “virtual” 来声明基类。

文字来源:ChatGPT


因为继承关系中有共享基类,为了避免共享基类产生多个对象副本浪费内存,虚拟继承的内存布局,也会与单一继承和多重继承不一样:

  1. 公共基类的成员数据,存放于对象内存底部。
  2. 虚拟继承引入 VTT(Virtual Table Table)构造虚表。
  3. 虚表前缀引入 vbase_offset 偏移量:当前虚表与公共基类虚表的内存位置偏移量。

虚拟继承的类层次关系结构有点复杂,有兴趣的朋友可以参考:What is the VTT for a class。


2.3.1. 对象整体布局
  • 测试代码。
/* g++ -O0 -std=c++11 -fdump-class-hierarchy test.cpp -o test */
#include <iostream>class Base {public:virtual void vBaseFunc() {}virtual void vBaseFunc2() {}long long m_base_data = 0x11;long long m_base_data2 = 0x12;
};class Base2 : virtual public Base {public:virtual void vBaseFunc() {}virtual void vBase2Func() {}virtual void vBase2Func2() {}long long m_base2_data = 0x21;long long m_base2_data2 = 0x22;
};class Base3 : virtual public Base {public:virtual void vBaseFunc2() {}virtual void vBase3Func() {}virtual void vBase3Func2() { std::cout << "Base3::vBase3Func2" << std::endl; }long long m_base3_data = 0x31;long long m_base3_data2 = 0x32;
};class Derived : public Base2, public Base3 {public:virtual void vBase2Func() {}virtual void vBase3Func2() { std::cout << "Derived::vBase3Func2" << std::endl; }virtual void vDerivedFunc() {}virtual void vDerivedFunc2() {}long long m_derived_data = 0x41;long long m_derived_data2 = 0x42;
};
  • 类对象内存布局。

在这里插入图片描述

Vtable for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTV7Derived: 21u entries
0     64u
8     (int (*)(...))0
# _ZTI7Derived: typeinfo for Derived
16    (int (*)(...))(& _ZTI7Derived)
24    (int (*)(...))Base2::vBaseFunc
32    (int (*)(...))Derived::vBase2Func
40    (int (*)(...))Base2::vBase2Func2
48    (int (*)(...))Derived::vBase3Func2
56    (int (*)(...))Derived::vDerivedFunc
64    (int (*)(...))Derived::vDerivedFunc2
72    40u
80    (int (*)(...))-24
88    (int (*)(...))(& _ZTI7Derived)
96    (int (*)(...))Base3::vBaseFunc2
104   (int (*)(...))Base3::vBase3Func
# _ZThn24_N7Derived11vBase3Func2Ev: non-virtual thunk to Derived::vBase3Func2()
112   (int (*)(...))Derived::_ZThn24_N7Derived11vBase3Func2Ev
120   18446744073709551576u # -40
128   18446744073709551552u # -64
136   (int (*)(...))-64
144   (int (*)(...))(& _ZTI7Derived)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
152   (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
160   (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2EvConstruction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0     64u
8     (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16    (int (*)(...))(& _ZTI5Base2)
24    (int (*)(...))Base2::vBaseFunc
32    (int (*)(...))Base2::vBase2Func
40    (int (*)(...))Base2::vBase2Func2
48    0u
56    18446744073709551552u # -64
64    (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72    (int (*)(...))(& _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80    (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88    (int (*)(...))Base::vBaseFunc2Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0     40u
8     (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16    (int (*)(...))(& _ZTI5Base3)
24    (int (*)(...))Base3::vBaseFunc2
32    (int (*)(...))Base3::vBase3Func
40    (int (*)(...))Base3::vBase3Func2
48    18446744073709551576u # -40
56    0u
64    (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72    (int (*)(...))(& _ZTI5Base3)
80    (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88    (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2EvVTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0     ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8     ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16    ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24    ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32    ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40    ((& Derived::_ZTV7Derived) + 152u)
48    ((& Derived::_ZTV7Derived) + 96u)Class Derivedsize=88 align=8base size=64 base align=8
Derived (0x0x7fd19d7401c0) 0vptridx=0u vptr=((& Derived::_ZTV7Derived) + 24u)Base2 (0x0x7fd19d6aea90) 0primary-for Derived (0x0x7fd19d7401c0)subvttidx=8uBase (0x0x7fd19d5ee840) 64 virtualvptridx=40u vbaseoffset=-24 vptr=((& Derived::_ZTV7Derived) + 152u)Base3 (0x0x7fd19d6aeaf8) 24subvttidx=24u vptridx=48u vptr=((& Derived::_ZTV7Derived) + 96u)Base (0x0x7fd19d5ee840) alternative-path

2.3.2. 构造顺序

我们可以通过类的构造顺序去理解:对象内存布局如何一步一步构造出来的。在构造派生类 Derived 时,先构造基类,当基类构造完了,才构造自己。

  • 构造流程。
|-- main|-- ...|-- Derived::Derived()|-- Base::Base()|-- Base2::Base2()|-- Base3::Base3()
  • 构造流程(汇编)。
...
0x400b33:    e8 34 02 00 00    callq  0x400d6c <Derived::Derived()>
...
0x400d83:    e8 06 ff ff ff    callq  400c8e <Base::Base()>
...
0x400d97:    e8 20 ff ff ff    callq  400cbc <Base2::Base2()>
...
0x400daf:    e8 60 ff ff ff    callq  400d14 <Base3::Base3()>
  • 构造 Base。

在这里插入图片描述

Vtable for Base
# _ZTV4Base: vtable for Base
Base::_ZTV4Base: 4u entries
0     (int (*)(...))0
# _ZTI4Base: typeinfo for Base
8     (int (*)(...))(& _ZTI4Base)
16    (int (*)(...))Base::vBaseFunc
24    (int (*)(...))Base::vBaseFunc2Class Basesize=24 align=8base size=24 base align=8
Base (0x0x7fd19d5ee720) 0vptr=((& Base::_ZTV4Base) + 16u)
  • 构造 Base2。

在这里插入图片描述

Construction vtable for Base2 (0x0x7fd19d6aea90 instance) in Derived
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
Derived::_ZTC7Derived0_5Base2: 12u entries
0     64u
8     (int (*)(...))0
# _ZTI5Base2: typeinfo for Base2
16    (int (*)(...))(& _ZTI5Base2)
24    (int (*)(...))Base2::vBaseFunc
32    (int (*)(...))Base2::vBase2Func
40    (int (*)(...))Base2::vBase2Func2
48    0u
56    18446744073709551552u # -64
64    (int (*)(...))-64
# _ZTI5Base2: typeinfo for Base2
72    (int (*)(...))(& _ZTI5Base2)
# _ZTv0_n24_N5Base29vBaseFuncEv: virtual thunk to Base2::vBaseFunc()
80    (int (*)(...))Base2::_ZTv0_n24_N5Base29vBaseFuncEv
88    (int (*)(...))Base::vBaseFunc2VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0     ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8     ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16    ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24    ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32    ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40    ((& Derived::_ZTV7Derived) + 152u)
48    ((& Derived::_ZTV7Derived) + 96u)
  • 构造 Base3。

在这里插入图片描述

VTT for Derived
# _ZTV7Derived: vtable for Derived
Derived::_ZTT7Derived: 7u entries
0     ((& Derived::_ZTV7Derived) + 24u)
# _ZTC7Derived0_5Base2: construction vtable for Base2-in-Derived
8     ((& Derived::_ZTC7Derived0_5Base2) + 24u)
16    ((& Derived::_ZTC7Derived0_5Base2) + 80u)
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
24    ((& Derived::_ZTC7Derived24_5Base3) + 24u)
32    ((& Derived::_ZTC7Derived24_5Base3) + 80u)
40    ((& Derived::_ZTV7Derived) + 152u)
48    ((& Derived::_ZTV7Derived) + 96u)Construction vtable for Base3 (0x0x7fd19d6aeaf8 instance) in Derived
# _ZTC7Derived24_5Base3: construction vtable for Base3-in-Derived
Derived::_ZTC7Derived24_5Base3: 12u entries
0     40u
8     (int (*)(...))0
# _ZTI5Base3: typeinfo for Base3
16    (int (*)(...))(& _ZTI5Base3)
24    (int (*)(...))Base3::vBaseFunc2
32    (int (*)(...))Base3::vBase3Func
40    (int (*)(...))Base3::vBase3Func2
48    18446744073709551576u # -40
56    0u
64    (int (*)(...))-40
# _ZTI5Base3: typeinfo for Base3
72    (int (*)(...))(& _ZTI5Base3)
80    (int (*)(...))Base::vBaseFunc
# _ZTv0_n32_N5Base310vBaseFunc2Ev: virtual thunk to Base3::vBaseFunc2()
88    (int (*)(...))Base3::_ZTv0_n32_N5Base310vBaseFunc2Ev
  • 构造 Derived (参考上面 整体布局)。

在这里插入图片描述


2.3.3. 虚函数调用
int main() {auto d = new Derived;std::cout << d << std::endl;auto b = static_cast<Base3 *>(d);std::cout << b << std::endl;b->vBase3Func2();return 0;
}// 输出:
// 0x9fa010
// 0x9fa028
// Derived::vBase3Func2
  1. b 指针指向存储 vptr.base3 的地址:从 Derived 对象内存顶部向高地址偏移 0x18 个字节。
  2. vptr.base3 指针指向的虚表地址向高地址偏移 0x10 个字节,获得 non-virtual thunk to Derived::vBase3Func2() 函数地址。
  3. 通过 non-virtual thunk to Derived::vBase3Func2() 地址跳转到 Derived::vBase3Func2 虚函数,获取虚表上对应的虚函数进行调用。

在这里插入图片描述


3. 后记

  • 要理解多态的对象内存布局,要注意理解(多个)虚指针是如何根据不同的基类指针进行偏移的,当虚指针指向虚表后,要获得对应的虚函数,虚指针要偏移一定的位置才能定位到对应的虚表上的虚函数。

  • 如果要用一个词来形容多态,那就是 覆盖,派生类重写基类虚函数,像图层一样,(派生类)上层覆盖下层(基类),层层叠加,最后得出了被覆盖的结果;这也是我们理解 虚表 结构的核心思维方式。

  • 关于有继承关系的 C++ 多态探索,因为本人水平有限,以上只作了一些基础简单的 Demo 的分析,还有一些应用场景没有涉及(例如 虚析构)。

  • 很多技术细节没有在文章中提及,有兴趣的朋友可以动手写写 demo 用 gdb 调试一下,查看对象内存布局上的地址数据,以及反汇编查看对象构造的逻辑,是否与自己的理解一致,这样才能在不断变化的问题表象里,寻获答案本质。

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

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

相关文章

驱动day10作业

基于platform驱动模型完成LED驱动的编写 驱动程序 #include <linux/init.h> #include <linux/module.h> #include<linux/platform_device.h> #include<linux/mod_devicetable.h> #include<linux/of.h> #include<linux/of_gpio.h> #inclu…

基于深度学习的安全帽识别检测系统(python OpenCV yolov5)

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、研究的内容与方法二、基于深度学习的安全帽识别算法2.1 深度学习2.2 算法流程2.3 目标检测算法2.3.1 Faster R-CNN2.3.2 SSD2.3.3 YOLO v3 三 实验与结果分析3.1 实验数据集3.1.1 实验数据集的构建3.1.2 数据…

iOS的应用生命周期以及应用界面

在iOS的原生开发中&#xff0c;我们需要特别关注两个东西&#xff1a;AppDelegate和ViewController。我们主要的编码工作就是在AppDelegate和ViewControlle这两个类中进行的。它们的类图如下图所示&#xff1a; AppDelegate是应用程序委托对象&#xff0c;它继承了UIResponder类…

均值、方差、标准差

1 中间值和均值 表现&#xff02;中间值&#xff02;的统计名词&#xff1a; a.均值:   mean&#xff0c;数列的算术平均值&#xff0c;反应了数列的集中趋势,等于有效数值的合除以有效数值的个数&#xff0e;b.中位值:  median&#xff0c;等于排序后中间位置的值&#x…

c++多线程

目录 一、进程与线程 二、多线程的实现 2.1 C中创建多线程的方法 2.2 join() 、 detach() 和 joinable() 2.2.1 join() 2.2.2 detach() 2.2.3 joinable() 2.3 this_thread 三、同步机制&#xff08;同步原语&#xff09; 3.1 同步与互斥 3.2 互斥锁&#xff08;mu…

在安装和配置DVWA渗透测试环境遇到的报错问题

安装环境 前面的安装我参考的这个博主&#xff1a;渗透测试漏洞平台DVWA环境安装搭建及初级SQL注入-CSDN博客 修改bug 1.首先十分感谢提供帮助的博主&#xff0c;搭建DVWA Web渗透测试靶场_dvwa 白屏-CSDN博客&#xff0c;解决了我大多数问题&#xff0c;报错如下&#xff1…

leetCode 137. 只出现一次的数字 II(拓展篇) + 模5加法器 + 真值表(数字电路)

leetCode 137. 只出现一次的数字 II 题解可看我的往期文章 leetCode 137. 只出现一次的数字 II 位运算 模3加法器 真值表&#xff08;数字电路&#xff09; 有限状态机-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/134138112?spm1001.2014.3001.5501…

N-131基于jsp,ssm物流快递管理系统

开发工具&#xff1a;eclipse&#xff0c;jdk1.8 服务器&#xff1a;tomcat7.0 数据库&#xff1a;mysql5.7 技术&#xff1a; springspringMVCmybaitsEasyUI 项目包括用户前台和管理后台两部分&#xff0c;功能介绍如下&#xff1a; 一、用户(前台)功能&#xff1a; 用…

040-第三代软件开发-全新波形抓取算法

第三代软件开发-全新波形抓取算法 文章目录 第三代软件开发-全新波形抓取算法项目介绍全新波形抓取算法代码小解 关键字&#xff1a; Qt、 Qml、 抓波、 截获、 波形 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object …

【Linux】jdk Tomcat MySql的安装及Linux后端接口部署

一&#xff0c;jdk安装 1.1 上传安装包到服务器 打开MobaXterm通过Linux地址连接到Linux并登入Linux&#xff0c;再将主机中的配置文件复制到MobaXterm 使用命令查看&#xff1a;ll 1.2 解压对应的安装包 解压jdk 解压命令&#xff1a;tar -xvf jdk 加键盘中Tab键即可…

「Dr. Bomkus 的试炼」排行榜说明

简要概括 七大区域&#xff0c;一个任务&#xff1a;六场扣人心弦的试炼&#xff0c;有一个休闲大厅作为每场试炼的起点。 试炼 排行榜&#xff1a;掌握每场试炼&#xff0c;攀登排行榜。 以 Ethos Point 来记分&#xff1a;每个试炼中的任务都会获得一个EP。 两种任务类型&am…

idea提交代码一直提示 log into gitee

解决idea提交代码一直提示 log into gitee问题 文章目录 打开setting->Version control->gitee,删除旧账号&#xff0c;重新配置账号&#xff0c;删除重新登录就好 打开setting->Version control->gitee,删除旧账号&#xff0c;重新配置账号&#xff0c;删除重新登…

局域网内远程控制电脑的软件

局域网内远程控制电脑的软件在日常办公中&#xff0c;非常常见了。它可以帮助用户在局域网内远程控制其他电脑&#xff0c;实现文件传输、桌面展示、软键盘输入等功能。 局域网内远程控制电脑的软件有很多种&#xff0c;其中比较实用的有域之盾软件、安企神软件、网管家软件等等…

专业135总400+合工大合肥工业大学833信号分析与处理信息通信上岸经验分享

专业135总400合工大合肥工业大学833信号分析与处理信息通信上岸经验分享 基础课经验很多&#xff0c;大同小异&#xff0c;我分享一下自己的833专业课复习经验。 一&#xff1a;用到的书本 1.《信号与系统》&#xff08;第三版&#xff09;郑君里&#xff0c;高等教育出版社…

最新Microsoft Edge浏览器如何使用圆角

引入 最近我看了edge官方的文档&#xff0c;里面宣传了edge的最新UI设计&#xff0c;也就是圆角&#xff0c;但是我发现我的浏览器在升级至最新版本之后&#xff0c;却没有圆角 网上有很多人说靠实验性功能即可解锁&#xff0c;但是指令我都试过了&#xff0c;每次都是搜索无结…

记一次红队打的逻辑漏洞(验证码绕过任意用户密码重置)

八月初参加某市演练时遇到一个典型的逻辑漏洞&#xff0c;可以绕过验证码并且重置任意用户的密码。 首先访问页面&#xff0c;用户名处输入账号会回显用户名称&#xff0c;输入admin会回显系统管理员。&#xff08;hvv的时候蓝队响应太快了&#xff0c;刚把admin的权限拿到了&a…

实力验证 | 求臻医学满分通过CAP及NCCL组织的国内外三项室间质评

近日&#xff0c;求臻医学以满分的优异成绩通过了由美国病理学家协会&#xff08;College of American Pathologists&#xff0c;CAP&#xff09;组织的NGS−A 2023&#xff1a;Next−Generation Sequencing (NGS) – Germline、NEO-B 2023 Neoplastic Cellularity能力验证项目…

Ansible中的playbook

目录 一、playbook简介 二、playbook的语法 三、playbook的核心组件 四、playbook的执行命令 五、vim 设定技巧 六、基本示例 一、playbook简介 1、playbook与ad-hoc相比&#xff0c;是一种完全不同的运用。 2、playbook是一种简单的配置管理系统与多机器部署系统的基础…

PostgreSQL在云端:部署、管理和扩展你的数据库

随着云计算技术的迅猛发展&#xff0c;将数据库迁移到云端已经成为许多企业的首选。而在众多数据库管理系统中&#xff0c;PostgreSQL因其稳定性、灵活性和可扩展性而成为了不少企业的首选之一。 部署PostgreSQL在云端 将PostgreSQL部署在云端是一个相对简单的过程。云服务提供…

MySQL数据库的存储引擎,底层存储结构,事物隔离级别,索引,日志等

存储引擎 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表而不是基于库的&#xff0c;所以存储引擎也可以被称为表引擎。 默认存储引擎是InnoDB。 InnoDB 在 MySQL 5.5 之后&#xff0c;InnoDB 是默认的 MySQL 引擎。 1.支持事务 2.行级锁…