当前位置: 首页 > news >正文

【C++】继承与模板

继承

1.继承的概念

概念:继承(inheritace)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称之为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承是类设计层次的复用

//	   派生类  继承方式 基类
class Student:public Person{   
public:int _name;int _sex;
}
  • 代码复用:通过继承,派生类可以复用基类的代码,减少重复编写相同功能的需要。
  • 扩展性:派生类可以在基类的基础上添加新的成员或方法,扩展功能。
  • 层次结构:继承建立了类之间的“is-a”关系,形成了一个类层次结构,有助于组织和理解代码。

2.继承方式和访问限定符

特征public继承protected继承private继承
基类的public成员变成派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员变成派生类的protected成员派生类的protected成员派生类的private成员
基类的private成员变成只能通过基类接口访问,派生类中不可见只能通过基类接口访问,派生类中不可见只能通过基类接口访问,派生类中不可见
能否隐式向上转换是(但只能在派生类中)

注意:

  • 基类的private成员派生类不可见(无法直接允许访问),但可以使用基类publicprotected成员函数间接访问。
  • 使用关键字class时默认继承方式是private;使用struct时默认继承方式是public最好显示的写出继承方式,以提高代码的可读性和可维护性。
  • public > protected > private,继承方式权限只能缩小不能放大,如基类的public成员的遇到protected继承方式就成了派生类的protected成员。
  • 访问限定符在基类中没有体现,在派生类中才产生区别,这也就是访问限定符产生的原因。
