C++多态(上)
目录
一、多态的概念
二、多态的定义及实现
1. 多态的构成条件
2. 虚函数
3. 虚函数的重写
4. C++11 override 和 final
4.1 final 关键字
4.2 override 关键字
5. 重载、覆盖(重写)、隐藏(重定义)的对比
三、抽象类
1. 概念
2. 接口继承和实现继承
2.1 接口继承
2.2 实现继承
2.3 两者的对比
四、多态的原理
1. 虚函数表
2. 多态的原理
3. 动态绑定与静态绑定
一、多态的概念
多态,顾名思义,就是函数调用的多种形态。它允许不同的对象去完成同一件事时,产生不同的动作和结果。其核心在于:相同的接口调用能根据对象类型产生不同的行为。
例如:
-
支付场景:扫码支付时,微信/支付宝/云闪付调用相同的
Pay()
接口但执行不同流程。 -
绘图软件:
Shape
基类的Draw()
方法,圆形/矩形/三角形各自实现不同绘制逻辑。 -
游戏开发:
Character
类的Attack()
方法,战士/法师/射手各自攻击方式不同。
二、多态的定义及实现
1. 多态的构成条件
多态是一种非常灵活且强大的面向对象编程特性。它的基本思想是:允许不同继承关系的类对象调用同一名称的函数,从而产生不同的行为。例如,Person 类和 Student 类(Student 继承 Person),当它们的对象调用买票函数时,Person 对象买全价票,Student 对象买半价票。
在继承体系中,要实现多态,必须满足以下两个条件:
1. 通过基类的指针或者引用调用虚函数
- 这是因为基类指针或引用可以向上转型,指向派生类对象。通过这种方式,编译器能够根据实际对象类型来决定调用哪个类的函数。例如,用 Person 类的引用去调用 buyTicket() 函数,如果该引用实际指向 Student 对象,就会调用 Student 类重写的 buyTicket() 函数,实现不同的买票行为。
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
- 虚函数是实现多态的关键。它是用
virtual
关键字修饰的类成员函数。只有当派生类重写了基类的虚函数后,才能根据对象的实际类型来调用相应的函数,从而实现多态行为。
2. 虚函数
虚函数就是用virtual
修饰的类成员函数。其定义方式如下:
class Animal
{
public:virtual void Speak() { cout << "动物叫声" << endl; // 虚函数声明}
};
注意:
只有类的非静态成员函数前可以加
virtual
,普通函数前不能加virtual
。这是因为虚函数是与类的对象紧密相关的,用于实现多态行为,而非静态成员函数可以访问类的成员变量和成员函数,适合这种动态绑定的场景。虽然虚函数和虚继承都使用
virtual
关键字,但它们的作用完全不同。虚函数是为了实现多态,而虚继承是为了解决菱形继承中的数据冗余和二义性问题。
3. 虚函数的重写
虚函数的重写是指派生类中有一个与基类完全相同的虚函数(返回值类型、函数名字、参数列表完全相同)。例如:
class Person
{
public:virtual void BuyTicket() { cout << "买票 - 全价" << endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout << "买票 - 半价" << endl; }
};
这里有一些需要注意的细节:
-
在派生类的虚函数前不加
virtual
关键字也可以构成重写。因为基类的虚函数被继承后,派生类中的该函数仍然保持虚函数属性。但为了代码的可读性和规范性,建议在派生类的虚函数前也加上virtual
关键字。 -
通过一个函数来演示多态的效果:
void Func(Person& p)
{ p.BuyTicket();
}int main()
{Person ps;Student st;Func(ps); // 输出 “买票 - 全价”Func(st); // 输出 “买票 - 半价”return 0;
}
🌵虚函数重写的两个例外
1. 协变(基类与派生类虚函数返回值类型不同)
当基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,这种情况也被称为协变。例如:
class Base {};
class Derived : public Base {};class Factory
{
public:virtual Base* Create() { return new Base; }
};class ImprovedFactory : public Factory
{
public:virtual Derived* Create() override { // 协变返回return new Derived;}
};
在这种情况下,虽然返回值类型不同,但派生类的函数仍然重写了基类的虚函数。这主要是因为派生类的指针或引用可以隐式转换为基类的指针或引用,从而保证了函数的兼容性。
2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,那么派生类析构函数只要定义,无论是否加virtual
关键字,都与基类的析构函数构成重写。例如:
class Person
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};class Student : public Person
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};
这是因为编译器对析构函数的名称做了特殊处理,将它们统一处理成
destructor
。这种重写机制在内存管理中非常重要。例如,当用基类指针指向派生类对象,并用
delete
释放对象时:
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1; // 调用 Person 的析构函数delete p2; // 调用 Student 的析构函数,然后调用 Person 的析构函数return 0;
}
如果没有将基类的析构函数定义为虚函数,delete p2 只会调用基类的析构函数,而不会调用派生类的析构函数,这样可能会导致内存泄漏等资源管理问题。因此,为了确保在多态情况下正确地释放资源,建议将基类的析构函数定义为虚函数。
4. C++11 override 和 final
C++11引入了
override
和final
关键字,用于增强对虚函数重写的控制。
4.1 final 关键字
final
修饰虚函数,表示该虚函数不能再被重写。例如:
class Vehicle
{
public:virtual void Start() final { // 禁止重写cout << "启动引擎" << endl;}
};class Car : public Vehicle
{
public:void Start() override { // 编译错误!cout << "电子打火" << endl;}
};
这意味着,Car
类无法重写Vehicle
类的Start()
函数,因为Vehicle
类的Start()
函数已经被final
修饰,表示这是虚函数的最终版本,不允许在派生类中进一步重写。
4.2 override 关键字
override
用于检查派生类虚函数是否重写了基类某个虚函数。如果没有重写,编译报错。例如:
class Shape
{
public:virtual void Draw(int color) {cout << "绘制图形" << endl;}
};class Circle : public Shape
{
public:void Draw(float color) override { // 错误!参数类型不匹配cout << "绘制圆形" << endl;}
};
这有助于开发者在编写代码时,确保派生类虚函数正确地重写了基类的虚函数,避免因函数名称拼写错误或其他因素导致无法构成重写的情况,从而提高代码的健壮性和可维护性。
5. 重载、覆盖(重写)、隐藏(重定义)的对比
特性 | 重载(Overload) | 重写(Override) | 隐藏(Hide) |
---|---|---|---|
作用域 | 同一作用域 | 父子类之间 | 父子类之间 |
函数签名 | 参数列表不同 | 完全相同 | 名称相同即可 |
virtual | 无关 | 必须使用 | 无关 |
访问权限 | 可不同 | 可不同 | 可不同 |
目的 | 扩展功能 | 实现多态 | 名称遮盖 |
三、抽象类
1. 概念
抽象类是一种特殊的类,它包含纯虚函数。纯虚函数是在虚函数后面加上 =0
,例如:
class Car {
public:virtual void Drive() = 0;
};
当一个类包含了纯虚函数,这个类就被称为抽象类。抽象类不能用来实例化对象,也就是说,你不能直接创建抽象类的对象。例如,对于Car
类,下面的代码是不允许的:
Car myCar; // 错误,不能实例化抽象类
这使得抽象类更像是一个接口规范,它定义了某些操作的接口,但没有提供具体的实现。派生类继承抽象类后,也不能直接实例化对象,除非它重写了所有的纯虚函数。例如:
class Benz : public Car
{
public:virtual void Drive() override { // 重写纯虚函数cout << "Benz - 舒适" << endl;}
};class BMW : public Car
{
public:virtual void Drive() override { // 重写纯虚函数cout << "BMW - 操控" << endl;}
};
只有当派生类(如Benz
和BMW
)重写了抽象类中的纯虚函数后,这些派生类才能被用来创建对象。例如:
void Test()
{Car* pBenz = new Benz; // 只有 Benz 重写了 Drive(),才能实例化pBenz->Drive(); // 输出 "Benz - 舒适"Car* pBMW = new BMW; // 只有 BMW 重写了 Drive(),才能实例化pBMW->Drive(); // 输出 "BMW - 操控"
}
这种机制确保了派生类在使用之前必须实现抽象类所规定的所有接口,从而保证了代码的规范性和一致性。
2. 接口继承和实现继承
2.1 接口继承
接口继承主要和虚函数相关。当派生类继承基类的虚函数时,它继承的是基类虚函数的接口。这种继承的目的是为了重写,以实现多态行为。例如,Car
类中的Drive()
是一个虚函数,Benz
和BMW
类继承了这个接口,并根据自己的特性重写了实现。这样,通过基类指针或引用调用Drive()
函数时,会根据实际对象类型来决定调用哪个类的实现。
接口继承强调的是规范和协议,它规定了派生类必须实现某些功能,但不关心具体的实现细节。这种继承方式使得代码具有良好的扩展性和灵活性,因为新的派生类可以按照相同的接口规范添加新的实现,而无需修改现有的代码逻辑。
2.2 实现继承
实现继承则是指派生类继承了基类函数的具体实现。例如,如果基类有一个普通函数(非虚函数),派生类会直接继承这个函数的实现代码。派生类可以直接使用这个函数,而无需自己重新实现。
实现继承适用于那些在基类中已经有一个通用实现,派生类可以直接使用或者稍作修改的情况。这种继承方式可以减少代码重复,提高代码的复用性。
2.3 两者的对比
(1) 继承的目的不同
-
接口继承主要是为了实现多态,通过统一的接口规范,让不同的派生类可以以不同的方式实现相同的操作。
-
实现继承主要是为了代码复用,直接利用基类中已经实现的功能。
(2) 对函数重写的要求不同
-
在接口继承中,虚函数通常需要被重写,以实现不同的行为。例如,
Car
类的Drive()
虚函数,Benz
和BMW
类根据自己的特点重写了这个函数。 -
在实现继承中,派生类可以直接使用基类的函数实现,无需重写,除非需要修改或扩展基类的功能。
(3) 多态支持不同
-
接口继承通过虚函数支持多态,使得可以通过基类指针或引用来调用派生类的函数。
-
实现继承中的普通函数调用是在编译时期就确定的,不支持多态。
(4) 适用场景不同
-
接口继承适用于需要定义一组规范,让不同的类按照这些规范实现自己的功能,并且在运行时根据对象类型动态调用相应的函数的场景。例如,定义一个图形接口,不同的图形类(如圆形、矩形等)按照这个接口实现自己的绘制方法。
-
实现继承适用于基类已经提供了一个通用的实现,派生类可以直接使用或者进行少量修改的场景。例如,一个通用的文件读取类,派生类可以继承其基本的读取功能,并添加一些特定的处理逻辑。
四、多态的原理
1. 虚函数表
让我们通过一个经典面试题切入:sizeof(Base)
的值是多少?
#include <iostream>
using namespace std;class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
通过观察测试,我们发现 b
对象是 8 字节。这是因为除了 _b
成员变量占用 4 字节外,对象中还包含一个虚函数表指针(__vfptr),虚函数表指针始终位于对象起始位置(不同编译器可能有差异,有些平台可能会放到对象的最后面)。
这个虚函数表指针指向虚函数表,虚函数表中存放着虚函数的地址。含有虚函数的类都至少有一个虚函数表指针。
再来看一个改造后的例子:
#include <iostream>
using namespace std;class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}
🌵从这个例子中,我们发现了以下几点:
派生类对象
d
中也有一个虚表指针。d
对象由两部分构成,一部分是继承自父类Base
的成员,包括父类的虚表指针,另一部分是派生类自己的成员变量_d
。基类对象
b
和派生类对象d
的虚表是不一样的。因为Derive
重写了Func1
,所以d
的虚表中存放的是重写的Derive::Func1
。虚函数的重写也叫作覆盖,覆盖是指虚表中虚函数的覆盖。
Func2
继承自基类Base
,且是虚函数,所以被放进虚表。而Func3
也是从基类继承来的,但不是虚函数,因此不会放进虚表。虚函数表本质上是一个存储虚函数指针的指针数组,一般情况下,这个数组最后会放一个 nullptr。
🍇总结一下派生类的虚表生成过程:
a. 先将基类中的虚表内容拷贝一份到派生类虚表中。
b. 如果派生类重写了基类中某个虚函数,就用派生类自己的虚函数覆盖虚表中基类的虚函数。
c. 派生类自己新增的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
🌻虚表是什么阶段初始化的?虚函数存在哪里?虚表存在哪里?
虚表实际上是在构造函数初始化列表阶段进行初始化的。虚表中存放的是虚函数的地址,这些虚函数和普通函数一样,都存在于代码段中,只是它们的地址被存到了虚表当中。对象中存储的不是虚表本身,而是指向虚表的指针。
通过以下代码我们可以判断出虚表的存在位置:
int i = 0;
int main()
{Base b;Derive d;int* j = new int;// 通过内存地址分析存储位置cout << "虚表地址:" << (void*)*(int*)&b << endl;cout << "栈变量地址:" << (void*)&b << endl;cout << "数据段地址:" << (void*)&i << endl;cout << "堆上地址:" << (void*)j << endl;cout << "代码段地址:" << (void*)"Hello" << endl;return 0;
}
从打印结果可以看出,虚表地址与代码段的地址非常接近,由此可知虚表实际上是存在代码段的。
2. 多态的原理
上面分析了这么多,那么多态的原理到底是什么?还记得这里Func函数传Person调用的 Person::BuyTicket,传Student调用的是Student::BuyTicket。
#include <iostream>
using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买票 - 全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票 - 半价" << endl;}
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0;
}
在这个例子中,当 p
指向 Mike
对象时,p.BuyTicket()
会在 Mike
的虚表中找到 Person::BuyTicket
并调用;当 p
指向 Johnson
对象时,p.BuyTicket()
会在 Johnson
的虚表中找到 Student::BuyTicket
并调用。这就实现了不同对象在执行同一行为时展现出不同的形态,即多态。
我们可以反过来思考一下要构成多态需要满足两个条件:一是完成虚函数的重写,二是必须使用父类的指针或者引用去调用虚函数。为什么呢?
如果使用父类对象去调用虚函数,则无法构成多态。因为当使用父类对象时,会发生切片行为,切片后父类对象只会包含父类的部分成员变量,并且会调用父类的拷贝构造函数对那部分成员变量进行拷贝构造。由于同类型的对象共享一张虚表,所以父类对象中的虚表指针指向的是父类的虚表。后续用这个父类对象调用虚函数时,都是通过虚表指针找到父类的虚表,进而调用父类的虚函数。
❌错误示范:
Person p1 = Mike; // 对象切片,丢失派生类信息 Person p2 = Johnson; // 对象切片,丢失派生类信息 p1.BuyTicket(); // 调用 Person::BuyTicket() p2.BuyTicket(); // 调用 Person::BuyTicket()
而使用父类指针或者引用时,不会发生切片行为。父类指针或引用可以指向子类对象,此时通过父类指针或引用来调用虚函数,会根据实际指向的对象的虚表来找到对应的虚函数。例如在上面的例子中,
p
是Person
引用,当它指向Mike
时调用BuyTicket()
,会调用Person::BuyTicket()
;当它指向Johnson
时调用BuyTicket()
,会调用Student::BuyTicket()
。这就实现了多态。总结来说,构成多态时,指向谁就调用谁的虚函数,跟对象的实际类型有关。而不构成多态时,对象类型是什么就调用谁的虚函数,跟编译时的类型有关。
3. 动态绑定与静态绑定
静态绑定 :静态绑定又称为前期绑定或早绑定,在程序编译期间就能确定程序的行为,例如函数重载。编译器在编译时就能确定调用哪个函数。
动态绑定 :动态绑定又称为后期绑定或晚绑定,在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也就是动态多态。
我们可以通过查看汇编代码来进一步理解静态绑定和动态绑定的区别,以下面代码为例:
#include <iostream>
using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买票 - 全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票 - 半价" << endl;}
};void Func(Person* p)
{p->BuyTicket();
}int main()
{Person mike;Func(&mike);mike.BuyTicket();return 0;
}
对于 Func(&mike)
中的 p->BuyTicket()
,其对应的汇编代码如下:
从这段汇编代码可以看出,当满足多态条件时,函数调用是在运行时确定的,需要先到对象的虚表中查找对应的虚函数地址,再进行调用。
而 mike.BuyTicket()
对应的汇编代码为:
这说明不满足多态条件时,函数调用是在编译时就确定的。
再来看一个对比例子:
#include <iostream>
using namespace std;class Person
{
public:virtual void BuyTicket(){cout << "买票 - 全价" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票 - 半价" << endl;}
};int main()
{Student Johnson;Person p = Johnson; // 不构成多态p.BuyTicket();return 0;
}
这里将 Student
对象 Johnson
赋值给 Person
对象 p
,发生了切片,不构成多态。调用 p.BuyTicket()
时,其对应的汇编代码只有两条指令,直接调用 Person::BuyTicket
函数,函数地址在编译时就已确定。
而若改为以下代码构成多态:
int main()
{Student Johnson;Person& p = Johnson; // 构成多态p.BuyTicket();return 0;
}
此时调用 p.BuyTicket()
对应的汇编代码就会像前面所说的那样,需要在运行时通过虚表查找函数地址,体现出动态绑定的特点。这也很好地体现了静态绑定是在编译时确定的,而动态绑定是在运行时确定的。
相关文章:
C++多态(上)
目录 一、多态的概念 二、多态的定义及实现 1. 多态的构成条件 2. 虚函数 3. 虚函数的重写 4. C11 override 和 final 4.1 final 关键字 4.2 override 关键字 5. 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类 1. 概…...
【AI提示词】 复利效应教育专家
提示说明 一位拥有金融学和教育学背景的知识型内容创作者,擅长用简单易懂的语言向读者解释复杂概念 提示词 # Role: 复利效应教育专家## Profile - language: 中文 - description: 一位拥有金融学和教育学背景的知识型内容创作者,擅长用简单易懂的语言…...
嵌入式系统基础知识
目录 一、冯诺依曼结构与哈佛结构 (一)冯诺依曼结构 (二)哈佛架构 二、ARM存储模式 (一)大端模式 (二)小端模式 (三)混合模式 三、CISC 与 RISC &am…...
如何克服情绪拖延症?
引言 你是否也曾有过这样的经历? 明明手头有重要的工作,却总是忍不住刷手机、看视频,直到最后一刻才匆忙赶工? 你是否在心里暗暗发誓“明天一定好好干”,但第二天依旧重复着同样的拖延? 其实࿰…...
【操作系统】哲学家进餐问题
问题描述 哲学家进餐问题是并发编程中的一个经典问题,描述了五位哲学家围坐在一张圆桌旁,他们的生活由思考和进餐组成。在圆桌上有五个盘子,每位哲学家面前一个盘子,盘子之间有一支叉子。哲学家进餐需要同时使用左右两支叉子。问题…...
Kotlin协程解析
目录 一、协程的使用 二、协程的执行原理 2.1、挂起函数的反编译代码及执行分析 2.2、协程执行流程分析 2.2.1、createCoroutineUnintercepted方法 2.2.2、intercepted方法 2.2.3、resumeCancellableWith方法 2.3、Dispatcher----分发器的实现 2.3.1、Main 分发器的实…...
Nginx核心功能 02
目录 Nginx代理技术核心概念 (一)正向代理(Forward Proxy) 1. 基本定义 2. 技术原理 3. 应用场景 (二)反向代理(Reverse Proxy) 1. 基本定义 2. 技术原理 3. 应用场景 一、…...
聊聊对Mysql的理解
目录 1、Sql介绍 1.1、SQL的分类 1.2、数据库的三大范式 1.3、数据表的约束 1.4、约束的添加与删除 2、核心特性 3、主要组件 4、数据结构原理 5、索引失效 6、常用问题 7、优势与局限 前言 MySQL是一个开源的关系型数据库管理系统(RDBMS),由瑞典MySQL A…...
「Mac畅玩AIGC与多模态17」开发篇13 - 条件判断与分支跳转工作流示例
一、概述 本篇在多节点串联的基础上,进一步引入条件判断与分支跳转机制,实现根据用户输入内容动态走不同执行路径。开发人员将学习如何配置判断节点、定义分支规则,以及如何在工作流中引导执行方向,完成基础的逻辑控制。 二、环境准备 macOS 系统Dify 平台已部署并可访问…...
pycharm terminal 窗口打不开了
参考添加链接描述powershell.exe改为cmd.exe发现有一个小正方形,最大化可以看见了。...
JAVA:使用 MapStruct 实现高效对象映射的技术指南
1、简述 在 Java 开发中,对象之间的转换是一个常见的需求,尤其是在 DTO(数据传输对象)和实体类之间的转换过程中。手动编写转换代码既耗时又容易出错,而 MapStruct 是一个优秀的对象映射框架,可以通过注解生成高效的对象转换代码,从而大大提升开发效率。 本文将介绍 M…...
Linux线程深度解析:从基础到实践
Linux线程深度解析:从基础到实践 一、线程基础概念 1. 进程与线程定义 进程:一个正在运行的程序,是操作系统资源分配的最小单位(拥有独立的地址空间、文件描述符等资源),状态包括就绪、运行、阻塞。线程…...
【ROS2】launch启动文件如何集成到ROS2(Python版本)
一、简单实操 1.创建/打开一个功能包 mkdir -p my_ws/src cd my_ws/src ros2 pkg create my_pkg_example --build-type ament_python 2.创建Launch文件的存放目录 将所有启动文件都存储在launch包内的目录中。 目录结构如下所示: src/my_pkg_example/launch/…...
用 PyTorch 轻松实现 MNIST 手写数字识别
用 PyTorch 轻松实现 MNIST 手写数字识别 引言 在深度学习领域,MNIST 数据集就像是 “Hello World” 级别的经典入门项目。它包含大量手写数字图像及对应标签,非常适合新手学习如何搭建和训练神经网络模型。本文将基于 PyTorch 框架,详细拆…...
碰撞检测学习笔记
目录 SUMO 模拟碰撞 LimSim pygame模拟碰撞检测 SUMO 模拟碰撞 LimSim 多模态大语言模型(M)LLM的出现为人工智能开辟了新的途径,特别是提供增强的理解和推理能力,为自动驾驶开辟了新途径。本文介绍LimSim,LimSim的…...
Sway初体验
Sway(缩写自 SirCmpwn’s Wayland compositor[1])是一款专为 Wayland 设计的合成器,旨在与 i3 完全兼容。根据官网所述: Sway 是 Wayland 的合成器,也是 x11 的 i3 窗口管理器的替代品。它可以根据您现有的 i3 配置工作…...
《工业社会的诞生》章节
工业革命的技术前奏 早期工业技术双引擎: 【火药武器】:重塑战争形态与经济地理 新式青铜炮助力殖民扩张,开辟全球贸易网络 高桅帆船(西班牙大帆船)实现洲际航行 战争规模化倒逼中央集权,催生国家-商人…...
消息队列MQ
参考资料:https://cloud.tencent.com/developer/article/2335397 https://www.cnblogs.com/hahaha111122222/p/18457859 消息队列是大型分布式系统不可缺少的中间件,也是高并发系统的基石中间件 消息队列 消息队列 Message Queue 消息队列是利用高效可…...
LangChain4J-XiaozhiAI 项目分析报告
LangChain4J-XiaozhiAI 项目分析报告 GitHub 链接 1. 项目概述 本项目名为 “硅谷小智(医疗版)”,是一个基于 Java 技术栈和 LangChain4J 框架构建的 AI 聊天助手应用。其核心目标是利用大型语言模型(LLM)的能力&am…...
学习spring boot-拦截器Interceptor,过滤器Filter
目录 拦截器Interceptor 过滤器Filter 关于过滤器的前置知识可以参考: 过滤器在springboot项目的应用 一,使用WebfilterServletComponentScan 注解 1 创建过滤器类实现Filter接口 2 在启动类中添加 ServletComponentScan 注解 二,创建…...
【程序+论文】大规模新能源并网下的火电机组深度调峰经济调度
目录 1 主要内容 讲解重点 2 讲解视频及代码 1 主要内容 该视频为《大规模新能源并网下的火电机组深度调峰经济调度》代码讲解内容,该程序有完全对照的论文,以改进IEEE30节点作为研究对象,系统包括5个火电机组和2个新能源机组,…...
【win11 】win11 键盘测试
我的键盘是支持mac和win的,fn tab 就能切换,有可能是用错了模式,导致 我alt a 就会弹出 win11的 wifi 等菜单控制 键盘测试网站 https://keyboard.bmcx.com/ 识别到我按下的是alt...
再识动静态库
动静态库 1 手动制作静态库2 手动调用静态库方式一:(安装到系统)方式二:(和源文件一起)方式三:(使用带路径的库) 3 动态库制作与使用方式一:拷贝到系统方式二…...
前端 uni-app 初步使用指南
在数字化浪潮下,实现应用多端适配成为开发者的刚需。uni-app 凭借 “一次编写,多端运行” 的特性,极大提升了开发效率,成为前端开发的热门选择。如果你是首次接触 uni-app,这篇文章将带你开启 uni-app 的使用之旅&…...
尼卡音乐 1.1.1 | 免费畅听全网音乐,支持无损下载,无广告无需注册登录
尼卡音乐是一款可以免费畅听全网音乐的应用程序,支持免费下载无损高品质音源,并且没有任何广告,无需注册登录。用户可以轻松搜索全网无损音质音源,并可将其他音乐APP的歌单导入,让音乐陪你开心一整天。该应用彻底拒绝臃…...
33.降速提高EMC能力
降速提高EMC能力 1. 电磁兼容问题的错误累积效应2. 降速减少累积效应的机理分析 1. 电磁兼容问题的错误累积效应 2. 降速减少累积效应的机理分析 降速之后,信号的波形更完整,容错空间更大;另外边沿变缓,对外干扰也会减小。...
【赵渝强老师】TiDB的MVCC机制
TiDB是一款开源的国产分布式关系型数据库。TiKV是TiDB的行存引擎,它支持多版本并发控制(Multi-Version Concurrency Control,MVCC)。假设有这样一种场景:某客户端A在写一个Key,另一个客户端B同时在对这个Key进行读操作。如果没有数据的多版本…...
数电填空题整理(适用期末考试)
在下列门电路中,OC门能实现“线与”逻辑功能; 三态门能用于总线结构的数 据传输;传输门 能实现模拟信号的双向传输。 并联比较型A/D转换器的转换速度最快, 双积分型A/D转换器的稳定性和抗干扰能力最好 TTL与非门多余的输入端应该…...
node核心学习
目录 1-1node概述 1-2全局对象 1-3Node的模块化细节 1-4Node中的ES模块化 1-5基本内置模块 OS模块: path模块: url模块: util模块: 1-6文件IO I/O:input output fs模块的方法 代码示例: 练习…...
基于 PyQt 的YOLO目标检测可视化界面+ nuitka 打包
在人工智能和计算机视觉领域,YOLO(You Only Look Once)是一种广泛使用的实时目标检测算法。为了直观地展示YOLO算法的检测效果,我们使用Pyqt框架进行检测结果的可视化,同时为了使其能够脱离Python环境,我们…...
234树和红黑树
首先,把目光聚集在234树中 以下是234的三种节点(可以有更多这里使用以下的三个): 右侧是节点转换成红黑树节点的样子。 接下来会用以下序列进行1234树的搭建和红黑树的搭建: 首先是234树 2-3-4树(234树&…...
GenCLS++:通过联合优化SFT和RL,提升生成式大模型的分类效果
摘要:作为机器学习中的一个基础任务,文本分类在许多领域都发挥着至关重要的作用。随着大型语言模型(LLMs)的快速扩展,特别是通过强化学习(RL)的推动,对于更强大的分类器的需求也在不…...
maven坐标导入jar包时剔除不需要的内容
maven坐标导入jar包时剔除不需要的内容 问题描述解决方案 问题描述 maven坐标导入jar包时剔除不需要的内容 解决方案 Spring Boot 默认使用 Logback,需在 pom.xml 中排除其依赖: <dependency><groupId>org.springframework.boot</gro…...
Oracle OCP认证考试考点详解083系列06
题记: 本系列主要讲解Oracle OCP认证考试考点(题目),适用于19C/21C,跟着学OCP考试必过。 26. 第26题: 题目 解析及答案: 关于块介质恢复,以下哪三项是正确的? A) 需恢复一个或多个…...
llfc项目分布式服务笔记
一、系统整体架构流程图(简明版) 复制代码 +---------------+ +------------------+ +----------------+ | 客户端 (Client) |--------->| GateServer |----------| StatusServer |<--+ +---------------+ +--------------…...
“链式前向星”等三种存图方式分别输出“无向无权图”的“DFS序列”
【DFS序列】 DFS序列(深度优先搜索序列),是树或图结构在深度优先遍历过程中生成的节点访问顺序记录。 下面三段代码,分别采用链式前向星、邻接表、邻接矩阵存图,输出图的“DFS序列”。 【DFS:链式前向星】…...
Lesson 16 A polite request
Lesson 16 A polite request 词汇 park n. 公园,停车场,庄园 v. 停车,泊车 例句:让我来停车。 Let me park. 相关:spot n. 车位 区别:garden n. 花园 [小,私家的] 例句:我们…...
【IP101】边缘检测技术全解析:从Sobel到Canny的进阶之路
🌟 边缘检测的艺术 🎨 在图像处理的世界里,边缘检测就像是给图像画眉毛 —— 没有它,你的图像就像一只没有轮廓的熊猫🐼。让我们一起来探索这个神奇的"美妆"技术! 📚 目录 基础概念 …...
Nx 智能分发机制(Nx Agents + Nx Cloud)
Nx 智能分发机制(Nx Agents Nx Cloud) 阶段关键做的事作用1. 收集信息- Project Graph:解析整个 workspace 依赖关系(谁依赖谁)- 历史统计:每次 CI 结束后将每个任务的实际用时与缓存命中情况上传…...
《“昊龙一号”:开启中国航天货运新时代》
中国航天新力量:昊龙一号登场 在 2024 年 10 月 29 日上午,神舟十九号载人飞行任务新闻发布会如一颗重磅炸弹,在航天领域激起千层浪。发布会上,一系列关乎中国载人航天工程未来走向的重要信息被披露,其中,“昊龙一号” 货运航天飞机入围空间站低成本货物运输系统总体方案…...
C++ 多态:原理、实现与应用
目录 引言 一、多态的概念 二、多态的定义及实现 (一)构成条件 (二)虚函数的深入理解 (三)虚函数的重写(覆盖) 三、抽象类 (一)概念 (二&…...
多模态大语言模型arxiv论文略读(五十八)
How Does the Textual Information Affect the Retrieval of Multimodal In-Context Learning? ➡️ 论文标题:How Does the Textual Information Affect the Retrieval of Multimodal In-Context Learning? ➡️ 论文作者:Yang Luo, Zangwei Zheng, …...
TS 枚举类型
枚举 参数为枚举成员中的一个 数字枚举 字符串枚举 枚举特点 、 缺点:转为JS代码时会编译成JS代码,增大开销...
Python容器与循环:数据处理的双剑合璧
Python作为一门简洁强大的编程语言,其容器类型和循环结构的完美结合为数据处理提供了极大的便利。本文将带领初学者深入理解Python中的四大容器(列表、元组、字典、集合)以及它们与循环结构的配合使用,助你掌握数据处理的核心技能…...
ST-LINKV2仿真器下载
ST-LINKV2仿真器 — 正点原子资料下载中心 1.0.0 文档...
RAGFlow 接入企业微信应用实现原理剖析与最佳实践
背景 近期有医美行业客户咨询我们智能客服产品,期望将自己企业的产品、服务以及报价信息以企微应用的方式给到客户进行体验互动,提升企业运营效率。关于企业微信对接,我们分享下最佳实践,抛砖引玉。效果图如下: 这里也…...
大模型实践:图文解锁Ollama在个人笔记本上部署llm
使用在线模型服务时,我们常常需要支付API调用费用,这对于个人开发者或小型组织来说可能是一笔不小的开支。那么,有没有方法可以在本地免费使用这些强大的模型呢?答案是肯定的——Ollama就是这样一个工具。 当然如果是比较大的组织…...
如何提高情商?(优化版)
引言 提高情商(EQ)是一个需要长期练习和自我反思的过程,核心在于理解自己、管理情绪、共情他人并有效沟通。以下是一些具体且可操作的方法,结合理论和实际场景,帮助你逐步提升: 一、核心方法:…...
学习黑客Linux权限
在 Linux 的王国里,“权限”就是装备与技能加成:决定谁能拔剑(读 r)、挥剑(写 w)、进入房间(执行 x)。本文用“闯关升级”视角,把常见 rwx、八进制数字、SUID/SGID/Stick…...
信息系统监理师第二版教材模拟题第二组(含解析)
信息系统监理师模拟题第二组(30题) 监理理论与法规 根据《信息系统工程监理暂行规定》,监理单位应当独立于( ) A. 建设单位和承建单位 B. 政府监管部门 C. 行业组织 D. 最终用户答案:A 解析:监理单位应当保持独立性,不得与建设单位和承建单位有隶属关系或其他利害关系…...