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

C++ STL 手写 String 类及高频易错点解析

C++ STL 中 string 类的模拟实现过程涵盖整体结构设计、构造析构函数、功能接口(reserve、c_str、push_back 等)、运算符重载及完整代码示例。重点分析内存管理、深浅拷贝陷阱、扩容策略及流输入输出实现细节,总结手写 String 类时的常见错误与解决方案。

疯疯癫癫发布于 2026/2/4更新于 2026/10/253.2K 浏览
C++ STL 手写 String 类及高频易错点解析

文章配图

文章配图

文章配图

一、整体结构

class string {
public:
private:
    char* _str;
    int _size;
    int _capacity;
};

1. char _str*

这是一个指向字符数组的指针,用来存储字符串的实际字符数据。

在 C++ 标准库的 std::string 中,它指向一块动态分配的内存,里面存放着以 \0 结尾的字符序列。模拟实现时,需要自己管理这块内存的分配、扩容和释放。

2. int _size

记录当前字符串中有效字符的个数(不包括末尾的 \0)。

3. int _capacity

记录当前已分配内存能容纳的最大字符数(不包括末尾的 \0)。

它代表了在不需要重新分配内存的情况下,字符串最多能存储的字符数量。当 _size 即将超过 _capacity 时,就需要触发扩容(比如申请一块更大的内存,将旧数据拷贝过去,再释放旧内存)。通常扩容会按一定倍数(如 1.5 倍或 2 倍)进行,以减少频繁扩容带来的性能开销。

二、构造/析构函数

2.1 默认构造

1. 无参构造

编写构造函数的时候需要注意的是,*char _str**不能初始化为 nullptr。需要开辟一字节的空间并默认初始化为 '\0' 也就是空串,因为字符串默认以 '\0' 结尾。否则就会导致字符串无法以 '\0' 结尾,打印的时候字符串尾部就会出现乱码。

string() { _str = new char[1]{'\0'}; _size = _capacity = 0; }

为了方便后续管理,这里开辟的一字节用来存储 '\0' 的空间不计入总的空间大小。

2. 字符串构造 string 类型

string(const char* str) { int len = strlen(str); _str = new char[len + 1]; strcpy(_str, str); _size = _capacity = len; }

这里需要注意的是,传进来的 str 字符串默认是带有 \0 的。strlen 会计算字符串的长度并记录在 len 变量中(不包含 \0),strcpy 会将 str(包含 \0)拷贝在 _str 这个空间中所以在开辟空间大小的时候开辟 len+1 是为了存储 \0。

2.2 拷贝构造

string(const string& ch) { _str = new char[ch._capacity + 1]; strcpy(_str, ch._str); _size = ch._size; _capacity = ch._capacity; }

编写拷贝构造接口需要特别注意的是:C++ 语法明确禁止拷贝构造传值传参,否则会导致无限递归。这是因为使用传值传参编译器会默认调用拷贝构造,但是如果拷贝构造接口本身就是传值传参的话就会一直重复递归调用自己最终导致栈溢出。

文章配图

要解决这个问题,拷贝构造函数的参数必须采用传常量引用(const 类名&),也可以传普通引用(类名&)。

2.3 析构函数

~string() { delete[] _str; _str = NULL; _size = _capacity = 0; }

三、功能接口

3.1 reserve

文章配图

reserve 接口的唯一作用是修改 string 的 _capacity,它会提前为字符串分配一块足够大的内存空间,让后续添加字符时无需频繁触发自动扩容,reserve 接口通常有以下特点:

  • 它只改变容量(_capacity),不改变有效长度(_size):调用 reserve 后,字符串的有效字符个数不变,也不会填充任何额外的字符,只是预留了内存空间。
  • 它只负责 扩容,不负责 缩容:如果传入的参数小于当前的 _capacity,std::string 的 reserve 通常不会做任何操作。
  • 预留的内存空间包含末尾 \0 的位置:比如 reserve(10),意味着 _capacity 会被设置为 10,实际分配的内存大小是 11(额外存放 '\0')。
