解锁C++继承的奥秘:从基础到精妙实践(下)

在这里插入图片描述

文章目录

    • 前言
    • 🥐五、多继承,菱形继承和菱形虚拟继承
      • 🧀5.1 多继承
      • 🧀5.2 菱形继承
      • 🧀5.3 虚拟继承(解决菱形继承问题)
        • 5.3.1 虚拟继承的语法:
        • 5.3.2 虚拟继承示例:
      • 🧀5.4 虚拟继承的工作原理
        • 5.4.1 继承路径的管理:
      • 🧀5.5 虚拟继承中的构造顺序
    • 🥐六、多继承的指针偏移问题
      • 🧀6.1 普通多继承中的指针偏移问题
      • 🧀6.2 指针偏移在内存中的表现
      • 🧀6.3 虚拟继承中的指针偏移问题
      • 🧀6.4 汇编视角下的指针偏移
        • 6.4.1 普通继承的汇编:
        • 6.4.2 虚拟继承的汇编:
      • 🧀6.5 虚拟继承中指针偏移的机制
    • 🥐七、虚拟继承与汇编之间的关系
      • 🧀7.1 虚拟继承的内存布局
        • 7.1.1 内存布局对比
      • 🧀7.2 虚基表(vbtable)与指针调整
      • 🧀7.3 汇编视角
        • 7.3.1 汇编代码中的指针调整
        • 7.3.2 普通继承的汇编代码:
        • 7.3.3 虚拟继承的汇编代码:
      • 🧀7.4 虚拟继承带来的开销
    • 🥐八、继承与组合
      • 🧀8.1 继承的优缺点:
      • 🧀8.2 组合
      • 🧀8.3 继承 vs 组合:如何选择?
      • 🧀8.4 继承与组合的混合使用
      • 🧀8.5 优先使用组合原则
    • 结语


前言

我们接上集解锁C++继承的奥秘:从基础到精妙实践(上),继续深入探讨C++继承的多重继承的处理、虚函数与多态的应用,以及如何在复杂系统中有效利用继承来构建可维护且扩展性强的代码架构。通过系统的学习,你将对C++继承有更深入的理解,并能够在实际开发中灵活应用这些知识。


🥐五、多继承,菱形继承和菱形虚拟继承

在C++中,多继承 是指一个类可以继承自多个基类。这是C++区别于其他语言(如Java)的一个特性。菱形继承(也叫“钻石继承”)是多继承中常见的一种继承结构,其中一个派生类通过不同路径继承了同一个基类。虚拟继承 是C++为解决菱形继承问题而提供的一个机制。

🧀5.1 多继承

多继承是指一个派生类可以继承多个基类。派生类可以同时继承基类的所有属性和方法。在多继承的情况下,派生类从多个基类获得特性。如图分析单继承与多继承的区别:

在这里插入图片描述

示例:

#include <iostream>
using namespace std;

class Base1 {
public:
    void show() {
        cout << "Base1::show()" << endl;
    }
};

class Base2 {
public:
    void display() {
        cout << "Base2::display()" << endl;
    }
};

// Derived类同时继承Base1和Base2
class Derived : public Base1, public Base2 {
};

int main() {
    Derived d;
    d.show();    // 调用Base1的方法
    d.display(); // 调用Base2的方法
    return 0;
}

说明:

  • Derived类继承了Base1Base2的成员,能够同时访问两个基类的方法。
  • 多继承允许一个类具备多个基类的功能,但也可能带来复杂性,尤其是涉及同名成员时。

🧀5.2 菱形继承

菱形继承(Diamond Inheritance)是多继承的一种特殊情况。它发生在一个派生类通过多个路径继承同一个基类时,形成菱形结构:

在这里插入图片描述

在这种结构中,D类通过BC分别继承了基类A。此时,D类会有两个A类的副本,造成数据冗余和不一致性的问题。这就是菱形继承问题

示例:

#include <iostream>
using namespace std;

class A {
public:
    int value;
    A() { value = 10; }
};

// B和C类都继承A
class B : public A {
};

class C : public A {
};

