【C++】多态(详解)

前言:今天学习的内容可能是近段时间最难的一个部分的内容了,C++的多态,这部分内容博主认为难度比较大,各位一起慢慢啃下来。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 什么是多态
  • 什么是虚函数
  • 多态的定义及实现
    • 1.多态构成的条件
    • 2. 虚函数的重写
    • 3.协变
    • 4.析构函数的重写
    • 5. override 和 final
    • 6. 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
  • 多态的原理
    • 虚函数表
    • 虚表指针的内容
    • 引用和指针如何实现多态
    • 动态绑定与静态绑定
    • 虚函数表存放位置
  • 单继承和多继承中的虚拟表


什么是多态

多态的概念:多态(polymorphism)是C++中面向对象编程的一个重要概念,它指的是同一种消息(方法调用)在不同的对象上产生不同的行为。这种特性使得程序设计更加灵活,提高了代码的可扩展性和可维护性。(通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态)。


什么是虚函数

在C++中,虚函数是一种特殊的成员函数,用于实现多态性。通过将基类的成员函数声明为虚函数,可以在派生类中对该函数进行重写。当通过基类指针或引用调用虚函数时,实际调用的是相应派生类中的函数。

虚函数的声明和定义如下:

class Base//基类
{
public:
    virtual void foo() {
        // 函数实现
    }
};

在基类的函数声明前加上virtual关键字,就将该函数声明为虚函数。派生类可以选择重写基类的虚函数,使用相同的函数签名来定义派生类中的函数:

class Derived : public Base//派生类 
{
public:
    void foo() override {
        // 函数实现
    }
};

注意,在派生类中重写虚函数时,可以使用override关键字显式标注,以增强代码可读性。

使用虚函数时,需要通过基类指针或引用来调用虚函数。根据指针或引用所指向的具体对象类型,调用的将是相应对象的虚函数实现。

例如:

Base* base = new Derived();
base->foo(); // 调用的是Derived的foo()函数

在上述示例中,通过基类指针base指向Derived对象,并调用虚函数foo(),实际调用的是Derived类中的foo()函数。

总结来说,虚函数实现了在基类中声明一个函数,使其可以在派生类中被重写,并能通过基类指针或引用调用派生类对象的对应实现,从而实现多态性。


多态的定义及实现

1.多态构成的条件

C++中的多态性是指在相同的函数签名下,通过基类指针或引用调用不同的派生类对象时,能够实现不同的行为。在C++中,实现多态性需要满足以下三个条件:

  1. 存在继承关系:多态性需要至少有一个基类和一个或多个派生类。
  2. 基类函数为虚函数:基类中的函数必须声明为虚函数,以便在派生类中进行重写。子类父类都有这个虚函数 + 子类的虚函数与父类虚函数的函数名/参数/返回值 都相同 。
  3. 使用基类指针或引用:通过基类指针或引用来调用派生类对象的函数,实现函数的动态绑定。

下面是一个示例代码,演示了多态性的实现:

class Animal //基类
{
public:
    virtual void sound() 
    {
        cout << "Animal is making a sound." << endl;
    }
};

class Cat : public Animal//派生类 
{
public:
    virtual void sound() {
        cout << "Cat is meowing." << endl;
    }
};

class Dog : public Animal//派生类  
{
public:
    virtual void sound() {
        cout << "Dog is barking." << endl;
    }
};

void func(Animal& s)//接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数
{
    s.sound();
}

void test1()
{
    Animal s1;
    Cat s2;
    Dog s3;
    func(s1);
    func(s2);
    func(s3);
}

void test2()
{
    Animal* animal1 = new Animal();//当基类的指针指向派生类的时候,只能操作派生类中从基类中继承过来的数因据和基类自身的数据
    Animal* animal2 = new Cat();
    Animal* animal3 = new Dog();

    animal1->sound(); // Animal is making a sound.
    animal2->sound(); // Cat is meowing.
    animal3->sound(); // Dog is barking.

    delete animal1;
    delete animal2;
    delete animal3;

}
int main() 
{
    test1();
    test2();
    return 0;
}

在上面的代码中,Animal类是基类,Cat和Dog类是派生类。基类Animal中的sound函数被声明为虚函数,而派生类Cat和Dog中重写了该函数。通过使用基类指针animal1、animal2和animal3,分别指向不同的派生类对象,实现了多态性。
注: 在test1中接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数。
在这里插入图片描述


