跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 多态详解

C++ 多态机制。多态指同一接口在不同对象下呈现不同行为。构成条件包括:基类指针或引用调用虚函数,且派生类重写虚函数。虚函数由 virtual 修饰,支持协变和析构函数重写。C++11 引入 override 和 final 关键字辅助管理重写。抽象类含纯虚函数,不可实例化。多态原理基于虚函数表(vtable)和虚表指针(vfptr),实现运行时动态绑定。单继承和多继承场景下虚表结构有所不同。

Kubernet发布于 2026/3/30更新于 2026/7/2454 浏览

多态

多态:多态就是函数调用的多种形态,调用函数更加灵活。具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; } 
}; 

class Student : public Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 打折" << endl; } 
}; 

void Func(Person* ptr) { 
    // 这里可以看到虽然都是 Person 指针 Ptr 在调用 BuyTicket
    // 但是跟 ptr 没关系,而是由 ptr 指向的对象决定的。
    ptr->BuyTicket(); 
}

int main() { 
    Person ps; 
    Student st; 
    Func(&ps); 
    Func(&st); 
    return 0; 
}

文章配图

我们可以看到上面的代码构成了多态,那么是怎么形成多态的呢?主要有两点:

1、子类重写父类的虚函数

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; } 
}; 

class Student : public Person { 
public: 
    void BuyTicket() { cout << "买票 - 打折" << endl; }
    // 重写上面的虚函数
}; 

2、必须是父类的指针或者引用去调用虚函数

// 指针去调用虚函数
void Func(Person* ptr) { 
    // 这里可以看到虽然都是 Person 指针 Ptr 在调用 BuyTicket
    // 但是跟 ptr 没关系,而是由 ptr 指向的对象决定的。
    ptr->BuyTicket(); 
}

// 引用去调用虚函数
void Func(Person& ptr) { 
    ptr.BuyTicket(); 
}

那么什么是虚函数?多态的原理又是怎么样的?上面为什么可以满足多态的条件?

接下来我们会带着这三个问题去学习:

有些书籍会把多态划分的更细:

静态的多态:函数重载,调用同一个函数,传不同的参数,就有不同的行为/形态
动态的多态:父类指针或引用调用重写虚函数,不同的对象去调用,会有不同的行为/状态,父类指针或者引用指向父类,调用的就是父类的虚函数,父类指针或引用指向那个子类,调用的就是子类的虚函数

多态的构成条件

**多态是在不同继承关系的类对象,去调用同一个函数,产生了不同的行为。比如 Student 继承了 Person。**Person 对象买的是全价票,Student 对象买的是打折票。

那么我们在继承中要构成多态有两个条件:

必须通过基类的指针或者引用调用虚函数
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

下面我们开始介绍虚函数和重写。

虚函数

虚函数:就是被 virtual 修饰的类成员函数称为虚函数

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; }
    // 虚函数
}; 

需要注意的是:

**√**只有类的非静态成员函数才可以加 virtual
**√**虚函数这里 virtual 和虚继承中用的 virtual 是同一个关键字,但是他们都没有关系,这里的虚函数是为了实现多态,虚继承是为了解决菱形继承的数据冗余性和二义性

虚函数的重写

虚函数的重写 (覆盖):派生类中有一个跟基类完全相同的虚函数 (及派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),我们叫做派生类的虚函数重写了基类的虚函数。

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; } 
}; 

class Student : public Person { 
public: 
    // 子类的虚函数重写了父类的虚函数
    void BuyTicket() { cout << "买票 - 打折" << endl; } 
}; 

那么下面我们就看完整构成多态的代码:

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; } 
}; 

class Student : public Person { 
public: 
    void BuyTicket() { cout << "买票 - 打折" << endl; } 
}; 

void Func(Person* ptr) { 
    // 传不同的类型对象,调用的是不同的函数,实现了调用的多种形态
    ptr->BuyTicket(); 
}

void Func(Person& ptr) { 
    // 传不同的类型对象,调用的是不同的函数,实现了调用的多种形态
    ptr.BuyTicket(); 
}