// D类通过B和C同时继承A
class D : public B, public C {
};

int main() {
    D d;
    // d.value; // 错误!不明确的访问,D有两个A的副本
    d.B::value = 20; // 通过B路径访问A
    d.C::value = 30; // 通过C路径访问A

    cout << "B::value = " << d.B::value << endl; // 输出: B::value = 20
    cout << "C::value = " << d.C::value << endl; // 输出: C::value = 30

    return 0;
}

说明:

  • D类通过BC分别继承了两个A类的副本,造成了两个A::value的独立实例。这意味着D类中存在两个A::value变量,通过不同路径访问会产生不同的结果。
  • 这种冗余会导致数据不一致和维护困难的问题,这就是菱形继承的主要问题。

🧀5.3 虚拟继承(解决菱形继承问题)

为了解决菱形继承中的冗余问题,C++提供了虚拟继承机制。通过虚拟继承,可以确保在菱形继承结构中,只存在一个基类的副本,而不是每条继承路径都创建一个基类的副本。

5.3.1 虚拟继承的语法:
class Derived : virtual public Base { };

通过virtual关键字声明的继承就是虚拟继承,虚拟继承确保在多条路径继承同一基类时,派生类中只保留一份基类的副本。

5.3.2 虚拟继承示例:
#include <iostream>
using namespace std;

class A {
public:
    int value;
    A() { value = 10; }
};

// B和C通过虚拟继承A
class B : virtual public A {
};

class C : virtual public A {
};

// D通过B和C继承A,但只有一个A的副本
class D : public B, public C {
};

int main() {
    D d;
    d.value = 100;  // D类中只有一个A的实例
    cout << "D::value = " << d.value << endl; // 输出: D::value = 100

    return 0;
}

说明:

  • 通过virtual继承,D类只继承了一个A类的副本,无论通过B还是C访问A::value,都是同一个值。
  • 虚拟继承消除了冗余问题,避免了菱形继承中的数据不一致性。

🧀5.4 虚拟继承的工作原理

  • 普通继承:在普通继承中,派生类每次从基类继承时都会复制一份基类的成员变量,派生类中会存在多个基类的副本。
  • 虚拟继承:在虚拟继承中,编译器确保派生类中只保留基类的一份副本。所有通过虚拟继承的路径都会共享同一个基类副本。
5.4.1 继承路径的管理:
  • 当派生类通过多个路径继承自虚拟基类时,派生类中的虚拟基类部分会被“合并”成一个。
  • 这个机制避免了菱形继承中的歧义问题,但虚拟继承也增加了一些内存开销和复杂性。

🧀5.5 虚拟继承中的构造顺序

在使用虚拟继承时,基类的构造顺序会发生变化。虚拟基类的构造会优先于其他非虚拟基类,并且由最终派生类负责调用虚拟基类的构造函数。

示例:

#include <iostream>
using namespace std;

class A {
public:
    A() { cout << "A constructor" << endl; }
};

class B : virtual public A {
public:
    B() { cout << "B constructor" << endl; }
};

class C : virtual public A {
public:
    C() { cout << "C constructor" << endl; }
};

class D : public B, public C {
public:
    D() { cout << "D constructor" << endl; }
};

int main() {
    D d;
    return 0;
}

输出

A constructor
B constructor
C constructor
D constructor

【说明】:

  • 尽管BC都继承了A,但A只会被构造一次(虚拟继承)。
  • 虚拟基类的构造函数由最派生类D负责调用,在构造BC之前构造A

🥐六、多继承的指针偏移问题

在C++的多继承中,指针偏移问题是指当使用基类指针指向派生类对象时,由于多继承导致内存布局复杂化,必须调整指针来正确访问派生类对象中的基类部分。这种指针偏移在多继承和虚拟继承中尤为明显。

🧀6.1 普通多继承中的指针偏移问题

在C++中,一个类可以从多个基类继承。每个基类在内存中占据不同的区域。因此,当基类指针指向派生类对象时,指针可能需要调整才能正确地指向对应基类的内存位置。

示例代码:

#include <iostream>
using namespace std;

class Base1 {
public:
    int x;
    Base1() : x(1) {}
    virtual void show() {
        cout << "Base1::x = " << x << endl;
    }
};

class Base2 {
public:
    int y;
    Base2() : y(2) {}
    virtual void show() {
        cout << "Base2::y = " << y << endl;
    }
};

// Derived继承了Base1和Base2
class Derived : public Base1, public Base2 {
public:
    int z;
    Derived() : z(3) {}

    void show() override {
        cout << "Derived::z = " << z << endl;
    }
};

int main() {
    Derived d;
    Base1* b1_ptr = &d;  // Base1指针指向Derived对象
    Base2* b2_ptr = &d;  // Base2指针指向Derived对象

    b1_ptr->show();  // 通过Base1指针访问,正确输出Base1的数据
    b2_ptr->show();  // 通过Base2指针访问,正确输出Base2的数据

    return 0;
}

解释:

  • Derived类继承了Base1Base2,因此派生类对象d在内存中包含了Base1Base2的成员。
  • 当我们将基类指针指向派生类对象时,Base1* b1_ptr = &d 这种指针的赋值实际上是一个隐式转换,编译器会自动调整指针偏移,使其指向d对象中的Base1部分。
  • 同样的,Base2* b2_ptr = &d会调整指针指向d对象中的Base2部分。

由于Derived对象包含了Base1Base2的两部分,指针指向派生类对象时,实际上指向了不同的内存位置:

  • b1_ptr 指向 dBase1 的部分。
  • b2_ptr 指向 dBase2 的部分。

在此情境下,编译器会根据内存布局自动调整基类指针偏移,确保它们正确指向派生类中对应基类的部分。

🧀6.2 指针偏移在内存中的表现

当派生类对象被创建时,派生类对象会在内存中分配连续的空间,其中每个基类的数据成员按照继承顺序依次排列。例如:

Derived:
[ Base1::x ][ Base2::y ][ Derived::z ]

Derived类对象的内存布局中:

  • Base1::x 位于派生类对象的开头。
  • Base2::y 紧随其后,位于Base1之后。
  • Derived::z 位于Base2之后。

Base1* b1_ptr = &d时,指针b1_ptr直接指向Derived对象的开头,即Base1部分。而当Base2* b2_ptr = &d时,指针需要被偏移到Derived对象的Base2部分。

🧀6.3 虚拟继承中的指针偏移问题

在虚拟继承中,指针偏移更加复杂,因为虚拟基类只存在一个共享的实例。这意味着派生类对象中的虚拟基类部分可能不在派生类对象的开头,而是通过指针间接访问。

示例代码:

#include <iostream>
using namespace std;

class Base {
public:
    int x;
    Base() : x(1) {}
    virtual void show() {
        cout << "Base::x = " << x << endl;
    }
};

class Derived1 : virtual public Base {
};

class Derived2 : virtual public Base {
};

class Final : public Derived1, public Derived2 {
public:
    void show() override {
        cout << "Final::show()" << endl;
    }
};

int main() {
    Final f;
    Base* b_ptr = &f;  // 基类指针指向派生类对象
    b_ptr->show();     // 通过Base指针调用虚函数

    return 0;
}

解释:

  • Derived1Derived2 虚拟继承了 Base,因此 Final 类只有一个 Base 的实例。
  • 当基类指针 Base* b_ptr = &f 被用来指向 Final 类对象时,指针需要被调整到 Final 对象中的 Base 部分。这个调整是在运行时通过 虚基表(vbtable) 完成的。
  • 虚拟继承中的内存布局更加复杂,Base 的成员并不是直接位于 Final 对象的开始位置,而是存储在某个虚基类共享的部分。

🧀6.4 汇编视角下的指针偏移

在汇编层面,指针偏移的处理体现在对象的内存布局和指针计算中。对于普通继承,指针的调整是通过编译时的偏移计算完成的。而对于虚拟继承,指针偏移的处理更加复杂,因为它涉及运行时的指针调整。

6.4.1 普通继承的汇编:
Base1* b1_ptr = &d;