2. 虚函数的重写

在C++中,虚函数的重写是指在派生类中对基类中已有的虚函数进行重新定义。通过重写虚函数,派生类可以改变基类中的函数实现,使其符合派生类的特定需求。

虚函数的重写要求派生类中的函数具有与基类中虚函数完全相同的函数签名(即函数名、参数列表和返回类型都一致)。可以使用override关键字显式标注派生类中的函数,以增强代码的可读性和可靠性。在刚刚的例子中我们通过派生类的Dog和Cat重写了基类中的虚函数sound


3.协变

在C++中,协变(covariant)指的是派生类中重写虚函数的返回类型与基类中的虚函数返回类型具有相关性。换句话说,派生类中重写的虚函数可以返回基类函数返回类型的派生类型。

在早期的C++标准中,派生类中重写虚函数的返回类型必须与基类函数的返回类型完全相同。但是在C++11标准引入了协变的概念,对于返回类型是指针或引用的虚函数,允许派生类中的返回类型是基类返回类型的派生类型。

:协变是子类虚函数与父类虚函数返回值类型不同,但子类和父类的返回值类型也必须是父子关系指针和引用。

以下是一个示例:

class Animal {
public:
    virtual Animal* clone() 
    {
        cout << "Animal clone" << endl;
        return new Animal;
    }
};

class Dog : public Animal 
{
public:
    virtual Dog* clone() override 
    {
        cout << "Dog clone" << endl;
        return new Dog;
    }
};

int main() {
    Dog s1;
    Animal* s2 = s1.clone();//基类接受派生类的虚函数的返回值构造对象
    return 0;
}

在这个例子中,基类Animal有一个虚函数clone,返回一个指向基类的指针。派生类Dog重写了clone函数,并返回一个指向派生类Dog的指针。

当通过基类指针调用clone函数时,根据派生类的类型,会返回相应的指针类型。也就是说,通过协变,Dog*指针会被正确返回。

总结起来,协变允许派生类中的虚函数返回类型与基类中的虚函数返回类型具有相关性,使得处理继承关系时更加灵活和准确。


4.析构函数的重写

在C++中,析构函数(Destructor)也可以被重写。重写析构函数是为了在派生类中定义自己的清理操作。

基类的析构函数通常应该声明为虚函数,以确保正确地调用派生类的析构函数。这是因为当使用基类指针或引用指向派生类对象时,通过基类指针或引用删除对象时,应该调用派生类的析构函数来释放派生类对象的资源。

以下是一个示例:

#include <iostream>
using namespace std;

class Animal {
public:
    virtual ~Animal() {
        cout << "Animal destructor" << endl;
    }
};

class Dog : public Animal {
public:
    virtual ~Dog() override {
        cout << "Dog destructor" << endl;
    }
};

int main() {
    Animal* animal = new Dog();//先调用子类的析构,在调用父类的析构
    delete animal;
    return 0;
}

在这个例子中,基类Animal的析构函数被声明为虚函数。派生类Dog重写了析构函数。

当通过基类指针删除派生类对象时,会正确调用派生类的析构函数。也就是说,首先调用派生类的析构函数,然后调用基类的析构函数。

输出结果为:

Dog destructor
Animal destructor

这表明析构函数按照派生类到基类的顺序被调用。

总结起来,C++中的析构函数可以被重写,在派生类中定义自己的清理操作。为了确保正确地调用派生类的析构函数,基类的析构函数通常应该声明为虚函数。


5. override 和 final

在C++11标准中,overridefinal是两个特殊的关键字,用于修饰成员函数。

  1. override关键字用于表示覆盖基类的虚函数。在C++中,当一个派生类的成员函数与基类的虚函数具有相同的名称和参数列表时,可以使用override关键字显式地告诉编译器这是一个覆盖函数。这样做的好处是可以提醒开发者在派生类中是否正确地覆盖了基类的虚函数。如果函数签名不匹配,编译器会给出错误提示。例如:
class Base {
public:
    virtual void foo() const;
};

class Derived : public Base {
public:
    void foo() const override;  // override关键字表示覆盖基类的虚函数
};
  1. final关键字用于表示禁止派生类进一步覆盖函数。在C++中,可以通过在基类的虚函数后面加上final关键字来禁止派生类进一步覆盖该函数。这样做的好处是可以防止派生类无意中修改这个函数的行为。例如:
