多态性:虚函数与动态多态的实现原理

多态性:虚函数与动态多态的实现原理

在C++面向对象编程中,**多态性(Polymorphism)是三大核心特性(封装、继承、多态)的终极体现------它允许不同类型的对象,对同一个接口(方法名)做出不同的响应,实现"一个接口,多种实现"。而这一切的核心,都依赖于虚函数(Virtual Function)**的机制,虚函数是动态多态的基石,决定了程序运行时如何"动态绑定"到具体的方法实现。

上一篇博客我们详细讲解了函数重写,核心是"子类覆盖父类的同名方法",但当时留下了一个关键疑问:为什么当用父类指针/引用指向子类对象时,调用重写后的方法,会自动执行子类的实现,而非父类?比如父类Animal有speak()方法,子类Dog、Cat重写该方法后,用Animal指针分别指向Dog和Cat对象,调用speak()会分别输出"汪汪汪"和"喵喵喵"------这背后,就是虚函数和动态多态的作用。

很多初学者在学习多态时,容易陷入两个核心困境:一是只知道"加virtual就能实现多态",却不懂其底层实现原理(比如虚函数表、动态绑定是怎么回事);二是混淆"动态多态"与"静态多态",不清楚两者的触发条件和区别。本文将从"多态的核心意义"切入,先区分静态与动态多态,再详解虚函数的语法、使用条件,深入剖析动态多态的底层实现原理(虚函数表、动态绑定),结合实战案例巩固用法,规避高频误区,同时衔接前序继承、函数重写知识点,帮你彻底打通"虚函数→动态多态"的逻辑链,不仅"会用",还能"懂原理",为后续学习抽象类、接口、菱形继承等高级知识点打下坚实基础。

核心前提回顾:1. 函数重写的四要素(继承关系、同名、同参、同返回值);2. 子类继承父类后,父类指针/引用可以指向子类对象("is-a"关系的体现);3. 函数重写是多态的前提,虚函数是多态的实现手段。

一、先明确:多态的核心定义与分类

在讲解虚函数之前,我们先理清多态的核心概念------多态的本质是"接口复用,实现不同",根据"绑定时机"的不同,C++中的多态分为两大类:静态多态动态多态。其中,动态多态是面向对象编程的核心,也是我们本文的重点,而静态多态则是更基础的"编译期绑定"机制。

1. 多态的核心定义

多态(Polymorphism):同一操作作用于不同类型的对象,会产生不同的执行结果。简单来说,就是"调用同一个方法名,不同对象会做不同的事情"。

通俗示例:"吃饭"这个操作,中国人对象执行"吃米饭",美国人对象执行"吃面包",狗对象执行"吃狗粮"------"吃饭"是同一个接口,不同对象有不同的实现,这就是多态。

2. 静态多态(编译期多态)

静态多态的核心是"编译期绑定 "------在程序编译阶段,编译器就已经确定了要调用的具体方法,无需等到运行时再决定。静态多态的实现方式主要有两种:函数重载运算符重载(后续单独讲解)。

关键特点:编译时确定调用哪个方法,效率高,无运行时开销,但灵活性低(无法适应运行时对象类型的变化)。

cpp 复制代码
#include <iostream>
using namespace std;

// 静态多态:函数重载(同一作用域,同名不同参)
class Calculator {
public:
    // 重载add方法,实现不同参数的加法
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    string add(string a, string b) {
        return a + b;
    }
};

int main() {
    Calculator calc;
    // 编译期就确定调用哪个add方法(根据参数类型匹配)
    cout << calc.add(10, 20) << endl;      // 调用int版本
    cout << calc.add(3.14, 2.86) << endl;   // 调用double版本
    cout << calc.add("Hello", "Polymorphism") << endl; // 调用string版本

    return 0;
}

解析:上述代码中,编译器在编译时,会根据调用add方法时的参数类型,确定要调用的具体重载版本,这就是静态多态------绑定时机在编译期,与运行时无关。

3. 动态多态(运行期多态)

动态多态的核心是"运行时绑定"------在程序编译阶段,编译器无法确定要调用的具体方法,只能等到程序运行时,根据实际指向的对象类型,动态绑定到对应的方法实现。