在普通继承的情况下,编译器知道基类 Base1 在派生类 Derived 中的内存偏移量。因此,编译器会在生成汇编代码时,通过简单的加法计算出 b1_ptr 的实际地址。指针偏移是静态的。

6.4.2 虚拟继承的汇编:

在虚拟继承中,指针偏移不能仅通过简单的加法计算,因为虚拟基类的地址是在运行时通过 虚基指针(vbptr) 来确定的。虚基指针指向 虚基表(vbtable),虚基表中存储了虚基类的实际内存偏移量。通过查找 vbtable,编译器可以在运行时计算出虚基类的地址,并进行指针调整。

🧀6.5 虚拟继承中指针偏移的机制

在虚拟继承中,派生类通过 虚基表(vbtable) 来管理虚拟基类的实例。每个包含虚拟基类的派生类都有一个 虚基指针(vbptr),指向其虚基表。虚基表中记录了虚拟基类的偏移量,编译器通过该表来计算实际的内存地址。

汇编中的虚基表查找流程:

  • 获取vbptr:从派生类对象中读取 vbptr,该指针指向 vbtable
  • 查找偏移量:通过 vbptr 查找 vbtable,获取虚基类的偏移量。
  • 调整指针:将偏移量加到当前指针上,以正确访问虚基类的成员。

🥐七、虚拟继承与汇编之间的关系

虚拟继承 在C++中是一个用于解决菱形继承问题的机制,它的实现涉及底层的内存布局与对象模型。虚拟继承与普通继承的一个主要区别在于,虚拟继承需要通过虚基表(vtable)指针调整 机制来处理基类的实例,而这些操作会影响对象的内存布局,并最终反映在编译后的汇编代码中。

下面将介绍虚拟继承与汇编之间的关系,特别是它如何影响内存布局、虚基表以及指针调整。

🧀7.1 虚拟继承的内存布局

在普通继承中,派生类会直接包含基类的成员。基类的成员是直接复制到派生类对象中,内存布局上派生类包含基类的所有数据成员。

而在虚拟继承中,基类的实例不再直接内嵌在派生类中,而是被共享。这意味着在派生类中,不再是直接存储基类的成员,而是通过一个指向**虚基表(virtual table for base classes,vbtable)**的指针来访问基类的成员。

7.1.1 内存布局对比
  • 普通继承: 派生类直接内嵌基类,继承的所有基类数据成员按顺序排列。

    class A {
        int a;
    };
    
    class B : public A {
        int b;
    };
    
    内存布局:
    B: [a] [b]
    
  • 虚拟继承: 虚基类的数据成员通过虚基表指针(vbptr)访问,基类在派生类中的位置是间接访问的。

    class A {
        int a;
    };
    
    class B : virtual public A {
        int b;
    };
    
    内存布局:
    B: [vbptr] [b]
        |
        |------> [A::a]
    
    • vbptr:一个指针,指向虚基表(vbtable),用于指示基类的实际存储位置。
    • 虚基类成员不直接出现在派生类中,而是通过 vbptr 间接访问。

🧀7.2 虚基表(vbtable)与指针调整

在虚拟继承中,C++编译器使用 虚基表 来解决多路径继承带来的二义性问题。虚基表类似于 虚函数表(vtable),用于记录虚拟基类的偏移量。每个包含虚拟继承的派生类都包含一个 虚基指针(vbptr),这个指针指向虚基表。

  • vbptr:虚基指针,它是派生类中的一个指针,指向虚基表。
  • vbtable:虚基表,它记录了虚拟基类在派生类对象内存中的偏移位置。每当访问虚基类成员时,编译器根据 vbptr 指向的 vbtable 来确定虚基类的实际位置。

虚基表结构示例

class A {
    int a;
};

class B : virtual public A {
    int b;
};

B 对象的内存布局:
B:
    [vbptr] -> 虚基表(vbtable)
    [b]
    A::a(通过 vbptr 指向的位置访问)

🧀7.3 汇编视角

