解锁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路径访问Ad.C::value = 30; // 通过C路径访问Acout << "B::value = " << d.B::value << endl; // 输出: B::value = 20cout << "C::value = " << d.C::value << endl; // 输出: C::value = 30return 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 = 100return 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前进的动力!
在这里插入图片描述

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

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

相关文章

springboot 整合 快手 移动应用 授权 发布视频 小黄车

前言&#xff1a; 因快手文档混乱&#xff0c;官方社区技术交流仍有很多未解之谜&#xff0c;下面3种文档的定义先区分。 代码中的JSON相关工具均用hutool工具包 1.快手 移动双端 原生SDK 文档https://mp.kuaishou.com/platformDocs/develop/mobile-app/ios.html 2.快手 Api 开…

Elasticsearch设置 X-Pack认证,设置账号和密码

前言 以下Elasticsearch版本&#xff1a;7.9.3 ES自带的X-Pack密码验证&#xff1a; X-Pack是elasticsearch的一个扩展包&#xff0c;将安全&#xff0c;警告&#xff0c;监视&#xff0c;图形和报告功能捆绑在一个易于安装的软件包中&#xff0c;所以我们想要开启账号密码验证…

网优学习干货:王者荣耀游戏用户体验洞察及质差识别(1)

一、课题背景 二、课题目的 针对热点游戏&#xff08;王者荣耀&#xff09;进行业务质量评估&#xff0c;并通过对端到端定界分析&#xff0c;从无线、核心网、互联网维度识别影响用户体验关键因素&#xff0c;为游戏用户的体验优化提供依据。 三、课题实施进度 王者荣耀卡顿特…

linux------缓冲区与C库的原理

前言 一、缓冲区 缓冲区的作用是提高效率&#xff0c;因为将数据写入到设备&#xff0c;是需要调用系统接口的&#xff0c;如果每次写入缓冲区的数据就调用一次系统调用&#xff0c;涉及到系统调用这时操作系统就会介入&#xff0c;用户态转为内核态&#xff0c;这个过程需要时…

linux 搭建sentinel

1.下载 linux执行下面的命令下载包 wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar2.启动 nohup java -Dserver.port9090 -Dcsp.sentinel.dashboard.serverlocalhost:9090 -Dproject.namesentinel-dashboard -jar sentin…

k8s中控制器的使用

一、replicaset控制器 ReplicaSet 是下一代的 Replication Controller&#xff0c;官方推荐使用ReplicaSet ReplicaSet和Replication Controller的唯一区别是选择器的支持&#xff0c;ReplicaSet支持新的基于集合的选择器需求 ReplicaSet 确保任何时间都有指定数量的 Pod 副…

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

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

数据库设计与开发—初识SQLite与DbGate

一、SQLite与DbGate简介 &#xff08;一&#xff09;SQLite[1][3] SQLite 是一个部署最广泛、用 C 语言编写的数据库引擎&#xff0c;属于嵌入式数据库&#xff0c;其作为库被软件开发人员嵌入到应用程序中。 SQLite 的设计允许在不安装数据库管理系统或不需要数据库管理员的情…

一篇文章快速认识YOLO11 | 关键改进点 | 安装使用 | 模型训练和推理

前言 本文分享YOLO11的关键改进点、性能对比、安装使用、模型训练和推理等内容。 YOLO11 是 Ultralytics 最新的实时目标检测器&#xff0c;凭借更高的精度、速度和效率重新定义了可能性。 除了传统的目标检测外&#xff0c;YOLO11 还支持目标跟踪、实例分割、姿态估计、OBB…

深入理解 C/C++ 指针

深入理解 C 指针&#xff1a;指针、解引用与指针变量的详细解析 前言 在 C 编程语言中&#xff0c;指针 是一个非常强大且重要的概念。对于初学者来说&#xff0c;指针往往会让人感到困惑不解。本文将通过形象的比喻&#xff0c;帮助大家深入理解指针、解引用与指针变量的概念…

【算法】哈希表:49.字母异位词分组