void reserve(int n) { if (n > _capacity) { char* temp = new char[n+1]; strcpy(temp,_str); delete[] _str; _str = temp; _capacity = n; } }

注意:如果我们提前实现了 reserve 可不可以在构造函数中调用 reserve 来开辟空间呢?

答案是,坚决不可以。因为调用 reserve 时其中的成员变量 _str,_size,_capacity 并没有第一时间初始化其内容可能是随机值。执行 strcpy(temp,_str); 时 _str 可能指向的是一块非法的空间导致非法访问,或者有可能 _capacity 的随机值比 n 大导致开辟空间失败。

3.2 c_str

文章配图

这个接口的核心作用是获取一个指向 string 内部以 \0 结尾的 C 风格字符串的常量指针,用来兼容那些只支持 C 语言风格字符串的函数接口。

  • 标准规定返回 const char*(常量字符指针),这意味着你只能读取该字符串的内容,不能通过这个指针修改字符串(否则会触发未定义行为)。
  • 返回的指针指向 string 内部存储的字符数组首地址,且该数组必然以 \0 结尾
  • 返回的指针依赖于 string 对象的生命周期,且当 string 对象发生修改(如添加字符、扩容)或销毁后,这个指针会失效(变成野指针),不能再使用。
const char* c_str() const { return _str; }

3.3 PushBack

文章配图

Push_Back 接口的作用是在原有 string 的结尾添加一个字符,并将其的长度增加 1。

void string::PushBack(const char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } _str[_size++] = ch; _str[_size] = 0; }

这里需要注意的是当空间不够扩容时不能直接按照 2 倍或者 1.5 倍扩容,首先需要判断 _capacity 是否为 0,不然就会导致新空间的大小一直为 0 导致错误。

运行示例:

文章配图

3.4 Append

文章配图

append 这个接口的核心作用是在当前字符串的末尾追加字符或字符串(支持多种格式)。

  • 核心行为:在字符串有效字符的末尾追加内容,追加后会更新 _size,并保证末尾有 \0。
  • 灵活性:支持多种追加格式(单个字符、C 风格字符串、string 对象、字符串的子串等)。
  • 内存处理:追加前会检查 _size + 追加内容长度 > _capacity,如果满足则触发扩容(和 push_back 逻辑一致),避免内存越界。

在代码中我们只模拟实现一种追加格式就是在原有 string 的末尾追加一段 C 风格字符串,代码如下:

void string::Append(const char* str) { int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len > 2*_capacity ? _size + len : 2 * _capacity); } strcpy(_str+_size,str); _size += len; }

在这里同样需要注意的是,如果追加的字符串加上已有的字符串的长度大于以及开辟的空间大小 _capacity 就会触发扩容。但是我们不能一上来就开始 2 倍扩容,可能 _size+len 的大小比 2_capacity 更大,这时我们就需要判断 _size+len 与 2_capacity 的大小关系。如果 _size+len>2*_capacity 就按照 _size+len 的大小扩容否则就按照 2 倍扩容。

3.5 Insert

文章配图

insert 这个接口的核心作用是在字符串的指定位置插入字符或字符串,相比 append(仅能在末尾追加),insert 提供了更灵活的插入位置选择。

  • 在字符串的 pos 索引位置(从 0 开始)插入内容,插入后原 pos 及之后的字符会向后移动,同时更新 _size,保证末尾有 \0。
  • 插入位置 pos 的有效范围是 [0, _size](pos = _size 等价于在末尾追加,和 append 效果一致),超出范围会触发越界错误。
  • 插入前会检查 _size + 插入内容长度 > _capacity,满足则触发扩容,避免内存越界。
  • 标准库中 insert 返回 string&(当前对象的引用),支持链式调用。
  • 因为插入会导致后续字符向后移动,频繁在字符串头部或中间插入数据,性能开销较大(时间复杂度 O(n))。

在我们的代码中主要实现两个版本,在指定位置插入字符或者在指定位置插入字符串:

//在指定位置插入一个字符 void string::Insert(int pos, const char ch) { assert(pos <= _size && pos>=0); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } int cur = _size; while (cur >= pos) { _str[cur + 1] = _str[cur]; cur--; } _str[pos] = ch; _size++; } //在指定位置插入字符串 void string::Insert(int pos, const char* str) { assert(pos <= _size && pos >= 0); int len = strlen(str); if (_size+len > _capacity) { reserve(_size + len > 2*_capacity ? _size + len : 2 * _capacity); } int cur = _size; while (cur >= pos) { _str[cur + len] = _str[cur]; cur--; } //这里不能 strcpy 因为 strcpy 会将\0 也拷贝进去 strncpy(_str+pos,str,len); _size+=len; }

3.6 erase

文章配图

string 的 erase 接口是用来删除字符串中指定位置或指定范围的字符的,它有三种重载的形式:

删除指定位置的单个字符

// pos:要删除的字符的下标(从 0 开始) string& erase(size_t pos = 0);

从指定位置开始,删除指定长度的字符

// pos:起始删除下标;len:要删除的字符个数 string& erase(size_t pos, size_t len);

删除迭代器指向的字符 / 迭代器范围的字符

// 子形式 3.1:删除迭代器 it 指向的单个字符 iterator erase(iterator it); // 子形式 3.2:删除 [first, last) 区间内的所有字符(左闭右开,不包含 last 指向的字符) iterator erase(iterator first, iterator last);

库中的 erase 返回 string& 主要是支持链式调用,在我们的代码中实现了其中的一种情况即从指定位置删除指定长度的字符:

void string::erase(size_t pos, size_t len) { assert(pos>=0&&pos<_size); if (len >= _size - pos) { _str[pos] = 0; _size -= (_size - pos); } else { while (pos + len <= _size) { _str[pos] = _str[pos + len]; pos++; } _size -= len; } }

文章配图

3.7 substr

文章配图

substr 接口用于从原字符串中截取一段子字符串并返回,它不会修改原字符串本身。注意关键字 const,这表明该成员函数不会修改调用它的 std::string 对象

string string::substr(size_t pos, size_t len) const { assert(pos>=0&&pos<_size); if (len > _size - pos) { len = _size - pos; } string temp; for (int i = pos; i < pos + len; i++) { temp += _str[i]; } return temp; }

需要注意的是,substr 接口必须使用值返回。代码执行到 return temp; 时会调用拷贝构造,当我们不显式地定义时编译器自动会生成一个拷贝构造。但是这里有个大坑,编译器默认生成的拷贝构造是浅拷贝(两个指针会指向同一块空间,运行程序的时候会导致同一空间被析构两次导致程序崩溃!)。所以在实现 substr 接口之前必须显式地定义拷贝构造函数。

下面我们可以将显式实现的拷贝构造函数禁用,使用编译器默认生成的拷贝构造进行浅拷贝复现一下这个 bug:

文章配图

此时我们运行程序会发现提供调用 substr 拿到的字串打印出来是乱码,结果调试我们发现编译器自动生成的拷贝构造并没有为对象开辟新空间,而是将仅仅将就旧空间的地址拷贝到 ret 管理的空间指针中。substr 调用结束里面的临时对象就被析构回收了,此时外部 ret 拿到的就是一段非法空间打印出来的结果自然是乱码。

文章配图

文章配图

所以这就要求我们显式地进行深拷贝,自己定义拷贝构造接口。在我们地的拷贝构造中,并不是单纯的将旧空间地址这一数值拷贝给新对象。而是重新开辟一段空间,首先将要拷贝的数据放到新空间中然后将新空间的地址交给新对象进行管理从而完成深拷贝。

下面当我们自己显式进行深拷贝后,代码的运行结果:

文章配图

3.8 Find

文章配图

find 接口的核心作用是在字符串中查找指定的字符或子串,返回其第一次出现的起始下标,如果查找失败则返回 std::string::npos(这是 size_t 类型的最大值,表示无效下标其可用于判断是否查找成功)