从汇编的角度来看,虚拟继承会增加额外的指针操作,特别是在访问基类成员时。编译器在生成汇编代码时,会通过 vbptr 查找 vbtable,然后根据偏移量计算出基类成员的位置。这些额外的指针解引用和偏移计算,反映在汇编指令中。

7.3.1 汇编代码中的指针调整

在虚拟继承的情况下,派生类对象中并不直接包含基类的成员。因此,编译器会生成额外的汇编代码,用于通过 vbptr 来间接访问虚基类成员。

class A {
public:
    int a;
};

class B : virtual public A {
public:
    int b;
};

int main() {
    B obj;
    obj.a = 5;  // 访问虚基类 A 的成员
    return 0;
}

如果我们通过编译器生成汇编代码(例如使用 g++ -S),会看到访问 obj.a 的汇编代码与普通继承不同:

7.3.2 普通继承的汇编代码:

普通继承中,基类的成员直接嵌套在派生类中,访问时仅需通过固定的偏移量计算位置:

mov DWORD PTR [ebp-12], 5   ; 直接访问 a 的位置
7.3.3 虚拟继承的汇编代码:

虚拟继承中,需要先通过 vbptr 访问 vbtable,计算出虚基类的偏移量,然后再访问基类成员:

mov eax, DWORD PTR [ebp-12]       ; 读取 B 对象的 vbptr
mov ecx, DWORD PTR [eax+4]        ; 读取 vbtable 中 A 的偏移量
mov DWORD PTR [ebp+ecx], 5        ; 通过偏移量访问 A::a

解释

  • [ebp-12]:表示对象 B 的地址。
  • [eax+4]:通过虚基表指针 vbptr 获取 A 在派生类 B 中的实际位置偏移量。
  • [ebp+ecx]:最终通过计算的偏移量访问 A::a

🧀7.4 虚拟继承带来的开销

由于虚拟继承引入了额外的指针操作(通过 vbptrvbtable 进行指针调整),它在性能和内存使用上有一些额外的开销:

  • 性能开销:每次访问虚基类的成员时,必须通过 vbptrvbtable 进行间接访问,这会增加额外的指针解引用操作,可能导致性能下降,特别是在频繁访问基类成员时。
  • 内存开销:派生类需要额外的空间存储 vbptr,虚基类的实际数据存储在 vbptr 指向的位置,而不是直接嵌入派生类对象中。

尽管有这些开销,但虚拟继承可以有效解决菱形继承中的冗余问题,特别是在大型复杂系统中,虚拟继承提供了一种清晰且有效的继承关系管理方式。

🥐八、继承与组合

在C++中,继承(Inheritance)和组合(Composition)是两种常见的类设计方式,用于在类之间建立联系和复用代码。它们都可以用于创建复杂的对象结构,但它们的应用场景、优势、劣势以及如何在类之间传递行为和属性方面有所不同。

🧀8.1 继承的优缺点:

  • 优点:
    • 简化代码:通过继承,派生类可以重用基类的代码。
    • 易于维护:继承可以减少重复代码,方便对代码的集中管理和维护。
    • 支持多态:基类的虚函数可以在派生类中实现不同的行为。
  • 缺点:
    • 强耦合:继承使基类和派生类之间紧密耦合,派生类依赖于基类的实现,这可能导致灵活性下降。
    • 不灵活:如果基类发生改变,所有派生类都可能需要修改。
    • 继承链过长:过多的继承层次可能导致代码的复杂度增加,理解和维护变得困难。

🧀8.2 组合

组合 是一种类与类之间的关系,表示 “有一个”(has-a)的关系。在组合中,一个类包含另一个类的对象作为成员变量。组合强调类的对象可以包含其他类的对象,并通过这些成员对象来实现某些功能。

组合示例:

#include <iostream>
using namespace std;

// 类:Engine
class Engine {
public:
    void start() {
        cout << "Engine started" << endl;
    }
};

// 类:Car
class Car {
private:
    Engine engine;  // Car "有一个" Engine
public:
    void startCar() {
        engine.start();  // 调用 Engine 对象的方法
        cout << "Car started" << endl;
    }
};