关键特点:运行时确定调用哪个方法,灵活性高(能适应运行时对象类型的变化),有轻微运行时开销(需要查询虚函数表),是面向对象编程的核心多态形式。

动态多态的实现必须满足两个核心条件(缺一不可):

  1. 父类中声明虚函数(在方法前加virtual关键字);

  2. 子类重写父类的虚函数(满足函数重写的四要素,且子类重写时可省略virtual关键字,但推荐加上,增强可读性);

  3. 父类指针或引用指向子类对象,通过指针/引用调用虚函数。

这里提前给出动态多态的简单演示,帮你建立直观认知,后续将深入剖析原理:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 父类:Animal
class Animal {
public:
    // 声明虚函数
    virtual void speak() {
        cout << "动物发出叫声" << endl;
    }
};

// 子类:Dog,重写父类虚函数
class Dog : public Animal {
public:
    // 重写虚函数(可省略virtual,推荐加上)
    virtual void speak() {
        cout << "汪汪汪!" << endl;
    }
};

// 子类:Cat,重写父类虚函数
class Cat : public Animal {
public:
    virtual void speak() {
        cout << "喵喵喵!" << endl;
    }
};

// 用父类引用指向子类对象,调用虚函数(动态绑定)
void animalSpeak(Animal& animal) {
    animal.speak(); // 运行时,根据animal实际指向的对象,调用对应的speak()
}

int main() {
    Dog dog;
    Cat cat;

    animalSpeak(dog); // 实际指向Dog对象,输出:汪汪汪!
    animalSpeak(cat); // 实际指向Cat对象,输出:喵喵喵!

    return 0;
}

解析:上述代码中,animalSpeak函数的参数是Animal引用,调用speak()方法时,编译器在编译期无法确定要调用Dog还是Cat的speak()------只有运行时,根据传入的实际对象(dog或cat),才能动态绑定到对应的方法,这就是动态多态。

4. 静态多态与动态多态核心对比(必记)

对比维度 静态多态(编译期多态) 动态多态(运行期多态)
绑定时机 编译期(程序运行前) 运行期(程序运行中)
实现方式 函数重载、运算符重载 虚函数+函数重写+父类指针/引用
灵活性 低(无法适应运行时对象类型变化) 高(可根据运行时对象类型动态切换实现)
运行开销 无(编译时已确定,直接调用) 轻微(需查询虚函数表)
核心依赖 编译器的参数匹配 虚函数表、动态绑定机制

二、核心基础:虚函数的语法与使用规则(必记)

动态多态的核心是虚函数,没有虚函数,就没有动态绑定,也就没有真正的面向对象多态。我们先掌握虚函数的语法规则,再深入剖析其底层原理。

1. 虚函数的语法格式

虚函数的声明非常简单,只需在父类的方法声明前加上virtual关键字即可,子类重写时,可省略virtual关键字(编译器会自动识别为虚函数),但推荐加上,增强代码可读性。

cpp 复制代码
// 父类:声明虚函数
class 父类名 {
public:
    // 虚函数声明(virtual不可省略)
    virtual 返回值类型 方法名(参数列表) {
        // 方法实现
    }
};

// 子类:重写父类虚函数
class 子类名 : public 父类名 {
public:
    // 重写虚函数(virtual可省略,推荐加上)
    virtual 返回值类型 方法名(参数列表) {
        // 子类个性化实现
    }
};

2. 虚函数的3个核心使用规则(必记)

  1. 虚函数只能声明在父类的public或protected区域:因为动态多态需要通过父类指针/引用调用虚函数,如果虚函数声明为private,父类指针/引用无法访问,无法实现动态绑定;

  2. 子类重写虚函数时,必须满足函数重写的四要素(同名、同参、同返回值、继承关系),否则无法构成"虚函数重写",只能视为子类新增方法(无法实现动态多态);

  3. 虚函数具有"传递性":如果父类声明了虚函数,子类重写后,子类的子类(孙子类)再次重写该方法时,无需再加virtual关键字,默认也是虚函数(推荐加上,增强可读性)。

cpp 复制代码
// 虚函数传递性示例
class Animal {
public:
    virtual void speak() { // 父类虚函数
        cout << "动物发出叫声" << endl;
    }
};

