当前位置: 首页 > news >正文

【C++篇】虚境探微:多态的流动诗篇,解锁动态的艺术密码

文章目录

  • C++ 多态详解(进阶篇)
    • 前言
    • 第一章:多态的原理
      • 1.1 虚函数表的概念
        • 1.1.1 虚函数表的生成过程
      • 1.2 虚表的存储位置
    • 第二章:动态绑定与静态绑定
      • 2.1 静态绑定
        • 2.1.1 静态绑定的实现机制:
        • 2.1.2 示例代码:
      • 2.2 动态绑定
        • 2.2.1 动态绑定的实现机制:
        • 2.2.2 示例代码:
        • 2.2.3 动态绑定的汇编分析:
      • 2.3 静态绑定与动态绑定的区别
        • 2.3.1 编译时绑定 vs 运行时绑定
        • 2.3.2 选择何时使用静态或动态绑定:
        • 2.3.3 示例对比:
    • 第三章:单继承和多继承中的虚函数表
      • 3.1 单继承中的虚函数表
        • 3.1.1 虚表的结构
        • 3.1.2 单继承虚表示例
        • 3.1.3 生成的虚函数表结构:
      • 3.2 多继承中的虚函数表
        • 3.2.1 多继承虚表示例
        • 3.2.2 多继承虚函数表的结构
      • 3.3 菱形继承中的虚函数表
        • 3.3.1 菱形继承的问题示例
        • 3.3.2 虚拟继承的解决方案
        • 3.3.3 虚拟继承下的内存布局
          • 3.3.3.1 Base 类的内存布局
          • 3.3.3.2 Derived1 类的内存布局
          • 3.3.3.3 Derived2 类的内存布局
          • 3.3.3.4 Final 类的内存布局
        • 3.3.4 调用过程解析
        • 3.3.5 小结
    • 写在最后

C++ 多态详解(进阶篇)

💬 欢迎讨论:在学习过程中,如果有任何疑问或想法,欢迎在评论区留言一起讨论。

👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?记得点赞、收藏并分享给更多的朋友吧!你们的支持是我不断进步的动力!
🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对 C++ 感兴趣的朋友,一起学习进步!


前言

在 C++ 中,多态(Polymorphism)是一种允许不同对象通过同一接口表现不同行为的机制。通过继承和虚函数的结合,多态为程序设计提供了灵活性和可扩展性。上一章我们讨论了多态的基础知识,涵盖了虚函数的基本概念及实现。这一章我们将深入分析多态的原理,包括虚函数表的构造及其在单继承和多继承中的表现,以及如何通过动态绑定实现灵活的函数调用。


第一章:多态的原理

1.1 虚函数表的概念

虚函数表(Virtual Table, VTable)是 C++ 实现运行时多态的核心机制。它是一个存储虚函数指针的数组,每个包含虚函数的类都至少有一个虚表。当一个类的虚函数被调用时,程序并不是直接调用函数的地址,而是通过虚函数表间接调用。每个对象实例都会保存一个指向虚表的指针(vptr),通过 vptr,程序可以找到对象对应的虚函数实现。

1.1.1 虚函数表的生成过程
  • 继承基类虚表:当一个派生类继承了基类,并且基类包含虚函数时,派生类会继承基类的虚表。
  • 覆盖虚函数:如果派生类重写了基类的虚函数,则派生类的虚表中会用派生类的函数覆盖基类的函数。
  • 派生类新函数:派生类新增的虚函数会被添加到虚表的末尾。
class Base {
public:virtual void func1() {cout << "Base::func1" << endl;}virtual void func2() {cout << "Base::func2" << endl;}void func3() {cout << "Base::func3" << endl;}
private:int _b;
};class Derived : public Base {
public:void func1() override {cout << "Derived::func1" << endl;}virtual void func4() {cout << "Derived::func4" << endl;}
private:int _d;
};int main() {Base b;Derived d;return 0;
}

在这个例子中,Derived 类重写了 Basefunc1,并且增加了一个新的虚函数 func4。此时 Derived 的虚表包含重写的 func1,以及新增的 func4,但不会包含 func3,因为它不是虚函数。