int main() {
    Car myCar;
    myCar.startCar();
    return 0;
}

解释:

  • Engine 类代表引擎的行为,它有一个 start() 方法。
  • Car 类并没有继承 Engine,而是将 Engine 对象作为其成员变量。这表明 Car “有一个” 引擎。
  • CarstartCar() 方法通过调用 Engine 对象的方法来启动引擎。

组合的特点:

  • “有一个” 关系Car “有一个” Engine
  • 灵活性更强:组合可以在运行时动态地改变组合的对象,允许对象之间的灵活组合。
  • 类之间的独立性:组合中的类彼此独立,不像继承那样产生紧密耦合。

组合的优缺点:

  • 优点:
    • 低耦合:类之间的耦合度较低,一个类的修改不会影响其他类。
    • 灵活性:组合允许根据需要创建更灵活的类组合,能够动态地创建类的对象,易于扩展和维护。
    • 单一职责原则:组合可以让每个类专注于自己的职责。
  • 缺点:
    • 代码可能更复杂:相比于继承,组合可能需要更多的代码来实现同样的功能(尤其是涉及多个类时)。
    • 不支持多态:组合本身不能直接使用多态,不能在运行时通过基类指针访问派生类的重写方法。

🧀8.3 继承 vs 组合:如何选择?

选择继承还是组合,取决于具体的设计需求和类之间的关系。以下是一些基本的建议:

  • 使用继承的场景
    • 当类之间存在**“是一个”**的关系时,使用继承。例如,CarVehicle 的一种,所以可以使用继承。
    • 需要使用多态性,即在运行时通过基类指针调用派生类的实现时,继承是必需的。
    • 需要复用基类的实现时。
  • 使用组合的场景
    • 当类之间存在**“有一个”**的关系时,使用组合。例如,Car 拥有一个 Engine,这是一种典型的组合关系。
    • 当需要在运行时灵活地组合不同的功能时,组合比继承更灵活。
    • 当需要避免类之间的紧密耦合,或者需要减少类层次结构时,组合是更好的选择。

🧀8.4 继承与组合的混合使用

在现实世界的设计中,继承和组合可以混合使用。比如,一个类既可以通过继承来获取基类的功能,同时通过组合来使用其他对象的功能。

示例:

#include <iostream>
using namespace std;

// 基类:Vehicle
class Vehicle {
public:
    void start() {
        cout << "Vehicle started" << endl;
    }
};

// 类:Engine
class Engine {
public:
    void start() {
        cout << "Engine started" << endl;
    }
};

// 派生类:Car
class Car : public Vehicle {  // 继承Vehicle
private:
    Engine engine;  // 组合Engine
public:
    void startCar() {
        engine.start();  // 调用Engine对象的方法
        start();         // 调用Vehicle基类的方法
        cout << "Car is running" << endl;
    }
};

int main() {
    Car myCar;
    myCar.startCar();  // 启动引擎并开始运行
    return 0;
}

解释:

  • Car 类通过继承获取了 Vehicle 的功能,并通过组合使用了 Engine 的功能。这是一种继承和组合相结合的设计方式。

🧀8.5 优先使用组合原则

在设计类结构时,常常提到的一条原则是:优先使用组合而非继承(Favor Composition over Inheritance)。这一原则的基础在于,组合比继承更加灵活,可以减少类之间的耦合,增强代码的扩展性和可维护性。

  • 继承 固定了类之间的关系,继承链过长会增加复杂度。
  • 组合 允许类在运行时动态组合,减少了类之间的依赖关系。

但是,继承也是必要的,尤其是在你需要利用多态性或构建清晰的层次结构时。因此,继承和组合并不是对立的,而是根据具体场景选择合适的工具。

结语

在这里插入图片描述

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大舍传媒-海外媒体发稿:为您打造全球品牌影响力

大舍传媒-海外媒体发稿&#xff1a;为您打造全球品牌影响力 在当今全球化的商业环境中&#xff0c;企业若想在激烈的市场竞争中脱颖而出&#xff0c;拓展全球市场&#xff0c;提升品牌影响力至关重要。大舍传媒的海外媒体发稿服务&#xff0c;正是您实现这一目标的得力助手。 …