目录 1、题目链接 2、题目介绍 3、解法 初始化设定--图解 步骤图解 4、代码 1、题目链接 49. 字母异位词分组 - 力扣&#xff08;LeetCode&#xff09; 2、题目介绍 3、解法 字母异位词的本质是字符相同但排列不同。因此&#xff0c;我们可以对字符串进行排序&#xf…

wordpress使用popup弹窗插件的对比

您在寻找最好的 WordPress 弹出插件吗&#xff1f;大多数网站利用某种形状或形式的弹出窗口来将访问者指向他们希望他们去的地方。例如&#xff0c;这可能用于结帐、电子邮件订阅或用于生成潜在客户。 表现 弹出插件会减慢您的网站速度。当插件使用 WordPress 跟踪弹出窗口的…

K8S配置MySQL主从自动水平扩展

前提环境 操作系统Ubuntu 22.04 K8S 1.28.2集群&#xff08;1个master2个node&#xff09; MySQL 5.7.44部署在K8S的主从集群 metrics-server v0.6.4 概念简介 在K8s中扩缩容分为两种 ●Node层面&#xff1a;对K8s物理节点扩容和缩容&#xff0c;根据业务规模实现物理节点自动扩…

《大规模语言模型从理论到实践》第一轮学习--强化学习(RLHF、PPO)

个人学习笔记,如有错误欢迎指出。 一、强化学习的意义 RLHF(Reinforcement Learning from Human Feedback):强化学习(Reinforcement Learning)结合人类反馈(Human Feedback)来微调大语言模型。 大语言模型的训练步骤包括:预训练、指令微调(SFT)、对齐。 对齐(a…

MWD天气图像多分类数据集,用于图像分类-共6个类别,共60000张图像数据 ,含有模型

MWD天气图像多分类数据集&#xff0c;用于图像分类- MWD天气图像多分类数据集&#xff0c;用于图像分类-共6个类别&#xff0c;共60000张图像数据 &#xff0c;含有模型 MWD天气图像多分类数据集及模型介绍 数据集概述 名称&#xff1a;MWD天气图像多分类数据集图像数量&…

使用node.js控制CMD命令——修改本机IP地址

设置每次打开cmd命令行窗口都是以管理员身份运行&#xff1a; 1. 按下Ctrl Shift Esc键组合&#xff0c;打开任务管理器。 2. 在任务管理器中&#xff0c;点击“文件”菜单&#xff0c;选择“运行新任务”。 3. 在“创建新任务”对话框中&#xff0c;输入cmd&#xff0c;勾…

基于知识图谱的宁夏非遗问答系统

八维视角探索宁夏非遗文化——基于知识图谱的非遗问答系统 作为一名程序员&#xff0c;能将大数据与文化传承结合&#xff0c;赋予历史新的生命&#xff0c;是件多么振奋的事&#xff01;今天给大家介绍的是一款基于知识图谱技术的宁夏非物质文化遗产问答系统。无论你是学术研…

Scrapy网络爬虫基础

使用Spider提取数据 Scarpy网络爬虫编程的核心就是爬虫Spider组件&#xff0c;它其实是一个继承与Spider的类&#xff0c;主要功能设计封装一个发送给网站服务器的HTTP请求&#xff0c;解析网站返回的网页及提取数据 执行步骤 1、Spider生成初始页面请求&#xff08;封装于R…

【未公开0day】9.9付费进群系统 wxselect SQL注入漏洞【附poc下载】

免责声明&#xff1a;本文仅用于技术学习和讨论。请勿使用本文所提供的内容及相关技术从事非法活动&#xff0c;若利用本文提供的内容或工具造成任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果均与文章作者及本账号无关。 fofa语…

Java Maven day1014

ok了家人们&#xff0c;今天学习了如何安装和配置Maven项目&#xff0c;我们一起去看看吧 一.Maven概述 1.1 Maven作用 Maven 是专门用于管理和构建 Java 项目的工具&#xff0c;它的主要功能有&#xff1a; 提供了一套标准化的项目结构 提供了一套标准化的构建流程&#x…