C++学习之旅【C++String类介绍】


🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法初阶》

《C++初阶知识内容》《Linux系统知识》

✨逆境不吐心中苦,顺境不忘来时路! 🎬 博主简介:

引言:前篇文章,小编已经介绍了关于C++内存管理、模板初阶以及STL简介的相关知识.接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++String类以及实现String类的接口,那么这里面到底有哪些知识需要我们去学习的呢?废话不多说,带着这些疑问,下面跟着小编的节奏🎵一起学习吧!

目录

1. 为什么学习string类?

关于C语言中的字符串,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP(面向对象编程)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问.C 语言风格的字符数组(char[]/char*),而 string 类是C++ 为解决字符数组的天生缺陷而生的最优字符串解决方案
C语言写字符数组的劣势:
✅ 痛点1:必须手动维护结束符 \0,极易出错.
C语言的字符数组,必须在最后一位手动添加\0才被认定为合法字符串,如果忘记加、或者覆盖了 \0,就会出现内存乱码、程序崩溃、读取越界等问题.

痛点2:内存必须手动申请、手动释放,极易造成内存问题.
字符数组的内存大小是固定的、编译期确定的,如果想存更长的字符串,只能用malloc/new手动申请堆内存,用完后必须用 free/delete 释放 ------但凡忘记释放,就会造成内存泄漏;释放早了,就会出现野指针;申请的内存不够,就会缓冲区溢出.

痛点3;字符串操作极度繁琐,必须依赖 C 库函数
C语言没有字符串的原生操作能力,拼接、比较、查找、截取 这些最常用的字符串功能,都必须调用 库的函数:strcat()、strcmp()、strcpy()、strstr() 等。
①拼接字符串用 strcat(a,b),比较字符串用 strcmp(a,b)(不能用 ==);
②函数参数多、返回值规则复杂,比如 strcmp 返回 0/1/-1,初学者极易记混;
③所有函数不做边界检查,比如 strcat 会无脑往目标数组后拼接,不管数组容量够不够,直接造成 缓冲区溢出(黑客攻击的常见漏洞来源).

痛点4:数组长度固定,无法动态扩容
字符数组的长度在定义时就确定了,比如 char str[10] 最多只能存 9 个有效字符(留1个存\0),如果后续想存更长的字符串,没有任何办法直接扩容,只能手动重新申请更大的内存、拷贝原数据、释放旧内存,代码量翻倍,还容易出错.
string类写字符数组的优势:
✅ 优势1:无需手动管理内存,自动扩容 + 自动释放(最大优势)
这是string类最核心、最实用的优势,没有之一!
string 内部封装了字符数组的内存,内存的申请、扩容、释放,全部由类的底层自动完成,完全不需要你写malloc/free、new/delete,彻底杜绝内存泄漏、野指针、缓冲区溢出这三类最常见的内存问题.
✅ 优势2:无需手动维护 \0 结束符,彻底告别乱码
string 类的底层虽然也是字符数组,但 \0 的添加、维护、校验,全部由 string 内部自动完成.
你只需要专注于存什么字符,不用关心结尾要不要加\0,string 会帮你处理好所有细节,永远不会出现因 \0 导致的乱码、越界问题.
✅ 优势3:支持原生运算符操作,语法极简,像用普通变量一样用字符串
这是 string类最爽的特性 ------ C++ 为 string 重载了我们最熟悉的运算符,字符串的赋值、拼接、比较、取值,全部可以用直觉化语法实现,完全不用记任何库函数,代码可读性拉满,这也是C++ 风格的核心体现.
✅ 优势4:自带丰富且安全的内置成员方法,一站式解决所有字符串需求
string 类封装了所有你能想到的字符串操作,这些方法都是,成员函数,调用简单,且全部做了边界检查,绝对不会出现越界、溢出的问题.不用再去查 库的函数手册.
✅ 优势5:彻底杜绝缓冲区溢出,天生安全
C 语言的 strcpy、strcat 是无边界检查的,只要目标数组容量不够,就会直接往内存里写数据,造成缓冲区溢出.
✅ 优势6:完美兼容 C++ STL 容器 / 算法,扩展性拉满
std::string 本质是 C++ STL 标准容器的一员(和vector、map同属 STL),它支持:
迭代器遍历(s.begin()、s.end());
直接使用 STL 算法(比如排序sort(s.begin(),s.end())、查找find(s.begin(),s.end(),'a'));
无缝和其他容器结合(比如vector存储字符串数组).


2.string类的了解

string类的文档介绍

接下来关于string类的学习我会通过上面的文档链接进行介绍和学习.
在使用string类时,必须包含#include头文件以及using namespace std.

2.1auto和范围for

auto关键字,在这里补充2个C++11的小语法,方便我们后面的学习.

1️⃣在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了.C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得.

2️⃣用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

3️⃣当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量.

4️⃣auto不能作为函数的参数,可以做返回值,但是建议谨慎使用.

5️⃣auto不能直接用来声明数组.

cpp 复制代码
#include<iostream>
using namespace std;
int func1()
{
return 10;
}
// 不能做参数
void func2(auto a)
{}
// 可以做返回值,但是建议谨慎使用
auto func3()
{
return 3;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
// 编译报错:rror C3531: "e": 类型包含"auto"的符号必须具有初始值设定项
auto e;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
int x = 10;
auto y = &x;
auto* z = &x;
auto& m = x;
cout << typeid(x).name() << endl;
cout << typeid(y).name() << endl;
cout << typeid(z).name() << endl;
auto aa = 1, bb = 2;
// 编译报错:error C3538: 在声明符列表中,"auto"必须始终推导为同一类型
auto cc = 3, dd = 4.0;
// 编译报错:error C3318: "auto []": 数组不能具有其中包含"auto"的元素类型
auto array[] = { 4, 5, 6 };
return 0;
}
cpp 复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
"橙子" }, {"pear","梨"} };
// auto的用武之地
//std::map<std::string, std::string>::iterator it = dict.begin();
auto it = dict.begin();
while (it != dict.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
return 0;
}