int main() { 
    Person ps;//普通人
    Student st;//学生
    // 指针调用
    Func(&ps); 
    Func(&st);
    // 引用调用
    Func(ps); 
    Func(st);
    return 0; 
}

文章配图

那么我们需要再次注意!构成多态的条件:

必须通过基类的指针或者引用调用虚函数
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

正常的虚函数重写,要求虚函数的函数名、参数、返回值都要相同,但是协变除外。

虚函数重写的两个例外:

1、协变 (基类与派生类虚函数返回类型不同)

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或引用,派生类虚函数返回派生类对象的指针或者引用的时候,叫做协变。

class A {}; 
class B : public A {}; 
// 协变
class Person { 
public: 
    virtual A* BuyTicket() { cout << "买票 - 全价" << endl; return nullptr; } 
}; 

class Student : public Person { 
public: 
    virtual B* BuyTicket() { cout << "买票 - 打折" << endl; return nullptr; } 
}; 

void Func(Person* ptr) { 
    ptr->BuyTicket(); 
}

int main() { 
    Person ps; 
    Student st; 
    Func(&ps); 
    Func(&st); 
    return 0; 
}

文章配图

但是他的意义不大,只需要了解一下就可以了。

2、析构函数的重写 (基类与派生类析构函数的名字不同)

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加 virtual 关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同,也就是函数名不相同,看起来违背了重写的规则。实际上,这里可以理解为编译器对析构函数的名字做了特殊的处理,编译后析构函数的名称同意处理成 destructor。

不构成重写:

class A { 
public: 
    ~A() { cout << "~A()" << endl; } 
}; 

class B : public A { 
public: 
    // 构成重写
    ~B() { cout << "~B()->delete:" << _p << endl; delete _p; } 
protected: 
    int* _p = new int[10]; 
}; 

int main() { 
    A* p1 = new A; 
    A* p2 = new B; 
    // p1->destructor() + operator delete
    delete p1; 
    delete p2; 
    return 0; 
}

文章配图

为什么呢?

delete p1;//p1->destuctor()+operator delete(p1)
delete p2;//p2->destuctor()+operator delete(p2) 

因为 delete 时底层会去调用该对象类的析构函数和 operator delete,不是虚函数时,他们构成隐藏,因为 p1 和 p2 都是父类指针,所以他们都是去调用父类的析构函数

构成重写时:

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

class B : public A { 
public: 
    // 构成重写
    ~B() { cout << "~B()->delete:" << _p << endl; delete _p; } 
protected: 
    int* _p = new int[10]; 
}; 

int main() { 
    A* p1 = new A; 
    A* p2 = new B; 
    // p1->destructor() + operator delete
    delete p1; 
    delete p2; 
    return 0; 
}

文章配图

只有派生类 Student 的析构重写了 Person 的析构函数,下面的 delete 对象调用了析构函数,才能构成多态,才能保证 p1 和 p2 指向的对象正确的调用析构函数

注意:如果A(),不加 virtual,那么 deletep2 时只调⽤的 A 的析构函数,没有调⽤ B 的析构函数,就会导致内存泄漏问题,因为B() 中在释放资源。

C++11 override 和 final

C++ 对虚函数重写的要求⽐较严格,但是有些情况下由于疏忽,⽐如函数名写错参数 写错等导致⽆法构成重写,⽽这种错误在编译期间是不会报出的,只有在程序运⾏时没有得到预期结 果才来 debug 会得不偿失,因此 C++11 提供了 override,可以帮助⽤⼾检测是否重写。如果我们不想让 派⽣类重写这个虚函数,那么可以⽤ final 去修饰

如果不想虚函数被重写,那么就在虚函数后面加关键字 final:

class Car { 
public: 
    virtual void Dirve() final {} 
}; 

class Benz :public Car { 
public: 
    virtual void Drive() { cout << "Benz-舒适" << endl; } 
}; 

文章配图

override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写就发生报错

class Car { 
public: 
    virtual void Dirve() {} 
}; 

class Benz :public Car { 
public: 
    virtual void Drive() override { cout << "Benz-舒适" << endl; } 
}; 