面对服务器掉包的时刻困扰,如何更好的解决

在数字化时代&#xff0c;服务器的稳定运行是企业业务连续性的基石。然而&#xff0c;服务器“掉包”现象&#xff0c;即数据包在传输过程中丢失或未能正确到达目的地的情况&#xff0c;却时常成为IT运维人员头疼的问题。它不仅影响用户体验&#xff0c;还可能导致数据不一致、…

【AI 新观察】“转人工!转人工!”——智能客服痛点与破局之路

在当今数字化时代&#xff0c;智能客服在电商等众多领域被广泛应用&#xff0c;然而&#xff0c;一句又一句“转人工&#xff01;转人工&#xff01;”却常常暴露出智能客服存在的痛点。一、智能客服之痛 1. 理解偏差引不满 智能客服在理解客户问题时&#xff0c;常常出现偏差…

mybatisPlus对于pgSQL中UUID和UUID[]类型的交互

在PGSQL中&#xff0c;有的类型是UUID和UUID[]这种类型&#xff0c;在mybatis和这些类型交互的时候需要手动设置类型处理器才可以&#xff0c;这里记录一下类型处理器的设置 /*** UUID类型处理器*/ public class UUIDTypeHandler extends BaseTypeHandler<UUID> {/*** 获…

Golang | Leetcode Golang题解之第478题在圆内随机生成点

题目&#xff1a; 题解&#xff1a; type Solution struct {radius, xCenter, yCenter float64 }func Constructor(radius, xCenter, yCenter float64) Solution {return Solution{radius, xCenter, yCenter} }func (s *Solution) RandPoint() []float64 {r : math.Sqrt(rand.…

热更新解决方案2 —— Lua语法相关知识点

概述 开发环境搭建 Lua语法 1.第一个Lua程序 2.变量 print("******变量*******"); --lua当中的简单变量类型 -- nil number string boolean -- lua 中所有的变量声明 都不需要声明变量类型 它会自动的判断类型 -- 类似C# 中的var --lua中的一个变量 可以随便赋值 ——…

Product1M 深度理解 PPT

系列论文研读目录 文章目录 系列论文研读目录 模态内检索&#xff1a;是指在同一模态&#xff08;例如&#xff0c;图像、文本或音频&#xff09;中进行的检索任务。它通常涉及在同一类型的数据中查找相关项。比如下面图像只能查询图像&#xff0c;文本只能查询文本&#xff0c…

modbus tcp wireshark抓包

Modbus TCP报文详解与wireshark抓包分析_mbap-CSDN博客 关于wireshark无法分析出modbusTCP报文的事情_wireshark 协议一列怎么没有modbus tcp-CSDN博客 使用Wireshark过滤Modbus功能码 - 技象科技 连接建立以后才能显示Modbus TCP报文 modbus.func_code 未建立连接时&…

D36【python 接口自动化学习】- python基础之函数

day36 函数的定义 学习日期&#xff1a;20241013 学习目标&#xff1a;输入输出与文件操作&#xfe63;-49 函数定义&#xff1a;如何优雅地反复引用同一段代码&#xff1f; 学习笔记&#xff1a; 函数的用途 定义函数 调用函数 # 定义函数 def foo():print(foo)print(foo …

胤娲科技:AI短视频——创意无界,即梦启航

在这个快节奏的时代&#xff0c;你是否曾梦想过用几秒钟的短视频&#xff0c;捕捉生活中的每一个精彩瞬间&#xff1f;是否曾幻想过&#xff0c;即使没有专业的摄影和剪辑技能&#xff0c;也能创作出令人惊艳的作品&#xff1f; 现在&#xff0c;这一切都不再是遥不可及的梦想。…

一区鱼鹰优化算法+深度学习+注意力机制!OOA-TCN-LSTM-Attention多变量时间序列预测

