多态常见面试问题
1、什么是多态?
多态(Polymorphism)是面向对象编程中的一个重要概念,它允许同一个接口表现出不同的行为。在C++中,多态性主要通过虚函数来实现,分为编译时多态(静态多态)和运行时多态(动态多态)。
多态的类型:
编译时多态(静态多态):在编译阶段就能确定调用哪个函数,通常通过函数重载、运算符重载和模板来实现。特点:在编译期决定函数调用,效率高,但灵活性相对较弱。
//示例函数重载
class Printer {
public:void print(int i) { cout << "Printing int: " << i << endl; }void print(double d) { cout << "Printing double: " << d << endl; }
};int main() {Printer p;p.print(10); // 调用 print(int i)p.print(3.14); // 调用 print(double d)
}
运行时多态(动态多态):在运行时根据对象的类型决定调用哪个函数。通过虚函数和继承实现。
特点:在运行时通过基类指针或引用指向派生类对象,从而动态地决定函数调用,灵活性高,但效率比静态多态低一些。
/*虚函数实现动态多态 在这个例子中,基类 Animal 的指针根据对象的具体类型,动态决定调用 Dog 的 makeSound 或 Cat 的 makeSound,这就是运行时多态。*/
class Animal {
public:virtual void makeSound() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:void makeSound() override { cout << "Woof!" << endl; }
};class Cat : public Animal {
public:void makeSound() override { cout << "Meow!" << endl; }
};int main() {Animal* animal1 = new Dog();Animal* animal2 = new Cat();animal1->makeSound(); // 输出 "Woof!",调用 Dog 的 makeSound()animal2->makeSound(); // 输出 "Meow!",调用 Cat 的 makeSound()delete animal1;delete animal2;
}
多态的实现原理:
运行时多态依赖于虚函数表(vtable)。当类中定义虚函数时,编译器会为该类创建一个虚函数表,表中存储指向类中虚函数的地址。每个对象包含一个指向该虚表的指针(vptr)。在运行时,通过vptr查找实际调用的函数地址,从而实现动态多态。
多态的作用:
代码灵活:多态允许你通过基类指针或引用操作派生类对象,而不必关心派生类的具体类型,提供了更高的灵活性。
可扩展性:新功能可以通过继承并重写虚函数来实现,而无需修改现有代码,方便系统的扩展。
代码复用:基类可以提供通用的接口和功能,而具体的实现则由派生类完成,减少代码重复。
总结:
多态是对象在不同上下文中表现出不同行为的能力。
静态多态是在编译时决定的函数调用,而动态多态是在运行时根据对象类型动态决定的函数调用。
动态多态通过虚函数、继承和虚函数表实现,极大地提高了代码的灵活性与可扩展性。
2、什么是重载、重写(覆盖)、重定义(隐藏)?
总结:
重载(Overloading):同一作用域中,函数名相同,但参数不同。
重写(Overriding):派生类中重新实现与基类虚函数相同的函数,用于多态。
重定义(Hiding):派生类中定义了与基类同名但参数不同的函数,隐藏基类的同名函数。
- 重载(Overloading)
重载是指在同一个类中,多个函数名称相同,但它们的参数列表不同(参数类型、数量或顺序不同),编译器根据调用时传递的参数类型和数量来决定调用哪个函数。
特点:
发生在同一个作用域。
参数列表必须不同,返回类型可以相同也可以不同。
可以重载普通函数、构造函数和运算符。
class Math {
public:int add(int a, int b) { return a + b; }double add(double a, double b) { return a + b; }int add(int a, int b, int c) { return a + b + c; }
};int main() {Math math;cout << math.add(2, 3) << endl; // 调用 add(int, int)cout << math.add(2.5, 3.5) << endl; // 调用 add(double, double)cout << math.add(1, 2, 3) << endl; // 调用 add(int, int, int)
}
- 重写(Overriding,覆盖)
重写是指在派生类中,重新定义与基类中的虚函数相同的函数,即函数名、参数列表、返回类型必须完全相同。重写主要用于实现运行时多态。重写的函数必须是虚函数,通过基类指针或引用调用时,动态决定调用派生类的实现。
特点:
发生在继承关系中。
函数签名(函数名、参数列表和返回类型)必须与基类中的虚函数完全相同。
重写的函数必须是虚函数,且派生类中的函数也默认是虚函数(使用override关键字可以明确表明函数是重写的)。
class Animal {
public:virtual void sound() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:void sound() override { cout << "Woof!" << endl; } // 重写基类的虚函数
};int main() {Animal* animal = new Dog();animal->sound(); // 输出 "Woof!",调用派生类 Dog 的 sound 函数delete animal;
}
- 重定义(Hiding,隐藏)
重定义是指在派生类中,定义了与基类同名但参数列表不同的函数。由于在派生类中定义了同名函数,基类中的同名函数会被隐藏,调用时只能使用派生类的函数,而基类的函数即便参数列表不同也无法通过派生类对象访问。
特点:
发生在继承关系中。
派生类的函数参数列表可以与基类不同,基类中的同名函数被隐藏。
如果基类的函数想继续保留,可以通过using声明重新引入。
class Base {
public:void display(int a) { cout << "Base class display: " << a << endl; }
};class Derived : public Base {
public:void display(double a) { cout << "Derived class display: " << a << endl; }
};int main() {Derived obj;obj.display(5.5); // 调用 Derived::display(double)// obj.display(5); // 编译错误,Base::display(int) 被隐藏
}
3、多态的实现原理?
见1题。
4、inline函数可以是虚函数吗?
可以、但是inline只是一个建议。当一个函数是虚函数以后,多态调用中,inline失效了。
**可以,inline只是一个建议。**当一个函数是虚函数时,编译器可能依然会将它内联,但前提是编译器可以确定具体的函数调用对象。通常情况下,如果通过具体对象调用虚函数(即编译器能够知道对象的静态类型),虚函数仍可能被内联。
然而,在多态调用(即通过基类指针或引用调用虚函数)中,由于编译器需要在运行时通过虚表动态决定调用哪个版本的函数,inline优化就无法生效。因为内联要求编译器在编译时知道要调用的具体函数,而多态性导致这一点无法确定,因此在这种情况下内联失效了。
5、static函数(静态成员)可以是虚函数吗?
不能,因为静态成员函数没有this指针,使用 类型::成员 函数的调用方式(类域指定的方式,如 Person::Func2())无法访问虚函数表,所以静态成员函数无法放进虚函数表。虚函数是为了实现多态,多态都是运行时去虚表找决议。static成员函数都是在编译时决议,他是virtual没有价值。
静态成员函数不能是虚函数的原因:
虚函数依赖对象实现:虚函数的多态性需要在运行时根据对象的实际类型来决定调用哪个函数,而静态成员函数不属于任何具体对象,因此无法通过虚表进行动态绑定。
没有 this 指针:虚函数通常需要 this 指针来访问对象的成员,但静态成员函数没有 this 指针,所以无法实现虚函数的特性。
结论:
静态成员函数不能是虚函数,因为虚函数依赖对象实现多态性,而静态成员函数与对象无关,不支持动态绑定。因此,static 和 virtual 是相互冲突的,无法在同一个函数上同时使用。
6、构造函数可以是多态吗?
不可以,virtual函数是为了实现多态,运行时去虚表找对应虚函数进行调用,对象中虚表指针都是构造函数初始化列表阶段才初始化的。
构造函数虚函数是没有意义的。
构造函数不能是多态的。构造函数在 C++ 中无法实现多态,主要原因如下:
构造函数不参与虚函数机制:
虚函数的多态性依赖于对象的类型在运行时动态绑定,而构造函数是在对象创建时调用的。在构造对象的过程中,虚表还没有被初始化或设置,因此无法实现多态行为。
构造函数的目的是初始化对象:构造函数的主要任务是初始化对象的成员变量和资源,它负责生成对象本身。多态依赖于已有的对象实例,但构造函数在创建对象的过程中,无法确定派生类的行为。
虚表初始化顺序:虚表(VTable)是在构造函数执行完毕后,派生类的构造函数才能设置。因此,在调用基类构造函数时,多态机制尚未建立,无法进行动态绑定。
总结:构造函数不能是多态的,因为多态依赖虚表和动态绑定,而虚表在构造函数调用期间尚未建立或完成初始化。
7、析构函数可以是虚函数吗?
可以。而且析构函数建议虚函数
8、拷贝构造和operator= 可以是虚函数吗?
拷贝构造不可以。拷贝构造也是构造函数,和构造函数一样,没有this指针,无法设置虚函数。
operator赋值 语法上可以(质疑),但是没有实际价值 。
8、对象访问普通函数快还是虚函数更快?
如果虚函数不构成多态(通过具体对象调用),编译器可以进行静态绑定或内联优化,调用开销与普通函数几乎一致。如果构成多态,普通函数更快。
如果虚函数构成多态(通过基类指针或引用调用),则需要进行虚表查找,存在动态绑定的开销。
普通函数调用:普通函数是静态绑定的,在编译时就已经确定调用哪个函数。调用过程是直接的,编译器会在生成代码时直接插入该函数的地址。调用速度更快,因为不需要额外的查找过程。
虚函数调用:虚函数是动态绑定的,依赖于对象的动态类型。在运行时通过虚表(VTable)来查找并调用正确的函数实现。虚函数调用涉及额外的步骤:首先,通过对象的虚表指针找到虚表,然后根据虚表中的函数指针找到具体的函数实现。这一过程增加了运行时开销。调用速度相对较慢,因为多了虚表查找的步骤。
总结:普通函数调用更快,因为它是静态绑定,编译时直接确定,不涉及任何额外查找。虚函数调用稍慢,因为需要通过虚表查找函数指针,存在运行时开销。
9、虚函数表是在什么阶段生成的,存在那的?
虚表在编译阶段生成,用于存储虚函数的指针。
虚表指针(vptr)在运行时存储于每个对象的内存中,用于动态绑定。
虚表本身是存储在全局内存区域,且每个类只有一个虚表。虚表(VTable)本身是存储在静态区域,通常是全局内存区域。虚函数表是编译器生成的全局结构,每个类有一个虚表,与对象无关,因此它不会随每个对象重复存储。
区域划分说明:
常量区:程序中不可修改的常量数据,如字符串字面量和 const 常量。
全局数据区(静态区):存储全局变量、静态变量和类的虚表。虚表属于这个区域。
栈区:用于存储局部变量、函数参数等。
堆区:用于动态分配的内存,如使用 new 分配的对象。
总结:虚表存储在静态区域(全局数据区),它在程序生命周期内存在。
虚表的内容(函数指针)会动态指向不同的函数实现,因此虚表不是存储在常量区,而是在静态内存区域的部分。
10、C++的菱形继承问题是什么?虚继承的原理是什么?
总结:
菱形继承问题:当一个类通过多个路径继承同一个基类时,会导致基类的多次拷贝,产生访问歧义,使用虚继承可以解决这个问题。
虚函数的原理:通过虚表和虚表指针实现动态多态,允许程序在运行时根据实际对象类型调用合适的函数版本。
菱形继承是指一个类通过多个继承路径从同一个基类继承,这种结构会导致一些问题,特别是关于基类成员的多次拷贝和访问歧义。
问题1:基类成员的多次拷贝
由于 B 和 C 都继承了 A,而 D 又从 B 和 C 继承,因此 D 类中会有两份 A 类的副本。这导致基类的成员函数和成员变量在 D 中存在两份。如果调用 D 对象的 func() 函数,编译器不知道该调用 B 中的 A::func() 还是 C 中的 A::func(),这会导致访问歧义。
解决方案:虚继承
为了解决这个问题,可以使用虚继承,即通过 virtual 关键字来指定基类 A 只保留一份副本。虚继承确保无论 A 被继承多少次,最终在派生类 D 中只会存在 一个 A 类的实例。使用虚继承后,D 类中只有一个 A 的实例,B 和 C 共享这个实例,从而避免了多次拷贝和访问歧义。
虚继承的原理:
虚继承的原理是为了避免在多重继承(尤其是菱形继承)时,基类的多次拷贝问题。它通过引入虚基表(VBTable)和虚基表指针(vbptr)来确保派生类中只有一个共享的基类实例。这种机制可以解决多路径继承时的重复继承问题,保证数据一致性。
虚继承通过让派生类共享一个共同的基类实例来解决这一问题。实现虚继承时,编译器会生成额外的数据结构和指针来确保基类在派生类中只有一个实例。
虚继承的实现步骤:
虚基表(VBTable):当一个类使用虚继承时,编译器会为其生成一个虚基表(VBTable)。这个表中存储了指向虚基类的地址偏移量,确保派生类在访问虚基类时能够找到唯一的基类实例。
虚基表指针(vbptr):每个虚继承的类中,都会有一个隐藏的虚基表指针(vbptr),指向虚基表。这相当于一个中间指针,用于帮助派生类找到基类的唯一实例。
偏移量访问虚基类:当派生类访问虚基类的成员时,编译器通过 vbptr 和 VBTable 确定虚基类在内存中的位置,确保派生类总是引用同一个基类实例。
虚函数的原理:
虚函数(virtual function)是用于实现动态多态的机制,允许程序根据运行时的对象类型(而非编译时的类型)调用适当的函数版本。虚函数的核心机制依赖于虚函数表(VTable)和虚表指针(vptr)。
虚函数的原理:
虚表(VTable):每个定义了虚函数的类都有一个虚表(VTable),虚表中存储该类的所有虚函数的函数指针。如果子类重写了基类的虚函数,子类的虚表中会包含指向子类实现的函数指针,而不是基类的版本。
虚表指针(vptr):每个对象都包含一个隐藏的指针,称为虚表指针(vptr),它指向该对象所属类的虚表。当对象创建时,构造函数负责初始化 vptr,指向对应类的虚表。
运行时多态:当通过基类指针或引用调用虚函数时,程序会通过对象的 vptr 查找虚表,然后从虚表中获取对应的函数指针并调用。这种机制允许在运行时根据实际的对象类型调用正确的函数版本,即实现动态多态。
虚函数的调用过程:
编译时:编译器无法确定虚函数的具体调用对象。
运行时:程序通过对象的 vptr 找到虚表,根据虚表中的函数指针调用具体的函数。
虚函数表与虚基表的区别