文章配图

重载、覆盖 (重写)、隐藏的对比

文章配图

总结:

多态:调用一个函数时,展现出多种形态 (通过调用不同的函数,完成不同的行为)。
多态分为静态的多态和动态的多态:
静态的多态:
函数重载就是静态的多态,在编译时确定地址。
动态的多态:
1、子类继承父类,完成虚函数重写
2、父类的指针或引用去调用这个重写的虚函数
父类的指针或引用指向父类对象,调用的是父类的虚函数
父类的指针或引用指向子类对象,调用的是子类的虚函数
虚函数重写条件:1、要是虚函数 2、函数名、参数、返回值都相等
例外:
1、协变 (返回值不一样,父类的虚函数返回的是基类对象指针和引用,子类的虚函数返回的是子类对象指针和引用)
2、析构函数
3、子类中的重写的虚函数可以不加 virtual 关键字 (建议加上)

抽象类

概念

在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类 (也叫接口类),抽象类不能实例化对象。派生类继承后也不能实例化对象,只有重写纯虚函数,派生类才能实例化对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承。

class Car { 
public: 
    // 纯虚函数
    virtual void Drive() = 0; 
}; 

int main() { 
    Car cc; 
    return 0; 
}

文章配图

抽象类不能实例化出对象,可以更好的去表示现实世界中没有实例化对象对应的抽象类型,比如:植物、人、动物,它体现了接口继承,强调子类去重写虚函数,如果不重写,继承下还是纯虚函数,照样无法实例化出对象。

class Car { 
public: 
    // 纯虚函数
    virtual void Drive() = 0; 
}; 

class Benz :public Car { 
public: 
    virtual void Drive() { cout << "Benz-舒适" << endl; } 
}; 

class BMW :public Car { 
public: 
    virtual void Drive() { cout << "BMW-操控" << endl; } 
}; 

int main() { 
    Car* pBenz = new Benz; 
    pBenz->Drive(); 
    Car* pBMW = new BMW; 
    pBMW->Drive(); 
    return 0; 
}

文章配图

注意:

要注意和 override 区分,override 检查子类虚函数是否完成重写。纯虚函数是强制子类去重写虚函数,如果不重写,继承下来还是纯虚函数,照样无法实例化出对象。

接口继承和实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

多态的原理

虚函数表

这里常考一道笔试题:sizeof(Base)是多少?

// 这里常考一道笔试题:sizeof(Base) 是多少?
class Base { 
public: 
    virtual void Func1() { cout << "Func1()" << endl; } 
private: 
    int _b = 1; 
    char _ch = 'a'; 
}; 

int main() { 
    cout << sizeof(Base) << endl; 
    return 0; 
} 

文章配图

为什么是 12 呢?根据内存对齐应该是 8 呀,这里为什么会是 12。

是因为只要包含虚函数的类,该类的对象就包含一个虚函数表指针 (简称虚表指针),这个虚函数表指针就是用来实现多态的:

文章配图

这个虚表指针指向一个数组,这个数组的元素是函数指针,这里面的函数指针指向该类中的虚函数。

虚函数被编译成指令后,还是和普通函数一样,存在代码段,只是它的地址放在虚表中。

需要注意的是:

这里跟虚继承那里是不一样的,他们虽然都用了 virtual 关键字,但是他们的使用场景完全不一样,解决的也是不一样的问题,他们之间没有关联,虚继承产生的是虚基表,虚基表里面存的是距离虚基类的偏移量。

class Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 全价" << endl; } 
    virtual void Func1() { cout<<"Person::Func1()"<<endl; } 
}; 

class Student : public Person { 
public: 
    virtual void BuyTicket() { cout << "买票 - 半价" << endl; } 
    //不重写 Func1,虚表里面的指针指向的是父类的虚函数
}; 

void Func(Person& p) { 
    p.BuyTicket(); 
}

int main() { 
    Person Mike; 
    Func(Mike); 
    Student Johnson; 
    Func(Johnson); 
    return 0; 
} 

文章配图

文章配图