class Dog : public Animal {
public:
    virtual void speak() { // 重写虚函数
        cout << "汪汪汪!" << endl;
    }
};

class Husky : public Dog {
public:
    // 无需加virtual,默认是虚函数(推荐加上)
    virtual void speak() {
        cout << "嗷呜嗷呜!" << endl;
    }
};

int main() {
    Animal& a = Husky();
    a.speak(); // 动态绑定,输出:嗷呜嗷呜!
    return 0;
}

3. 虚函数的常见错误用法(必避)

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void speak() {
        cout << "动物发出叫声" << endl;
    }

    virtual void eat(int a) { // 父类虚函数,带int参数
        cout << "动物吃食物" << endl;
    }
};

class Dog : public Animal {
public:
    // 错误1:参数列表不同(父类int,子类double)→ 不是虚函数重写,是子类新增方法
    virtual void eat(double a) {
        cout << "狗吃骨头" << endl;
    }

    // 错误2:方法名拼写错误(speek≠speak)→ 不是重写,是子类新增方法
    virtual void speek() {
        cout << "汪汪汪!" << endl;
    }
};

int main() {
    Animal& a = Dog();
    a.speak(); // 调用父类speak(),输出:动物发出叫声(子类未重写成功)
    a.eat(10); // 调用父类eat(),输出:动物吃食物(子类未重写成功)
    return 0;
}

解析:上述代码中,子类Dog的eat(double a)和speek()都没有满足"虚函数重写"的条件,因此无法实现动态多态,父类引用调用时,依然执行父类的方法。

三、底层剖析:动态多态的实现原理(虚函数表+动态绑定)

这是本文的核心难点,也是笔试、面试的高频考点------很多初学者只知道"加virtual就能实现多态",却不懂其底层是如何工作的。动态多态的底层实现,依赖于虚函数表(vtable)虚指针(vptr),核心逻辑是"通过虚指针查询虚函数表,找到对应的方法地址,实现动态绑定"。

我们先明确两个核心概念,再逐步拆解实现过程,尽量用通俗的语言讲解,避免过于晦涩的底层细节(适合初学者理解)。

1. 核心概念:虚函数表(vtable)与虚指针(vptr)

当一个类声明了虚函数(或继承了虚函数),编译器会自动做两件事:

(1)虚函数表(vtable)

虚函数表是一个存储虚函数地址的数组,由编译器自动生成,每个包含虚函数(或继承虚函数)的类,都会对应一个唯一的虚函数表。

核心特点:

  • 虚函数表属于"类",而非"对象"(所有该类的对象,共享同一个虚函数表);

  • 虚函数表中存储的是类中所有虚函数的地址(父类继承的虚函数、子类重写的虚函数、子类新增的虚函数);

  • 子类重写父类虚函数时,会将虚函数表中"父类虚函数的地址"替换为"子类虚函数的地址"------这是动态多态的核心关键。

(2)虚指针(vptr)

虚指针是一个指向虚函数表的指针,由编译器自动在类的对象中添加(隐藏成员,用户无法直接访问),每个包含虚函数的对象,都会有一个虚指针。

核心特点:

  • 虚指针属于"对象",每个对象都有一个独立的虚指针;

  • 对象创建时,编译器会自动将虚指针指向该类的虚函数表;

  • 当用父类指针/引用指向子类对象时,父类指针/引用会"继承"子类对象的虚指针------通过这个虚指针,就能找到子类的虚函数表,进而调用子类的虚函数。

2. 动态多态实现的3个核心步骤(结合示例拆解)

我们结合前文"Animal→Dog→Cat"的示例,拆解动态多态的完整实现过程,帮你直观理解"虚函数表+虚指针"是如何工作的。

示例代码回顾(简化):

cpp 复制代码
class Animal {
public:
    virtual void speak() { cout << "动物发出叫声" << endl; }
};

class Dog : public Animal {
public:
    virtual void speak() { cout << "汪汪汪!" << endl; }
};

class Cat : public Animal {
public:
    virtual void speak() { cout << "喵喵喵!" << endl; }
};
步骤1:编译器生成虚函数表(vtable)