struct BaseStruct {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class DerivedClass : BaseStruct { // 默认 private 继承
public:void accessMembers() {publicMember = 1;      // private 继承,基类的 public 成员变为 privateprotectedMember = 2;   // private 继承,基类的 protected 成员变为 private// privateMember = 3;  // 无法访问}
};class DerivedPublic : public BaseStruct { // public 继承
public:void accessMembers() {publicMember = 1;      // 保持 publicprotectedMember = 2;   // 保持 protected// privateMember = 3;  // 无法访问}
};

3.基类和派生类对象赋值转换

3.1 赋值兼容规则:

  • 子类对象可以赋值给父类对象、指针或引用(称为”切割“或”切片“)。在这种赋值过程中,派生类特有的成员将被忽略,只保留基类部分。
  • 基类对象不能直接赋值给派生对象,因为基类对象不包含派生类新增的成员。
class Person{
public:string _name;int _sex;int _age;
};
class Student : public Person{
public:int _No;
};
int main(){Student s;s.name = "Alice";s.age = 20;s._No = 12345;Person p;p = s; // 切割,只复制基类部分std::cout << p.name << ", " << p.age << std::endl; // 输出:Alice, 20Person* ptr = &s; // 多态,ptr 指向 Student 对象Person& ref = s;  // 引用,ref 引用 Student 对象  return 0;
}

image-20240715143458274

3.2 指针和引用的转换:

  • 指针转换

    • 向上转型(Upcasting):将派生类指针转换为基类指针,是隐式且安全的。

    • 向下转型(Downcasting):将基类指针转换为派生类指针,需要使用 dynamic_cast 进行类型检查,确保转换的安全性。

  • 引用转换

  • 类似于指针转换,向上转型是隐式的,而向下转型需要显式的类型转换。

int main() {Student s;s.name = "Bob";s.age = 22;s.studentID = 67890;Person* basePtr = &s; // 向上转型,隐式转换basePtr->introduce(); // 调用基类方法// 向下转型,需要使用 dynamic_castStudent* derivedPtr = dynamic_cast<Student*>(basePtr);if (derivedPtr) {derivedPtr->study(); // 调用派生类方法}return 0;
}

4.继承作用域与成员隐藏

  • 作用域独立:继承中的基类和派生类都有各自独立的作用域,成员隐藏仅在派生类作用域内有效。
  • 成员隐藏只要成员名称相同,无论类型或参数列表,派生类成员都会重定义(隐藏)基类的成员。

解决函数的隐藏与重载

class A {
public:virtual void fun() {cout << "A::fun()" << endl;}
};class B : public A {
public:void fun(int i) { // 重载,不隐藏基类的 fun()cout << "B::fun(int): " << i << endl;}void callBaseFun() {A::fun(); // 显式调用基类的 fun()}void fun() override { // 重定义cout << "B::fun()" << endl;}
};int main() {B b;b.fun();          // 调用 B::fun()b.fun(10);        // 调用 B::fun(int)b.callBaseFun();  // 调用 A::fun()return 0;
}
  • B::fun(int i):重载了 A::fun(),但不隐藏基类的 fun(),在同一作用域内(派生类B中)。
  • B::fun():重定义了 A::fun(),提供了新的实现。

也可以通过using A::fun引入基类的fun(),从而调用A类的fun方法。

重载、重写和重定义的区别

特性重载(Overloading)重写(Overriding)重定义(Hiding)
作用域同一类内派生类和基类之间派生类和基类之间
是否需要继承
关键字virtualoverride
参数列表必须不同必须相同可以不同
返回类型可以不同必须相同(或协变)可以不同
调用时间编译时决定运行时决定编译时决定
用途提供同名函数的不同版本实现多态性派生类中隐藏基类同名函数

5.派生类的默认成员函数

当创建派生类时,编译器会自动为其生成一些默认的成员函数,包括构造函数、拷贝构造函数、赋值运算符和析构函数。这些默认成员函数在大多数情况下是足够的,但在特定需求下,程序员可以显式地定义或删除它们。

默认成员函数的行为

  1. 构造函数
    • 默认构造函数:派生类的构造函数必须调用基类的构造函数以初始化基类部分。如果基类没有默认构造函数,派生类构造函数必须在初始化列表中显式调用基类的其他构造函数。
    • 拷贝构造函数:自动调用基类的拷贝构造函数,完成基类部分的拷贝初始化。
  2. 赋值运算符(operator=
    • 自动调用基类的赋值运算符,完成基类部分的赋值。
  3. 析构函数
    • 派生类的析构函数在执行完自己的清理工作后,自动调用基类的析构函数,确保基类部分的资源得到正确释放。
    • 虚析构函数:如果基类的析构函数是虚函数(virtual),可以确保通过基类指针删除派生类对象时,派生类的析构函数被正确调用,避免资源泄漏。

image-20241027160711419

QQ_1721094131676

#include <iostream>
using namespace std;class Base {
public:Base() { cout << "Base Constructor" << endl; }Base(const Base&) { cout << "Base Copy Constructor" << endl; }Base& operator=(const Base&) { cout << "Base Assignment Operator" << endl; return *this; }virtual ~Base() { cout << "Base Destructor" << endl; }
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }Derived(const Derived& d) : Base(d) { cout << "Derived Copy Constructor" << endl; }Derived& operator=(const Derived& d) { Base::operator=(d); cout << "Derived Assignment Operator" << endl; return *this; }~Derived() { cout << "Derived Destructor" << endl; }
};int main() {Derived d1;Derived d2 = d1; // 拷贝构造d2 = d1;         // 赋值运算符return 0;
}/*
输出:
Base Constructor
Derived Constructor
Base Copy Constructor
Derived Copy Constructor
Base Assignment Operator
Derived Assignment Operator
Derived Destructor
Base Destructor
Derived Destructor
Base Destructor
*/

6.友元与静态成员

  • 友元关系不能继承:基类的友元在派生类中不是友元,派生类需要重新声明友元关系。
class Base {
private:int secret;friend class FriendClass;
};class Derived : public Base {// FriendClass 不是 Derived 的友元
};
  • 静态成员:基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,所有派生类共享这一个静态成员。
class Base {
public:static int staticValue;
};int Base::staticValue = 0;class Derived1 : public Base {};
class Derived2 : public Base {};int main() {Derived1::staticValue = 5;cout << Derived2::staticValue << endl; // 输出:5return 0;
}

7.多继承

优点

  • 功能整合:允许派生类同时拥有多个基类的功能,适用于需要结合多种特性的复杂类。
  • 灵活性:提供了更大的设计灵活性,适用于多维度的类层次结构。

缺点

  • 复杂性增加:类层次结构更加复杂,增加了理解和维护的难度。
  • 名称冲突:多个基类中可能存在同名成员,导致名称冲突和二义性。
  • 菱形继承问题:多个基类继承自同一个祖先类,导致数据冗余和二义性。
7.1 单继承与多继承
  • 单继承:一个子类只有一个直接父类时这个继承关系为单继承。
class Person
class Student:public Person
class PostGraduate:public Student
  • 多继承:一个子类有两个或以上的直接父类时成为多继承。
class Teacher
class Student
class Assistant:public Student,public Teacher
7.2 菱形继承

菱形继承:菱形继承是多集成的一种特殊情况。

class Person
class Teacher:public Person
class Student:public Person
class Assistant:public Student,public Teacher

菱形继承的问题

  • 数据冗余:派生类中存在多个基类的副本,导致数据冗余。例如,Assistant 类中会有两份 Person 的成员。
  • 二义性:当访问祖先类的成员时,编译器无法确定访问哪一个基类的成员,导致二义性错误。

img

class Person {
public:string _name; 	// 姓名
};
class Student : public Person {
protected:int _num; 	//学号
};
class Teacher : public Person {int _id;	//职工号   
};
class Assistant : public Student, public Teacher {
protected:string _majorCourse;	// 主修课程
};
void Test() {// 这样会有二义性无法明确知道访问的是哪一个Assistant a;// 编译错误:二义性,无法确定是 Student::Person::name 还是 Teacher::Person::name//a._name = "peter";// 需要显示指定访问那个父类的成员可以解决二义性问题// 但是数据冗余问题无法解决a.Student::_name = "XXX";a.Teacher::_name = "YYY";
}

8.虚拟继承

为了解决菱形继承带来的数据冗余和二义性问题,C++ 引入了虚继承(Virtual Inheritance)。通过虚继承,派生类共享基类的唯一实例,消除数据冗余和二义性。

如在上面Student和Teacher在继承Person时使用虚继承即可解决问题:

class Person {
public:std::string name;
};class Student : virtual public Person {
protected:int studentID;
};class Teacher : virtual public Person {
protected:int employeeID;
};class Assistant : public Student, public Teacher {
protected:std::string majorCourse;
};int main() {Assistant a;a.name = "Charlie"; // 唯一的 Person::name 成员return 0;
}

下面是一个直接继承的例子:

注:由于x86指针采用4字节对齐方式,x64采用8字节对齐方式。方便起见以下全用x86为例

class A {
public:int _a;
};
class B : public A {
public:int _b;
};
class C : public A {
public:int _c;
};
class D : public B, public C {
public:int _d;
};
void Test(){D d;// sizeof(B)=8; sizeof(C)=8// sizeof(D) = sizeof(B)+sizeof(C)+sizeof(_d) = 20cout << "sizeof(d)=" << sizeof(d) << endl;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;// 编译报错:_a 不明确,需要指明是那个父类下连带的属性,如上// d._a = 6;
}

image-20240716161623437

从图中可以见得数据中存在两个_a,造成了数据的冗余和二义性;

下面看一下虚继承的示例:

class A {
public:int _a;
};
class B : virtual public A {
public:int _b;
};
class C : virtual public A {
public:int _c;
};
class D : public B, public C {
public:int _d;
};
void Test(){D d;cout << "sizeof(d)=" << sizeof(d) << endl;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;d._a = 6;
}

image-20240716170952184

8.1 虚拟继承工作原理
  • 虚基表指针(VBPtr):编译器为每个类对象添加一个虚基表指针,用于指向虚基类的信息。这与虚函数表指针(Vptr)不同,用于虚函数的多态性。
  • 虚基表(VBTable):虚基表记录虚基类的位置信息和偏移量,确保在派生类中正确访问共享基类成员。
虚拟指针的“三板斧”

由上面例子我们能够发现:但虚拟继承时,原本的父类成员会被替换为一个虚基表指针,这个指针指向一张虚基表,虚基表里存放虚基类与虚基表指针的偏移量。要注意和虚函数表区分开来。

正常继承:(下图中展示的是x64环境下的)

正常继承

虚拟继承:

img

编译器处理虚继承的方法是:编译器在处理虚继承(虚函数)时,会给每个派生类添加一个隐藏成员类似:

class A{
public:void *vptr;// 续集表指针 --> 4个字节...
}

同时在编译器会给向派生类的构造函数中安插一条赋值语句 来为vptr赋值,类似:

A(){vptr = &A::vftable;
}

虚基类存储在最后继承它的派生类

9.继承与组合

继承和组合都是实现类复用和构建复杂对象的手段,但它们在设计哲学和应用场景上有所不同。

9.1 继承(Inheritance)
  • 关系:表示“is-a”关系,即派生类是基类的一种。例如,StudentPerson
  • 复用方式:派生类通过继承基类的成员,实现代码的复用和扩展。
  • 继承是一种白箱复用,父类对子类基本是透明的,但是它一定程度破坏了父类的封装
  • 优点:
    • 简单直接,适用于明确的类型层次结构。
    • 允许派生类直接访问基类的 publicprotected 成员。
  • 缺点:
    • 高耦合,派生类依赖基类的实现细节。
    • 破坏基类的封装性,基类的改变可能影响派生类。
8.2 组合(Composition)
  • 关系:表示“has-a”关系,即一个类包含另一个类的对象。例如,Car 有一个 Engine
  • 复用方式:通过在类中包含成员对象,实现功能的复用和扩展。
  • 组合式一种黑箱复用,C对D是不透明的,C保持着他的封装。
  • 优点:
    • 低耦合,类之间的依赖关系较弱。
    • 保持了各自类的封装性和独立性。
    • 更加灵活,可以在运行时动态组合不同的组件。
  • 缺点:
    • 需要通过成员对象的接口间接访问功能,可能增加代码复杂性。

示例

  • 继承示例:
// 继承示例
class Engine {
public:void start() {std::cout << "Engine started." << std::endl;}
};class Car : public Engine { // Car is-a Engine(不符合实际逻辑,仅为示例)
public:void drive() {start(); // 直接访问 Engine 的成员std::cout << "Car is driving." << std::endl;}
};int main() {Car car;car.start(); // 直接调用 Engine 的方法car.drive();return 0;
}
/*
输出:
Engine started.
Engine started.
Car is driving.
*/
  • 组合示例:
// 组合示例
class Engine {
public:void start() {std::cout << "Engine started." << std::endl;}
};class Car {
private:Engine engine; // Car has-a Engine
public:void drive() {engine.start(); // 通过 Engine 的接口访问std::cout << "Car is driving." << std::endl;}
};int main() {Car car;// car.start(); // 错误,Engine 的 start() 是私有的car.drive();return 0;
}
/*
输出:
Engine started.
Car is driving.
*/

组合的类耦合度更低,而继承的类是一种高耦合。

最佳实践

  • 优先使用组合:当类之间的关系不明确或“has-a”关系更符合实际需求时,优先选择组合。
  • 谨慎使用继承:仅在明确需要“is-a”关系并且派生类确实需要基类的功能时,才使用继承。

面试题

C++的缺陷有哪些?
  1. 复杂性
    • C++ 的语法和特性非常丰富,这使得学习曲线陡峭,容易出错。
    • 复杂的模板和泛型编程可能导致编译错误难以理解。
  2. 内存管理
    • 手动管理内存(如使用 newdelete)容易导致内存泄漏和野指针问题。
    • 虽然 C++11 引入了智能指针(如 unique_ptrshared_ptr),但仍然需要开发者谨慎使用。
  3. 多继承
    • 多继承可能导致复杂的对象模型和潜在的二义性问题。
    • 菱形继承问题是一个典型的多继承问题,需要使用虚继承来解决。
  4. 性能和效率
    • 虽然 C++ 通常被认为是高性能的语言,但某些高级特性(如虚函数、RTTI)可能会引入额外的开销。
    • 优化代码需要深入理解编译器和硬件特性。
  5. 缺乏内置的垃圾回收机制
    • C++ 没有内置的垃圾回收机制,需要手动管理内存,增加了开发复杂度。
  6. 标准库的局限性
    • 标准库虽然强大,但某些领域(如网络编程、GUI 开发)的支持相对薄弱。
    • 第三方库的质量和兼容性参差不齐。
什么是菱形继承?菱形继承的问题是什么?如何解决?虚继承的原理是什么?
  1. 什么是菱形继承?

    菱形继承是一种多继承的情况,其中派生类从两个基类派生,而这两个基类又共同派生自同一个基类。这种继承结构形成了一个菱形的形状。

class A {
public:int value;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
  1. 菱形继承的问题是什么?

    (1) 二义性:

    • 由于 DBC 继承,而 BC 都有一个 A 的子对象,D 会有两个 A 的子对象。
    • 当尝试访问 A 的成员时,编译器无法确定应该使用哪个 A 的子对象,导致二义性问题。

    (2) 对象模型复杂:

    • 菱形继承会导致对象模型变得复杂,增加内存开销和管理难度。
  2. 如何解决菱形继承的问题?

    使用虚继承可以解决菱形继承带来的二义性和对象模型复杂的问题。

class A {
public:int value;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
  1. 虚继承的原理?

    (1) 单一子对象:

    • 虚继承确保派生类中只有一个基类的子对象,而不是多个。
    • 在上述例子中,D 只有一个 A 的子对象,而不是两个。

    (2) 初始化顺序:

    • 虚基类的构造函数会在最派生类的构造函数中被调用,而不是在中间派生类的构造函数中被调用。
    • 这确保了虚基类的子对象在所有派生类的子对象之前被初始化。

    (3) 内存布局:

    • 虚继承会导致对象的内存布局更加复杂,因为编译器需要在对象中添加额外的指针来管理虚基类的子对象。
    • 这可能会引入一定的性能开销。

模版

1.概念和工作原理

1.1 概念:

模版(Template)是C++中实现泛型变成的核心机制。它运行程序员编写与类型无关的代码,通过实例化时指定具体类型,从而实现代码的复用和灵活性。模版主要分为函数模版类模版。模版的设计旨在在不牺牲性能的前提下,实现代码的高度复用。通过在编译时生成针对不同类型的代码,模版避免了运行时的多态开销,同时保持了类型安全。这种编译时多态性与运行时多态性(如虚函数)形成鲜明对比,各有优劣。

1.2 工作原理:

模版在C++中通过实例化机制工作。当编译器遇到模板的使用时,会根据提供的类型参数生成具体的函数或类。这一过程发生在编译期,确保了生成的代码在类型上是正确的。

编译器在编译阶段根据模板参数生成响应的代码,这意味着每个不同的模板参数组合都会生成独立的代码实例。这种机制带了以下优点:

  • 类型安全: 所有类型检查在编译期完成,避免了运行时错误。
  • 性能优化:生成的代码针对特定类型进行了优化,消除了不必要的抽象层。

但同时也存在一些缺点:

  • 编译时间增加:大量的模版实例化可能导致编译时间显著增加。
  • 代码膨胀:每个模版实例化都会生成独立的代码,可能导致可执行文件体积增大。

2.函数模板

2.1 定义与使用

函数模板允许编写与类型无关的函数,通过模板参数在调用时指定具体类型。函数模板的语法以template关键字开头,紧随其后的是模板参数列表。

// 函数模版
template <typename T>
T getMax(T a, T b) {return (a > b) ? a : b;
}int main() {cout << getMax<int>(3, 7) << endl;         // 输出:7cout << getMax<double>(3.5, 2.5) << endl;  // 输出:3.5cout << getMax<char>('g', 'e') << endl;    // 输出:greturn 0;
}
2.2 模版参数推导

在大多数情况下编译期能够根据函数参数自动推导处模板类型,此时可以不用显式的指定类型。,因此上面对模版的使用还可以这样

int main() {cout << getMax(3, 7) << endl;          // 自动推导为 getMax<int>cout << getMax(3.5, 2.5) << endl;      // 自动推导为 getMax<double>cout << getMax('g', 'e') << endl;      // 自动推导为 getMax<char>return 0;
}

注意事项:

  • 如果模版参数无法从函数参数中推导出来,必须显式指定类型。
  • 模版参数推导在函数重载解析中起重要作用,可能影响函数的选择。
2.3 多参数模板

函数模板可以接收多个类型参数,以处理不同类型的参数组合。这为函数的通用性提供了更大的灵活性:

// 多参数函数模版
template <typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {// 根据传入值a、b相加的结果推导类型return a + b;
}int main() {cout << add(3, 4.5) << endl;         // 输出:7.5cout << add(2.3f, 4) << endl;        // 输出:6.3return 0;
}

拓展分析

decltype关键字:是C++11新增的一个关键字,和auto的功能一样,用来编译时期进行自动类型推导,确保返回类型与操作符的实际结果类型一致。它的引入很好的弥补了auto不适用或跟不无法使用的场景。

decltype基本语法:

int a = 10;
decltype(a) b = a; // b的类型与a相同,即int

3.类模板

3.1 定义与使用
#include <iostream>
#include <string>
using namespace std;// 类模版
template <typename T>
class MyContainer {
private:T element;
public:MyContainer(T elem) : element(elem) {}void display() const {cout << "Element: " << element << endl;}
};int main() {MyContainer<int> intObj(42);intObj.display();  // 输出:Element: 42MyContainer<string> strObj("Hello");strObj.display();  // 输出:Element: Helloreturn 0;
}
3.2 模板类的默认参数

类模板可以为模板参数指定默认类型,简化实例化时的类型指定。允许在需要时覆盖默认类型,保持代码的通用性。

// 类模版,默认类型为int
template <typename T = int>
class MyContainer {
private:T element;
public:MyContainer(T elem) : element(elem) {}void display() const {cout << "Element: " << element << endl;}
};int main() {MyContainer<> defaultObj(100); // 默认类型为intdefaultObj.display();           // 输出:Element: 100MyContainer<double> doubleObj(99.99);doubleObj.display();            // 输出:Element: 99.99return 0;
}

4.模板特化

模板特化允许为特定类型或类型组合提供专门的实现,以满足特殊需求。模板特化分为全特化偏特化

4.1 全特化

全特化为模板所有参数指定具体类型,提供专门的实现。这在处理特定类型时非常有用,例如针对指针类型或某些自定义类型提供不同的行为。

#include <iostream>
using namespace std;// 原始类模版
template <typename T>
class MyContainer {
private:T element;
public:MyContainer(T elem) : element(elem) {}void display() const {cout << "Generic Element: " << element << endl;}
};// 全特化,针对char*类型
template <>
class MyContainer<char*> {
private:char* element;
public:MyContainer(char* elem) : element(elem) {}void display() const {cout << "Specialized Element: " << element << endl;}
};int main() {MyContainer<int> intObj(10);intObj.display();  // 输出:Generic Element: 10char msg[] = "Hello, World!";MyContainer<char*> charPtrObj(msg);charPtrObj.display();  // 输出:Specialized Element: Hello, World!return 0;
}
  • 限制: 全特化允许部分模板参数进行特化,适用于部分类型参数的特殊实现。
4.2 偏特化

偏特化允许部分模板参数进行特化,实现更加灵活和细粒度的模板行为。适用于部分类型的特殊实现。偏特化主要用于类模板,函数模板不支持偏特化。

#include <iostream>
#include <string>
using namespace std;// 原始类模版
template <typename T1, typename T2>
class Pair {
private:T1 first;T2 second;
public:Pair(T1 a, T2 b) : first(a), second(b) {}void display() const {cout << "Pair: (" << first << ", " << second << ")" << endl;}
};// 偏特化,当T2为char*时
template <typename T1>
class Pair<T1, char*> {
private:T1 first;char* second;
public:Pair(T1 a, char* b) : first(a), second(b) {}void display() const {cout << "Specialized Pair: (" << first << ", " << second << ")" << endl;}
};int main() {Pair<int, double> p1(1, 3.14);p1.display();  // 输出:Pair: (1, 3.14)char msg[] = "C++ Templates";Pair<string, char*> p2("Topic", msg);p2.display();  // 输出:Specialized Pair: (Topic, C++ Templates)return 0;
}

http://www.mrgr.cn/news/60472.html

相关文章:

  • python编程-闭包
  • 已经安装好Ubuntu,10分钟配好Anaconda3
  • 在Spring Boot中配置Map类型数据
  • 量子计算突破:下一个科技革命的风口浪尖在哪里?
  • 【力扣 + 牛客 | SQL题 | 每日4题】牛客大厂笔试真题SQLW6, W7, W8
  • 搭建 mongodb 副本集,很详细
  • vLLM推理部署Qwen2.5
  • 【云原生】云原生后端:数据管理
  • 有手就行的大模型教程:如何在个人电脑上部署盘古大模型
  • 2024最新保姆级Python下载安装教程
  • 小白也能轻松制作产品宣传册的软件
  • 消息队列-RabbitMQ
  • npcap-1.80
  • OpenIPC开源FPV之msposd配置
  • 本地搭建Trilium Notes轻松创建个人知识库并实现远程查看文档资料
  • 内衣洗衣机真的可以洗得更干净吗?入手这四款洗衣机真心不后悔!
  • 全家桶工具介绍
  • 10.28.2024刷华为OD C题型
  • 采购管理系统有哪些基础的功能
  • js中 没值用 ??还是||
  • DDRPHY数字IC后端设计实现系列专题
  • WebGL进阶(四)-视点和视线
  • JVM进阶调优系列(7)JVM调优监控必备命令、工具集合|实用干货
  • react18中react-thunk实现公共数据仓库的异步操作
  • WSGI、uwsgi与uWSGI
  • [ComfyUI]Mochi:最强视频开源模型,它来了!