1.2 虚表的存储位置

虚表并不存储在对象内部。每个对象只包含一个指向虚表的指针(vptr)。虚表本身存储在程序的全局静态区中。每个包含虚函数的类的所有对象共享同一个虚表,而 vptr 是指向这个表的指针。虚表中记录了类中所有虚函数的地址,用于动态绑定函数调用。


第二章:动态绑定与静态绑定

2.1 静态绑定

静态绑定(Static Binding),也称为早期绑定,是在编译阶段决定函数调用的过程。编译器通过变量的静态类型(即声明时的类型)来确定调用的函数。这意味着函数的地址在编译时就已经确定,调用效率较高。通常,非虚函数和普通函数使用静态绑定。

2.1.1 静态绑定的实现机制:

对于静态绑定,编译器根据对象的声明类型直接生成目标代码。在这种情况下,编译器会直接调用函数的地址,而不需要在运行时查找。

2.1.2 示例代码:
class Base {
public:void print() {cout << "Base::print()" << endl;}
};int main() {Base b;b.print();  // 静态绑定,编译时已确定调用 Base::printreturn 0;
}

在这个例子中,print() 是一个非虚函数,因此编译器在编译时已经决定了 Base::print() 将被调用。这就是静态绑定的体现:函数的调用在编译时已知,执行时无需额外的查找。

2.2 动态绑定

动态绑定(Dynamic Binding),也称为晚期绑定,是在程序运行时根据对象的实际类型(而非声明类型)决定函数调用的过程。通过使用虚函数,动态绑定允许程序在运行时灵活选择调用哪一个派生类的函数。这种绑定方式依赖于虚函数表(VTable)机制。

2.2.1 动态绑定的实现机制:

动态绑定通过虚函数表实现。虚函数表是一个存储类中虚函数地址的数组。每个包含虚函数的类都拥有一个虚函数表,每个对象实例通过一个指针(vptr)指向它所属类的虚函数表。当程序调用虚函数时,实际的调用流程如下:

  1. 通过对象找到虚表指针(vptr)。
  2. 根据虚函数的偏移量,从虚表中获取函数指针。
  3. 通过函数指针进行实际的函数调用。
2.2.2 示例代码:
class Base {
public:virtual void print() {cout << "Base::print()" << endl;}
};class Derived : public Base {
public:void print() override {cout << "Derived::print()" << endl;}
};int main() {Base* basePtr = new Derived();basePtr->print();  // 动态绑定,运行时调用 Derived::print()delete basePtr;return 0;
}

在这个例子中,print() 是一个虚函数。当 basePtr 指向 Derived 对象时,尽管 basePtr 的类型是 Base*,但在运行时,程序通过虚表找到 Derived::print() 并调用它。这就是动态绑定的核心,通过虚表间接调用函数。

2.2.3 动态绑定的汇编分析:

动态绑定的底层实现可以通过汇编代码更直观地理解。在调用虚函数时,编译器会生成以下类似的汇编代码:

参考示例,读者大大可以自己通过调试来看喔💕

mov eax, dword ptr [basePtr]   ; 加载 basePtr 对象的地址到寄存器 eax
mov edx, dword ptr [eax]       ; 通过 eax 获取虚函数表指针(vptr)
mov eax, dword ptr [edx]       ; 从虚表中获取实际虚函数的地址
call eax                      ; 调用虚函数

这段汇编代码可以解释为:

  1. mov eax, dword ptr [basePtr]:将对象 basePtr 的值加载到寄存器 eax 中,eax 此时存放了 basePtr 对象的地址。
  2. mov edx, dword ptr [eax]:通过对象地址,获取对象的虚函数表指针 vptr,并存放在 edx 中。
  3. mov eax, dword ptr [edx]:从虚函数表中获取对应虚函数的地址。
  4. call eax:调用虚函数的实际地址。

通过这一流程可以看到,动态绑定并不是直接调用函数地址,而是通过虚表间接访问函数地址,这就是动态绑定的底层实现。


2.3 静态绑定与动态绑定的区别