一区鱼鹰优化算法深度学习注意力机制&#xff01;OOA-TCN-LSTM-Attention多变量时间序列预测 目录 一区鱼鹰优化算法深度学习注意力机制&#xff01;OOA-TCN-LSTM-Attention多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.基于OOA-TCN-LSTM-Attenti…

Mysql(八) --- 视图

文章目录 前言1.什么是视图&#xff1f;2.创建视图3. 使用视图4. 修改数据4.1.注意事项 5. 删除视图6.视图的优点 前言 前面我们学习了索引&#xff0c;这次我们来学习视图 1.什么是视图&#xff1f; 视图是一个虚拟的表&#xff0c;它是基于一个或多个基本表或其他视图的查询…

Docker 入门篇

&#x1f3dd;️ 博主介绍 大家好&#xff0c;我是一个搬砖的农民工&#xff0c;很高兴认识大家 &#x1f60a; ~ &#x1f468;‍&#x1f393; 个人介绍&#xff1a;本人是一名后端Java开发工程师&#xff0c;坐标北京 ~ &#x1f389; 感谢关注 &#x1f4d6; 一起学习 &…

05 django管理系统 - 部门管理 - 修改部门

04我们已经实现了新增部门的功能&#xff0c;下面开始修改部门模块的实现。 按道理来说&#xff0c;应该是做成弹框样式的&#xff0c;通过ajax悄咪咪的发数据&#xff0c;然后更新前端数据&#xff0c;但是考虑到实际情况&#xff0c;先用页面跳转的方式实现&#xff0c;后面…

106页PPT企业管控模式方案:战略、产业与职能管理体系核心规划

企业集团管控模式的设计方案是一个复杂而系统的过程&#xff0c;其核心规划涉及到战略、产业与职能管理体系。以下是对这三个方面的详细规划&#xff1a; 一、战略规划 明确集团战略目标&#xff1a;集团应根据市场环境和自身优势&#xff0c;明确战略发展方向和目标&#xf…

Tailwind Starter Kit 一款极简的前端快速启动模板

Tailwind Starter Kit 是基于TailwindCSS实现的一款开源的、使用简单的极简模板扩展。会用Tailwincss就可以快速入手使用。Tailwind Starter Kit 是免费开源的。它不会在原始的TailwindCSS框架中更改或添加任何CSS。它具有多个HTML元素&#xff0c;并附带了ReactJS、Vue和Angul…

JavaScript 网页设计案例:使用 Canvas 实现趣味打气球小游戏

JavaScript 网页设计案例&#xff1a;使用 Canvas 实现趣味打气球小游戏 在网页设计中&#xff0c;交互性和趣味性是吸引用户的重要因素。借助 JavaScript 和 HTML5 的 canvas 元素&#xff0c;我们可以轻松实现各种动画效果&#xff0c;今天将带你打造一个有趣的 打气球小游戏…

Metasploit渗透测试之攻击终端设备和绕过安全软件

概述 在之前&#xff0c;重点讨论了针对服务器端的利用。但在当下&#xff0c;最成功的攻击都是针对终端的&#xff1b;原因是&#xff0c;随着大部分安全预算和关注都转向面向互联网的服务器和服务&#xff0c;越来越难找到可利用的服务&#xff0c;或者至少是那些还没有被破…

大规模多传感器滑坡检测数据集,利用landsat,哨兵2,planet,无人机图像等多种传感器采集数据共2w余副图像,mask准确标注滑坡位置

大规模多传感器滑坡检测数据集&#xff0c;利用landsat&#xff0c;哨兵2&#xff0c;planet&#xff0c;无人机图像等多种传感器采集数据共2w余副图像&#xff0c;mask准确标注滑坡位置 大规模多传感器滑坡检测数据集介绍 数据集概述 名称&#xff1a;大规模多传感器滑坡检测…

云计算第四阶段-----CLOUND二周目 04-06

cloud 04 今日目标&#xff1a; 一、Pod 生命周期 图解&#xff1a; [rootmaster ~]# vim web1.yaml --- kind: Pod apiVersion: v1 metadata:name: web1 spec:initContainers: # 定义初始化任务- name: task1 # 如果初始化任务失败&#…