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

news2025/1/25 11:08:24

在这里插入图片描述

文章目录

    • 前言
    • 🥐五、多继承,菱形继承和菱形虚拟继承
      • 🧀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前进的动力!
在这里插入图片描述

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

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

相关文章

C语言 | Leetcode C语言题解之第479题最大回文数乘积

题目&#xff1a; 题解&#xff1a; int largestPalindrome(int n){if (n 1) {return 9;}int upper pow(10, n) - 1;for (int left upper;; --left) { // 枚举回文数的左半部分long p left;for (int x left; x > 0; x / 10) {p p * 10 x % 10; // 翻转左半部分到其自…

相机畸变模型

文章目录 概述相机畸变类型径向畸变切向畸变 畸变数学模型径向畸变模型切向畸变模型畸变数学模型总结 去畸变数学过程去畸变步骤 畸变测试结论参考 概述 相机畸变是图像处理和计算机视觉中的常见问题。由于透镜的物理特性&#xff0c;图像边缘的物体往往会呈现扭曲&#xff0c…

(34)FFT与信号频谱(双边谱)

文章目录 前言一、仿真代码二、仿真结果画图 前言 本文首先使用MATLAB生成一段余弦信号&#xff0c;然后对其进行FFT变换&#xff0c;给出了信号的双边幅度谱。 一、仿真代码 代码如下&#xff08;示例&#xff09;&#xff1a; %% 生成余弦波 % 指定信号的参数&#xff0c;…

k8s中的微服务

一、什么是微服务 用控制器来完成集群的工作负载&#xff0c;那么应用如何暴漏出去&#xff1f;需要通过微服务暴漏出去后才能被访问 Service是一组提供相同服务的Pod对外开放的接口。 借助Service&#xff0c;应用可以实现服务发现和负载均衡。 service默认只支持4层负载均…

Java 输入与输出(I\O)详解

一、Java 输入与输出&#xff08;I\O)系统概述 Java 输入/输出流&#xff08;Input/Output&#xff0c;简称I/O&#xff09;是Java语言用于读写数据的API&#xff0c;它提供了一系列类和接口&#xff0c;用于读取和写入各种类型的数据信息。 输入/输出&#xff08;I/O&#xff…

SpringBoot项目热部署-devtools

DevTools 会使用两个类加载器&#xff08;一个用于加载不变的类&#xff0c;一个用于加载可能会变化的类&#xff09;&#xff0c;每次重启只重新加载管理变化的类的加载器&#xff0c;因此会快很多 1.导入依赖 <dependency> <groupId>org.springframework.boot&l…

动态设置placeholder-class.默认搜索图标在中间获取焦点之后再左边

默认状态:placeholder和图标在中间位置 获取焦点&#xff1a;placeholder和图标在左边光标前面位置 动态设置placeholder-class <view class"search"><input type"search" class"select-input" input-align"center" v-model.…

算法题---动态规划

题目展示&#xff1a; 动态规划的题目我们一半分为5个步骤去分析&#xff0c;第一&#xff0c;状态表示&#xff1b;第二&#xff0c;动态转移方程&#xff1b;第三&#xff0c;初始化&#xff1b;第四&#xff0c;填表顺序&#xff1b;第五&#xff0c;返回值。 状态表示 这…

[权威出版|稳定检索]2024年大数据经济与公共管理国际会议(BDEPM 2024)

2024年大数据经济与公共管理国际会议 2024 International Conference on Big Data Economy and Public Management 【1】大会信息 会议名称&#xff1a;2024年大数据经济与公共管理国际会议 会议简称&#xff1a;BDEPM 2024 大会时间&#xff1a;请查看官网 大会地点&#xf…

网络协议原理

文章目录 TCP通信原理TCP与UDP的对比应用层应用层协议 --- tcp协议定制直接传递对象自定义协议现在要解决的问题业务处理 json的使用使用json进行序列化和反序列化操作 总结 TCP通信原理 tcp是面向字节流的 同时他也是面向连接的 所以TCP的服务器编写代码如图所示: 客户端的编…