class Base {
public:
    virtual void foo() const final;  // final关键字表示禁止派生类进一步覆盖该虚函数
};

class Derived : public Base {
public:
    // 下面的代码会导致编译错误,因为foo()函数被标记为final,无法再被派生类覆盖
    // void foo() const;
};

总之,overridefinal关键字是在C++11中引入的,用于增强对虚函数覆盖的控制。override关键字表示派生类覆盖基类的虚函数,final关键字表示禁止派生类进一步覆盖函数。


6. 重载、覆盖(重写)、隐藏(重定义)的对比

在C++中,有三种不同的函数特性:重载、覆盖(重写)和隐藏(重定义),它们的区别如下:

  1. 重载(Overload):重载是指在同一个作用域内,根据函数的参数类型和/或数量的不同,可以定义多个同名函数。重载函数在调用时根据传入的参数类型和/或数量来决定使用哪个函数。重载函数可以在同一个类中或者不同的类中定义。例如:
void foo(int x);
void foo(float x);
  1. 覆盖(重写,Override):覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了多态性。派生类中的函数必须具有和基类中虚函数相同的名称、参数列表和返回类型,而且在基类中该虚函数必须被声明为virtual。在运行时,根据对象的实际类型来确定使用哪个函数。例如:
class Base {
public:
    virtual void foo();
};

class Derived : public Base {
public:
    void foo() override;
};
  1. 隐藏(重定义,Hide):隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。派生类中的函数必须具有和基类中被隐藏的函数相同的名称,但是参数列表和返回类型可以不同。在编译时,根据对象的静态类型来确定使用哪个函数。例如:
class Base {
public:
    void foo(int x);
};

class Derived : public Base {
public:
    void foo(float x);
};

总结:

  • 重载是根据函数的参数类型和/或数量来决定使用哪个函数,主要是静态多态性。
  • 覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了动态多态性。
  • 隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。
    在这里插入图片描述

抽象类

在C++中,抽象类是一个不能被直接实例化的类。它只能作为其他类的基类来派生出新的类。抽象类包含至少一个纯虚函数,也可以包含非纯虚函数。

纯虚函数是一个没有实现的虚函数,它通过在函数声明的末尾使用 “= 0” 来指定。纯虚函数的存在使得抽象类无法被实例化,因为任何一个派生类都必须实现所有纯虚函数,才能被实例化。

抽象类主要用于定义公共的接口,而具体的实现则由派生类来完成。它可以作为一种设计工具,用于实现多态性和封装性。在实际应用中,抽象类常常作为基类被其他具体类继承使用。

使用C++中的抽象类需要以下步骤:

  1. 创建一个抽象类:使用class关键字定义一个类,并在需要的成员函数前声明成纯虚函数。至少有一个成员函数是纯虚函数,用于使类成为抽象类。
class AbstractClass {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
    virtual void virtualFunction() { // 非纯虚函数
        // 具体实现
    }
};
  1. 派生一个具体类:从抽象类派生一个具体的子类,该子类必须实现抽象类中的所有纯虚函数。
class ConcreteClass : public AbstractClass {
public:
    void pureVirtualFunction() override {
        // 实现纯虚函数
    }
};
  1. 实例化具体类:通过具体类实例化对象,可以直接调用抽象类中定义的非纯虚函数,或者通过指针或引用调用纯虚函数。
ConcreteClass obj;
obj.virtualFunction();

AbstractClass* ptr = new ConcreteClass();
ptr->pureVirtualFunction();
delete ptr;

通过使用抽象类,可以定义一个通用接口,而具体的实现则由派生类完成。这样可以提高代码的可维护性,支持多态性,并且遵循面向对象的封装性原则。


多态的原理

虚函数表

C++中的虚函数表(Virtual Function Table,简称vtable)是用于实现多态的一种机制。每个含有虚函数的类都会有一个对应的虚函数表,用于存储该类的虚函数的地址。

虚函数表是一个由函数指针组成的数组,每个函数指针指向相应的虚函数的地址。当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。

当通过基类指针或引用调用虚函数时,编译器会将其替换为通过虚函数表来调用相应的虚函数。具体过程如下:

  1. 编译器根据对象的类型找到它的虚函数表。
  2. 根据函数在虚函数表中的位置索引,调用相应的虚函数。

