
🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法初阶》
✨逆境不吐心中苦,顺境不忘来时路! 🎬 博主简介:

引言:前篇文章,小编已经介绍了关于C++内存管理、模板初阶以及STL简介的相关知识.接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++String类以及实现String类的接口,那么这里面到底有哪些知识需要我们去学习的呢?废话不多说,带着这些疑问,下面跟着小编的节奏🎵一起学习吧!
目录
- [1. 为什么学习string类?](#1. 为什么学习string类?)
- 2.string类的了解
- 3.C++官方中string类部分内容
- 4.string类对象的容量操作
- 5.string类对象的修改操作
-
- [5.1push back 介绍](#5.1push back 介绍)
- 5.2append介绍
- 5.3operator+=介绍
- 5.4c_str介绍
- 5.5substr介绍
- 6.string类非成员函数
-
- 6.1operator+
- 6.2operator>>介绍
- 6.3operator<<介绍
- 6.4getline介绍
- [6.5relational operators介绍](#6.5relational operators介绍)
- 7.vs和g++下string结构的说明
- 8.关于string类一些oj算法题
- 9.string类的模拟实现
- 10.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类时,必须包含#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️⃣模板的底层关联
string是basic_string模板类的"特化":模板参数用了
char(字符类型);同时用了默认的
char_traits(负责字符的比较、复制等基础操作)和allocator(负责内存分配).5️⃣关键注意点
std::string是按"字节"处理数据 的,不是按"实际字符":比如处理 UTF-8 编码的中文(一个汉字通常占 3 字节)时,
length()/size()得到的是"字节数"(比如一个汉字会算成3),迭代器也是按字节遍历------它不认识"多字节字符",只认"字节".
3.2npos介绍
这是
std::string类里的静态成员常量npos的官方说明 ,核心讲它的定义、本质和用途.1️⃣基本定义
std::string::npos是string类的公共静态常量 ,定义为:static const size_t npos = -1;
注意:
size_t是无符号整型 ,给它赋值-1会触发"无符号数溢出",最终npos会变成size_t类型能表示的最大值 (比如 64位系统中通常是18446744073709551615).2️⃣核心本质
npos是size_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)(支持前后移动的迭代器,比如string、vector的迭代器都属于这种).2️⃣核心功能
reverse的作用是 反转左闭右开区间[first, last)内的元素顺序 ([first, last)表示包含first指向的元素,不包含last指向的元素).3️⃣实现逻辑
函数内部通过调用
iter_swap(交换两个迭代器指向的元素)来完成反转,文档里给出了等价的代码逻辑是:从区间的两端向中间 逐步交换元素,直到迭代器
first和last相遇,完成整个范围的反转.4️⃣适用场景
只要是支持双向迭代器的容器(比如
std::string、std::vector、std::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::size和string::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()返回的是"实际使用的字节数",而string的capacity()是"已分配的内存字节数",两者不一定相等(容量通常大于等于实际长度).
编码相关注意 :string仅按"字节"处理数据,不识别字符编码.若字符串包含多字节字符(比如UTF-8编码的中文,1个汉字占3字节),length()返回的字节数 ≠ 实际字符数 .
与size()的关系 :string::length和string::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都可能被改变(这和vector的capacity保证不同,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的成员函数(比如append、push_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++ 标准库中针对
string的operator>>流提取运算符重载 说明,核心信息如下:1️⃣函数原型
istream& operator>> (istream& is, string& str);2️⃣核心功能
从输入流
is(比如cin)中提取字符串 ,并将结果存储到str里------str原有的内容会被覆盖 .3️⃣提取逻辑
它的行为和"C风格字符串的流提取"一致:提取到的每个字符,会像
push_back那样追加到str中.4️⃣关键限制
输入流的提取操作以空白符(空格、换行、制表符等)为分隔符 ,所以这个操作只能提取"一个单词"(遇到空白符就停止).
如果要提取整行文本 (包含空白符),需要用
getline函数.简单说:
cin >> str是"从输入里读一个单词到字符串".但读不了带空格的整行.
6.3operator<<介绍
这是 C++ 标准库中针对
string的operator<<流插入运算符重载 说明,核心信息如下: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++ 标准库中针对
string的std::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对象 vsstring对象(如str1 == str2)C风格字符串(
const char*)vsstring对象(如"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类介绍!


