范围for

1️⃣对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误.因此C++11中引入了基于范围的for循环.for循环后的括号由冒号" :"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束.

2️⃣范围for可以作用到数组和容器对象上进行遍历

3️⃣范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到.

cpp 复制代码
#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << endl;
}
// C++11的遍历
for (auto& e : array)
e *= 2;
for (auto e : array)
cout << e << " " << endl;
string str("hello world");
for (auto ch : str)
{
cout << ch << " ";
}
cout << endl;
return 0;
}

3.C++官方中string类部分内容

3.1string类介绍

这是C++ 标准库中 std::string 类的官方文档说明 ,核心是解释 std::string 的本质、定义和注意.

1️⃣类的基本信息
所属头文件std::string 要包含 <string> 头文件才能使用.
类的定义std::string 不是独立的类,而是通过 typedef basic_string<char> string; 定义的------它是 basic_string 模板类针对 char 类型的实例化版本 (可以理解为:basic_string 是个通用的"字符序列模板",string 是它专门给单字节字符(比如英文、数字)用的"现成版本").

2️⃣string的本质

它是表示字符序列的对象 ,不是 C 语言那种简陋的字符数组------是 C++ 封装好的、带功能的字符串对象.

3️⃣接口与功能特点
std::string 的用法和 STL 里的"字节容器"(比如 vector)很像,但额外加了专门给单字节字符串设计的功能 (比如字符串拼接、查找、替换这些字符数组做不到的便捷操作).

4️⃣模板的底层关联
stringbasic_string 模板类的"特化":

模板参数用了 char(字符类型);

同时用了默认的 char_traits(负责字符的比较、复制等基础操作)和 allocator(负责内存分配).

5️⃣关键注意点
std::string按"字节"处理数据 的,不是按"实际字符":

比如处理 UTF-8 编码的中文(一个汉字通常占 3 字节)时,length()/size() 得到的是"字节数"(比如一个汉字会算成3),迭代器也是按字节遍历------它不认识"多字节字符",只认"字节".


3.2npos介绍

这是 std::string 类里的静态成员常量 npos 的官方说明 ,核心讲它的定义、本质和用途.

1️⃣基本定义
std::string::nposstring 类的公共静态常量 ,定义为:

static const size_t npos = -1;

注意:size_t无符号整型 ,给它赋值 -1 会触发"无符号数溢出",最终 npos 会变成 size_t 类型能表示的最大值 (比如 64位系统中通常是 18446744073709551615).

2️⃣核心本质
npossize_t 类型的最大值 ,是 string 类专门用来表示"特殊状态"的标记值.

3️⃣两个主要用途