编译器会为每个包含虚函数的类,生成一个虚函数表:

  • Animal类:有一个虚函数speak(),因此Animal的虚函数表中,存储着Animal::speak()的地址;

  • Dog类:继承了Animal的虚函数speak(),并进行了重写,因此Dog的虚函数表中,会将Animal::speak()的地址替换为Dog::speak()的地址;

  • Cat类:同理,Cat的虚函数表中,存储着Cat::speak()的地址(替换了父类的地址)。

步骤2:对象创建时,初始化虚指针(vptr)

当创建Dog、Cat对象时,编译器会自动为每个对象添加一个虚指针(vptr),并将其指向该对象所属类的虚函数表:

  • 创建Dog对象dog时,dog的vptr指向Dog类的虚函数表;

  • 创建Cat对象cat时,cat的vptr指向Cat类的虚函数表。

步骤3:运行时,通过虚指针查询虚函数表,实现动态绑定

当用父类指针/引用指向子类对象,并调用虚函数时,程序运行时会执行以下操作:

  1. 父类指针/引用获取子类对象的虚指针(vptr);

  2. 通过虚指针(vptr),找到子类的虚函数表(vtable);

  3. 在虚函数表中,找到虚函数(如speak())对应的地址;

  4. 调用该地址对应的虚函数(子类的实现),完成动态绑定。

通俗理解:父类指针/引用就像一个"遥控器",虚指针(vptr)是"遥控器的信号接收器",虚函数表(vtable)是"信号对应的功能列表"------运行时,遥控器通过接收器找到功能列表,调用对应的功能(子类方法),实现"一个遥控器,控制不同设备(不同对象),产生不同效果"。

3. 关键补充:虚函数表的内存开销

很多初学者会疑惑:虚函数表和虚指针会增加内存开销吗?答案是"会,但开销很小":

  • 虚函数表:属于类,每个类只生成一个,无论创建多少个对象,虚函数表都只占用一份内存(存储虚函数地址,内存占用可忽略);

  • 虚指针:属于对象,每个对象都会增加一个指针的内存开销(32位系统4字节,64位系统8字节)------对于大多数场景,这个开销完全可以接受。

核心结论:动态多态的灵活性,是以"轻微的内存开销和运行时查询开销"为代价的,这是面向对象编程中"灵活性与效率"的平衡。

四、实战演示:动态多态的完整用法(结合场景,可运行)

结合前文知识点,我们设计一个完整的实战场景,演示动态多态的实际应用,同时验证虚函数表、动态绑定的效果,代码可直接复制运行,帮助你巩固所学内容。

1. 实战场景需求

  1. 定义父类Shape(图形),声明虚函数calculateArea()(计算面积)、showInfo()(显示图形信息);

  2. 定义子类Circle(圆形)、Rectangle(矩形),继承Shape,重写父类的两个虚函数;

  3. 定义一个通用函数showShapeInfo(),参数为Shape引用,调用虚函数,实现"传入不同图形对象,显示不同图形的信息和面积";

  4. 验证动态多态的效果:传入Circle对象,显示圆形信息和面积;传入Rectangle对象,显示矩形信息和面积。

2. 完整代码实现

cpp 复制代码
#include <iostream>
#include <cmath> // 用于圆的面积计算(M_PI)
using namespace std;

// 父类:Shape(图形),声明虚函数
class Shape {
public:
    // 虚函数:计算面积(父类仅声明,无具体实现,后续可讲解抽象类)
    virtual double calculateArea() {
        cout << "图形的面积计算方法" << endl;
        return 0.0;
    }

    // 虚函数:显示图形信息
    virtual void showInfo() {
        cout << "图形基础信息" << endl;
    }
};

// 子类:Circle(圆形),重写父类虚函数
class Circle : public Shape {
private:
    double radius; // 圆形半径

public:
    // 构造函数:初始化半径
    Circle(double radius) {
        this->radius = radius;
    }

    // 重写虚函数:计算圆形面积(面积=πr²)
    virtual double calculateArea() {
        return M_PI * radius * radius;
    }

    // 重写虚函数:显示圆形信息
    virtual void showInfo() {
        cout << "图形类型:圆形,半径:" << radius << endl;
    }
};