父子类无论是否完成虚函数重写,都有各自的独立虚表,一个类的所有对象共享一个虚表。

满足多态条件以后,构成多态:指针或引用在调用虚函数时,不是在编译时确定,是在运行时到指针或引用指向的对象的虚表中去找对应的虚函数调用,如果指向的时父类对象,则调用的就是父类的虚函数,指向的是子类对象,调用的就是子类的虚函数。需要注意的是,如果不构成多态,那么这里调用的时候就是编译时确定的调用哪个函数,主要看的 p 的类型,调用的就是 Person 和 Buyticket,跟传上面类型对象过来没有关系。

总结:

构成多态,指向谁,调用谁的虚函数,跟对象有关;不构成多态,对象类型是什么,调用那个对象的函数,跟类型有关。

为什么多态的条件之一必须是父类的指针或引用去调用虚函数时才会发生多态,父类对象却不行?

父类的指针和引用,在切片时,指向或者引用父类对象 或者 指向或引用子类对象中切出来的父类那一部分。vfptr 在对象的前四个字节保存,指向父类看到的是父类的虚表,指向子类看到的是子类的虚表
如果为父类对象时,切片只会拷贝成员变量过去,不会拷贝 vfptr 过去,因为拷贝过去不合理,如果可以拷贝过去,因为一个类共享一个虚表,在创建一个父类对象,这个父类对象的虚表时子类的虚表,这样不合理。

多态实际上有一些性能开销的:

我们通过汇编代码分析,可以看出满足多态的函数调用不是在编译时确定的,是运行起来以后到对象中去找的,不满足多态的函数调用是编译或者链接时确认好的:

满足多态时调用的虚函数汇编代码:

文章配图

call eax 其实就是调用虚函数。

不满足多态时调用的虚函数汇编代码:

文章配图

动态绑定和静态绑定

静态绑定又叫前期绑定 (早绑定),在程序编译期间确定了程序的行为,也称为静态多态