通过虚函数表,C++实现了运行时多态性,允许在运行时根据对象的实际类型来调用相应的虚函数,而不是根据指针或引用的静态类型来调用相应的函数。这为面向对象的程序设计提供了灵活性和可扩展性。

下面我看看一个面试题:
在这里插入图片描述
这里我们直接说结果:在这里插入图片描述

为什么是8呢?因为我们刚刚提到的虚函指针在这里出现了,我们去监视窗口看看,是不是真的有这个虚函数指针的存在。
在这里插入图片描述

结合刚刚的例子,这里就充分解释了,为什么这对象b中会是8个字节了,当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。


虚表指针的内容

话不多说先看一个例子:
在下图中我们会发现,你创建的对象中,第一个存的就是虚表指针的地址,且发现我们查看虚表指针的地址会发现,虚函数的地址依次存储在该虚表中。

在这里插入图片描述


既然这样,我们接着去派生类中查看他的虚表指针有什么不同,如下图所示:

  1. 基类Animal对象s1和派生类Cat对象s2虚表是不一样的,这里我们发现sound完成了重写,所以s2的虚表中存的是重写的Animal::sound,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法
  2. 另外fn继承下来后是虚函数,所以放进了虚表,f1也继承下来了,但是不是虚函数,所以不会放进虚表。
  3. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  4. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  5. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
    在这里插入图片描述

引用和指针如何实现多态

我们之前提到过多态,可以通过传过来的基类的对象或者派生类的对象,来调用其对应的虚函数,但是他是如何识别的呢?
在这里插入图片描述
:这里我们在强调一下,你无论对基类取地址,还是父类取地址,如果有虚函数,那么这个地址都是他虚函数表指针的地址。

这里我们就可以分析,当我们传递基类的对象地址时,就会去基类的虚表指针中去调用对应的虚函数。当我们传递派生类对象的地址时,会将派生类的内容切割掉(切片),然后去调用其虚表指针的地址,然后再去调用对应的虚函数。


动态绑定与静态绑定

在C++中,动态绑定(dynamic binding)和静态绑定(static binding)是两种不同的绑定方式,它们是实现多态性的关键。

静态绑定是在编译时确定函数调用的地址。当使用对象的指针或引用调用函数时,编译器会根据指针或引用的类型来确定调用的函数。这种绑定方式是静态的,因为调用的函数在编译时就已经确定了。静态绑定通常用于非虚函数。

动态绑定是在运行时确定函数调用的地址。当使用对象的指针或引用调用虚函数时,编译器会在运行时根据对象的实际类型来确定调用的函数。这种绑定方式是动态的,因为调用的函数直到运行时才能确定。动态绑定实现了多态性,因为可以通过基类的指针或引用调用派生类的成员函数。动态绑定通常用于虚函数。

动态绑定通过虚函数表(virtual function table)来实现。每个带有虚函数的对象都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,通过对象的指针或引用访问虚函数表,并根据对象的实际类型调用正确的函数。

总结起来,静态绑定在编译时确定函数调用的地址,而动态绑定在运行时根据对象的实际类型确定函数调用的地址,实现了多态性。在使用函数时,如果希望实现多态性,需要使用虚函数和动态绑定。


虚函数表存放位置

在 C++ 中,虚函数表的存放位置通常是在 可执行目标文件 的 只读数据段 ( .rodata )1。具体来说,虚函数表指针( vptr )存储在对象实例的内存中,而虚函数表本身则存储在可执行文件的只读数据段中。这意味着直到程序启动并加载可执行文件时,虚函数表的地址才会确定。

举个例子验证一下:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
 
void func()
{}
 
int main()
{
	Base b1;
	Base b2;
	static int a = 0;
	int b = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&b1));//虚表的地址是存放在类对象的头4个字节上因此我们对其进行强转,取地址就会得到虚表的位置了,
	printf("虚函数地址:%p\n", & Base::func1);
	printf("普通函数:%p\n", func);
}

如下图所示,我们会发现,虚表的地址和代码段的地址十分相近,因此我们可以得出虚表存放在代码段这个位置,和我们前面的结论相似。
在这里插入图片描述


单继承和多继承中的虚拟表