size_t string::find(char ch, size_t pos ) { assert(pos<_size); for (int i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t string::find(const char* str, size_t pos) { assert(pos < _size); char* ret=strstr(_str+pos,str); if (ret != nullptr) { return ret - _str; } return npos; }

在代码中我们们实现了 Find 接口的两种不同形式,在指定位置之后查找指定字符或字符串第一次出现的位置并返回其下标。其中查找字符串我们使用到了 strstr,该函数的核心作用是**在一个 C 风格字符串(以 \0 结尾的字符数组)中查找另一个 C 风格子串的首次出现位置。**但是需要注意的是,strstr 的返回值是一个指向首次出现的起始位置的指针,查找失败则会返回 NULL,因为我们要求 Find 接口的返回值是被查找元素第一次出现位置的下标所以我们还需要计算相对位置。

四、运算符重载

4.1 =

1. 传统写法

string& operator=(const string& str) { if(*this==str) { return; } delete[] _str; reserve(str._capacity); strcpy(_str,str._str); _size = str._size; return *this; }

2. 现代写法

void Swap(string& str) { std::swap(_str,str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); } string& operator=(string str) { Swap(str); return *this; }

在赋值运算符的传统写法中需要我们手动开辟空间并释放旧空间,然后将数据拷贝到新空间中,这种手动管理内存的代码不仅复杂而且还可能存在隐患。而现代写法的赋值运算符重载则要求传值传参,在此过程中拷贝构造函数就自动完成了临时对象 str 的拷贝构造,我们只需要把临时对象的资源交换给当前对象。因为 str 是临时的,而且经过交换之后它所管理的空间和数据都是旧空间和旧数据函数调用结束 str 自动被析构,完成资源的回收。

特性传统写法现代写法(拷贝并交换)
异常安全❌ 存在风险✅ 绝对安全
代码复杂度较高,需手动管理内存极低,依赖拷贝构造和交换
自赋值处理需要额外判断 if (this != &str)✅ 天然支持,无需额外判断
性能正常与拷贝构造性能一致,无额外开销

文章配图

注意:

string s2=s1;//这里调用的是拷贝构造 string s2; s2=s1; //这里调用的才是赋值运算符重载

4.2 <、<=、>、>=、==、!=

bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; } bool operator<=(const string& s1, const string& s2) { return s1 < s2 || s1 == s2; } bool operator>(const string& s1, const string& s2) { return !(s1 <= s2); } bool operator>=(const string& s1, const string& s2) { return !(s1 < s2); } bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) == 0; } bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); }

在实现大小比较的运算符重载时我们只需要通过 strcmp 实现<和==其余运算符都通过复用这两个基础实现来完成,减少了重复代码和出错概率。

还有一点需要注意的是,几个函数的声明不可以放在类内。因为类内的函数参数列表有隐含的 this 指针。

4.3 +=

void operator+=(const char* str) { Append(str); } void operator+=(const char ch) { PushBack(ch); }

4.4 <<、>>