2.3.1 编译时绑定 vs 运行时绑定
  • 静态绑定:函数调用在编译阶段就已确定,调用效率高。适用于非虚函数和静态函数。
  • 动态绑定:函数调用在运行时决定,灵活性高,但引入了一定的性能开销。适用于虚函数,尤其是在多态场景中。
2.3.2 选择何时使用静态或动态绑定:
  1. 性能要求高的场景:如果程序的性能要求高,并且函数调用的多态性不强,可以选择静态绑定。静态绑定没有虚表的查找开销。
  2. 多态需求强的场景:当需要通过基类指针或引用调用派生类的不同实现时,动态绑定是必不可少的。虚函数通过虚表机制,可以在运行时调用不同的派生类函数。
2.3.3 示例对比:
class Base {
public:void staticPrint() {  // 静态绑定cout << "Base static print" << endl;}virtual void dynamicPrint() {  // 动态绑定cout << "Base dynamic print" << endl;}
};class Derived : public Base {
public:void staticPrint() {  // 覆盖非虚函数,仍是静态绑定cout << "Derived static print" << endl;}void dynamicPrint() override {  // 重写虚函数,动态绑定cout << "Derived dynamic print" << endl;}
};int main() {Base* ptr = new Derived();ptr->staticPrint();   // 静态绑定,调用 Base::staticPrintptr->dynamicPrint();  // 动态绑定,调用 Derived::dynamicPrintdelete ptr;return 0;
}

在这个例子中,staticPrint() 是静态绑定,而 dynamicPrint() 是动态绑定。尽管 ptr 指向的是 Derived 对象,但因为 staticPrint() 是静态绑定,调用的是 Base 类中的实现。而 dynamicPrint() 是虚函数,最终调用的是 Derived 类中的实现。


第三章:单继承和多继承中的虚函数表

3.1 单继承中的虚函数表

在单继承的场景下,派生类会继承基类的虚函数表(VTable)。当派生类重写了基类的虚函数时,虚表中的基类函数指针会被派生类函数的指针替换。如果派生类定义了新的虚函数,这些新的虚函数指针将会追加到派生类的虚表末尾。

3.1.1 虚表的结构

在单继承的情况下,虚表的构造过程可以分为以下几个步骤:

  1. 继承基类虚表:当派生类继承了基类,并且基类中含有虚函数时,派生类会自动继承基类的虚表。派生类可以通过这个虚表调用基类中的虚函数。
  2. 重写虚函数:如果派生类重写了基类的虚函数,则派生类的虚表中相应的函数指针将会被覆盖为派生类的函数实现,重写的虚函数替换基类的虚函数。
  3. 添加新虚函数:派生类定义的新虚函数会被添加到派生类的虚表末尾。
3.1.2 单继承虚表示例
class Base {
public:virtual void func1() { cout << "Base::func1()" << endl; }virtual void func2() { cout << "Base::func2()" << endl; }
};class Derived : public Base {
public:void func1() override { cout << "Derived::func1()" << endl; }virtual void func3() { cout << "Derived::func3()" << endl; }
};

在这个例子中:

  • Base 类中有两个虚函数 func1()func2(),这些虚函数的地址会存储在 Base 类的虚表中。
  • Derived 类继承了 Base 类,并且重写了 func1()。因此,Derived 类的虚表会用 Derived::func1() 的地址替换 Base::func1() 的地址,而 func2() 仍然指向 Base::func2()
  • Derived 类定义了一个新的虚函数 func3(),因此 func3() 的指针会被追加到 Derived 类的虚表末尾。
3.1.3 生成的虚函数表结构:
  1. Base 类虚表

    • func1 -> Base::func1
    • func2 -> Base::func2
  2. Derived 类虚表

    • func1 -> Derived::func1(替换了基类的 func1
    • func2 -> Base::func2
    • func3 -> Derived::func3(新增)

3.2 多继承中的虚函数表

在多继承中,派生类继承自多个基类,每个基类都有自己的虚函数表。派生类会为每个基类维护一个独立的虚表,来存储对应基类的虚函数指针。当调用虚函数时,派生类会根据继承自哪个基类,选择相应的虚表来查找虚函数的地址。

3.2.1 多继承虚表示例
class Base1 {
public:virtual void func1() { cout << "Base1::func1()" << endl; }
};class Base2 {
public:virtual void func2() { cout << "Base2::func2()" << endl; }
};class Derived : public Base1, public Base2 {
public:void func1() override { cout << "Derived::func1()" << endl; }void func2() override { cout << "Derived::func2()" << endl; }
};

在这个例子中,Derived 类从 Base1Base2 继承。Derived 类会生成两个虚表,一个用于继承自 Base1 的虚函数,另一个用于继承自 Base2 的虚函数。

3.2.2 多继承虚函数表的结构
  1. Base1 类虚表

    • func1 -> Base1::func1
  2. Base2 类虚表

    • func2 -> Base2::func2
  3. Derived 类虚表

    • Base1 虚表 -> func1 -> Derived::func1Derived 重写了 Base1 的虚函数 func1
    • Base2 虚表 -> func2 -> Derived::func2Derived 重写了 Base2 的虚函数 func2

多继承的情况下,派生类会为每个基类生成单独的虚表,当调用派生类的虚函数时,会根据调用的基类函数选择相应的虚表。例如,当调用 Derived 对象的 func1() 时,程序会访问 Base1 的虚表,而调用 func2() 时,程序会访问 Base2 的虚表。


3.3 菱形继承中的虚函数表

菱形继承指的是派生类通过两个基类继承,而这两个基类又继承自同一个公共祖先类。由于这种继承路径,派生类可能会从多个路径继承相同的基类,从而产生两个问题:

  1. 数据冗余:每条继承路径都会创建一个独立的基类实例,导致派生类中出现多个相同的基类副本。
  2. 函数调用的二义性:如果公共基类中有虚函数,派生类会有多个虚函数表,导致编译器不知道调用哪个基类的虚函数实现。
3.3.1 菱形继承的问题示例

以下是一个典型的菱形继承示例:

class Base {
public:virtual void func() { cout << "Base::func()" << endl; }
};class Derived1 : public Base {};
class Derived2 : public Base {};class Final : public Derived1, public Derived2 {};

在这个例子中:

  • Derived1Derived2 都继承自 Base
  • Final 类通过 Derived1Derived2 继承自 Base

由于没有使用虚拟继承,Final 类会继承两个独立的 Base 类实例,这就带来了以下问题:

  • 数据冗余Final 类将会有两个 Base 类的实例。
  • 函数调用的二义性:如果我们调用 Final 对象的 func() 方法,编译器不知道该调用 Derived1Base::func() 还是 Derived2Base::func()
3.3.2 虚拟继承的解决方案

为了解决菱形继承带来的数据冗余和函数调用二义性问题,C++ 提供了虚拟继承。通过虚拟继承,派生类只会保留一个公共基类的实例,而不是在每条继承路径上都生成一个基类实例。

class Base {
public:virtual void func() { cout << "Base::func()" << endl; }
};class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};class Final : public Derived1, public Derived2 {};

在这个版本中,Derived1Derived2 都使用了 虚拟继承virtual public Base)。这意味着 Final 类最终只有一个 Base 类实例,解决了以下两个问题:

  • 数据冗余:无论通过多少条继承路径,Final 类中最终只有一个 Base 实例。
  • 函数调用的二义性:因为 Final 类中只有一个 Base 实例,虚函数调用时不会产生二义性。
3.3.3 虚拟继承下的内存布局

在使用虚拟继承时,类的内存布局变得更加复杂,特别是对于菱形继承的情况。我们会通过图解展示 BaseDerived1Derived2Final 类的内存布局,重点关注 虚函数表指针(vptr)虚基表指针(vbase ptr)


3.3.3.1 Base 类的内存布局
  • Base 类有一个 虚函数表指针(vptr),它指向虚函数表,用于记录 func() 函数的地址。
Base 内存布局:
+-------------------+
| vptr -> Base VTable|
+-------------------+
| 其他成员数据(如果有)|
+-------------------+
  • Base VTable 包含虚函数 Base::func() 的地址。
Base VTable:
+------------------+
| func -> Base::func|
+------------------+

3.3.3.2 Derived1 类的内存布局
  • Derived1 通过虚拟继承自 Base,因此它不会直接包含 Base 的实例。相反,它有两个指针:
    • 虚函数表指针(vptr):指向 Derived1 的虚函数表,用于存储虚函数的地址。
    • 虚基表指针(vbase ptr):指向共享的 Base 类实例。
Derived1 内存布局:
+-----------------------+
| vptr -> Derived1 VTable|
+-----------------------+
| vbase ptr -> Base      |  (虚基表指针,指向唯一的 Base 实例)
+-----------------------+
  • Derived1 VTable 继承了 Base::func(),因此虚函数表包含 Base::func() 的地址。
Derived1 VTable:
+-------------------+
| func -> Base::func |
+-------------------+

3.3.3.3 Derived2 类的内存布局
  • Derived2 也通过虚拟继承自 Base,因此它的内存布局与 Derived1 类似。
    • 虚函数表指针(vptr):指向 Derived2 的虚函数表。
    • 虚基表指针(vbase ptr):指向 Base 类的唯一实例。
Derived2 内存布局:
+-----------------------+
| vptr -> Derived2 VTable|
+-----------------------+
| vbase ptr -> Base      |  (虚基表指针,指向唯一的 Base 实例)
+-----------------------+
  • Derived2 VTable 同样继承了 Base::func()
Derived2 VTable:
+-------------------+
| func -> Base::func |
+-------------------+

3.3.3.4 Final 类的内存布局
  • Final 类通过 Derived1Derived2 继承 Base,它拥有:
    • 两个 虚函数表指针(vptr),分别指向 Derived1Derived2 的虚函数表。
    • 两个 虚基表指针(vbase ptr),它们都指向唯一的 Base 实例。
    • 唯一的 Base 类实例。
Final 内存布局:
+------------------------+
| vptr -> Derived1 VTable |
+------------------------+
| vbase ptr -> Base       |  (来自 Derived1 的虚基表指针)
+------------------------+
| vptr -> Derived2 VTable |
+------------------------+
| vbase ptr -> Base       |  (来自 Derived2 的虚基表指针)
+------------------------+
| Base                    |  (唯一的 Base 实例)
+------------------------+
  • Final 类通过 虚基表指针(vbase ptr) 确保它共享同一个 Base 类实例,而不会有多个 Base 类副本。

3.3.4 调用过程解析

当我们调用 Final 类对象的 func() 方法时,虚拟继承保证调用过程如下:

  1. Final 类中的 vptr(虚函数表指针)指向唯一的 Base 类实例的虚函数表。
  2. Final 类中的 vbase ptr(虚基表指针)确保所有路径指向唯一的 Base 类实例。
  3. 最终,通过虚表找到 Base::func() 的地址并执行,避免了函数调用的二义性。
int main() {Final f;f.func();  // 调用 Base::func(),只有一个 Base 实例return 0;
}

输出:

Base::func()
3.3.5 小结
  • 虚拟继承消除了冗余:通过虚基表,Final 类只会包含一个 Base 类实例,避免了菱形继承中的数据冗余。
  • 函数调用无二义性:虚拟继承保证了虚表指针只指向唯一的 Base 实例,从而解决了函数调用时的歧义。

写在最后

在这篇文章中,我们深入探索了 C++ 中的多态机制,从静态绑定与动态绑定的差异,到虚函数表(VTable)背后的运作原理,再到菱形继承中的虚拟继承解决方案,逐步揭开了多态在编程中的神秘面纱。我们看到了 C++ 如何通过虚表实现动态调用的灵活性,如何在多继承和虚拟继承中有效解决基类重复和函数调用二义性的问题。通过掌握这些知识,不仅能够更高效地设计系统,还能够在实际项目中运用多态的强大力量,使代码更加灵活、可扩展。

多态不仅仅是编程的一个特性,它更像是一首灵动的乐章,让代码在不同对象之间自由流动,展现出不同的形态与生命力。在未来的开发中,多态将继续成为构建强大、灵活系统的核心要素,值得我们深入研究与灵活运用。


以上就是关于【C++篇】虚境探微:多态的流动诗篇,解锁动态的艺术密码的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

相关文章:

【C++篇】虚境探微:多态的流动诗篇,解锁动态的艺术密码

文章目录 C 多态详解&#xff08;进阶篇&#xff09;前言第一章&#xff1a;多态的原理1.1 虚函数表的概念1.1.1 虚函数表的生成过程 1.2 虚表的存储位置 第二章&#xff1a;动态绑定与静态绑定2.1 静态绑定2.1.1 静态绑定的实现机制&#xff1a;2.1.2 示例代码&#xff1a; 2.…...

uniapp的相关知识(1)

1、hover-class&#xff1a;当有鼠标按下时&#xff0c;会切换对应的样式&#xff1b;也可以设置对应的变色时间。 2、selectable&#xff1a;设置text组件的文本是否可以进行复制。 3、with&#xff1a;当设置为80%时&#xff0c;表示宽占整个屏幕的80%。 4、border&#x…...

uniapp生成随机数

推荐学习文档 golang应用级os框架&#xff0c;欢迎stargolang应用级os框架使用案例&#xff0c;欢迎star案例&#xff1a;基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识&#xff0c;这里有免费的golang学习笔…...

使用jenkins将airflow-dbt部署到服务器上

系列文章目录 文章目录 系列文章目录课程地址YT一、jenkins服务器的初始化配置1.1 运行第一个jenkins pipeline二、编写本地dbt项目2.1 克隆git上的初始文件到本地2.2 本地创建虚拟环境2.3 创建airflow的Dockerfile2.4 安装dbt2.5 创建dbt所需要的snowflake数据库2.6 配置docke…...

初学java练习题【1】

import java.util.Scanner;public class HelloWorld{public static void main(String[] args){Scanner scannernew Scanner(System.in);//输入工资System.out.println("请输入您的工资&#xff1a;");double d1scanner.nextDouble();System.out.println("请输入…...

大模型应用探讨,免费AI写作、一键PPT、免费PDF百种应用、与AI对话

大模型应用平台知识普及, 应用可见评论区 我们生活在一个充满无限可能的数字时代&#xff0c;人工智能技术正在推动着各种创新的边界。大模型应用平台一般包含以下功能。 ## 1. 一键生成论文 写作是学生、研究人员和职场人士都无法避免的任务。大模型应用平台拥有强大的文本生…...

计算机视觉之OpenCV vs YOLO

好多开发者希望搞明白OpenCV 和YOLO区别&#xff0c;实际上&#xff0c;二者在计算机视觉领域都有广泛应用&#xff0c;但它们有很大的不同。 一、OpenCV 概述 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它…...

【深度学习基础模型】胶囊网络(Capsule Networks, CapsNet)详细理解并附实现代码。

【深度学习基础模型】胶囊网络&#xff08;Capsule Networks, CapsNet&#xff09;详细理解并附实现代码。 【深度学习基础模型】胶囊网络&#xff08;Capsule Networks, CapsNet&#xff09;详细理解并附实现代码。 文章目录 【深度学习基础模型】胶囊网络&#xff08;Capsul…...

科普向 -- 什么是RPC

科普向 – 什么是RPC RPC&#xff0c;全称为远程过程调用&#xff08;Remote Procedure Call&#xff09;&#xff0c;是一种计算机通信协议&#xff0c;允许程序在不同的地址空间&#xff08;通常是不同的计算机&#xff09;上执行代码。RPC使得程序可以像调用本地函数一样调…...

SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(基础)

SpringBoot教程&#xff08;二十四&#xff09; | SpringBoot实现分布式定时任务之Quartz&#xff08;基础&#xff09; 简介适用场景Quartz核心概念Quartz 存储方式Quartz 版本类型引入相关依赖开始集成方式一&#xff1a;内存方式(MEMORY)存储实现定时任务1. 定义任务类2. 定…...

【现代控制理论】第2-5章课后题刷题笔记

文章目录 第二章&#xff1a;线性控制系统的状态空间描述第三章&#xff1a;控制系统状态空间描述的特性3.1 计算状态转移矩阵&#xff08;矩阵指数函数&#xff09;3.2 计算系统的时间响应&#xff08;状态方程的解&#xff09;3.3 判断系统的能控性和能观性&#xff0c;以及能…...

(四)Proteus仿真STM32单片机使用定时器控制LED

&#xff08;四&#xff09;Protues仿真STM32单片机使用定时器控制LED – ARMFUN 定时器在单片机中具有重要的作用&#xff0c;它可以提供精确的时间控制和事件触发功能。相比之下&#xff0c;使用延时函数&#xff08;delay function&#xff09;来实现时间控制存在以下一些坏…...

Python快速编程小案例——打印蚂蚁森林植树证书

提示&#xff1a;&#xff08;个人学习&#xff09;&#xff0c;案例来自工业和信息化“十三五”人才培养规划教材&#xff0c;《Python快速编程入门》第2版&#xff0c;黑马程序员◎编著 蚂蚁森林是支付宝客户端发起“碳账户”的一款公益活动:用户通过步行地铁出行、在线消费等…...

Cherno游戏引擎笔记(61~72)

---------------一些维护和更改------------- 》》》》 Made Win-GenProjects.bat work from every directory 代码更改&#xff1a; echo off->pushd ..\->pushd %~dp0\..\call vendor\bin\premake\premake5.exe vs2019popdPAUSE 为什么要做这样的更改&#xff1f; …...

FWA(固定无线接入),CPE(客户终端设备)简介

文章目录 FWA&#xff08;Fixed Wireless Access&#xff09;&#xff0c;固定无线接入CPE&#xff08;Customer Premise Equipment&#xff09;&#xff0c;用户驻地设备 FWA&#xff08;Fixed Wireless Access&#xff09;&#xff0c;固定无线接入 固定无线接入&#xff08…...

使用IDEA启动项目build时,解决Java编译时内存溢出问题:OutOfMemoryError深入解析

文章目录 简介问题描述解决方案常见解决方案示例代码示例1&#xff1a;增加JVM堆内存代码示例2&#xff1a;检查并修复内存泄漏代码示例3&#xff1a;分批编译代码示例4&#xff1a;使用编译器参数减少内存使用代码示例5&#xff1a;升级编译器和库 结论进一步的资源 简介 在J…...

Kafka如何实现高可用

Kafka实现高可用性主要依赖于其副本机制和Leader选举。以下是Kafka实现高可用的关键点&#xff1a; 多副本机制&#xff1a;Kafka中的每个分区&#xff08;Partition&#xff09;都有多个副本&#xff08;Replica&#xff09;&#xff0c;这些副本分布在不同的Broker上。其中一…...

高级java每日一道面试题-2024年10月1日-服务器篇[Redis篇]-Redis数据结构压缩列表和跳跃表的区别?

如果有遗漏,评论区告诉我进行补充 面试官: Redis数据结构压缩列表和跳跃表的区别&#xff1f; 我回答: 关于Redis数据结构的理解是一个重要的考察点&#xff0c;特别是压缩列表&#xff08;ziplist&#xff09;和跳跃表&#xff08;skiplist&#xff09;这两种数据结构&…...

使用 ElLoading 组件来实现加载(loading)功能

在 Element Plus 中&#xff0c;你可以使用 ElLoading 组件来实现加载&#xff08;loading&#xff09;功能。ElLoading 通常用于在数据加载或某些异步操作进行时&#xff0c;向用户展示一个覆盖整个页面的加载提示。 以下是如何在你的 Vite Vue 3 JavaScript 项目中使用 El…...

Win10 IDEA连接虚拟机中的Hadoop(HDFS)

获取虚拟机的ip 虚拟机终端输入 ip a关闭虚拟机防火墙 sudo ufw disable修改Hadoop的core-site.xml文件 将localhost修改为虚拟机局域网IP # 位置可能不一样&#xff0c;和Hadoop安装位置有关 cd /usr/local/hadoop/etc/hadoop vim core-site.xmlIDEA 连接 创建Maven项目…...

tp8自带的文件缓存如何配置

TP8自带的缓存是文件缓存。‌ ThinkPHP6默认的缓存驱动是文件缓存&#xff0c;它将缓存数据存储在应用的runtime目录下的cache目录中。文件缓存适用于单机环境下的应用&#xff0c;对于数据量较小且读写频率较低的应用场景&#xff0c;是一种简单有效的缓存方案‌。 ThinkPHP8…...

【环境搭建】MAC M1安装ElasticSearch

STEP1 官网下载ES Download Elasticsearch | Elastic&#xff0c;下载mac m1对应版本的es STEP2 进入bin文件夹&#xff0c;执行./elasticSearch 浏览器输入 127.0.0.1:9200 STEP 3 下载对应Kibana版本&#xff0c;Download Kibana Free | Get Started Now | Elastic 出现报错…...

[linux 驱动]网络设备驱动详解

目录 1 描述 2 结构体 2.1 net_device 2.2 sk_buff 2.3 net_device_ops 2.4 ethtool_ops 3 相关函数 3.1 网络协议接口层 3.1.1 dev_queue_xmit 3.1.2 netif_rx 3.1.3 alloc_skb 3.1.4 kfree_skb 3.1.5 skb_put 3.1.6 skb_push 3.1.7 skb_reserve 3.2 网络设备驱…...

【ShuQiHere】 重新定义搜索:本体搜索引擎的时代

&#x1f310; 【ShuQiHere】 什么是本体搜索引擎&#xff1f;&#x1f916; 本体搜索引擎&#xff08;Ontological Search Engine, OSE&#xff09; 是一种基于语义理解和本体结构的智能搜索工具。与传统的关键词搜索不同&#xff0c;本体搜索引擎能够理解搜索背后的深层语义…...

Ruby脚本:自动化网页图像下载的实践案例

随着互联网的快速发展&#xff0c;网页上的内容变得越来越丰富&#xff0c;尤其是图像资源。对于需要大量图像资源的设计师、内容创作者或数据分析师来说&#xff0c;手动下载这些图片不仅耗时耗力&#xff0c;而且效率低下。因此&#xff0c;自动化网页图像下载成为了一个迫切…...

ArcGIS中分区统计栅格值前需要进行投影吗(在投影坐标系下进行吗),为什么?

最近&#xff0c;我接到了一个分区统计栅格数值前需要进行投影&#xff0c;或者说是必须需要在投影坐标系下进行吗的咨询。 答案是不需要刻意去变。 但是他又说他把地理坐标系下分区统计结果与投影坐标系下的分区统计结果分别做了一遍&#xff0c;并进行了对比&#xff0c;两个…...

怎么将视频原声提出来?视频原声提取,让创作更自由

在数字媒体时代&#xff0c;视频已成为我们日常生活和工作中不可或缺的一部分。有时&#xff0c;我们可能想要提取视频中的音频部分&#xff0c;无论是为了制作音频素材、学习语言&#xff0c;还是为了其他创意用途。那么&#xff0c;怎么将视频原声提出来呢&#xff1f;本文将…...

在IDEA里用XDebug调试PHP,断点....

做程序开发,调试必不可少,这里最近用到了PHP,顺便写个关于PHP的调试安装使用: 1、首先是PHP先安装xdebug扩展(还有zend的),这个我的工具是IDEA,所以安装方法也相对简单,如果你是用VSCode等应该也是一样,如下图,找到这个PHP->DEBUG 2、直接点上面的Install XDebug 就可以帮你…...

如何设置 GitLab 密码过期时间?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 60天专业…...

重学SpringBoot3-集成Redis(十二)之点赞功能实现

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Redis&#xff08;十二&#xff09;之点赞功能实现 1. 点赞功能的场景分析2. 项目环境配置2.1. 依赖引入2.2. Redis 配置 3. 点赞功能的实现3.1. 点…...