在C++中,每个类都有一个虚函数表(virtual function table),用于存储该类的虚函数的地址。当一个类中定义了虚函数时,编译器会为该类创建一个虚函数表,并将虚函数表的地址存储在对象的内存布局中。当通过指针或引用访问对象的虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于单继承关系,每个类只有一个虚函数表。当子类继承父类时,子类会继承父类的虚函数表,并在自己的虚函数表中添加自己的虚函数。当通过子类的指针或引用访问虚函数时,编译器会根据子类对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于多继承关系,每个类都有自己的虚函数表。当一个类通过多个父类进行多继承时,每个父类会有自己的虚函数表,并在子类的内存布局中存储这些虚函数表的地址。在访问虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

需要注意的是,多继承中可能会出现虚函数表的冲突或者大小不一致的问题,编译器会根据不同的实现采取不同的解决方案来处理这些问题。

:每一个虚函数都会放到虚表里面,但是有的编译器并不会显示一些虚函数!!!


好啦,今天的内容就到这里啦,下期内容预告搜索树的学习与模拟实现.


结语:进阶的内容有点繁杂,大家一起加油呐!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

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

相关文章

【深海王国】小学生都能玩的语音模块?ASRPRO打造你的第一个智能语音助手(4)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛勤工作的你今天也辛苦啦(/≧ω) 今天大都督继续为大家带来系列——小学生都能玩的语音模块&#xff0c;帮你一周内快速学会语音模块的使用方式&#xff0c;打造一个可用于智能家居、物联网领域的语音助…

NPDP究竟值不值得去考?

一、NPDP是什么&#xff1f; NPDP其实就是产品经理国际资格认证&#xff08;New Product Development Professional&#xff09;&#xff0c;是美国产品开发管理协会发起的&#xff0c;集理论、方法和实践一体&#xff0c;在新产品开发方面有一个很全面的知识体系。是国际公认…

对秒杀的思考

一、秒杀的目的 特价商品&#xff0c;数量有限&#xff0c;先到先得&#xff0c;售完为止 二、优惠券的秒杀 和特价商品的秒杀是一样的&#xff0c;只不过秒杀的商品是优惠券 三、秒杀的需求 秒杀前&#xff1a;提前将秒杀商品&#xff0c;存放到Redis秒杀中&#xff1a;使…

The First Descendant第一后裔卡顿的处理措施

The First Descendant第一后裔中&#xff0c;玩家可以体验具有不同个性概念和战斗风格的多种角色。后续将为每个角色推出各种皮肤和个性要素&#xff0c;让玩家能够打造个人专属角色The First Descendant第一后裔的世界中&#xff0c;角色的个性化不仅仅局限于他们独特的战斗风…

后端之路(集合项目)——结合案例正式搭建项目

在前面学完java后端的Maven、spring boot、Mysql、Mybatis之后&#xff0c;我们现在就应该集合它们开始搭建一个项目试试手了 这里我还是跟着黑马程序员的步骤来走好每一步&#xff0c;也给各位讲清楚怎么弄 先看一下这个图&#xff0c;觉得太笼统不明白的话不着急&#xff0c…

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…

新型发电系统——光伏行业推动能源转型

一、发展背景 “十四五”期间&#xff0c;随着“双碳”目标提出及逐步落实&#xff0c;本就呈现出较好发展势头的分布式光伏发展有望大幅提速。就“十四五”光伏发展规划&#xff0c;国家发改委能源研究所可再生能源发展中心副主任陶冶表示&#xff0c;“双碳”目标意味着国家…

动物检测yolo格式数据集(水牛 、大象 、犀牛 、斑马四类)

动物检测数据集 1、下载地址&#xff1a; https://download.csdn.net/download/qq_15060477/89512588?spm1001.2101.3001.9500 2、数据集介绍 本数据集含有四种动物可以检测&#xff0c;分别是水牛 、大象 、犀牛 、斑马四类&#xff0c;数据集格式为yolo格式&#xff0c;…

企业LoRA模型定制服务

&#x1f308; 最强AI绘画模型训练、定制服务公司出炉 —— 触站AI&#xff0c;设计界的智能魔法师 &#x1f9d9;‍♂️ &#x1f3a8; 触站AI&#xff0c;用智能技术解锁设计的无限可能 &#x1f3a8;在创意与科技交织的今天&#xff0c;触站AI以其AI绘画模型训练和定制服务…

C++ 实现QT信号槽