这是 npos 最常用的场景:
作为函数参数(比如 len/sublen :表示"一直处理到字符串末尾".

例:s.substr(2, string::npos) → 从索引2开始,截取到字符串最后一个字符.
作为函数返回值 :表示"没有找到匹配项".

例:s.find("abc") == string::npos → 说明字符串 s 里没有"abc"这个子串.


3.3operator[]介绍

这是 std::string 类的下标运算符 operator[]的官方说明 ,核心讲它的作用、用法和注意事项.

1️⃣函数作用
operator[]string 类的下标运算符重载 ,用来获取字符串中指定位置的字符 (类似数组的下标访问方式).

2️⃣函数的两个版本(适配不同对象)
string 提供了两种重载形式,对应可修改的字符串对象和不可修改的const字符串对象:
char& operator[](size_t pos);

→ 用于非const的string对象 ,返回字符的可修改引用 (可以通过它修改对应位置的字符).
const char& operator[](size_t pos) const;

→ 用于const的string对象 ,返回字符的不可修改引用 (只能读取,不能修改).

3️⃣参数说明

参数 pos:表示要访问的字符的位置索引 ,类型是 size_t(无符号整型).

注意:字符串的索引是从0开始 的(第一个字符是 pos=0,最后一个有效字符是 pos = 字符串长度-1).

4️⃣特殊规则(C++11后新增)

如果 pos 等于字符串的长度 (即 pos = s.size(),超出最后一个有效字符的位置):

若字符串是 const 限定的 → 函数会返回空字符 '\0' 的引用 (这是C++11规定的安全行为).

若字符串是非 const 的 → 属于未定义行为(可能导致程序崩溃、内存乱码等).


3.4reverse介绍

这是 STL算法库中 std::reverse 函数模板的官方说明 ,核心讲它的作用、用法和实现逻辑.

1️⃣基本信息
所属头文件 :需要包含 <algorithm> 才能使用.
类型 :函数模板,模板参数是双向迭代器(BidirectionalIterator)(支持前后移动的迭代器,比如 stringvector 的迭代器都属于这种).

2️⃣核心功能
reverse 的作用是 反转左闭右开区间 [first, last)内的元素顺序 ([first, last) 表示包含 first 指向的元素,不包含 last 指向的元素).

3️⃣实现逻辑

函数内部通过调用 iter_swap(交换两个迭代器指向的元素)来完成反转,文档里给出了等价的代码

逻辑是:从区间的两端向中间 逐步交换元素,直到迭代器 firstlast 相遇,完成整个范围的反转.

4️⃣适用场景

只要是支持双向迭代器的容器(比如 std::stringstd::vectorstd::list 等),都可以用 reverse 反转其元素:反转整个容器 :用 begin()end() 作为参数(比如 reverse(s.begin(), s.end()) 反转整个字符串 s);

反转部分范围 :指定对应区间的迭代器(比如 reverse(v.begin()+2, v.end()-1) 反转 vector 从第3个元素到倒数第2个元素的范围).

⭐️⭐️⭐️
迭代器的意义:
①统一类似的方式遍历修改容器
②算法脱离具体底层结构,和底层结构解耦(降低耦合,降低关联关系),算法独立模板实现针对多个容器处理


3.5begin和rbegin介绍

这是 std::string 类的 begin() 成员函数说明 ,核心讲它的定义与功能:

1️⃣基本信息

所属:std::string公共成员函数 ,需包含 <string> 头文件.

版本支持:C++98 及之后版本(C++11 无功能变更).

2️⃣函数重载形式
begin() 有两个重载版本,适配不同类型的 string 对象:

①用于非const的string对象: iterator begin();

②用于const的string对象: const_iterator begin() const;

3️⃣核心功能

标题"Return iterator to beginning"的含义是:

返回一个迭代器 ,该迭代器指向字符串的第一个字符 (即字符串的起始位置).

补充说明:

string 是普通非const对象,begin() 返回的 iterator 可以修改指向的字符;

string 是const对象,begin() 返回的 const_iterator 只能读取字符、不能修改.

这是 std::string 类的 rbegin() 成员函数说明 ,核心讲它的定义、功能和反向迭代器的特点:

1️⃣基本信息

所属:std::string公共成员函数 ,需包含 <string> 头文件,支持 C++98 及之后版本.

重载形式:适配不同类型的 string 对象,返回反向迭代器 :

①用于非const的string对象,返回可修改的反向迭代器: reverse_iterator rbegin();

②用于const的string对象,返回不可修改的const反向迭代器: const_reverse_iterator rbegin() const;

2️⃣核心功能

标题"Return reverse iterator to reverse beginning"的含义是:

返回一个反向迭代器 ,该迭代器指向字符串的最后一个字符 (也就是"反向遍历的起始位置").

3️⃣反向迭代器的关键特点

反向迭代器是"反向遍历专用"的,和普通迭代器的行为相反:

对反向迭代器执行 ++(递增)操作,会往字符串的开头方向移动
rbegin() 指向的位置,恰好是 end()(普通迭代器指向的"字符串末尾后一位")的前一个字符(即字符串的最后一个有效字符).


4.string类对象的容量操作

4.1size介绍

这是 std::string 类的 size() 成员函数说明 ,核心讲它的功能、返回值及注意事项:

1️⃣基本信息

所属:std::string公共const成员函数 ,需包含 <string> 头文件,支持 C++98 及之后版本.

函数签名:size_t size() const;(返回类型是无符号整型 size_t).

2️⃣核心功能

标题"Return length of string"的含义是:

返回字符串的长度(按字节数计算) ,即字符串内容实际占用的字节数量.

3️⃣关键细节
与容量(capacity)的区别size() 返回的是"实际使用的字节数",而 string 的存储容量(capacity())是"已分配的内存字节数",两者不一定相等(容量通常大于等于size).
编码相关注意点string 仅按"字节"处理数据,不识别字符编码.若字符串包含多字节字符(比如UTF-8编码的中文,1个汉字占3字节),size() 返回的字节数≠实际字符数 .
length() 的关系string::sizestring::length同义词,功能、返回值完全一致(只是命名不同).


4.2length介绍

这是 std::string 类的 length() 成员函数说明 ,核心讲它的功能、返回值及核心特点:

1️⃣基本信息

所属:std::string公共const成员函数 ,需包含 <string> 头文件,支持 C++98 及之后版本

函数签名:size_t length() const;(返回无符号整型 size_t,且不会修改字符串本身).

2️⃣核心功能

标题"Return length of string"的含义是:

返回字符串的长度(以字节为单位) ,即字符串内容实际占用的字节数量.

3️⃣关键细节
与存储容量(capacity)的区别length() 返回的是"实际使用的字节数",而 stringcapacity() 是"已分配的内存字节数",两者不一定相等(容量通常大于等于实际长度).
编码相关注意string 仅按"字节"处理数据,不识别字符编码.若字符串包含多字节字符(比如UTF-8编码的中文,1个汉字占3字节),length() 返回的字节数 ≠ 实际字符数 .
size() 的关系string::lengthstring::size完全同义词 ,功能、返回值完全一致(只是命名不同,实际开发中两者可以互换使用).
⭐️注意⭐️
①size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size().
②clear()只是将string中有效字符清空,不改变底层空间大小.
③resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, charc)用字符c来填充多出的元素空间.注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变.
④reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小.


4.3capacity介绍

这是 std::string 类的 capacity() 成员函数说明 ,核心讲它的功能、与字符串长度的区别.

1️⃣基本信息

所属:std::string公共const成员函数 ,需包含 <string> 头文件,支持 C++98 及之后版本.

函数签名:size_t capacity() const;(返回无符号整型 size_t,且不会修改字符串本身).

2️⃣核心功能

标题"Return size of allocated storage"的含义是:

返回当前为字符串分配的存储空间大小 ,单位是字节(即字符串实际占用的内存总容量).

3️⃣关键细节
与字符串长度(size/length)的区别
capacity 是"分配的总内存字节数",size/length 是"实际使用的字节数"------capacity 通常大于等于 字符串长度,额外的空间是为了优化后续添加字符的操作(避免每次新增字符都重新分配内存).
不是长度的限制
capacity 不代表字符串的最大长度.当现有容量被用完、需要更多空间时,string自动扩容 (重新分配更大的内存);字符串长度的理论上限由 max_size() 决定.
修改时的特性