动态绑定又叫后期绑定 (晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也成为多态动态

普通函数的调用,编译 (当在一个文件当中时在编译阶段确定) 链接 (当声明和定义分离时,在链接时确定) 时确定地址,多态的调用是运行时确定地址,如何确定?去指向对象的虚函数表中找到虚函数地址。

下面我们来看这样的几个问题:

对象中虚表指针是在什么阶段初始化的呢?虚表又是在什么阶段生成的呢?

对象中虚表指针是在构造函数初始化列表进行初始化,虚表是在编译时就生成好了。

文章配图

可以看到调用了构造函数后虚表指针依旧进行初始化了。

虚函数放在虚表里面的,这句话对吗?
这句话不准确,虚表里面放的是虚函数地址,虚函数跟普通函数一样,编译完成后,都是放在代码段。

一个类中所有的虚函数地址,都会放在虚表中。
这句话是正确的,虽然可能大家有时候在调式的监视窗口看不到某个虚函数,这是因为编译器进行了优化,其实在内存中是可以看到有的,这些虚函数的地址都会放在虚表当中。

虚函数的重写,也叫做虚函数的覆盖,原因是子类刚开始是拷贝父类的虚函数过来,如果重写了哪个虚函数,就会将该虚函数拿过来进行覆盖从父类拷贝过来的虚函数。

vs 下会在虚表结束位置放一个空指针表示虚表结束了。

文章配图

在面试中,面试官可能会问虚表是存在哪里的?想办法写一段程序,论证一下虚表存在哪个区域的?那么怎么论证呢?我们定义各个区域的变量或者常量,通过看地址的方式看哪个地址和虚表指针的内容相近:

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 j = 0;

int main() { 
    // 取虚表地址打印一下
    Person p; 
    Person* pp = &p;//pp 指向整个对象 p
    printf("vftptr:%p\n",*((int*)pp));//将 pp 强转为 int*,即 pp 指向对象 p 的前四个字节,对它解引用就拿到了前四个字节,前四个字节就是 vftptr(虚表指针)
    int i;
    printf("栈上地址:%p\n",&i);
    printf("数据段地址:%p\n",&j);
    int *k = new int;
    printf("堆地址:%p\n",k);
    char* cp = "hello world";
    printf("代码段地址:%p\n",cp);
    return 0; 
}
printf("vftptr:%p\n",*((int*)pp)); 

这个代码就打印出来了虚表指针,为什么呢?pp 指向整个对象 p,首先将 pp 强转为 int,此时 pp 指向 p 对象的前四个字节,对它解引用就拿到了前四个字节,这前四个字节就是虚表指针。*

文章配图

可以看到它是更接近代码段地址的,所以虚表是存在代码段的。虚函数编译出来函数指令跟普通函数一样,存在代码段,虚函数地址又被放到虚函数表中

单继承和多继承关系的虚函数表

单继承的虚函数表

class Base { 
public: 
    virtual void func1() { cout << "Base::func1" << endl; } 
    virtual void func2() { cout << "Base::func2" << endl; } 
private: 
    int a; 
}; 

class Derive :public Base { 
public: 
    virtual void func1() { cout << "Derive::func1" << endl; } 
    virtual void func3() { cout << "Derive::func3" << endl; } 
    virtual void func4() { cout << "Derive::func4" << endl; } 
private: 
    int b; 
}; 

int main() { 
    Base b; 
    Derive d; 
    return 0; 
}

我们通过调试发现监视窗口看不到子类自己的虚函数 fun3 和 funn4。

文章配图

监视窗口不一定真实,实际上是由的:

文章配图

我们可以通过写一个程序打印一下虚表,通过调用虚表中的虚函数,确定上面两个就是我们说的 fun3 和 fun4 的地址:

class Base { 
public: 
    virtual void func1() { cout << "Base::func1" << endl; } 
    virtual void func2() { cout << "Base::func2" << endl; } 
private: 
    int a; 
}; 

class Derive :public Base { 
public: 
    virtual void func1() { cout << "Derive::func1" << endl; } 
    virtual void func3() { cout << "Derive::func3" << endl; } 
    virtual void func4() { cout << "Derive::func4" << endl; } 
private: 
    int b; 
}; 

typedef void(*VFunc)();//VFunc 是函数指针

void PrintVFT(VFunc ptr[])//存函数指针的数组指针,ptr 指向函数指针数组
{ 
    for(int i = 0;ptr[i]!=nullptr;++i) { 
        printf("VFT[%d]:%p\n",i,ptr[i]); 
        ptr[i]();//调用该函数,确认地址是哪个函数的地址
    } 
    printf("\n"); 
}

int main() { 
    Base b; 
    PrintVFT((VFunc*)(*(int*)&b));//将 b 的虚表指针传过去,*(int*)&b) 拿到虚表指针
    Derive d; 
    PrintVFT((VFunc*)(*(int*)&d));//将 d 的虚表指针传过去
    return 0; 
}

文章配图

可以看到明确调用了 func3 和 func4。

多继承的虚函数表

我们来看一下多继承的虚函数表是怎么样的:

class Base1{ 
public: 
    virtual void func1() {cout << "Base1::func1" << endl;}
    virtual void func2() {cout << "Base1::func2" << endl;}
private: 
    int b1; 
}; 

class Base2 { 
public: 
    virtual void func1() {cout << "Base2::func1" << endl;}
    virtual void func2() {cout << "Base2::func2" << endl;}
private: 
    int b2; 
}; 

class Derive : public Base1, public Base2 { 
public: 
    virtual void func1() {cout << "Derive::func1" << endl;}
    virtual void func3() {cout << "Derive::func3" << endl;}
private: 
    int d1; 
}; 

typedef void(*VFunc)();//VFunc 是函数指针

void PrintVFT(VFunc* ptr)//存函数指针的数组指针,ptr 指向函数指针数组
{ 
    for(int i = 0;ptr[i]!=nullptr;++i) { 
        printf("VFT[%d]:%p\n",i,ptr[i]); 
        ptr[i]();//调用该函数,确认地址是哪个函数的地址
    } 
    printf("\n"); 
}

int main() { 
    Base1 b1; 
    Base2 b2; 
    Derive d; 
    PrintVFT((VFunc*)(*(int*)&d));//打印第一个虚表 (d 对象中起始位置为虚表指针)
    PrintVFT((VFunc*)(*(int*)((char*)&d+sizeof(Base1))));//打印第二个虚表
    return 0; 
}

可以看到多继承中,Derive 既继承了 Base2,Derive 就有两种虚表。

文章配图

我们看到 Derive 中自己的虚函数 func3 在监视窗口并没有,那么怎么证明它是存在的呢?和上面其实是一样的,只不过打印第二张虚表有些不一样:

typedef void(*VFunc)();//VFunc 是函数指针

void PrintVFT(VFunc ptr[])//存函数指针的数组指针,ptr 指向函数指针数组
{ 
    for(int i = 0;ptr[i]!=nullptr;++i) { 
        printf("VFT[%d]:%p\n",i,ptr[i]); 
        ptr[i]();//调用该函数,确认地址是哪个函数的地址
    } 
    printf("\n"); 
}

int main() { 
    Base1 b1; 
    Base2 b2; 
    Derive d; 
    PrintVFT((VFunc*)(*(int*)&d));//打印第一个虚表 (d 对象中起始位置为虚表指针),需要强转为 VFunc*,因为虚表指针指向的类型是函数指针数组
    PrintVFT((VFunc*)(*(int*)((char*)&d+sizeof(Base1))));//打印第二个虚表
    return 0; 
}

打印第一张虚表:

PrintVFT((VFunc*)(*(int*)&d)); 

因为 d 对象中起始位置为第一张虚表的虚表指针,需要强转为 VFunc*,因为虚表指针指向的类型是函数指针数组,这个和前面验证单继承没有区别,但是打印第二张虚表就有些不一样了:

PrintVFT((VFunc*)*((int*)((char*)&d+sizeof(Base1)))); 

首先将取地址 d 将他转为 char类型,加上 Base1 的大小就到了 Base2,Base2 的前四个字节是虚表指针,所以再强转为 int,然后解引用拿到这四个字节,最后强转为 VFunc*

这样就可以打印这两张虚表了:

文章配图

可以看到 func3 是存在第一张虚表当中的。

目录

  1. 多态
  2. 多态的构成条件
  3. 虚函数
  4. 虚函数的重写
  5. C++11 override 和 final
  6. 重载、覆盖 (重写)、隐藏的对比
  7. 抽象类
  8. 概念
  9. 接口继承和实现继承
  10. 多态的原理
  11. 虚函数表
  12. 动态绑定和静态绑定
  13. 单继承和多继承关系的虚函数表
  14. 单继承的虚函数表
  15. 多继承的虚函数表
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 大模型分布式训练与高效调参实战
  • Python 利用 Turtle 模块绘制爱心树
  • Java 面向对象三大特性入门笔记
  • 医疗 AI 中的模型融合与集成策略实战
  • Python 数据科学工具链入门:NumPy、Pandas 与 Matplotlib 实战
  • 火影忍者主题网页设计与实现
  • RabbitMQ 深度详解
  • Lottie-Web 完整技术指南
  • 结合 cpolar 内网穿透远程部署 Open-Lovable 网页克隆工具
  • Visual Studio 17.14 中 GitHub Copilot 的 AI 模型选择与配置
  • Figma + Claude + Weavy AI:构建高效设计工作流
  • Docker 部署 Coze 应用服务指南
  • Higress 将 REST API 转换为 MCP Server 工具配置
  • Spring Boot 从入门到实战开发指南
  • 国产 AI 编程助手全景:Claude Code 平替方案与成本差异
  • Ubuntu18+ROS Melodic 配置 XT-Drone/Gazebo 无人机仿真及 Mid360 雷达集成
  • 无人机低空智能巡飞巡检平台:全域感知与智能决策
  • Spring Boot 基于 Vue 的工厂车间管理系统设计
  • UltraScale FPGA 系统管理向导配置与温压监测实战
  • Spring Boot 优雅停机演进:原理与最佳实践

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online