Scala入门基础(10)高级函数

一.什么是高阶函数 二.map函数 三.foreach函数 四.filter函数 五.flatten函数 正文&#xff1a; 一.什么是高阶函数 高阶函数&#xff1a;是一个特殊的函数&#xff0c;特殊之处在于&#xff1a;它指使用其他函数作为参数或返回值 &#xff08;演示&#xff09; 二.map函…

maven项目打jar包之后如何指定外部配置文件运行java类

在maven项目中,常常会用到一些配置文件,一旦打成jar包之后,想要用外部的配置文件运行,怎么做呢? 一、配置文件config.ini 在maven项目中的src/main/resources目录下存放了一个配置文件config.ini。这个文件是默认的配置文件。 db.url=jdbc:mysql://localhost:3306/qyxx?u…

【JavaScript】LeetCode:66-70

文章目录 66 组合总和67 括号生成68 单词搜索69 分割回文串70 N皇后 66 组合总和 回溯sum&#xff1a;当前组合的数字和。递归终止条件&#xff1a;sum > target。收集结果条件&#xff1a;sum target&#xff0c;找到了满足条件的组合。注意&#xff1a;因为可以重复取数&…

亚洲最具影响力人物颜廷利:心理健康对身体健康的重要影响

在当代社会&#xff0c;面对疾病与痛苦&#xff0c;人们往往在西医与中医之间做出选择。21世纪世界上知名度最高的人物颜廷利教授的精辟见解指出了这两种医学体系的根本差异&#xff1a;西医以其高昂的费用&#xff0c;针对生理上的疾苦提供快速而直接的解决之道&#xff1b;相…

Python应用指南:利用高德地图API获取公交可达圈

参考文章&#xff1a;城市公交可达圈绘制方法&#xff08;一&#xff09; - 知乎 (zhihu.com) 本篇文章我们聚焦于通过公共交通出行方式&#xff08;包括公交、地铁、公交地铁的组合&#xff09;来获取一定时间内可以到达的范围。为了实现这一目标&#xff0c;我们将使用高德地…

在 Android 应用程序中实现与WebSocket 服务器的实时通信

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

spring-boot学习(2)

上次学习截止到拦截器 1.构建RESfun服务 PathVariable通过url路径获取url传递过来的信息 2.MyBatisPlus 第三行的mydb要改为自己的数据库名 第四&#xff0c;五行的账号密码改成自己的 MaooerScan告诉项目自己的这个MyBatisPlus是使用在哪里的&#xff0c;包名 实体类的定义…

PL/SQL Developer15和Oracle Instant Client安装配置详细图文教程

一、下载介质 1、Oracle Instant Client Oracle Instant Client Downloads | Oracle 中国 2、PL/SQL DEVELOPER PL/SQL Developer - Allround Automations Free trial - Allround Automations 二、安装介质。 1、安装plsqldev1504x64.msi。 一路默认下一步。 选择输入许可信…

实跑 YOLO V11在 OAK内部运行的效果

哈喽&#xff0c;各位OAK中国的朋友们! 大家好我是张伯生 今天&#xff0c;我想给大家演示一下最新发布的Yolo V11神经网络 下面我将演示的一个程序是&#xff1a;同时在我们的OAK相机上跑Yolo V11和RGB-D&#xff0c;也就是彩色相机和深度图的一个叠加的一个效果 RGB-D和Yo…

java_for循环

基本语法 for 关键字&#xff0c;表示循环控制for 有四要素: (1)循环变量初始化(2)循环条件(3)循环操作(4)循环变量迭代循环操作 , 这里可以有多条语句&#xff0c;也就是我们要循环执行的代码如果 循环操作(语句) 只有一条语句&#xff0c;可以省略 {}, 建议不要省略 流程图 …