总结:
虚函数表用于动态多态的虚函数调用,通过基类指针调用派生类的函数。
虚基表用于虚继承中处理菱形继承问题,确保基类不会被多次拷贝,派生类只会继承一个基类实例。
11、什么是抽象类?抽象类的作用?
抽象类是包含至少一个纯虚函数的类,不能直接实例化对象,必须通过派生类重写其纯虚函数后才能实例化。抽象类通常作为接口使用,规定派生类必须实现某些功能。
抽象类的特点:
纯虚函数:抽象类中至少有一个纯虚函数,形式为 virtual 函数名() = 0;。
不能实例化:抽象类不能创建对象,必须通过派生类实现纯虚函数后才能实例化派生类对象。
派生类的实现:派生类继承抽象类时,必须实现所有的纯虚函数,否则该派生类也将成为抽象类。
抽象类的作用:
接口设计:抽象类可以定义一组规范,派生类必须按照这些规范去实现。这种方式提供了一种设计接口的机制。
多态性:抽象类常用于多态的实现,通过基类指针或引用调用派生类的具体实现,而不关心派生类的具体类型。
代码复用:抽象类可以为派生类提供公共接口,减少代码重复,让多个派生类共享基础的功能。
抽象类的作用总结:
通过定义接口,规范派生类的行为。
提供多态机制,使代码更加灵活和可扩展。
提供公共的功能和接口,减少代码冗余。
相关文章:
多态常见面试问题
1、什么是多态? 多态(Polymorphism)是面向对象编程中的一个重要概念,它允许同一个接口表现出不同的行为。在C中,多态性主要通过虚函数来实现,分为编译时多态(静态多态)和运行时多态…...
案例-登录认证(上)
案例-登录认证 在前面的课程中,我们已经实现了部门管理、员工管理的基本功能,但是大家会发现,我们并没有登 录,就直接访问到了Tlias智能学习辅助系统的后台。 这是不安全的,所以我们今天的主题就是登录 认证。 最终我…...
对BSV区块链下一代节点Teranode的答疑解惑(上篇)
发表时间:2024年8月7日 2024年初BSV区块链研发团队揭晓了即将到来的Teranode更新的突破性特性,这些特性将显著提升网络的效率和处理速度,使BSV区块链能够达到百万级TPS。 Teranode的项目主管Siggi Oskarsson强调:“当你阅读这…...
vue父子组件传参的方法
在Vue.js中,父子组件之间的参数传递是常见的需求。Vue提供了几种方法来实现这一点,主要包括使用props传递数据给子组件,以及使用事件(如自定义事件)从子组件向父组件发送数据。以下是详细的说明: 父组件向…...
关于this指针
在普通成员函数里 1.this指针不能显式说明,但能显示使用,是个常指针,只能改变指针指向的对象的内容,不能改变指针存储的对象的地址。 2.this指针一般不用特别写上,只有在(我目前的知识范围内)类…...
机器学习西瓜书
绪论 1.1绪论1.2课程定位 科学:是什么,为什么; 技术:怎么做; 工程:做的多快好省; 应用: 1.3机器学习 经典定义:利用经验改善系统自身的性能 1.4典型的机器学习过程 1.5计算学习理论 机器学习有坚实的理论基础,由Leslie Valiant的计算学习理论现在有一个数据样本x,现在…...
如何使用 Puppeteer 和 Browserless 运行自动化测试?
Puppeteer:什么是 Puppeteer 及其功能 Puppeteer 是一个 Node.js 库。使用 Puppeteer,您可以在所有基于 Chromium 的浏览器上测试您的网站,包括 Chrome、Microsoft Edge Chrome 和 Chromium。此外,Puppeteer 可用于网页抓取、自动…...
python菜鸟知识
去除空格 str 这是 含 空格 print(f去除两端空格{str.strip()}) print(f去除左端空格{str.lstrip()}) print(f去除右端空格{str.rstrip()}) print(f去除全部空格{str.replace(" ", "")}) 方法返回对象yield yield :.join([ip, port])yield {ranking…...
GPT4o,GPTo1-preview, 拼
兄弟们GPT刚开的 需要上车的扣,工作用 大家一起PIN分摊点压力。 在当今数字化的时代,程序员这一职业已经从幕后走到了前台,成为推动科技进步和社会变革的关键力量。编写代码、解决问题、不断学习新技术,程序员们的日常充满了挑战与…...
论文笔记:Pre-training to Match for Unified Low-shot Relation Extraction
论文来源:ACL 2022 论文地址:https://aclanthology.org/2022.acl-long.397.pdf 论文代码:https://github.com/fc-liu/MCMN (笔记不易,请勿恶意转载抄袭!!!) 目录 A…...
一篇文章带你快速了解linux中关于信号的核心内容
1. 信号概念 信号是操作系统用来通知进程某个特定事件已经发生的一种方式。它们是一种软件中断,可以被发送到进程以对其进行异步通知。 2. 信号处理的三种方式 执行默认动作执行自定义动作忽略 signal() 函数:将信号处理设置为 SIG_IGN,可…...
openEuler、Linux操作系统常见操作-(6)如何登录Linux
如何登录Linux Linux登陆方式主要有如下两种: 。本地登陆 。一个典型的Linux系统将运行六个虚拟控制台和一个图形控制台,openEuler目前暂未支持图形化界面; 可以通过CtrlAltF[1-6]在6个虚拟控制台之间进行切换。 远程登录 。默认情况下openEuler支持远程登录&…...
Python基础语法条件
注释 注释的作用 通过用自己熟悉的语言,在程序中对某些代码进行标注说明,这就是注释的作用,能够大大增强程序的可读性。 注释的分类及语法 注释分为两类:单行注释 和 多行注释。 单行注释 只能注释一行内容,语法如下…...
006-MAVEN 的使用
MAVEN 的使用 一、依赖范围二、依赖的传递性三、依赖的原则四、依赖的排除 一、依赖范围 在引入log4j 依赖的时候,有一个scope设置,这个scope设置的值就是对应的依赖范围(因为compile 是默认的依赖范围,所以有时也可以省略)。 Maven 提供了…...
npm使用时报错:Could not retrieve https://npm.taobao.org/mirrors/node/index.json.
在使用npm时报错,报错信息如下: 报错的原因:是原来的淘宝镜像地址过期了 解决办法:修改镜像地址。打开nvm的安装地址 -->settings.txt文件 -->配置下载源 1、将settings.txt文件中的 node_mirror: https://npm.taobao.or…...
软考中级网络工程师——高级配置
文章目录 IS-ISBGP(边境网关协议)-IBGP-EBGP配置BFD(双向转发侦测)与Router-Static联动BFD与OSPF联动BFD与VRRP(虚拟路由器冗余协议)联动VRRP配置(基于网关备份)FW基础配置FW高级配置DHCP路由策略 IS-IS 第一步:每一个路由设置环回口地址 第二部:配置接…...
Leetcode 第 141 场双周赛题解
Leetcode 第 141 场双周赛题解 Leetcode 第 141 场双周赛题解题目1:3314. 构造最小位运算数组 I思路代码复杂度分析 题目2:3315. 构造最小位运算数组 II思路代码复杂度分析 题目3:3316. 从原字符串里进行删除操作的最多次数思路代码复杂度分析…...
Linux性能调优,还可以从这些方面入手
linux是目前最常用的操作系统,下面是一些常见的 Linux 系统调优技巧,在进行系统调优时,需要根据具体的系统负载和应用需求进行调整,并进行充分的测试和监控,以确保系统的稳定性和性能。同时,调优过程中要谨…...
STM32的独立看门狗定时器(IWDG)技术介绍
在嵌入式系统中,确保系统的稳定性和可靠性至关重要。看门狗定时器(Watchdog Timer, WDT) 是一种常用的硬件机制,用于监控系统的运行状态,防止系统因软件故障或意外情况进入不可预期的状态。STM32系列微控制器提供了两种…...
自动化生成工作流?英伟达提出ComfyGen:通过LLM来匹配给定的文本提示与合适的工作流程
ComfyGen的核心在于通过LLM来匹配给定的文本提示与合适的工作流程。该方法从500个来自用户的多样化提示生成图像,随后使用一系列美学预测模型对生成结果进行评分。这些评分与相应的工作流程形成了一个训练集,包含提示、工作流程及其得分的三元组。 然后…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