// 子类:Rectangle(矩形),重写父类虚函数
class Rectangle : public Shape {
private:
    double length;  // 矩形长
    double width;   // 矩形宽

public:
    // 构造函数:初始化长和宽
    Rectangle(double length, double width) {
        this->length = length;
        this->width = width;
    }

    // 重写虚函数:计算矩形面积(面积=长×宽)
    virtual double calculateArea() {
        return length * width;
    }

    // 重写虚函数:显示矩形信息
    virtual void showInfo() {
        cout << "图形类型:矩形,长:" << length << ",宽:" << width << endl;
    }
};

// 通用函数:传入Shape引用,调用虚函数(动态多态)
void showShapeInfo(Shape& shape) {
    shape.showInfo(); // 动态绑定,调用子类的showInfo()
    cout << "图形面积:" << shape.calculateArea() << endl; // 动态绑定,调用子类的calculateArea()
    cout << "------------------------" << endl;
}

int main() {
    // 创建不同图形对象
    Circle circle(5.0);       // 圆形,半径5.0
    Rectangle rectangle(4.0, 6.0); // 矩形,长4.0,宽6.0

    // 调用通用函数,传入不同图形对象
    showShapeInfo(circle);
    showShapeInfo(rectangle);

    return 0;
}

3. 运行结果与代码解析

运行结果:

Plain 复制代码
图形类型:圆形,半径:5
图形面积:78.5398
------------------------
图形类型:矩形,长:4,宽:6
图形面积:24
------------------------

核心解析:

  • showShapeInfo函数的参数是Shape引用,调用showInfo()和calculateArea()时,编译器无法确定要调用哪个子类的方法------运行时,根据传入的实际对象(circle或rectangle),通过虚指针查询虚函数表,动态绑定到对应的子类方法;

  • 新增其他图形(如三角形)时,只需继承Shape类,重写两个虚函数,无需修改showShapeInfo函数------这就是多态的核心优势:代码复用、扩展灵活(符合"开闭原则":对扩展开放,对修改关闭)。

五、高频误区:动态多态的常见坑(必避,笔试重点)

结合初学者的常见错误,聚焦动态多态的"实现条件、虚函数使用、底层原理",总结5个高频坑,每个坑对应错误示例和正确写法,帮你少走弯路。

误区1:忘记用父类指针/引用指向子类对象,无法实现动态多态

cpp 复制代码
class Animal {
public:
    virtual void speak() { cout << "动物发出叫声" << endl; }
};

class Dog : public Animal {
public:
    virtual void speak() { cout << "汪汪汪!" << endl; }
};

int main() {
    // 错误:直接创建子类对象,调用方法,不是动态多态(编译期绑定)
    Dog dog;
    dog.speak(); // 直接调用Dog::speak(),与多态无关

    // 正确:用父类指针/引用指向子类对象
    Animal& a = dog;
    a.speak(); // 动态绑定,输出:汪汪汪!

    return 0;
}

关键提醒:动态多态的触发,必须满足"父类指针/引用指向子类对象"------直接用子类对象调用方法,只是普通的函数调用,与多态无关。

误区2:父类未声明虚函数,子类重写后无法实现动态多态

cpp 复制代码
class Animal {
public:
    // 错误:未加virtual,不是虚函数
    void speak() { cout << "动物发出叫声" << endl; }
};

class Dog : public Animal {
public:
    void speak() { cout << "汪汪汪!" << endl; }
};

int main() {
    Animal& a = Dog();
    a.speak(); // 调用父类speak(),输出:动物发出叫声(无动态多态)
    return 0;
}

关键提醒:父类未声明虚函数,即使子类重写了方法,编译器也会视为"普通函数重写",无法生成虚函数表和虚指针,无法实现动态绑定。

误区3:子类重写虚函数时,参数列表不同,误以为能实现动态多态

cpp 复制代码
class Animal {
public:
    virtual void eat(int food) { cout << "动物吃" << food << "份食物" << endl; }
};

class Dog : public Animal {
public:
    // 错误:参数列表不同(父类int,子类double)→ 不是虚函数重写
    virtual void eat(double food) { cout << "狗吃" << food << "份骨头" << endl; }
};

int main() {
    Animal& a = Dog();
    a.eat(10); // 调用父类eat(),输出:动物吃10份食物(无动态多态)
    return 0;
}