https://github.com/libsigcplusplus/libsigcplusplus #include <iostream>/* 在sigslot.h的420,将&#xff1a; //typedef sender_set::const_iterator const_iterator; 改为&#xff1a; //typedef typename sender_set::const_iterator const_iterator;#include <…

【LeetCode】十二、递归:斐波那契 + 反转链表

文章目录 1、递归2、leetcode509&#xff1a;斐波那契数列3、leetcode206&#xff1a;反转链表4、leetcode344&#xff1a;反转字符串 1、递归 函数自己调用自己 递归的4个点&#xff1a; 递归的例子&#xff1a;给一个数n&#xff0c;在斐波那契数列中&#xff0c;找到n对应的…

x264 编码器汇编模块介绍

aarch64汇编架构 解释:AArch64 是 ARM 架构的 64 位版本,也称为 ARMv8-A特点: 64位寻址能力,支持更大的地址空间,理论上可达16EB(Exabyte)使用64位宽的寄存器,有31个通用寄存器(X0-X30),外加一个链接寄存器(X31)支持扩展的 NEON SIMD 指令集,提供更多的执行单元和…

慧哥Saas充电桩开源平台 V2.5.5

文章目录 原地址&#xff1a;https://gitee.com/chouleng/cdzkjjh&#xff0c;更换新的地址如下 [点击此链接 https://gitee.com/chouleng/huili-cloud](https://gitee.com/chouleng/huili-cloud)一、产品功能部分截图1.手机端&#xff08;小程序、安卓、ios&#xff09;2.PC端…

Java 虚拟机 一

运行时数据区 我们先看线程隔离的数据区 程序计数器 程序计数器&#xff08; Program Counter Register&#xff09; 是一块较小的内存空间&#xff0c; 它可以看作是当前线程所执行的字节码的行号指示器。 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执…

cesium方案论证实现功能

仓库地址&#xff1a;Harvey-Andrew 演示地址&#xff1a;哔哩哔哩-满分观察网友z 文章目录 1. 场景加载2. 3D 模型2.1. 坐标转换2.2. 放置模型2.3. 调整模型2.4. 提交方案 3. 查看方案3.1. 场景还原3.2. 删除 1. 场景加载 加载Cesium的Melbourne Photogrammetry的倾斜摄影作…

【Kafka】记录一次Kafka消费者重复消费问题

文章目录 现象业务背景排查过程Push与Pull 现象 用户反馈消费者出现消息积压&#xff0c;并且通过日志看&#xff0c;一直重复消费&#xff0c;且没有报错日志。 业务背景 用户的消费者是一个将文件做Embedding的任务&#xff0c;&#xff08;由于AI技术的兴起&#xff0c;大…

keil5模拟 仿真 报错没有读写权限

debug*** error 65: access violation at 0x4002100C : no write permission 修改为&#xff1a; Dialog DLL默认是DCM3.DLL Parameter默认是-pCM3 应改为 Dialog DLL默认是DARMSTM.DLL Parameter默认是-pSTM32F103VE

Qt开发 | qss简介与应用

文章目录 一、qss简介与应用二、QLineEdit qss介绍与使用三、QPushButton qss1.常用qss1.1 基本样式表1.2 背景图片1.3 图片在左文字在右 2.点击按钮弹出菜单以及右侧箭头样式设置3.鼠标悬浮按钮弹出对话框 四、QCheckBox qss妙用&#xff1a;实时打开关闭状态按钮五、QComboBo…

Docker部署ETCD 3.5.14(保姆级图文教程)

系列文章目录 Docker部署Nginx 1.21.5&#xff08;保姆级图文教程&#xff09; Docker部署MySQL 8.3.0&#xff08;保姆级图文教程&#xff09; Docker部署ETCD 3.5.14&#xff08;保姆级图文教程&#xff09; 文章目录 一、环境二、拉取镜像2.1 查找 Docker Hub 上的 ETCD 镜像…

解决前端登录成功之后,往后端发请求携带cookie问题

项目背景&#xff1a; 今天在做伙伴匹配系统&#xff1a; 我现在实现的功能是&#xff1a; 在我登录成功之后&#xff0c;就进入了主页&#xff08;默认页&#xff09;&#xff0c;在我访问用户页的时候产生的问题 首先说明一下这个Cookie的问题&#xff1a; 我们登录成功…