【C++篇】继承之巅:超越法则束缚,领略面向对象的至臻智慧

文章目录

  • C++ 继承详解:虚拟继承与进阶实战
    • 前言
    • 第一章:继承与友元、静态成员
      • 1.1 继承与友元
        • 1.1.1 友元函数的定义
      • 1.2 继承与静态成员
        • 1.2.1 静态成员的继承与访问
    • 第二章:复杂的菱形继承及虚拟继承
      • 2.1 菱形继承问题
        • 2.1.1 菱形继承的基本结构
      • 2.2 菱形继承的二义性问题
      • 2.3 解决方案:虚拟继承
        • 2.3.1 虚拟继承的定义
      • 2.4 虚基表(VBTable)与虚基类指针(VBPTR)
        • 2.4.1 虚基表的工作机制
        • 2.4.2 偏移量的用途
      • 2.5 虚拟继承的优缺点
        • 2.5.1 优点
        • 2.5.2 缺点
    • 第三章:虚拟继承与多态应用
      • 3.1 虚拟继承与多态的结合
        • 3.1.1 虚基类中的虚函数与多态
      • 3.2 虚拟继承的注意事项
        • 3.2.1 构造函数中的调用顺序
        • 3.2.2 虚基类成员的访问
    • 第四章:虚拟继承与传统继承的对比
      • 4.1 虚拟继承与传统继承的区别
        • 4.1.1 实例化方式的区别
        • 4.1.2 内存布局的区别
      • 4.2 选择传统继承还是虚拟继承?
        • 4.2.1 何时使用传统继承?
        • 4.2.2 何时使用虚拟继承?
      • 4.3 虚拟继承的最佳实践
        • 4.3.1 小心使用多层次的虚拟继承
      • 4.4 实际项目中的继承选择案例
        • 4.4.1 案例:设计多功能打印机
    • 第五章:继承的总结与反思
      • 5.1 C++ 继承的核心要点回顾
      • 5.2 常见继承误区与陷阱
        • 5.2.1 忽视虚析构函数的定义
      • 5.2 优先使用组合而非继承
      • 5.3 继承的设计模式与应用
        • 5.3.1 工厂模式的应用
      • 5.4 继承的未来发展趋势
    • 写在最后

C++ 继承详解:虚拟继承与进阶实战

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

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


前言

接上篇【C++篇】继承之韵:解构编程奥义,感悟面向对象的至高法则

C++ 继承机制在面向对象编程中扮演着至关重要的角色。继承不仅能够帮助我们复用代码,还能够通过多态实现灵活的程序设计。在上一篇文章中,我们深入探讨了继承的基础知识与常见用法。在本篇文章中,我们将进一步探讨更复杂的继承机制,特别是虚拟继承,以及如何通过虚拟继承来解决多重继承中的难题。


第一章:继承与友元、静态成员

1.1 继承与友元

在 C++ 中,友元是一种特殊机制,它允许指定的非成员函数或者其他类访问类的私有成员和保护成员。然而,友元关系不能继承,也就是说,基类的友元不会自动成为派生类的友元,反之亦然。

1.1.1 友元函数的定义

如果基类定义了一个友元函数,该友元函数只能访问基类的私有和保护成员,而不能访问派生类的私有或保护成员。反之,如果友元函数在派生类中定义,它也无法访问基类的私有和保护成员。

示例代码:

class Person {
public:
    friend void Display(const Person& p);  // 声明友元函数
protected:
    string _name = "Alice";  // 姓名
};

void Display(const Person& p) {
    cout << "Name: " << p._name << endl;  // 友元函数可以访问_person中的私有成员
}

class Student : public Person {
protected:
    int _studentID = 1001;  // 学号
};

int main() {
    Student s;
    Display(s);  // 友元函数只能访问基类的保护成员
    // 无法访问Student类中的_studentID
    return 0;
}

在以上代码中,Display 函数是 Person 类的友元,它可以访问 Person 的保护成员 _name。但是,即使 Display 函数可以操作 Student 对象,它也无法访问 Student 类中的 _studentID 成员。

1.2 继承与静态成员