只要 string 对象被修改(哪怕是缩小长度),capacity 都可能被改变(这和 vectorcapacity 保证不同,vector 缩小长度时不会主动释放容量).
手动控制容量

可以通过 reserve() 成员函数,显式调整 string 的容量.


4.4resize介绍

这是 std::string 类的 resize() 成员函数说明 ,核心讲它的功能与不同场景下的行为:

1️⃣基本信息

所属:std::string 的公共成员函数,需包含 <string> 头文件.

重载版本:提供两种调用方式:

void resize(size_t n); 调整长度为n,扩展时用空字符填充.
void resize(size_t n, char c); 调整长度为n,扩展时用字符c填充.
2️⃣核心功能
resize() 的作用是 将字符串的长度调整为 n 个字符 ,行为分两种场景:
n < 当前字符串长度
字符串会被
截断
,只保留前 n 个字符,后面的字符会被删除.
n > 当前字符串长度

字符串会在末尾扩展 ,补充足够的字符直到长度为 n;若指定了 c,补充的字符是 c;没指定的话,补充的是空字符('\0'.


4.5reserve介绍

这是 C++ 标准库中 std::string::reserve 函数的说明,作用是预分配字符串的容量 ,核心要点如下

1️⃣函数原型
void reserve (size_t n = 0);

2️⃣功能

请求将字符串的容量(capacity)调整为能容纳至少 n 个字符的大小(用于提前规划字符串的扩容).
3️⃣具体功能
n 大于当前容量 :容器会将容量增大到 n(或更大,由实现决定).
n 小于/等于当前容量 :这是一个"非强制"的缩容请求------容器可以选择不缩容(保持容量大于 n),具体由实现优化决定.
4️⃣注意点
该函数
不影响字符串的长度(length/size)
,也不会修改字符串的内容 .

简单说:reserve(n) 是提前给字符串"预留空间",避免后续频繁扩容的性能开销;缩容则是建议性的,不一定生效.


4.6clear介绍

这是 C++ 标准库中 std::string::clear 函数的说明,核心信息如下:

1️⃣函数原型
void clear(); 从 C++98 开始支持,C++11 也兼容该接口.

2️⃣功能

擦除字符串的所有内容,将其变为空串 .

3️⃣实际效果

执行后,字符串的长度(length/size)会变为 0 ,但容量(capacity)不会改变 (即之前预分配的内存空间依然保留).

简单总结:clear() 是用来"清空字符串内容"的,执行后字符串变成空串,但预留的内存空间不会释放.


4.7empty介绍

这是 C++ 标准库中 std::string::empty 函数的说明,核心信息如下:

1️⃣函数原型
bool empty() const;

从 C++98 开始支持,C++11 也兼容;const 修饰表示它不会修改字符串本身 .

2️⃣功能

用于判断字符串是否为空

3️⃣判断逻辑

返回 true(空串)的条件是"字符串的长度(length)为 0";否则返回 false(非空串).

4️⃣注意点

该函数仅做"查询",不会改变字符串的内容 ;若要清空字符串,需使用 string::clear 函数.

简单说:empty() 是个"只读"的判断工具,专门用来确认字符串是不是空的.


5.string类对象的修改操作

5.1push back 介绍

这是 C++ 标准库中 std::string::push_back 成员函数的说明,核心信息如下:

1️⃣函数原型
void push_back (char c);

2️⃣功能

把单个字符 c 追加到字符串的末尾 .

3️⃣执行效果

追加完成后,字符串的长度(length)会增加 1 (因为多了一个字符).

简单总结:push_back(c) 是给字符串"末尾加一个字符"的工具,专门用于单个字符的追加操作.


5.2append介绍

这是 C++ 标准库中 std::string::append 成员函数的说明,它的核心作用是在字符串末尾追加内容 ,支持多种重载形式(适配不同的追加场景,从 C++98 到 C++14 逐步扩展).
各重载版本的功能

1️⃣版本1(追加整个string)
string& append (const string& str);

把另一个 string 对象 str 的全部内容追加到当前字符串末尾.

2️⃣版本2(追加string的子串)
string& append (const string& str, size_t subpos, size_t sublen);

追加 str从subpos位置开始、长度为sublen 的子串.

3️⃣版本3(追加C风格字符串)
string& append (const char s); *

追加以 \0 结尾的C风格字符串 s(整个字符串).

4️⃣版本4(追加C风格字符串的前n个字符)
string& append (const char s, size_t n); *

只追加C风格字符串 s前n个字符 (不管是否包含\0).

5️⃣版本5(追加n个重复字符)
string& append (size_t n, char c);

在当前字符串末尾追加 n 个相同的字符 c.

6️⃣版本6(追加迭代器范围的内容)
template < class InputIterator>
string& append (InputIterator first, InputIterator last);

模板重载,追加迭代器 [first, last) 范围内的所有字符(比如其他容器的一段内容).
核心效果

无论使用哪个重载,append 都会在当前字符串末尾追加内容,执行后字符串的长度会增加对应字符数.


5.3operator+=介绍

这是 C++ 标准库中 std::string::operator+=(+=运算符重载)的说明,核心作用是在字符串末尾追加内容 ,是更简洁的"追加操作"语法,支持3种基础重载形式(从C++98开始支持):
重载版本与功能

1️⃣版本1(追加string对象)
string& operator+= (const string& str);

把另一个 string 对象 str 的全部内容追加到当前字符串末尾.

2️⃣版本2(追加C风格字符串)
string&operator+= (const char*s);

追加以 \0 结尾的C风格字符串 s(整个字符串).

3️⃣版本3(追加单个字符)
string& operator+= (char c);

把单个字符 c 追加到当前字符串末尾.
核心特点
operator+= 的功能和 append 类似(都是末尾追加),但重载形式更少 (append 支持子串、迭代器范围等更多追加场景);它的优势是语法更简洁直观.


5.4c_str介绍

这是 C++ 标准库中 std::string::c_str 成员函数的说明,核心信息如下:

1️⃣函数原型
const char*c_str() const;

从C++98 开始支持,const 修饰表示它不会修改 string 对象本身 .

2️⃣核心功能

返回一个指向以空字符(\0)结尾的字符数组 的指针(即"C风格字符串"),用于表示当前 string 对象的内容.

这个字符数组的内容 = string 的现有字符序列 + 末尾额外添加的 \0 终止符(C风格字符串的标志).

3️⃣关键注意事项
不可修改 :返回的是 const char*,程序不能修改 这个字符数组里的任何内容.
指针可能失效 :如果后续调用了修改 string 的成员函数(比如 appendpush_back 等),c_str() 返回的指针可能会失效(因为 string 内存可能被重新分配).

简单总结:c_str() 是用来把 C++ 的 string 转换成 C 风格字符串的工具,方便兼容 C 语言的接口,但使用时要注意指针的有效性和不可修改性.


5.5substr介绍

这是 C++ 标准库中 std::string::substr 成员函数的说明,核心信息如下:

1️⃣函数原型
string substr (size_t pos = 0, size_t len = npos) const;
pos:子串的起始位置 ,默认从字符串开头(pos=0)开始.
len:子串的长度 ,默认值 npos 表示"取到原字符串的末尾".
const 修饰:该函数不会修改原字符串.

2️⃣核心功能

返回一个新构造的 string 对象 ,其内容是原字符串的"子串"(原字符串的一部分).

3️⃣子串的取值逻辑

子串是从原字符串的 pos 位置开始,连续取 len 个字符;如果 len 超过了"从 pos 到原字符串末尾的剩余字符数",则只取到原字符串的末尾.

简单总结:substr(pos, len) 是从原字符串中"截取一段内容,生成新字符串"的工具.不会影响原字符串本身.
⭐️注意⭐️
①在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串.
②对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好.


6.string类非成员函数

6.1operator+

这是 C++ 标准库中 std::operator+(针对 string 的加号运算符重载)的说明,核心作用是拼接字符串/字符 ,返回新的 string 对象,支持多种拼接场景:

1️⃣重载版本分类
版本1(string+string)
string operator+ (const string& lhs, const string& rhs);

拼接两个 string 对象.
版本2(string与C风格字符串互加)
string operator+ (const string& lhs, const char*rhs);
string operator+ (const char*lhs, const string& rhs);

拼接 string 和 C风格字符串(char*).
版本3(string与单个字符互加)
string operator+ (const string& lhs, char rhs);
string operator+ (char lhs, const string& rhs);

拼接 string 和单个字符.

2️⃣核心功能

返回一个新构造的 string 对象 ,其内容是"左操作数(lhs)的字符序列 + 右操作数(rhs)的字符序列"的拼接结果.

3️⃣C++11 新增特性(性能优化)

若参数包含右值引用 (比如临时 string 对象),返回的新对象会通过移动构造 实现(减少内存拷贝,提升性能);若两个参数都是右值引用,只会移动其中一个(具体移动哪个由实现决定),另一个保留原内容.

注意:operator+ 是"生成新字符串",不会修改原操作数的内容.


6.2operator>>介绍

这是 C++ 标准库中针对 stringoperator>> 流提取运算符重载 说明,核心信息如下:

1️⃣函数原型
istream& operator>> (istream& is, string& str);

2️⃣核心功能

从输入流 is(比如 cin)中提取字符串 ,并将结果存储到 str 里------str 原有的内容会被覆盖 .

3️⃣提取逻辑

它的行为和"C风格字符串的流提取"一致:提取到的每个字符,会像 push_back 那样追加到 str 中.

4️⃣关键限制

输入流的提取操作以空白符(空格、换行、制表符等)为分隔符 ,所以这个操作只能提取"一个单词"(遇到空白符就停止).

如果要提取整行文本 (包含空白符),需要用 getline 函数.

简单说:cin >> str 是"从输入里读一个单词到字符串".但读不了带空格的整行.


6.3operator<<介绍

这是 C++ 标准库中针对 stringoperator<< 流插入运算符重载 说明,核心信息如下:

1️⃣函数原型
ostream& operator<< (ostream& os, const string& str);

2️⃣核心功能

string 对象 str 中的字符序列,插入到输出流 os (比如常用的 cout 就是输出流).

3️⃣行为特点

它的效果和"C风格字符串的 ostream::operator<<"一致,只是专门适配了 C++ 的 string 对象------简单说就是实现了"直接用输出流打印 string"的功能.

举个常见场景:cout << "hello" << str; 里的 << str 就是调用这个重载,把 str 的内容输出到控制台.


6.4getline介绍

这是 C++ 标准库中针对 stringstd::getline 函数说明,核心作用是从输入流读取字符到字符串(支持自定义结束分隔符) ,具体信息如下:

1️⃣函数重载版本
版本1(自定义分隔符)
istream& getline (istream& is, string& str, char delim);
版本2(默认分隔符)
istream& getline (istream& is, string& str);

等价于版本1中 delim='\n'(默认以"换行符"为结束标志).

2️⃣核心功能

从输入流 is(比如 cin)中提取字符并存储到 str,直到遇到以下情况之一停止:

①碰到分隔符 delim (版本2是换行符);

②输入流到达末尾(文件结束);

③输入过程出错.

3️⃣关键细节

分隔符的处理:会被"提取但丢弃"(不会存到 str 里,下一次输入从分隔符后开始);
str 的内容替换:调用 getline 后,str 原有的内容会被新提取的字符序列覆盖;

字符的存储方式:提取的字符会以 push_back 的方式追加到 str(实际是先清空 str 再追加).

简单总结:getline 是解决"operator>>只能读单个单词"的工具,能读取包含空格的整段内容(直到指定分隔符为止).


6.5relational operators介绍

这是C++标准库中std::string关系运算符重载说明 ,核心用于实现字符串的比较操作,信息如下:

1️⃣包含的运算符类型

涵盖6类关系运算:

相等(==)、不等(!=

小于(<)、小于等于(<=

大于(>)、大于等于(>=

2️⃣支持的操作数组合

每个运算符均重载了3种场景(以==为例):
string对象 vs string对象(如str1 == str2

C风格字符串(const char*)vs string对象(如"abc" == str
string对象 vs C风格字符串(如str == "abc"

3️⃣比较逻辑

所有运算符的比较依据是字典序(逐字符按ASCII值比较) ,底层通过string::compare函数实现.

4️⃣作用

实现字符串(含与C风格字符串)之间的关系判断,例如判断两个字符串是否相等、字符串的字典序大小等,是字符串逻辑判断的基础工具.
上面的几个接口大家了解一下,下面的OJ题目中会有一些体现他们的使用.string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可.


7.vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节.

7.1vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义.
string中字符串的存储空间:
①当字符串长度小于16时,使用内部固定的字符数组来存放.
②当字符串长度大于等于16时,从堆上开辟空间.
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高.
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情.
故总共占16+4+4+4=28个字节.

cpp 复制代码
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

7.2g++下string的结构

g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
①空间总大小②字符串有效长度
③引用计数④指向堆空间的指针,用来存储字符串.

cpp 复制代码
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};

8.关于string类一些oj算法题

8.1把字符串转换成整数

cpp 复制代码
class Solution {
  public:
    int StrToInt(string str) {
        // 1. 处理空字符串
        if (str.empty()) return 0;
        long long num = 0; // 用long long避免中间计算溢出
        int sign = 1;      // 符号:1为正,-1为负
        int i = 0;         // 遍历起始索引
        // 2. 处理首位符号(仅允许+/-出现在首位)
        if (str[0] == '+' || str[0] == '-') {
            sign = (str[0] == '-') ? -1 : 1;
            i = 1; // 符号后从索引1开始遍历
        }
        // 3. 遍历字符串,逐字符转换为数字
        for (; i < str.size(); ++i) {
            // 遇到非数字字符,直接返回0(非法)
            if (str[i] < '0' || str[i] > '9') {
                return 0;
            }
            // 累加计算数值
            num = num * 10 + (str[i] - '0');
            // 4. 检查数值是否溢出int范围
            if (sign == 1 && num > INT_MAX) {
                return 0;
            }
            if (sign == -1 && -num < INT_MIN) {
                return 0;
            }
        }
        return static_cast<int>(num * sign);
    }
};

8.2字符串相加

cpp 复制代码
//方法一:时间复杂度为O(N^2)
class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size()-1, end2 = num2.size()-1;
        int carry = 0; // 进位
        while(end1 >= 0 || end2 >= 0)
        {
            int x1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int x2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int val = x1 + x2 + carry;
            carry = val / 10;
            val = val % 10;
            // str.insert(0, 1, ('0'+val));
            str.insert(str.begin(), ('0'+val));
        }
        if(carry == 1)
            str.insert(str.begin(), '1');
        return str;
    }
};
cpp 复制代码
//方法二:时间复杂度为O(N)
class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        str.reserve(max(num1.size(), num2.size())+1);
        int end1 = num1.size()-1, end2 = num2.size()-1;
        int carry = 0; // 进位
        while(end1 >= 0 || end2 >= 0)
        {
            int x1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int x2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int val = x1 + x2 + carry;
            carry = val / 10;
            val = val % 10;
            str += ('0' + val);
        }
        if(carry == 1)
            str += '1';
        reverse(str.begin(), str.end());
        return str;
    }
};

8.3仅仅反转字母

cpp 复制代码
class Solution {
public:
    bool isLetter(char ch)
    {
        if(ch >= 'a' && ch <= 'z')
            return true;
        if(ch >= 'A' && ch <= 'Z')
            return true;
        return false;
    }
    string reverseOnlyLetters(string s) {
        size_t begin = 0, end = s.size()-1;
        while(begin < end)
        {
            while(begin < end && !isLetter(s[begin]))
                ++begin;
            while(begin < end && !isLetter(s[end]))
                --end;
            swap(s[begin], s[end]);
            ++begin;
            --end;
        }
        return s;
    }
};

8.4字符串中的第一个唯一字符

cpp 复制代码
class Solution {
public:
    int firstUniqChar(string s) {
        int count[26] = {0};
        // 统计次数
        for(auto ch : s)
        {
            count[ch-'a']++;
        }
        for(size_t i = 0; i < s.size(); ++i)
        {
            if(count[s[i]-'a'] == 1)
                return i;
        }
        return -1;
    }
};

8.5验证回文串

cpp 复制代码
class Solution {
public:
bool isLetterOrNumber(char ch)
{
return (ch >= '0' && ch <= '9')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= 'A' && ch <= 'Z');
}
bool isPalindrome(string s) {
// 先小写字母转换成大写,再进行判断
for(auto& ch : s)
{
if(ch >= 'a' && ch <= 'z')
ch -= 32;
}
int begin = 0, end = s.size()-1;
while(begin < end)
{
while(begin < end && !isLetterOrNumber(s[begin]))
++begin;
while(begin < end && !isLetterOrNumber(s[end]))
--end;
if(s[begin] != s[end])
{
return false;
}
else
{
++begin;
--end;
}
}
return true;
}
};

8.6反转字符串

cpp 复制代码
class Solution {
public:
    void reverseString(vector<char>& s) {
        int left = 0;                  // 左指针:起始位置
        int right = s.size() - 1;      // 右指针:末尾位置
        while (left < right) {         // 指针未相遇时持续交换
            swap(s[left], s[right]);   // 交换左右指针的字符
            left++;                    // 左指针右移
            right--;                   // 右指针左移
        }
    }
};

8.7字符串最后一个单词的长度

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
string line;
// 不要使用cin>>line,因为会它遇到空格就结束了
// while(cin>>line)
while(getline(cin, line))
{
size_t pos = line.rfind(' ');
cout<<line.size()-pos-1<<endl;
}
return 0;
}