void clear() { _str[0] = 0; _size = 0; }
std::ostream& operator<<(std::ostream& out, const string& s) { for (auto ch : s) { out << ch; } return out; } std::istream& operator>>(std::istream& in, string& s) { s.clear(); const int N = 256; char buff[N]; int i = 0; char ch; //in >> ch; ch = in.get(); while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } //in >> ch; ch = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; } 

在流输入和流输出的代码实现中输出比较简单,就是将 string 中的各个字符依次输出到 ostream 对象中这是最直接的实现方式,时间复杂度为 O (n),n 为字符串长度。

在流输入的代码实现比较复杂,代码流程如下:

  • 清空旧数据:先调用 s.clear(),避免输入内容追加到旧字符串之后。
  • 缓冲区设计:使用一个固定大小(256 字节)的字符数组 buffer 作为临时缓冲区,避免频繁的内存分配。
  • 读取到分隔符为止:用 in.get() 逐个读取字符,直到遇到空格(' ')或换行符('\n')才停止,这让它能读取一整段连续的非空白字符。
  • 缓冲区满时追加:当缓冲区满时,在末尾加上 '\0' 转成 C 风格字符串,追加到目标字符串 s 中,然后重置缓冲区索引。
  • 处理剩余字符:循环结束后,若缓冲区还有未处理的字符,同样追加到 s 中。
  • 返回输入流:返回 in 以支持链式调用。

文章配图

需要注意的是,除了 clear 函数>>和<<运算符重载的函数声明不可以放在类内。因为类内的函数参数列表有隐含的 this 指针。

五、完整代码

#define _CRT_SECURE_NO_WARNINGS #pragma once #include<string.h> #include<assert.h> #include<iostream> class string { public: typedef char* iterator; iterator begin() const { return _str; } iterator end() const { return _str+_size; } void clear() { _str[0] = 0; _size = 0; } void reserve(int n) { if (n > _capacity) { char* temp = new char[n+1]; strcpy(temp,_str); delete[] _str; _str = temp; _capacity = n; } } //构造 string() { _str = new char[1]{'\0'}; _size = _capacity = 0; } //拷贝构造 (C++ 语法明确禁止拷贝构造传值传参,否则会导致无限递归) //string(const string ch) //但是可以传引用传参 //string(const string& ch) string(const char* str) { int len = strlen(str); _str = new char[len + 1]; strcpy(_str, str); _size =_capacity =len; } string(const string& ch) { //reserve(len);构造函数中不能用 reserve,因为该 string 还没完成初始化 strcpy 会访问到 //随机值或者空指针 _str = new char[ch._capacity + 1]; strcpy(_str, ch._str); _size = ch._size; _capacity = ch._capacity; } const char* c_str() const { return _str; } //赋值运算符重载 /*string& operator=(const string& str) { if (*this == str) { return; } delete[] _str; reserve(str._capacity); strcpy(_str,str._str); _size = str._size; return *this; }*/ void Swap(string& str) { std::swap(_str,str._str); std::swap(_size, str._size); std::swap(_capacity, str._capacity); } string& operator=(string str) { Swap(str); return *this; } //尾插一个字符 void PushBack(const char ch); //追加一个字符串 void Append(const char* str); //指定位置插入一个字符 void Insert(int pos, const char ch); //指定位置插入一个字符串 void Insert(int pos, const char* str); //删除指定位置之后的 len 个字符 (包括 pos) void erase(size_t pos, size_t len); //将指定位置之后长度为 len 的子串返回 (包括 pos) string substr(size_t pos, size_t len) const; //从 pos 位置开始寻找字符 ch 或者字符串 str 第一次出现的位置并返回下标 size_t find(char ch, size_t pos = 0); size_t find(const char* str, size_t pos = 0); void operator+=(const char* str) { Append(str); } void operator+=(const char ch) { PushBack(ch); } char operator[](int pos) { assert(pos >= 0&&pos<_size); return _str[pos]; } int size() { return _size; } int capacity() { return _capacity; } ~string() { delete[] _str; _str = NULL; _size = _capacity = 0; } private: char* _str; int _size; int _capacity; static const size_t npos; }; bool operator<(const string& s1, const string& s2); bool operator<=(const string& s1, const string& s2); bool operator>(const string& s1, const string& s2); bool operator>=(const string& s1, const string& s2); bool operator==(const string& s1, const string& s2); bool operator!=(const string& s1, const string& s2); std::ostream& operator<<(std::ostream& out, const string& s); std::istream& operator>>(std::istream& in, string& s);
#include"string.h" //npos 为无符号整形赋值为 -1 会赋值成为整形最大值 const size_t string::npos = -1; void string::PushBack(const char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } _str[_size++] = ch; _str[_size] = 0; } void string::Append(const char* str) { int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len > 2*_capacity ? _size + len : 2 * _capacity); } strcpy(_str+_size,str); _size += len; } void string::Insert(int pos, const char ch) { assert(pos <= _size && pos>=0); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : 2 * _capacity); } int cur = _size; while (cur >= pos) { _str[cur + 1] = _str[cur]; cur--; } _str[pos] = ch; _size++; } void string::Insert(int pos, const char* str) { assert(pos <= _size && pos >= 0); int len = strlen(str); if (_size+len > _capacity) { reserve(_size + len > 2*_capacity ? _size + len : 2 * _capacity); } int cur = _size; while (cur >= pos) { _str[cur + len] = _str[cur]; cur--; } //这里不能 strcpy 因为 strcpy 会将\0 也拷贝进去 strncpy(_str+pos,str,len); _size+=len; } void string::erase(size_t pos, size_t len) { assert(pos>=0&&pos<_size); if (len >= _size - pos) { _str[pos] = 0; _size -= (_size - pos); } else { while (pos + len <= _size) { _str[pos] = _str[pos + len]; pos++; } _size -= len; } } //值返回调用拷贝构造,默认浅拷贝,需要实现深拷贝 string string::substr(size_t pos, size_t len) const { assert(pos>=0&&pos<_size); if (len > _size - pos) { len = _size - pos; } string temp; for (int i = pos; i < pos + len; i++) { temp += _str[i]; } return temp; } size_t string::find(char ch, size_t pos ) { assert(pos<_size); for (int i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t string::find(const char* str, size_t pos) { assert(pos < _size); char* ret=strstr(_str+pos,str); if (ret != nullptr) { return ret - _str; } return npos; } bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; } bool operator<=(const string& s1, const string& s2) { return s1 < s2 || s1 == s2; } bool operator>(const string& s1, const string& s2) { return !(s1 <= s2); } bool operator>=(const string& s1, const string& s2) { return !(s1 < s2); } bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) == 0; } bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); } std::ostream& operator<<(std::ostream& out, const string& s) { for (auto ch : s) { out << ch; } return out; } std::istream& operator>>(std::istream& in, string& s) { s.clear(); const int N = 256; char buff[N]; int i = 0; char ch; //in >> ch; ch = in.get(); while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff; i = 0; } //in >> ch; ch = in.get(); } if (i > 0) { buff[i] = '\0'; s += buff; } return in; } 

目录

  1. 一、整体结构
  2. 二、构造/析构函数
  3. 2.1 默认构造
  4. 2.2 拷贝构造
  5. 2.3 析构函数
  6. 三、功能接口
  7. 3.1 reserve
  8. 3.2 c_str
  9. 3.3 PushBack
  10. 3.4 Append
  11. 3.5 Insert
  12. 3.6 erase
  13. 3.7 substr
  14. 3.8 Find
  15. 四、运算符重载
  16. 4.1 =
  17. 4.2 <、<=、>、>=、==、!=
  18. 4.3 +=
  19. 4.4 <<、>>
  20. 五、完整代码

更多推荐文章

查看全部
  • 大语言模型(LLM)核心原理与实战指南
  • 预训练语言模型与 BERT 实战应用
  • 网络安全入门指南:职业价值观、学习方法与核心工具解析
  • 5 款免费 AIGC 检测工具推荐与论文降重实践指南
  • C 语言实战:比较两个分数的大小
  • P1604 B 进制星球:C++ 高精度加法实现
  • Flutter 三方库 bavard 鸿蒙适配指南:聊天协议与机器人逻辑
  • 多源 BFS 算法详解与经典题目实战
  • 大语言模型训练指南:从基础概念到微调实战
  • Flutter 三方库 ethereum_addresses 的鸿蒙化适配指南
  • 使用 Continue 插件本地部署 AI 代码助手替代 Cursor 或 Copilot
  • LM Studio 本地部署 DeepSeek R1 模型指南
  • Python Anaconda 与 Pip 配置清华镜像源指南
  • 实测 ToClaw 信息检索与分析能力:AI 实现先找再写
  • 汇川 RobotLab 软件常规操作指南
  • 通义万相 2.1 技术解析:多模态生成的突破与应用
  • 利用 Qoder 自然语言生成动漫短剧的 AIGC 智能体实战
  • C++ 红黑树原理与实现详解
  • Midjourney AI 绘图工具使用指南与基础操作教程
  • 斯坦福 2025 AI Index Report 深度解读:技术、产业与治理趋势

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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