C++ 中的静态成员在继承关系中具有一些特殊的行为。无论继承了多少次,基类中的静态成员在整个继承体系中始终只有一个实例。派生类可以共享访问基类中的静态成员

1.2.1 静态成员的继承与访问

基类定义的静态成员在派生类中共享。无论派生类如何使用该静态成员,它们操作的都是同一个静态成员变量。

示例代码:

class Person {
public:
    static int _count;  // 静态成员,用于计数
    Person() { ++_count; }
};

int Person::_count = 0;  // 初始化静态成员

class Student : public Person {
};

int main() {
    Student s1;
    Student s2;
    cout << "Person count: " << Person::_count << endl;  // 输出 2
    cout << "Student count: " << Student::_count << endl;  // 输出 2,Student类共享Person类的静态成员
    return 0;
}

在以上代码中,_countPerson 类的静态成员,用于统计创建的 Person 对象数量。由于 Student 类继承自 Person,因此 Student 也可以访问 _count。无论是通过 Person::_count 还是 Student::_count,它们都指向同一个静态成员。

在这里插入图片描述


第二章:复杂的菱形继承及虚拟继承

2.1 菱形继承问题

菱形继承是 C++ 多重继承中的一种特殊情况。当一个类从两个基类继承,而这两个基类又有共同的基类时,就会形成一个菱形结构。菱形继承会导致基类的多次实例化,进而引发数据冗余和二义性问题

2.1.1 菱形继承的基本结构

在菱形继承中,子类会直接或间接继承自同一个基类,形成一个“菱形”的继承结构,这样的设计很容易导致基类的数据被重复继承。

下图展示了菱形继承的结构:

在这里插入图片描述
在这里插入图片描述

简单示例代码:

class A {
public:
    int _a;
};

class B : public A {
};

class C : public A {
};

class D : public B, public C {
};

在上述代码中,D 类通过 BC 间接继承了 A,这就形成了一个菱形结构。D 类中实际上会有两份 _a,分别属于从 BC 继承来的 A。这就导致了数据冗余和访问的二义性。

2.2 菱形继承的二义性问题

二义性问题 是指在访问基类成员时,编译器无法确定访问的是哪一个基类实例。例如,D 类对象在访问 _a 时,编译器无法判断是访问 B 中的 _a 还是 C 中的 _a

示例代码:

int main() {
    D d;
    d._a = 5;  // 错误:二义性
    return 0;
}

在这个例子中,d._a 会导致编译错误,因为编译器无法决定 _a 是从 B 还是从 C 继承的 A 中访问的。这种二义性问题在实际开发中会带来严重的维护和理解困难。

2.3 解决方案:虚拟继承

虚拟继承可以解决菱形继承中的数据冗余和二义性问题。通过虚拟继承,派生类会共享同一个虚基类的实例,从而避免基类被多次实例化。

2.3.1 虚拟继承的定义

虚拟继承通过在继承时使用 virtual 关键字,指示编译器在继承关系中只生成一个基类实例,从而解决数据冗余和二义性问题。

class A
{
    public:
    int _a;
};
// class B : public A
class B : virtual public A
{
    public:
    int _b;
};
// class C : public A
class C : virtual public A
{
    public:
    int _c;
};
class D : public B, public C
{
    public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

在这里,BC 虚拟继承了 A,这样在 D 类中,A 的实例只存在一份。

2.4 虚基表(VBTable)与虚基类指针(VBPTR)

在虚拟继承中,编译器会在每个虚基类对象中加入一个指向虚基表(VBTable)的指针,即虚基类指针(VBPTR),用于存储偏移量信息。

2.4.1 虚基表的工作机制

虚基表中存储的是虚基类相对于派生类对象的偏移量。通过虚基类指针,派生类对象可以在运行时计算出虚基类在内存中的实际位置。

示例内存布局:

  • 0x005EF75C 处存储了 D 对象的起始地址。
  • 虚基类指针(VBPTR)指向虚基表(VBTable)。
  • 虚基表中的偏移量帮助定位虚基类 AD 对象内存中的实际位置。
    在这里插入图片描述
2.4.2 偏移量的用途

偏移量的设计让编译器能够在运行时调整虚基类的位置,确保派生类在访问基类成员时能够定位到唯一的基类实例。

在虚拟继承中,虚基表中的偏移量解决了菱形继承中的访问问题,使得派生类 D 能够直接访问基类 A 的成员,而不会再有二义性。

int main() {
    D d;
    d._a = 5;  // 正确:通过虚基表解决了二义性
    return 0;
}

此时,D 对象通过虚基表定位到 A 的唯一实例,d._a 可以正确访问到基类 A 中的成员。

2.5 虚拟继承的优缺点

2.5.1 优点
  • 解决数据冗余问题:虚拟继承可以确保在菱形继承中,基类只有一个实例,避免了数据冗余。
  • 消除访问的二义性:通过虚基表和虚基类指针,派生类可以唯一地访问到虚基类的实例,消除了访问时的二义性问题。
2.5.2 缺点
  • 内存开销增加:虚拟继承引入了虚基表和虚基类指针,有时候增加了内存的额外开销。(但当虚基类很大的时候,其实还是节省了空间)
  • 性能开销:每次访问虚基类成员时,都需要通过虚基表进行偏移计算,这可能带来一定的性能开销。

第三章:虚拟继承与多态应用

3.1 虚拟继承与多态的结合

虚拟继承在解决菱形继承问题的同时,也为实现多态提供了更高的灵活性。通过使用 virtual 关键字,我们不仅可以避免基类的重复实例化,还可以确保派生类对象通过基类指针或引用来访问重写后的方法。

3.1.1 虚基类中的虚函数与多态

这里先大致看一下,之后会有专门讲解多态的文章滴

在多态机制中,基类的函数被声明为虚函数(virtual)后,派生类可以对该函数进行重写(override)。通过基类的指针或引用调用该函数时,实际运行时会调用派生类的版本,这就是多态的核心。

class A {
public:
    virtual void show() {
        cout << "Base A" << endl;
    }
};

// 虚拟继承确保 A 在 D 中只有一个实例
class B : virtual public A {
public:
    void show() override {
        cout << "Derived B" << endl;
    }
};

class C : virtual public A {
public:
    void show() override {
        cout << "Derived C" << endl;
    }
};

class D : public B, public C {
public:
    void show() override {
        cout << "Derived D" << endl;
    }
};

int main() {
    D d;
    A* pa = &d;  // 基类指针指向派生类对象
    pa->show();  // 输出 "Derived D"
    return 0;
}

在上述代码中,通过虚拟继承,D 类对象 d 中只有一个 A 的实例。A 类的虚函数 show()D 类重写后,通过基类指针 pa 调用时,实际调用的是 D 类的 show() 方法,实现了多态。

3.2 虚拟继承的注意事项

3.2.1 构造函数中的调用顺序

使用虚拟继承时,基类的构造函数调用顺序会略有不同。虚基类总是最先被初始化,无论虚基类是在继承链中出现的位置。

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

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

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

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

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

输出

Constructing A
Constructing B
Constructing C
Constructing D

在这个例子中,即使 A 类作为虚基类出现于 BC 的虚拟继承中,在 D 的构造过程中,A 的构造函数仍然是最先被调用的。虚基类的这种初始化顺序确保 A 的实例在 BC 之前就已经准备好。

3.2.2 虚基类成员的访问

虚基类成员的访问在派生类中可能需要显式地指定基类。虽然虚拟继承解决了二义性,但为了代码的可读性,通常仍然使用 类名::成员名 的形式来访问。

class A {
public:
    int _value;
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
public:
    void setValue(int val) {
        A::_value = val;  // 显式指定访问A的_value
    }
    
    int getValue() {
        return A::_value;
    }
};

int main() {
    D d;
    d.setValue(10);
    cout << "Value: " << d.getValue() << endl;  // 输出 "Value: 10"
    return 0;
}

在此示例中,D 类中通过 A::_value 来访问 A 中的 _value 成员。虽然虚拟继承避免了数据冗余,但使用显式的访问方式可以增强代码的可读性。


第四章:虚拟继承与传统继承的对比

4.1 虚拟继承与传统继承的区别

虚拟继承和传统继承在多重继承中的处理方式存在明显差异。理解这两者的区别有助于在实际项目中做出合适的设计选择。

4.1.1 实例化方式的区别

在传统继承中,当多个派生类继承自同一个基类时,基类会被每个派生类实例化一次,从而导致数据冗余。而虚拟继承通过 virtual 关键字使得基类在派生类中只实例化一次,避免了冗余。

示例代码对比:

  • 传统继承
class A {
public:
    int _a;
};

class B : public A {
};

class C : public A {
};

class D : public B, public C {
};

int main() {
    D d;
    // d.B::_a 和 d.C::_a 是不同的两个变量,导致数据冗余。
    return 0;
}

在上述代码中,D 类中存在两份 A_a 变量,这就导致了数据冗余问题。

  • 虚拟继承
class A {
public:
    int _a;
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};

int main() {
    D d;
    // d._a 是唯一的,避免了数据冗余。
    return 0;
}

在虚拟继承的版本中,A 的实例在 D 中只存在一份,因此 d._a 是唯一的。这解决了传统继承中的数据冗余问题。

4.1.2 内存布局的区别

在虚拟继承中,编译器通过引入虚基表(VBTable)和虚基类指针(VBPTR),使得派生类对象可以通过偏移量访问到基类的数据。传统继承则直接将基类对象的数据存储在派生类对象中。

  • 传统继承的内存布局:派生类对象中包含每个基类对象的数据。
  • 虚拟继承的内存布局:派生类对象通过虚基表定位到唯一的虚基类实例。

4.2 选择传统继承还是虚拟继承?

在实际项目中,选择传统继承还是虚拟继承,取决于代码的需求以及对继承结构的复杂性管理。以下是一些建议和注意事项:

4.2.1 何时使用传统继承?
  • 单一继承:如果类的设计只涉及到一个基类和一个派生类,那么使用传统继承即可,不需要引入虚拟继承的复杂性。
  • 没有菱形继承的问题:如果类的多重继承不会导致基类的重复实例化(即没有菱形结构),传统继承是更简单的选择。
  • 性能要求高的场景:由于传统继承不涉及虚基表的查找,访问速度更快,适用于性能要求更高的场景。
4.2.2 何时使用虚拟继承?
  • 解决菱形继承问题:如果设计中存在菱形继承结构,虚拟继承是解决数据冗余和二义性问题的首选。
  • 共享基类资源:当多个派生类需要共享同一个基类的资源(如单个计数器实例),虚拟继承可以确保资源的唯一性。
  • 更强的扩展性:虚拟继承在类的设计上提供了更高的灵活性,使得以后扩展新的派生类时,不会因为基类的重复实例化而产生冲突。

4.3 虚拟继承的最佳实践

4.3.1 小心使用多层次的虚拟继承

虚拟继承可以解决菱形继承的问题,但如果继承层次过多,代码的可读性和维护性会大幅降低。因此,在设计类层次结构时,应尽量保持清晰和简洁。

  • 减少继承层次:尽量避免多层次的虚拟继承,保持类的结构简单化。
  • 使用组合替代继承:如果可以使用对象组合(has-a 关系)替代继承(is-a 关系),那么优先选择组合,这样可以降低代码的耦合度。

4.4 实际项目中的继承选择案例

4.4.1 案例:设计多功能打印机

假设我们要设计一个多功能打印机(MFP),它可以进行打印、扫描和复印。我们可以通过多重继承来实现这三种功能。

  • 基类 Printer:提供打印功能。
  • 基类 Scanner:提供扫描功能。
  • 派生类 Copier:继承自 PrinterScanner,实现复印功能。

如果我们希望 Copier 共享 PrinterScanner 的基础硬件(如设备接口),可以使用虚拟继承,确保 Copier 只有一个 Device 实例。

class Device {
public:
    void connect() {
        cout << "Device connected" << endl;
    }
};

class Printer : virtual public Device {
public:
    void print() {
        cout << "Printing..." << endl;
    }
};

class Scanner : virtual public Device {
public:
    void scan() {
        cout << "Scanning..." << endl;
    }
};

class Copier : public Printer, public Scanner {
public:
    void copy() {
        cout << "Copying..." << endl;
    }
};

int main() {
    Copier copier;
    copier.connect();  // 调用唯一的Device实例的方法
    copier.print();
    copier.scan();
    copier.copy();
    return 0;
}

在这个案例中,PrinterScanner 虚拟继承自 DeviceCopier 只会持有一个 Device 的实例,确保设备连接的资源不会被重复使用。


第五章:继承的总结与反思

5.1 C++ 继承的核心要点回顾

在学习 C++ 继承的过程中,我们探讨了多种继承方式及其实际应用场景。以下是一些关键要点的总结:

  • 继承的本质:继承是面向对象编程的核心特性,允许派生类复用基类的属性和方法,从而避免代码的重复编写。继承通过 is-a 关系体现类之间的层次关系。
  • 多重继承与菱形继承:多重继承允许一个类从多个基类继承,但也引入了复杂性,特别是菱形继承问题。虚拟继承通过 virtual 关键字,可以解决菱形继承中的数据冗余和二义性问题。
  • 虚基表与偏移量:虚拟继承通过虚基表(VBTable)和虚基类指针(VBPTR),在运行时动态计算虚基类的位置,从而保证了多重继承中的唯一性。

5.2 常见继承误区与陷阱

在实际开发中,继承的使用容易出现一些常见的误区和陷阱,以下是几个需要特别注意的点:

5.2.1 忽视虚析构函数的定义

当基类的析构函数未被声明为 virtual 时,通过基类指针删除派生类对象,会导致派生类的析构函数无法正确调用,从而引发内存泄漏。

也是多态的内容,下一篇博客就会讲解

class Base {
public:
    ~Base() { cout << "Base destructor called" << endl; }
};

class Derived : public Base {
public:
    ~Derived() { cout << "Derived destructor called" << endl; }
};

int main() {
    Base* p = new Derived();
    delete p;  // 只调用了Base的析构函数,未调用Derived的析构函数,导致内存泄漏
    return 0;
}

在上述代码中,delete p 只会调用 Base 的析构函数,而不会调用 Derived 的析构函数。解决方法是将 Base 的析构函数声明为 virtual

5.2 优先使用组合而非继承

在设计类时,组合优先于继承是一种常见的设计原则。组合关系使类之间的耦合度降低,更便于代码的扩展和维护。

例如,如果我们有一个 Car 类和一个 Engine 类,可以通过组合的方式来实现,而不是让 Car 继承自 Engine

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

class Car {
private:
    Engine _engine;  // Car组合了一个Engine对象
public:
    void start() {
        _engine.start();
        cout << "Car is running" << endl;
    }
};

通过组合,我们可以更灵活地替换和扩展 Engine 类,而不会影响 Car 类的设计。

5.3 继承的设计模式与应用

在 C++ 开发中,继承与组合是实现多态的重要手段。合理地使用继承,可以实现更灵活的设计模式,如工厂模式策略模式等。这些设计模式广泛应用于实际项目中,有助于提高代码的复用性和扩展性。

5.3.1 工厂模式的应用

工厂模式利用继承机制,实现对象的动态创建和管理,是设计模式中的经典应用之一。

class Product {
public:
    virtual void use() = 0;  // 定义一个抽象产品类
};

class ConcreteProductA : public Product {
public:
    void use() override { cout << "Using Product A" << endl; }
};

class ConcreteProductB : public Product {
public:
    void use() override { cout << "Using Product B" << endl; }
};

class Factory {
public:
    static Product* createProduct(int type) {
        if (type == 1) {
            return new ConcreteProductA();
        } else {
            return new ConcreteProductB();
        }
    }
};

int main() {
    Product* product = Factory::createProduct(1);
    product->use();  // 输出 "Using Product A"
    delete product;
    return 0;
}

在这个例子中,通过继承 Product 类,我们实现了对不同产品对象的动态创建和管理。

5.4 继承的未来发展趋势

随着 C++ 标准的不断演进,新的语言特性(如 std::variantconcepts)提供了更多替代继承的方式。对于未来的 C++ 开发者来说,理解这些新特性,并在合适的场景下替代传统继承,将会成为新的挑战和机遇。


写在最后

通过本篇文章的学习,我们深入探讨了 C++ 继承中的进阶知识,包括多重继承、虚拟继承的使用和内存管理,以及它们在实际项目中的应用。虚拟继承在解决菱形继承问题的同时,也增加了代码的复杂性,因此在使用时需要格外谨慎。

继承是面向对象编程中的利器,但也是一把双刃剑。合理地使用继承可以大大提高代码的复用性和可扩展性,而不合理的继承则会带来维护上的负担。

在设计类结构时,务必根据实际需求选择最适合的方案,掌握继承的精髓,才能在 C++ 编程中游刃有余。

💬 讨论区:如果你在学习过程中有任何疑问,欢迎在评论区留言讨论。
👍 支持一下:如果你觉得这篇文章对你有帮助,请点赞、收藏并分享给更多 C++ 学习者!你的支持是我继续创作的动力。


以上就是关于【C++篇】继承之巅:超越法则束缚,领略面向对象的至臻智慧的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

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

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

相关文章

「漏洞复现」东胜物流软件 GetProParentModuTreeList SQL注入漏洞

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

数据结构——树——二叉树——大小堆

目录 1>>导言 2>>树 2.1>>树的相关术语 2.2>>树的表示和应用场景 3>>二叉树 3.1>>完全二叉树 3.2>>大小根堆 4>>结语 1>>导言 上篇小编将队列的内容给大家讲完了&#xff0c;这篇要步入新的篇章&#xff0c;请宝…

基于Spark的共享单车数据存储系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

ML 系列:机器学习和深度学习的深层次总结(17)从样本空间到概率规则概率

一、说明 概率是支撑大部分统计分析的基本概念。从本质上讲&#xff0c;概率提供了一个框架&#xff0c;用于量化不确定性并对未来事件做出明智的预测。无论您是在掷骰子、预测天气还是评估金融市场的风险&#xff0c;概率都是帮助您驾驭不确定性的工具。本篇将讲授概率的原理和…

Linux使用Dockerfile部署Tomcat以及jdk

资源准备 首先提供本教程所有资源包。 当然也可以根据自己需求去官网下载。 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;f31y #我们开始吧 首先我们需要一台linux操作系统的机器&#xff0c;当然windows也是可以的&#xff0c;本系列教程是基于Linux的&#…

【网络】H3C交换机配置

1. 网关配置&#xff08;web管理界面&#xff09; 默认S5048PV2_EI交换机 第一步&#xff1a;若是首次配置&#xff0c;通过Console口配置以太网交换机管理VLAN的IP地址&#xff0c;默认的网关是192.168.0.253。 system-view [H3C] interface Vlan-interface 1&#xff08;进入…

[mysql]聚合函数GROUP BY和HAVING的使用和sql查询语句的底层执行逻辑

#GROUP BY的使用 还是先从需求出发,我们现在想求员工表里各个部门的平均工资,最高工资 SELECT department_id,AVG(salary) FROM employees GROUP BY department_id 我们就会知道它会把一样的id分组,没有部门的就会分为一组,我们也可以用其他字段来分组,我们想查询不同jb_id…

ArcGIS计算多个面要素范围内栅格数据各数值的面积

本文介绍在ArcMap软件中&#xff0c;基于面积制表工具&#xff08;也就是Tabulate Area工具&#xff09;&#xff0c;基于1个面要素数据集与1个栅格数据&#xff0c;计算每一个面要素中各栅格数据分布面积的方法。 首先&#xff0c;来看一下本文的需求。现有一个矢量面的要素集…

水陆两栖车应对应急事件发挥的作用_鼎跃安全

随着气候变化&#xff0c;城市内涝等问题日益严重。为了应对可能出现的洪水灾害&#xff0c;许多城市开始将水陆两栖车纳入应急救援装备体系。在暴雨引发城市积水时&#xff0c;水陆两栖车可以作为一种高效的救援和运输工具&#xff0c;及时疏散被困群众&#xff0c;运送应急物…

Hallo2 长视频和高分辨率的音频驱动的肖像图像动画 (数字人技术)

HALLO2: LONG-DURATION AND HIGH-RESOLUTION AUDIO-DRIVEN PORTRAIT IMAGE ANIMATION 论文&#xff1a;https://arxiv.org/abs/2410.07718 代码&#xff1a;https://github.com/fudan-generative-vision/hallo2 模型&#xff1a;https://huggingface.co/fudan-generative-ai/h…

执行Django项目的数据库迁移命令时报错:(1050, “Table ‘django_session‘ already exists“);如何破?

一、问题描述&#xff1a; 当我们写Django时&#xff0c;由于自己的操作不当&#xff0c;导致执行数据库迁移命令时报错&#xff0c;报错的种类有很多&#xff0c;例如&#xff1a; 迁移文件冲突&#xff1a;可能你有多个迁移文件试图创建同一个表。数据库状态与迁移文件不同…

Javascript数据结构——哈希表

18_哈希表_深入链地址法_哔哩哔哩_bilibili 哈希表&#xff08;Hash Table&#xff09;&#xff0c;又称为散列表&#xff0c;是一种通过哈希函数组织数据以实现快速访问的数据结构。下面将从其概述、底层实现和前端应用场景等方面进行详细阐述。 概述 哈希表的基本思路是&a…

C#与C++交互开发系列(九):字符串传递的几种形式

前言 在C#与C交互开发中&#xff0c;字符串的传递是非常常见的需求。字符串作为数据类型在托管代码&#xff08;C#&#xff09;和非托管代码&#xff08;C&#xff09;之间的传递存在一些特殊的挑战&#xff0c;因为两者的字符串内存管理和编码方式不同。本篇博客将详细介绍几…

gitlab不同账号间·仓库转移

背景&#xff1a;公司业务调整&#xff0c;原先在海外仓库的代码转移回国内 诉求&#xff1a;完整的保留项目记录 操作&#xff1a; 步骤一: 定位到需要迁移的原项目地址 步骤二&#xff1a;创建新项目 步骤三&#xff1a;打开命令行&#xff0c;创建好文件路径为需要clo…

软件工程中的建造者模式:用于构建复杂对象

在软件工程中&#xff0c;我们经常会遇到需要构建复杂对象的场景。这些对象可能包含多个组件&#xff0c;而这些组件的创建过程可能相当繁琐。为了解决这个问题&#xff0c;设计模式提供了一种优雅的方法&#xff0c;这就是建造者模式&#xff08;Builder Pattern&#xff09;。…

HTTP之响应消息Response

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ HTTP之响应消息Response 1 Response 组成2 状态…

基于SpringBoot+Vue+MySQL的实践性教学系统

系统展示 用户前台界面 后台界面 系统背景 随着信息技术的快速发展&#xff0c;企业对于高效、智能的管理系统需求日益迫切。传统的管理系统大多采用单机版或C/S架构&#xff0c;存在操作复杂、维护困难、数据共享性差等问题。而基于SpringBootVueMySQL的全栈管理系统&#xff…

通信协议——UART

目录 基础概念串行&并行串行的优缺点 单工&双工 UART基本概念时序图思考&#xff1a;接收方如何确定01和0011 基础概念 串行&并行 串行为8车道&#xff0c;并行为1车道 串行的优缺点 通行速度快浪费资源布线复杂线与线之间存在干扰 单工&双工 单工&#xf…

018集——c# 实现CAD添加侧栏菜单(WPF控件)(CAD—C#二次开发入门)

本例实现的效果如下&#xff1a; 第一步&#xff1a;添加引用 using UserControl System.Windows.Controls.UserControl; using System.Windows.Forms.Integration;//PaletteSet integration 第二步 <UserControl x:Class"AcTools.UserControl1"xmlns"htt…

【数据分析】Power BI的使用教程

目录 1 Power BI架构1.1 Power BI Desktop1.2 Power BI服务1.3 Power BI移动版 2 Power Query2.1 Power Query编辑器2.2 Power Query的优点2.3 获取数据2.4 数据清洗的常用操作2.4.1 提升标题2.4.2 更改数据类型2.4.3 删除错误/空值2.4.4 删除重复项2.4.5 填充2.4.6 合并列2.4.…