误区4:虚函数用private修饰,无法实现动态多态

cpp 复制代码
class Animal {
private:
    // 错误:虚函数用private修饰,父类指针/引用无法访问
    virtual void speak() { cout << "动物发出叫声" << endl; }
};

class Dog : public Animal {
private:
    virtual void speak() { cout << "汪汪汪!" << endl; }
};

int main() {
    Animal& a = Dog();
    // a.speak(); // 编译错误:speak()是private成员,父类引用无法访问
    return 0;
}

关键提醒:虚函数必须声明在父类的public或protected区域,确保父类指针/引用能访问,才能实现动态绑定。

误区5:认为虚函数表属于对象,增加大量内存开销

错误认知:每个包含虚函数的对象,都会有一个独立的虚函数表,内存开销很大;

正确认知:虚函数表属于"类",每个类只生成一个虚函数表(无论创建多少个对象),对象只包含一个虚指针(指向类的虚函数表),内存开销很小。

六、延伸知识点:虚析构函数(避免内存泄漏)

这是动态多态中一个非常重要的延伸知识点------当用父类指针指向子类对象,并且子类有堆内存分配时,如果父类的析构函数不是虚函数,会导致内存泄漏

1. 问题演示(内存泄漏)

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    // 父类析构函数:非虚函数
    ~Animal() {
        cout << "Animal析构函数调用" << endl;
    }
};

class Dog : public Animal {
private:
    // 子类堆内存分配
    int* p;

public:
    Dog() {
        p = new int(10); // 分配堆内存
    }

    // 子类析构函数:释放堆内存
    ~Dog() {
        delete p; // 释放堆内存
        cout << "Dog析构函数调用(堆内存已释放)" << endl;
    }
};

int main() {
    // 父类指针指向子类对象(堆内存分配)
    Animal* ptr = new Dog();
    delete ptr; // 释放父类指针

    return 0;
}

运行结果:

Plain 复制代码
Animal析构函数调用

问题分析:父类析构函数不是虚函数,delete父类指针时,编译器只会调用父类的析构函数,子类的析构函数不会被调用------子类分配的堆内存(p指针指向的空间)无法释放,导致内存泄漏。

2. 解决方案:父类析构函数声明为虚析构函数

只需将父类的析构函数声明为虚函数,子类的析构函数会自动成为虚函数,delete父类指针时,会动态绑定到子类的析构函数,先调用子类析构函数(释放堆内存),再调用父类析构函数,避免内存泄漏。

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    // 父类析构函数:声明为虚析构函数
    virtual ~Animal() {
        cout << "Animal析构函数调用" << endl;
    }
};

class Dog : public Animal {
private:
    int* p;

public:
    Dog() {
        p = new int(10);
    }

    // 子类析构函数:自动成为虚函数
    ~Dog() {
        delete p;
        cout << "Dog析构函数调用(堆内存已释放)" << endl;
    }
};

int main() {
    Animal* ptr = new Dog();
    delete ptr; // 动态绑定,先调用Dog析构,再调用Animal析构

    return 0;
}

运行结果:

Plain 复制代码
Dog析构函数调用(堆内存已释放)
Animal析构函数调用

关键提醒:只要用父类指针指向子类对象,并且子类有堆内存分配,就必须将父类的析构函数声明为虚析构函数,避免内存泄漏------这是动态多态开发中的"必做操作"。

七、总结:多态、虚函数的核心要点(实战+笔试必备)

多态性是C++面向对象编程的核心,虚函数是动态多态的基石,掌握其语法、使用规则和底层原理,能帮你写出更具扩展性、可维护性的代码,同时应对笔试、面试中的高频考点。

相关推荐
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc7 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
偷吃的耗子7 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
l1t7 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
今天只学一颗糖7 小时前
1、《深入理解计算机系统》--计算机系统介绍
linux·笔记·学习·系统架构
青云计划8 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿8 小时前
Jsoniter(java版本)使用介绍
java·开发语言
testpassportcn8 小时前
AWS DOP-C02 認證完整解析|AWS DevOps Engineer Professional 考試
网络·学习·改行学it
化学在逃硬闯CS8 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1238 小时前
C++使用format
开发语言·c++·算法