8.8反转字符串||

cpp 复制代码
class Solution {
public:
    string reverseStr(string s, int k) {
        int n = s.size();
        // 按 2k 步长分组遍历
        for (int i = 0; i < n; i += 2 * k) {
            // 确定反转的结束位置:不超过字符串长度
            int end = min(i + k, n);
            // 反转当前组的前 k 个字符(左闭右开区间)
            reverse(s.begin() + i, s.begin() + end);
        }
        return s;
    }
};

8.9反转字符串中的单词|||

cpp 复制代码
class Solution {
public:
    string reverseWords(string s) {
        int n = s.size();
        int start = 0; // 标记当前单词的起始下标
        for (int i = 0; i <= n; ++i) {
            // 遇到空格 或 遍历到字符串末尾 → 当前单词结束
            if (i == n || s[i] == ' ') {
                // 反转当前单词:[start, i) 区间的字符
                reverse(s.begin() + start, s.begin() + i);
                start = i + 1; // 更新下一个单词的起始下标
            }
        }
        return s;
    }
};

8.10字符串相乘

cpp 复制代码
class Solution {
public:
    string multiply(string num1, string num2) {
        // 特殊情况:有一个数是0,直接返回"0"
        if (num1 == "0" || num2 == "0") {
            return "0";
        }
        int m = num1.size();
        int n = num2.size();
        vector<int> res(m + n, 0); // 乘积最大长度为m+n
        // 从后往前遍历num1的每一位
        for (int i = m - 1; i >= 0; --i) {
            int a = num1[i] - '0'; // 字符转数字
            // 从后往前遍历num2的每一位
            for (int j = n - 1; j >= 0; --j) {
                int b = num2[j] - '0'; // 字符转数字
                int sum = a * b + res[i + j + 1]; // 乘积 + 当前位已有值
                res[i + j + 1] = sum % 10; // 个位存入当前位置
                res[i + j] += sum / 10;    // 十位作为进位存入前一位
            }
        }
        // 数组转字符串,跳过前导零
        string result;
        for (int num : res) {
            if (result.empty() && num == 0) {
                continue; // 跳过开头的0
            }
            result.push_back(num + '0'); // 数字转字符
        }
        return result;
    }
};

8.11找出字符串中第一个只出现一次的字符

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main() {
    string s;
    cin >> s;
    int count[26] = {0}; // 用数组统计26个小写字母的出现次数
    // 第一次遍历:统计每个字符的出现次数
    for (char c : s) {
        count[c - 'a']++; // 字符转对应数组下标(a对应0,b对应1...)
    }
    // 第二次遍历:找第一个出现次数为1的字符
    for (char c : s) {
        if (count[c - 'a'] == 1) {
            cout << c << endl;
            return 0; // 找到后直接输出并结束程序
        }
    }
    // 若遍历完无符合条件的字符,输出-1
    cout << -1 << endl;
    return 0;
}

9.string类的模拟实现

9.1经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可.最重要的是自己来模拟实现string类,主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数.大家看下以下string类的实现是否有问题?

cpp 复制代码
// 为了和标准库区分,此处使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 错误示范
//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
// 构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};
// 测试
void TestString()
{
String s1("hello bit!!!");
String s2(s1);
}

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造.最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝.


9.2浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来.如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规.可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享.


9.3深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出.一般情况都是按照深拷贝方式提供.

9.4传统版写法的String类

cpp 复制代码
class String
{
public:
String(const char* str = "")
{
//构造String类对象时,如果传递nullptr指针,可以认为程序非
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String& operator=(const String& s)
{
if (this != &s)
{
char* pStr = new char[strlen(s._str) + 1];
strcpy(pStr, s._str);
delete[] _str;
_str = pStr;
}
return *this;
}
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

9.5现代版写法的String类

cpp 复制代码
class String
{
public:
String(const char* str = "")
{
if (nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
: _str(nullptr)
{
String strTmp(s._str);
swap(_str, strTmp._str);
}
// 对比下和上面的赋值那个实现比较好?
String& operator=(String s)
{
swap(_str, s._str);
return *this;
}
/*
String& operator=(const String& s)
{
if(this != &s)
{
String strTmp(s);
swap(_str, strTmp._str);
}
return *this;
}
*/
~String()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
private:
char* _str;
};

9.6写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的.
引用计数:用来记录资源使用者的个数.在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源.


10.string类的代码实现

string.h

cpp 复制代码
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;
namespace cz
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;
		//string();
		string(const char* str = "");
		const char* c_str() const;
		~string();
		string(const string& s);
		size_t size() const;
		char& operator[](size_t i);
		const char& operator[](size_t i) const;
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		void pop_back();
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos = 0, size_t len = npos);
		size_t find(char ch, size_t pos = 0) const;
		size_t find(const char* str, size_t pos = 0)  const;
		string substr(size_t pos, size_t len = npos) const;
		bool operator<(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;
		bool operator==(const string& s) const;
		bool operator!=(const string& s) const;
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	};
	ostream& operator<<(ostream& out, const string& s);
}

string.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace cz
{
	const size_t string::npos = -1;
	/*string::string()
		:_str(new char[1]{'\0'})
		,_size(0)
		,_capacity(0)
	{}*/
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];
		//strcpy(_str, str);
		memcpy(_str, str, _size + 1);
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	// s2(s1)
	string::string(const string& s)
	{
		_str = new char[s._capacity + 1];
		memcpy(_str, s._str, s._size + 1);
		_size = s._size;
		_capacity = s._capacity;
	}
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}
	string::const_iterator string::begin() const
	{
		return _str;
	}
	string::const_iterator string::end() const
	{
		return _str + _size;
	}
	const char* string::c_str() const
	{
		return _str;
	}
	size_t string::size() const
	{
		return _size;
	}
	char& string::operator[](size_t i)
	{
		assert(i < _size);

		return _str[i];
	}
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);

		return _str[i];
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			//strcpy(tmp, _str);
			memcpy(tmp, _str, _size + 1);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void string::push_back(char ch)
	{
		if (_size >= _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
			reserve(newcapacity);
		}
		//strcpy(_str+_size, str);
		memcpy(_str + _size, str, len+1);
		_size += len;
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	void string::pop_back()
	{
		assert(_size > 0);

		--_size;
		_str[_size] = '\0';
	}
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);

		if (_size >= _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		// 挪动数据
		/*int end = _size;
		while (end >= (int)pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}*/
		size_t end = _size+1;
		while (end > pos)
		{
			_str[end] = _str[end-1];
			--end;
		}
		_str[pos] = ch;
		++_size;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
			reserve(newcapacity);
		}
		// 挪动数据
		/*int end = _size;
		while (end >= (int)pos)
		{
			_str[end + len] = _str[end];
			--end;
		}*/

		size_t end = _size+len;
		while (end > pos+len-1)
		{
			_str[end] = _str[end-len];
			--end;
		}

		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = str[i];
		}

		_size += len;

		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);

		// 要删除的数据,大于pos后面的字符个数
		// pos后面全删
		if (len == npos || len >= (_size-pos))
		{
			_size = pos;
			_str[_size] = '\0';
		}
		else
		{
			size_t i = pos + len;
			memmove(_str + pos, _str + i, _size + 1 - i);
			_size -= len;
		}
		return *this;
	}
	size_t string::find(char ch, size_t pos)  const
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos)  const
	{
		// kmp
		const char* p1 = strstr(_str + pos, str);
		if (p1 == nullptr)
		{
			return npos;
		}
		else
		{
			return p1 - _str;
		}
	}
	string string::substr(size_t pos, size_t len) const
	{
		if (len == npos || len >= _size - pos)
		{
			len = _size - pos;
		}
		string ret;
		ret.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			ret += _str[pos + i];
		}
		return ret;
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		//out << s.c_str();
		for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}
		return out;
	}
	// s1 < s2
	// "hello"  "hello"   -> false
	// "hellox" "hello"   -> false
	// "hello"  "hellox"  -> true
	bool string::operator<(const string& s) const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] < s[i2])
			{
				return true;
			}
			else if (_str[i1] > s[i2])
			{
				return false;
			}
			else
			{
				++i1;
				++i2;
			}
		}
		return i2 < s._size;
	}
	bool string::operator<=(const string& s) const
	{
		return *this < s || *this == s;
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}
	bool string::operator==(const string& s) const
	{
		size_t i1 = 0, i2 = 0;
		while (i1 < _size && i2 < s._size)
		{
			if (_str[i1] != s[i2])
			{
				return false;
			}
			else
			{
				++i1;
				++i2;
			}
		}
		return i1 == _size && i2 == s._size;
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
}

敬请期待下一篇文章内容-->C++中vector类介绍!


相关推荐
week_泽2 小时前
第10课:从零构建生产级AI Agent服务技术方案 - 学习笔记_10
人工智能·笔记·学习·ai agent
非凡ghost2 小时前
GiliSoft Audio Recorder(音频录制工具)
学习·音视频·软件需求
IT=>小脑虎2 小时前
AI时代下后端的出路在哪?
人工智能·后端·学习
前端程序猿之路2 小时前
30天大模型学习之Day3:高级 Prompt 工程
人工智能·python·学习·语言模型·大模型·prompt·ai编程
专注于大数据技术栈2 小时前
java学习--ArrayList
java·学习
王老师青少年编程2 小时前
2025年12月GESP真题及题解(C++八级): 宝石项链
c++·gesp·csp·信奥赛·八级·csp-s·提高组
郝学胜-神的一滴2 小时前
深入理解Qt中的坐标系统:鼠标、窗口与控件位置详解
开发语言·c++·qt·程序人生
福楠2 小时前
C++ | 继承
c语言·开发语言·数据结构·c++·算法
程序员zgh2 小时前
汽车以太网协议 —— DDS
c语言·开发语言·c++·网络协议·udp·汽车·信息与通信