【string类:详解 + 实现】目录
- 前言:
- [---------------详细介绍 ---------------](#---------------详细介绍 ---------------)
-
- [1. 为什么要学习string类?](#1. 为什么要学习string类?)
- [2. 标准库中的string类是什么样的呢?](#2. 标准库中的string类是什么样的呢?)
- [3. string类在MSVC和g++编译器下有什么区别?](#3. string类在MSVC和g++编译器下有什么区别?)
- -------------标准接口---------------
-
- -----------成员函数-----------
- 1.常见构造
- 2.容量操作
- 3.访问操作
- 4.修改操作
- [5. 其他操作](#5. 其他操作)
- -----------非成员函数-----------
-
- [relational operators](#relational operators)
- std::operator<<
- std::operator>>
- std::getline
- --------------模拟实现---------------

往期《C++初阶》回顾:
/------------ 入门基础 ------------ /
【C++的前世今生】
【命名空间 + 输入&输出 + 缺省参数 + 函数重载】
【普通引用 + 常量引用 + 内联函数 + nullptr】/------------ 类和对象 ------------ /
【类 + 类域 + 访问限定符 + 对象的大小 + this指针】
【类的六大默认成员函数】
【初始化列表 + 自定义类型转换 + static成员】
【友元 + 内部类 + 匿名对象】
【经典案例:日期类】/------------ 内存管理 ------------ /
【内存分布 + operator new/delete + 定位new】/------------ STL ------------ /
【泛型编程 + STL简介】
【auto关键字 + 范围for循环 + 迭代器】
前言:
hi~ 小伙伴们大家好呀!☀️ 新的一周里希望大家每天都能元气满满、活力四射✨
今天可是第十二个节气 ------"大暑" 哦~ 虽然夏天眼看着就要悄悄溜走啦😉,但天气依旧会热到让人想躲进空调房里吹冷风呢🥵~(;一_一)
今天我们要一起解锁的知识点是【string 类:详解 + 实现】】🔍💡!相信细心的小伙伴已经开始歪着头疑惑啦:"咦?🤔💭 我记得 string 不是标准库里面的内容吗?怎么会在《C++ 初阶之 STL》里学习呀?"
哈哈,问得超棒!准确来说呀,string 是一个类,并不是 STL 中的容器(也就是类模板)哦~ 但从本质上看呢,它完全可以被当作 "半个容器" 来对待哒😎!
因为 :string 本质上是从
类模板basic_string
实例化来的类
------typedef basic_string<char> string;
🌟
所以:在初学 STL 容器之前,先吃透 string 类,就能循序渐进地帮我们打好基础,更快理解 STL 容器的奥秘啦~ 一起加油学起来吧💪!(•̀ᴗ•́)و
---------------详细介绍 ---------------
1. 为什么要学习string类?
在 C 语言里,字符串被定义为 :以字符'\0'作为结尾标志的一组字符集合。
这种定义方式使得字符串在
存储
和识别
上有了明确的界定。为了让开发者能更便捷地对字符串进行诸如:
复制
、拼接
、比较
等操作,C 标准库精心提供了一系列以str
开头的库函数,例如:
strcpy
(字符串复制
函数)strcat
(字符串拼接
函数)strcmp
(字符串比较
函数)- ......
然而 :这些库函数存在一定的局限性,它们与字符串本身在逻辑上是相互分离的。
从面向对象编程(OOP)的理念来看,OOP 强调将数据和操作数据的方法封装在一起,形成一个有机的整体,这样可以让代码的结构更加清晰,可维护性更强。
但 C 标准库中的
str系列库函数
并非如此,它们独立于字符串对象之外,不太契合 OOP 这种将数据与操作紧密结合的思想。不仅如此 :在使用这些库函数操作字符串时,底层的内存空间管理责任完全落在了用户身上。
用户需要自行负责分配足够的空间来存储字符串以及确保在对字符串进行操作时不会超出所分配空间的边界。
这就要求开发者在编写代码时格外小心谨慎,稍有疏忽,比如:在复制字符串时没有正确计算目标空间的大小,就极有可能导致越界访问的问题,进而引发程序崩溃、数据损坏等严重后果。
基于上述种种原因,我们会发现 C 语言中字符串操作存在一些不便之处,而 C++ 的
string 类
能够很好地解决这些问题:
- string类将字符串数据及其相关操作封装在一起,完美契合面向对象编程(OOP)的思想,使得代码结构更加清晰、易于维护。
- string类内部会自动管理底层的内存空间,无需开发者手动操心内存分配与释放的细节,极大地降低了因内存管理不当导致越界访问等错误的风险。
2. 标准库中的string类是什么样的呢?
cplusplus网站上关于C++的string类的介绍 :C++ Reference
标准库中的string
类相关知识主要可分为以下三大部分:1. 成员函数
迭代器相关
:用于获取指向string类内部字符的迭代器。
- 例如:方便对字符串进行遍历、查找等操作
容器特性相关
:实现类似容器的功能。
- 例如:获取字符串长度、判断是否为空等
元素访问
:提供接口用于直接访问字符串中的字符。
- 例如:通过下标访问特定位置的字符
修改操作
:用于改变字符串的内容。
- 例如:插入、删除、替换字符或子串等
字符串操作
:比较字符串等操作。
- 例如:字符串的拼接、查找子串
2. 非成员函数重载
- 对一些常见运算符(如 :
+
用于字符串拼接等 )以及输入输出流运算符(<<
、>>
)进行重载。- 以支持与
string类对象
的自然交互。
3. 成员常量
string类
中定义的一些具有固定值的常量,可能用于表示特定的属性
或边界条件
等。

3. string类在MSVC和g++编译器下有什么区别?
3.1:结构上不同
注意:下述结构是在 32 位平台下验证的,在 32 位平台中指针占 4 个字节
在 MSVC(Microsoft Visual C++)环境下,string的结构相对复杂,总共占用28 个字节
其内部包含一个联合体,该联合体用于定义string中字符串的存储空间。
cpp//联合体的定义如下: union _Bxty { // 用于存放小缓冲区或指向更大缓冲区的指针 value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // 用于允许别名 } _Bx;
当字符串长度 < 16 时:使用内部固定的字符数组来存放字符串。
当字符串长度 >= 16 时:则从堆上开辟空间来存储。
这种设计是有道理的,在大多数情况下,字符串长度小于 16 。
此时,string对象创建后,内部已具备 16 个字符数组的固定空间,无需从堆上动态创建,提高了效率。
此外,string结构中还有一个size_t类型字段,用于保存字符串的实际长度。
另一个size_t类型字段,用来保存从堆上开辟空间的总容量。
最后,还有一个指针,用于其他相关操作。
因此 :string的总字节数为 16(
联合体
中字符数组的大小) + 4(保存长度的size_t字段
) + 4(保存容量的size_t字段
) + 4(指针
大小) = 28 个字节。
在 g++ (GNU C++)环境下,string采用写时拷贝机制实现。string对象本身仅占4个字节
- 内部仅有一个指针,该指针指向一块位于堆上的空间。
这块堆空间包含以下字段:
字符串有效长度
空间总容量
引用计数
cppstruct _Rep_base { size_type _M_length; //字符串有效长度 size_type _M_capacity; //空间总容量 _Atomic_word _M_refcount; //引用计数 };
3.2:扩容规则不同
MSVC(Microsoft Visual C++)的扩容规则:
- 扩容因子 :通常为
1.5 倍
(非固定值,可能随版本调整)- 行为特点:相对保守的内存增长,减少内存浪费
cpp
int main()
{
// reverse 反转 逆置
// reserve 保留、预留
//1.使用标准库中的string类实例化一个对象
string s;
//2.使用标准库中的string类封装的成员函数reserve:提前开空间,避免扩容,提高效率
//s.reserve(100);
//3.使用标准库中的string类封装的成员函数capacity:获取当前的字符串的总容量
size_t sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
//3.使用标准库中的string类封装的成员函数push_back:向字符串的尾部添加字符(目的:观察一下MSVC环境下的string的扩容规则)
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
return 0;
}

g++ (GNU C++)的扩容规则:
- 扩容因子 :通常为
2 倍
(早期版本常见)- 行为特点:更激进的内存增长,减少频繁扩容的开销
-------------标准接口---------------
-----------成员函数-----------
1.常见构造
下面我们直接来看标准库中关于string类构造函数的介绍:
早期 C++ 设计者为string类设计了多达 9 个构造函数,目前我们先集中学习以下红色方框中的 4 个。
暂未学习其他构造函数,主要有两点原因:
- 部分构造函数
实际应用场景较少
,现阶段学习性价比不高,可暂不关注- 部分构造函数
涉及较复杂的知识
(如:模板进阶等),对当前学习进度而言难度较大,适合后续阶段深入学习
构造函数名称 | 功能说明 |
---|---|
string() |
构造一个空的 string 类对象(即:空字符串) |
string(const char* s) |
用 C 风格字符串(C-string )构造 string 类对象 |
string(size_t n, char c) |
构造包含 n 个相同字符 c 的 string 类对象 |
string(const string& s) |
拷贝构造函数,用于复制另一个 string 对象 |

cpp
#include <iostream>
#include <string>
using namespace std;
void Teststring()
{
// 使用string()构造空的string类对象s1
cout << "使用string()构造函数" << endl;
string s1;
cout << "s1: " << s1 << endl;
// 使用string(const char* s)用C-string来构造string类对象s2
cout << "使用string(const char* s)构造函数" << endl;
string s2("hello world");
cout << "s2: " << s2 << endl;
// 使用string(size_t n, char c)构造包含n个字符c的string类对象s3
cout << "使用string(size_t n, char c)构造函数" << endl;
string s3(5, 'a');
cout << "s3: " << s3 << endl;
// 使用string(const string& s)拷贝构造函数构造s4,以s2为蓝本
cout << "使用string(const string& s)拷贝构造函数" << endl;
string s4(s2);
cout << "s4: " << s4 << endl;
}
int main()
{
Teststring();
return 0;
}

2.容量操作
注:和上面一样这里我们还是只是挑选几个常用的进行介绍

函数名称 | 功能说明 | 备注 |
---|---|---|
size() |
返回字符串中有效字符的长度(不包含结尾的 \0 ) |
与 length() 功能完全相同,推荐使用 size() 保持容器一致性 |
length() |
返回字符串中有效字符的长度 | 历史遗留接口,行为与 size() 一致,通常用于字符串操作的可读性 |
capacity() |
返回当前字符串分配的总空间大小(单位:字节) | 总空间 ≥ 有效字符长度(size() ) |
empty() |
检查字符串是否为空(即:size() == 0 ) 空则返回 true ,否则返回 false |
比手动检查 size() == 0 更直观高效 |
clear() |
清空字符串中的所有有效字符(size() 变为 0 ),但不释放内存空间 |
清空后 capacity() 保持不变,适合复用对象避免重复分配内存 |
reserve(n) |
预分配至少容纳 n 个字符的内存空间(可能扩容) |
仅影响容量 ,不改变内容或长度 若 n < capacity() ,可能无操作 |
resize(n, c) |
调整有效字符数量为 n 个,多出的空间用字符 c 填充 若 n < size() 则截断 |
默认填充 \0 (若:未指定 c ) 可能触发扩容(若:n > capacity() ) |
std::string::size

std::string::length

std::string::capacity

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("Test string");
cout << "size: " << str.size() << "\n";
cout << "length: " << str.length() << "\n";
cout << "capacity: " << str.capacity() << "\n";
return 0;
}

std::string::empty

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.用于临时存储每一行输入的文本
string line;
//2.用于存储用户输入的完整文本内容
string content;
cout << "请输入一段文本,输入空行以结束:\n";
//3.循环读取用户输入的每一行,直到遇到空行为止
do
{
//3.1:从标准输入读取一行文本(包括空格),存入 line 中
getline(cin, line); //注意:getline 会读取到换行符为止,但不会将换行符存入 line
//3.2:将当前行追加到 content 中,并手动添加换行符(因为 getline 不保留换行符)
content += line + '\n';
} while (!line.empty()); //循环条件:只要当前行不为空(!line.empty())就继续循环
//4.输出用户输入的完整文本(包含所有行,包括最后的空行)
cout << "您输入的文本是:\n" << content;
return 0;
}

std::string::clear

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.用于存储从输入流读取的单个字符
char c;
//2.用于拼接读取到的字符,形成一行文本
string str;
cout << "请输入几行文本以(.)作为结束标志\n";
//3.循环读取字符,直到读取到 '.' 时结束循环
do
{
//3.1:从标准输入流读取一个字符(包括空格、换行符等)
c = cin.get();
//3.2:将读取到的字符追加到字符串str的末尾
str += c;
//3.3:当读取到换行符 '\n' 时,表示一行输入结束
if (c == '\n')
{
cout << str; // 输出当前拼接好的一行文本(包含换行符)
str.clear(); // 清空字符串str,准备接收下一行的输入
}
} while (c != '.');
//4.输出读取到的文本内容
cout << str << endl;
return 0;
}

std::string::reserve

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
/*--------------------创建一个包含长字符串的string对象--------------------*/
cout << "创建一个包含长字符串的string对象" << endl;
string s2("hello worldxxxxxxxxxxxxx");
cout << "长度:" << s2.size() << endl;
cout << "容量:" << s2.capacity() << endl << endl;
/*--------------------要求string保留至少20个字符的容量--------------------*/
cout << "要求string保留至少20个字符的容量" << endl;
s2.reserve(20);
cout << "长度:" << s2.size() << endl; //长度不变
cout << "容量:" << s2.capacity() << endl << endl; //容量不变
/*--------------------要求string保留至少28个字符的容量--------------------*/
cout << "要求string保留至少28个字符的容量" << endl;
s2.reserve(28);
cout << "长度:" << s2.size() << endl; //长度不变
cout << "容量:" << s2.capacity() << endl << endl; //容量可能增长
/*--------------------要求string保留至少40个字符的容量--------------------*/
cout << "要求string保留至少40个字符的容量" << endl;
s2.reserve(40);
cout << "长度:" << s2.size() << endl; //长度不变
cout << "容量:" << s2.capacity() << endl << endl; //容量进一步增长
/*--------------------清空字符串内容--------------------*/
cout << "清空字符串内容" << endl;
s2.clear(); //注意:这只会清除内容,不会释放已分配的内存
cout << "长度:" << s2.size() << endl; //长度为0
cout << "容量:" << s2.capacity() << endl << endl; //容量保持不变
return 0;
}

std::string::resize

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.初始化字符串str为"I like to code in C"
string str("I like to code in C");
cout << str << '\n'; //输出初始字符串
//2.获取当前字符串的长度(字符数量),不包含结尾的空字符'\0'
unsigned sz = str.size();
//3.调整字符串长度:在原有长度基础上增加2个字符
str.resize(sz + 2, '+'); //新增的字符用'+'填充
cout << str << '\n'; //输出调整后的字符串(末尾多了两个'+')
//4.再次调整字符串长度:将长度改为14个字符
str.resize(14); //注意:若新长度小于原长度,会截断字符串(只保留前14个字符)
cout << str << '\n'; //输出截断后的字符串(前14个字符为"I like to code")
return 0;
}

3.访问操作
std::string::operator[]

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.初始化字符串str为"Test string"
string str("Test string");
//2.循环遍历字符串中的每个字符并输出
for (int i = 0; i < str.length(); ++i)
{
//2.1:使用 operator[] 访问字符串的第i个字符
cout << str[i];
}
cout << endl;
return 0;
}

4.修改操作

函数名称 | 功能说明 | 参数说明 | 注意事项 |
---|---|---|---|
push_back(c) |
在字符串末尾插入单个字符 c |
c : 要插入的字符 (char 类型) |
效率高于+= 追加单字符,但批量操作建议用append 或operator+= |
append(str) |
在字符串末尾追加另一个字符串 (支持string /char* /子串) |
str : 要追加的字符串 (支持多种重载形式) |
比+= 功能更灵活(如可追加部分子串或重复字符) |
operator+=(str) |
最常用的字符串追加方式 (支持string /char* /字符) |
str : 要追加的内容 (支持字符串或单字符) |
代码简洁,性能与append() 相当,推荐日常使用 |
insert(pos, str) |
在指定位置 pos 插入字符串 str (支持多种插入形式) |
pos : 插入位置索引 str : 要插入的内容(字符串 / 字符 / 子串等) |
插入后原位置及后续字符自动后移 pos 超出范围会抛出异常(at() 风格重载)或导致未定义行为([] 风格) |
erase(pos, len) |
从位置 pos 开始删除 len 个字符 (len 省略时删除到末尾) |
pos : 起始删除位置 len : 要删除的字符数(可选) |
若 pos 超出范围会抛出异常 若 len 超出剩余字符数,仅删除到字符串末尾 |
关于 string 需注意以下两点:
1. 尾部追加字符的三种方式:
s.push_back(c)
:向string
尾部添加单个字符c
s.append(1, c)
:明确指定追加 1 个字符c
s += 'c'
:简洁的运算符重载方式,既可以追加单个字符'c'
,也能直接连接字符串(如:"hello"
)实际应用中 ,
+=
操作因语法简洁、适用场景灵活(字符 / 字符串均可),是最常用的追加方式
2. 空间预留优化:
- 若能预先估算
string
需要存储的字符数量,建议通过reserve(n)
提前预留n
个字符的空间作用:避免频繁的内存重新分配(扩容),减少性能损耗,提升操作效率
std::string::push_back

cpp
#include <iostream>
#include <fstream> // 包含"文件操作"相关头文件
#include <string>
using namespace std;
int main()
{
//1.定义一个空字符串,用于存储从文件读取的内容
string str;
//2.以只读模式打开名为"test.txt"的文件
ifstream file("test.txt", ios::in);
/* 说明:
* 1. ifstream是文件输入流类,用于从文件读取数据
* 2. ios::in表示打开模式为只读
*/
//3.检查文件是否成功打开
if (file)
{
//3.1:循环读取文件内容,直到文件结束(eof = end of file)
while (!file.eof()) //file.eof():当到达文件末尾时返回true
{
str.push_back(file.get()); //file.get():读取文件中的一个字符(包括"空格、换行"等)
}
}
//4.输出从文件读取到的所有内容
cout << str << '\n';
return 0;
}

std::string::append

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义一个空的字符串 str,用于存储最终拼接后的结果
string str;
//2.定义字符串 str2,初始内容为 "Writing "
string str2 = "Writing ";
//3.定义字符串 str3,初始内容为 "print 10 and then 5 more"
string str3 = "print 10 and then 5 more";
//1. 将 str2 的内容追加到 str 中
str.append(str2);
cout << str << '\n'; //此时 str 的内容变为 "Writing "
//2. 将 str3 中从索引为6开始截取的3个字符追加到 str 中
str.append(str3, 6, 3);
cout << str << '\n'; //此时 str 的内容变为 "Writing 10 "
//3. 从字符串 "dots are cool" 的开头截取5个字符追加到 str 中
str.append("dots are cool", 5);
cout << str << '\n'; //此时 str 的内容变为"Writing 10 dots "
//4. 将字符串 "here: " 追加到 str 中
str.append("here: ");
cout << str << '\n'; //此时 str 的内容变为 "Writing 10 dots here: "
//5. 追加10个字符 '.' 到 str 中
str.append(10u, '.');
cout << str << '\n'; //此时 str 的内容变为 "Writing 10 dots here: .........."
//6. 从 str3 的迭代器 begin()+8 位置(对应字符 'a' )到 end() 位置的子串追加到 str 中
str.append(str3.begin() + 8, str3.end());
cout << str << '\n'; //此时 str 的内容变为 "Writing 10 dots here: ......... and then 5 more"
//7. 追加5个 ASCII 码为 0x2E(即字符 '.' )的字符到 str 中
str.append(5, 0x2E);
cout << str << '\n'; //此时 str 的内容变为"Writing 10 dots here: ......... and then 5 more....."
return 0;
}

std::string::operator+=

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.初始化字符串name为"John"
string name("John");
//2.初始化字符串family为"Smith"
string family("Smith");
//3.使用operator+=追加"C风格字符串"(以'\0'结尾的字符数组)
name += " K. "; // 追加C字符串:空格+K.+空格
//4.使用operator+=追加另一个"string对象"
name += family;
//5.使用operator+=追加"单个字符"
name += '\n'; // 追加换行符,使输出后自动换行
//6.输出最终拼接后的字符串
cout << name;
return 0;
}

std::string::insert

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义初始字符串:"to be question"
string str = "to be question";
//2.定义用于插入的字符串:str2和str3
string str2 = "the ";
string str3 = "or not to be";
//3.定义迭代器,用于定位插入位置
string::iterator it;
// ==================== 1. 在指定索引位置插入"字符串" ====================
//在str的索引6处插入str2("the ")
cout << str.insert(6, str2) << endl; //插入后变为:"to be the question"
// ==================== 2. 在指定索引插入"另一个字符串的子串" ====================
//从str3的索引3开始,截取4个字符("not "),插入到str的索引6处
cout << str.insert(6, str3, 3, 4) << endl; //此时str变为:"to be not the question"
// ==================== 3. 在指定索引插入"C风格字符串的前n个字符" ====================
//在str的索引10处,插入"C风格字符串"的前8个字符("that is ")
cout << str.insert(10, "that is cool", 8) << endl; //插入后str变为:"to be not that is the question"
// ==================== 4. 在指定索引插入"完整的C风格字符串" ====================
//在str的索引10处插入"C风格字符串"("to be ")
cout << str.insert(10, "to be ") << endl; //插入后str变为:"to be not to be that is the question"
// ==================== 5. 在指定索引插入"n个重复字符" ====================
//在str的索引15处插入1个字符':'
cout << str.insert(15, 1, ':') << endl; //插入后str变为:"to be not to be: that is the question"
// ==================== 6. 通过迭代器在指定位置"插入单个字符" ====================
//在str的begin()+5位置(即第6个字符前)插入','
it = str.insert(str.begin() + 5, ','); //插入','后变为:"to be, not to be: that is the question"
cout << str << '\n';
// ==================== 7. 在字符串末尾插入"n个重复字符" ====================
//在str的末尾(end()位置)插入3个'.'
str.insert(str.end(), 3, '.'); //插入后str变为:"to be, not to be: that is the question..."
cout << str << '\n';
// ==================== 8. 通过迭代器范围插入"另一个字符串的子串" ====================
//在迭代器it+2的位置(即','后第2个位置)插入str3的begin()到begin()+3的子串("or ")
str.insert(it + 2, str3.begin(), str3.begin() + 3); //最终str变为:"to be, or not to be: that is the question..."
cout << str << '\n';
return 0;
}

std::string::erase

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.初始化字符串str为"This is an example sentence."
string str("This is an example sentence.");
cout << str << '\n';
// ==================== 1. 从指定索引删除指定长度的字符 ====================
//从索引10开始,删除8个字符
str.erase(10, 8);
cout << str << '\n'; // 输出结果:This is an sentence.
// ==================== 2. 删除迭代器指向位置的单个字符 ====================
//删除迭代器 begin()+9 指向的字符
str.erase(str.begin() + 9);
cout << str << '\n'; // 输出结果:This is a sentence.
// ==================== 3. 删除迭代器范围 [first, last) 内的字符 ====================
//删除[first, last)区间内的所有字符(包含first,不包含last)
str.erase(str.begin() + 5, str.end() - 9);
cout << str << '\n'; // 输出结果:This sentence.
return 0;
}

5. 其他操作

函数名称 | 功能说明 | 参数说明 | 注意事项 |
---|---|---|---|
c_str() |
返回C风格字符串(const char* ),以\0 结尾 |
无参数 | 返回的指针在字符串修改后失效,需立即使用或复制数据 |
find(c, pos) |
从pos 位置向后查找字符c |
c : 目标字符 pos : 起始位置(默认0) |
检查结果时需用pos != string::npos |
rfind(c, pos) |
从pos 位置向前查找字符c (默认从末尾开始) |
c : 目标字符 pos : 起始位置(默认npos ) |
适合反向搜索,如查找文件扩展名 |
substr(pos, n) |
从pos 位置截取n 个字符的子串 |
pos : 起始位置 n : 截取长度(默认到结尾) |
若pos 越界会抛出out_of_range 异常 |
std::string::c_str

cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
//1. 定义C++字符串str
string str("Please split this sentence into tokens");
//2. 分配C风格字符串(字符数组)的内存
char* cstr = new char[str.length() + 1]; //str.length()获取字符串长度,+1是为了预留结尾的空字符'\0'
//3. 将C++字符串转换为C风格字符串并复制到cstr中
strcpy(cstr, str.c_str());
/* 说明:
* 1. str.c_str()返回指向C风格字符串的指针(以'\0'结尾)
* 2. strcpy将c_str()指向的内容复制到cstr中
*/
//4. 使用strtok函数分割C风格字符串
char* p = strtok(cstr, " ");
/* 说明: strtok
* 1. 第一个参数:要分割的字符串
* 2. 第二个参数:分隔符(这里是空格" ")
* 3. strtok首次调用返回第一个分割后的子串指针
*/
//5. 循环获取所有分割后的子串
while (p != 0) //当p为NULL时,表示没有更多子串
{
//5.1:输出当前分割得到的子串
cout << p << '\n';
//5.2:后续调用strtok时,第一个参数传NULL,表示继续处理上一次的字符串
p = strtok(NULL, " ");
}
//6. 释放动态分配的字符数组内存,避免内存泄漏
delete[] cstr;
return 0;
}

std::string::find

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义源字符串str,内容为包含"needle"的句子
string str("There are two needles in this haystack with needles.");
//2.定义要查找的目标字符串str2为"needle"
string str2("needle");
// ==================== 1. 查找整个字符串在源字符串中的位置 ====================
size_t found = str.find(str2);
if (found != string::npos) //检查是否找到(npos是string的静态成员,表示"未找到")
cout << "第一个 'needle' 出现的位置:" << found << '\n';
// ==================== 2. 从指定位置开始,查找子串的前n个字符 ====================
found = str.find("needles are small", found + 1, 6);
/* 说明:
* 1. "needles are small":要查找的源字符串
* 2. found + 1` :从上次找到的位置+1开始查找(避免重复找到同一个位置)
* 3. 6 :只取前6个字符(即"needle")进行查找
*/
if (found != string::npos)
cout << "第二个 'needle' 出现的位置:" << found << '\n';
// ==================== 3. 查找单个字符 ====================
// 查找句号'.'在str中首次出现的位置
found = str.find('.');
if (found != string::npos)
cout << "句号出现的位置:" << found << '\n';
// ==================== 4. 结合find和replace替换字符串 ====================
// 1. 先找到第一个str2("needle")的位置
// 2. 使用replace替换:从找到的位置开始,替换str2长度的字符为"preposition"
str.replace(str.find(str2), str2.length(), "preposition");
// 3. 输出替换后的字符串
cout << "替换后的字符串:" << str << '\n';
return 0;
}

std::string::rfind

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义源字符串str,内容为包含重复"sixth"的句子
string str("The sixth sick sheik's sixth sheep's sick.");
//2.定义要查找的目标字符串key为"sixth"
string key("sixth");
//3.rfind(key):从字符串末尾开始向前查找key最后一次出现的位置
size_t found = str.rfind(key);
//4.检查是否找到目标(npos表示未找到)
if (found != string::npos)
{
//4.1:若找到,从found位置开始,替换与key长度相同的字符为"seventh"
str.replace(found, key.length(), "seventh");
}
//5.输出替换后的字符串
cout << str << '\n';
return 0;
}

std::string::substr

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//定义源字符串str
string str = "We think in generalities, but we live in details.";
// ==================== 1. 从指定位置截取固定长度的子串 ====================
string str2 = str.substr(3, 5);
cout << str2 << endl; // str2的值为"think"
// ==================== 2. 先查找目标子串位置,再截取到末尾 ====================
// 1. 查找"live"在str中首次出现的位置,返回起始索引
size_t pos = str.find("live"); // "live"在str中的起始索引为29
// 2. 截取从pos到字符串末尾的所有字符
string str3 = str.substr(pos); // substr(pos):只传起始位置,默认截取从pos到字符串末尾的所有字符
cout << str2 << ' ' << str3 << '\n'; // 输出截取的两个子串
return 0;
}

-----------非成员函数-----------

relational operators

std::operator<<

cpp
// inserting strings into output streams
#include <iostream>
#include <string>
int main()
{
std::string str = "Hello world!";
std::cout << str << '\n';
return 0;
}
std::operator>>

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义一个string类型的变量name,用于存储用户输入的姓名
string name;
cout << "Please, enter your name: ";
//2.从标准输入流(键盘)读取用户输入的姓名,并存储到name变量中
cin >> name; // cin >> name 会自动忽略开头的空白字符(空格、换行等),遇到下一个空白字符时停止读取
//3.输出问候语,包含用户输入的姓名
cout << "Hello, " << name << "!\n";
return 0;
}

std::getline

cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
//1.定义string类型变量name,用于存储用户输入的全名
string name;
cout << "Please, enter your full name: ";
//2.使用getline函数读取一整行输入(包括空格),并存储到name中
getline(cin, name);
/* 说明:
* 1. getline(cin, name):从标准输入流cin读取字符,直到遇到换行符为止
* 2. 换行符会被读取但不会被存入name,同时会从输入流中移除
*/
//3.输出包含用户全名的问候语
cout << "Hello, " << name << "!\n";
return 0;
}

--------------模拟实现---------------
头文件:string.h
c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
//任务1:包含需要使用的头文件
#include <iostream>
#include <assert.h>
//任务2:定义自定义命名空间
namespace mySpace
{
/*-------------------------------实现一个自定义的string类-------------------------------*/
class string
{
public:
/*------------------------第一部分:迭代器相关的函数------------------------*/
/*-----------迭代器类型的定义-----------*/
typedef char* iterator;
typedef const char* const_iterator;
/*-----------实现迭代器相关的函数-----------*/
//1.返回指向字符串首字符的指针
iterator begin()
{
return _str;
}
const_iterator begin()const //这里使用常函数构成了函数重载
{
return _str;
}
//2.返回指向字符串结尾('\0')的指针
iterator end()
{
return _str + _size;
}
const_iterator end()const
{
return _str + _size;
}
/*------------------------第二部分:重写默认函数------------------------*/
//由于string类的成员变量中有资源,所以我们必须重写默认构造函数
/*-----------默认构造函数-----------*/
/*
string()
:_str(new char[1]{'\0'}) //分配最小的内存(1字节)
,_size(0) //注意:上面的new的使用
,_capacity(0)
{}
*/
//这里我们就将上面的内容给注释掉了,因为下面我们还要实现一个全缺省的构造函数
//构造函数可以重载,但默认构造函数和所有参数都有默认值的构造函数不能同时存在,因为它们会导致调用歧义
/*-----------有参构造函数(全缺省)-----------*/
string(const char* str="")
{
//注意:这里我们初始化顺序和成员变量声明的顺序不一致
//原因:这里我们是使用一个字符串字面量创建出一个字符串对象
//必须按照下面的初始化顺序:_size ------> _capacity------> _str,才可以更好的进行初始化
//1.先初始化:字符串的尺寸(即:字符串的字符的数量)
_size = strlen(str); //注意:这里的strlen的返回值其实返回的是字符串中字符的数量
//2.再初始化:字符串的容量
_capacity = _size; //注意:这里我们直接使用了字符串的长度初始化字符串的容量,等会开辟空间的时候我们要多开辟一个空间用来存储'\0'
//3.最后初始化:字符串的内存空间
//3.1:先在堆区开辟足够的空间
_str = new char[_capacity + 1]; //注意:strlen函数返回的字符串的长度,所以开辟空间的大小为_capacity+1
//3.1:再将传入的字符串中的内容拷贝到开辟的空间中
strcpy(_str, str);
}
/*-----------拷贝构造函数-----------*/
/*===========拷贝函数的"传统写法"===========*/
/*
string(const string& s)
{
//注意:这里我们初始化顺序和成员变量声明的顺序一致
//原因:这里我们是使用一个已经存在字符串对象拷贝一个不存在的字符串对象
//所以初始化的顺序可以是随意的
//1.先初始化:字符串的内存空间
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
//2.再初始化:字符串的尺寸和容量
_size = s._size;
_capacity = s._capacity;
}
*/
/*===========拷贝函数的"现代写法"===========*/
string(const string& s)
{
string tmp(s._str); //通过调用有参数构造函数初始化对象tmp
swap(tmp); //交换资源的所有权
}
/*-----------赋值运算符重载函数-----------*/
/*===========赋值运算符重载函数的"传统写法"===========*/
/*
string& operator=(const string& s)
{
//0.防止对自身进行赋值操作
if (this != &s)
{
//1.释放被赋值的内存空间
delete[] _str;
//2.开辟传入的字符串的空间大小
_str = new char[s._size + 1];
//3.将传入的字符串中的内容拷贝到新开辟的空间中
strcpy(_str, s._str);
//4.跟新字符串的尺寸和容量
_size = s._size;
_capacity = s._capacity;
}
//5.返回已赋值好的string对象
return *this;
}
*/
/*===========赋值运算符重载函数的"现代写法"(初版)===========*/
/*
string& operator=(const string& s)
{
//0.防止对自身进行赋值操作
if (this != &s)
{
//1.使用传入的string对象创建一个"临时对象" //注意:我们创建的东西和我们要想的东西是一样的
string tmp(s);
//2.交换资源的所有权
swap(tmp);
}
//3.返回已赋值好的string对象
return *this;
}
*/
/*===========赋值运算符重载函数的"现代写法"(终版)===========*/
string& operator=(string s) //注意:参数为值传递,自动调用拷贝构造函数
{
swap(s);
return *this;
}
/*-----------析构函数-----------*/
~string()
{
if (_str)
{
//1.释放在堆区开辟的空间
delete[] _str;
//2.将字符串指针置为空
_str = nullptr;
//3.将字符串的尺寸和容量都更新为0
_size = _capacity = 0;
}
}
/*------------------------第三部分:string类内实现的简单函数------------------------*/
/*-----------"交换"函数-----------*/
//1.实现:"交换两个string类对象的函数"
void swap(string&s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
/*-----------"风格转换"的函数-----------*/
//2.实现:"将一个string类型的对象转换为C风格的字符串的函数"(const版本)
const char* c_str()const
{
return _str;
}
/*-----------"获取字符串的长度"的函数-----------*/
//3.实现:"返回字符串的长度的函数"
size_t size()const
{
return _size;
}
/*-----------"获取字符串的容量"的函数-----------*/
//4.实现:"返回字符串的容量的的函数"
size_t capacity()const
{
return _capacity;
}
/*-----------"清空字符串的内容"的函数-----------*/
//5.实现:"清空字符串的内容的函数"
void clear()
{
_str[0] = '\0';
_size = 0;
//注意:这里我们并不重置_capacity的值,这是因为clear只清理字符串中的内容并不会释放内存
}
/*-----------"下标访问运算符重载"的函数(非const版本)-----------*/
char& operator[](size_t pos)
{
//1.断言:保证访问的位置合法,防止越界
assert(pos < _size);
//2.返回字符的引用
return _str[pos];
}
/*-----------"下标访问运算符重载"的函数(const版本)-----------*/
const char& operator[](size_t pos)const
{
//1.断言:保证访问的位置合法,防止越界
assert(pos < _size);
//2.返回字符的引用
return _str[pos];
}
/*------------------------第四部分:string类内声明的函数------------------------*/
//1.字符串的"预留容量"的函数
//2.字符串的"追加字符"的函数
//3.字符串的"追加字符串"的函数
//4.字符串的"+=字符的重载"的函数
//5.字符串的"+=字符串的重载"的函数
//6.字符串的"插入字符"的函数
//7.字符串的"插入字符串"的函数
//8.字符串的"删除子串"的函数
//9.字符串的"查找字符"的函数
//10.字符串的"查找子串"的函数
//11.字符串的"获取子串"的函数
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 insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, size_t len = npos); //注意:这里有一个缺省值,后面的源文件中实现的时候不用再给缺省值了
//注意:这里的细节,平时我们是将pos作为函数的第一个参数,
//但是由于这里我们要单独给形参pos一个缺省值,所以我们选择在find函数中将pos作为形参的最后一个参数
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos, size_t len = npos);
private:
char* _str; //动态分配的字符数组指针
size_t _size; //当前字符串长度(不含'\0')
size_t _capacity; //当前分配容量(不含'\0')
static const size_t npos;
//注意:这里的细节
//这个静态成员变量我们没有在头文件的string类域外进行定义,而是选择在源文件中进行定义:
//这是因为:如果初始化放在头文件中,且该头文件被多个源文件包含,会导致多个定义,引发链接错误
};
/*------------------------第五部分:string类外声明的函数------------------------*/
//注意:下面的函数的参数是字符串的时候。我们写的形参是const string& 而不再是const char*
//这是因为:前面接口函数的实现本质都是我们调用C标准库实现的,所以形参我们写成的C风格的字符串
/*-----------比较运算符重载函数-----------*/
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);
}
实现文件:string.cpp
c
#include "string.h"
namespace mySpace
{
/*-----------------------------------定义string类中的静态的成员变量-----------------------------------*/
const size_t string::npos = -1;
/*-----------------------------------类内声明的函数的实现-----------------------------------*/
//1.实现:字符串的"预留容量"的函数
void string::reserve(size_t n)
{
//1.判断
if (n > _capacity)
{
//1.开辟
char* tmp = new char[n + 1];
//2.拷贝
strcpy(tmp, _str);
//3.释放
delete[] _str;
//4.更新(字符串指针 + 字符串的容量)
_str = tmp;
_capacity = n; //注意:在预留容量的时候我们并不需要进行更新字符串的长度
}
}
//2.实现:字符串的"追加字符"的函数
void string::push_back(char ch)
{
/*---------------第一步:判断是否进行扩容---------------*/
if (_size == _capacity)
{
//扩容策略:
//1.如果当前字符串的容量为0(第一次进行扩容),初始化为4字节
//2.如果当前字符串的容量非0(非初次进行扩容),按2倍扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
/*---------------第二步:在字符串的尾部进行追加字符---------------*/
_str[_size] = ch; //这里相当于覆盖掉了原字符串的字符串结束标志'\0'
_size++;
/*---------------第三步:手动在字符串的末尾添加字符串的结束标志---------------*/
_str[_size] = '\0';
}
//3.实现:字符串的"追加字符串"的函数
void string::append(const char* str)
{
/*---------------第一步:判断是否进行扩容---------------*/
size_t len = strlen(str);
if (_size + len > _capacity) //追加字符串后的新字符串的长度"大于"原字符串的容量的1倍 ----> 有必要进行扩容了
{
//扩容规则:追加字符串后的新字符串的长度
//1.如果"大于"原字符串的容量的2倍 ----> 新字符的长度有多大开多大的空间
//2.如果"小于"原字符串的容量的2倍 ----> 按原字符串的2倍进行扩容
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
/*---------------第二步:在字符串的尾部进行追加字符---------------*/
strcpy(_str + _size, str);
_size += len;
//注意:在字符串的末尾"追加字符串"的时候,并不需要像追加字符时那样的,在手动的在字符串的末尾添加一个\0
//因为:追加字符串虽然覆盖了原来的字符串的\0,但是追加的字符串末尾也有一个\0,充当了新字符串的\0
}
//4.实现:字符串的"+=字符的重载"的函数
string& string::operator+=(char ch)
{
push_back(ch); //注意:这个运算符的重载函数的功能和函数push_back函数的功能类似,所以这里我们直接调用了push_back函数
return *this; //注意:+=与push_back函数的不同之处就是:+=要返回本身
}
//5.实现:字符串的"+=字符串的重载"的函数
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
//6.实现:字符串的"插入字符"的函数
void string::insert(size_t pos, char ch)
{
/*---------------第一步:检查插入位置的合法性---------------*/
assert(pos <= _size);
/*---------------第二步:判断是否需要进行扩容---------------*/
if (_size == _capacity)
{
//扩容策略:
//1.如果当前字符串的容量为0(第一次进行扩容),初始化为4字节
//2.如果当前字符串的容量非0(非初次进行扩容),按2倍扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
/*---------------第三步:挪动数据腾出位置---------------*/
size_t end = _size + 1; //注意:这里的end是:原字符串中最后一个元素的要挪动的位置的索引
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
/*---------------第四步:在指定位置插入字符---------------*/
_str[pos] = ch;
_size++;
}
//7.实现:字符串的"插入字符串"的函数
void string::insert(size_t pos, const char* str)
{
/*---------------第一步:检查插入位置的合法性---------------*/
assert(pos <= _size);
/*---------------第二步:判断是否需要进行扩容---------------*/
size_t len = strlen(str);
if (_size + len > _capacity) //插入字符串后的新字符串的长度"大于"原字符串的容量的1倍 ----> 有必要进行扩容了
{
//扩容规则:插入字符串后的新字符串的长度
//1.如果"大于"原字符串的容量的2倍 ----> 新字符的长度有多大开多大的空间
//2.如果"小于"原字符串的容量的2倍 ----> 按原字符串的2倍进行扩容
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
/*---------------第三步:挪动数据腾出位置---------------*/
size_t end = _size + len; //注意:这里的end的意思是:原字符串中最后一个字符应该挪动到的位置的索引
while (end > pos + len - 1) //注意:这里的判断条件是:end>pos+len-1而不是:end>pos
{ //其实上面的任意插入一个字符本质上应该是:while(end>pos+1-1)
//向后挪动数据:后面的位置 = 前面的数据
_str[end] = _str[end - len];
--end;
}
/*---------------第四步:在指定位置插入字符串---------------*/
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
_size += len;
}
//8.实现:字符串的"删除子串"的函数
void string::erase(size_t pos, size_t len)
{
/*---------------第一步:检查删除位置的合法性---------------*/
assert(pos < _size); //注意:这里和插入位置的合法性的区别
/*---------------第二步:"处理"要删除的字符串的长度不合法的情况---------------*/
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
/*---------------第三步:处理要删除的字符串的长度合法的情况---------------*/
else
{
//1.将后面未被字符串挪动到开始删除的位置
//1)字符串的后面没有被删除的"第一个字符":pos+len
//2)字符串的后面没有被删除的"最后个字符":_size
for (size_t i = pos + len; i <= _size; i++)
{
_str[i - len] = _str[i];
}
_size -= len;
}
}
//9.实现:字符串的"查找字符"的函数
size_t string::find(char ch, size_t pos)
{
/*---------------第一步:检查查找位置的合法性---------------*/
assert(pos < _size);
/*---------------第二步:使用for循环遍历搜索字符---------------*/
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
//10.实现:字符串的"查找子串"的函数
size_t string::find(const char* str, size_t pos)
{
/*---------------第一步:检查查找位置的合法性---------------*/
assert(pos < _size);
/*---------------第二步:使用strstr函数查找子串---------------*/
//1.使用ptr指针保存子串首次出现位置的地址
const char* ptr = strstr(_str + pos, str);
//3.使用ptr处理出子串首次出现位置的下标
if (ptr == nullptr)
{
return npos; // 没有找到子串
}
else
{
return ptr - _str; //找到了子串,返回其首次出现的位置
}
}
//11.实现:字符串的"获取子串"的函数
string string::substr(size_t pos, size_t len)
{
/*---------------第一步:检查查找位置的合法性---------------*/
assert(pos < _size);
/*---------------第二步:"修正"要删除的字符串的长度不合法的情况---------------*/
if (len > _size - pos)
{
len = _size - pos;
}
/*---------------第三步:"处理"要删除的字符串的长度合法的情况---------------*/
//1.创建子串对象
string sub;
//2.为子串开辟空间
sub.reserve(len);
//3.将子串的内容拷贝到新空间中
for (size_t i = 0; i < len; ++i)
{
sub += _str[pos + i]; //注意:这里拷贝的实现
}
return sub;
}
/*-----------------------------------类外声明的函数的实现-----------------------------------*/
/*----------------------比较运算符重载函数----------------------*/
//1.实现:比较运算符<的重载
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
//注意细节:
//1.这里我们形参字符串写成了:const string& 的形式 ---> 下面我们要使用C库中的strcmp函数之前要将其转换为C风格的字符串
//2.strcmp对于比较结果可以返回三种的结果:负数、0、正数
}
//2.实现:比较运算符=的重载
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
//3.根据上面的实现的两个运算符:实现剩下的四个运算符
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 !(s1 == s2);
}
/*----------------------流运算符重载函数----------------------*/
//1.实现:"输出流运算符重载函数"
std::ostream& operator<<(std::ostream& out, const string& s)
{
//1.使用范围for循环字符串中的每一个变量
for (auto ch : s)
{
out << ch;
}
//2.返回输出流对象
return out;
}
//2.实现:"输入流运算符重载函数"
std::istream& operator>>(std::istream& in, string& s)
{
/*---------------第一步:清空目标字符串中的原有内容---------------*/
s.clear(); //注意:这样做的目的是保证每次读取的都是全新的内容
/*---------------第二步:定义缓冲区---------------*/
//1.定义设置缓冲区大小的常量N
const int N = 256;
//2.创建字符缓冲区
char buffer[N];
//3.定义变量记录当前缓冲区写入位置的索引
int i = 0;
//4.定义变量临时存储读取的字符
char ch;
/*---------------第三步:使用while循环不断读取字符---------------*/
ch = in.get(); //注意:原始实现使用 in >> ch 会跳过空白符(不符合字符串输入需求)
while (ch != ' ' && ch != '\n') //改用 in.get() 逐个读取字符(包括空白符)
{
//1.将读入的的字符存储到缓冲区
buffer[i++] = ch;
//2.进行缓冲区安全检查
if (i == N - 1)
{
//2.1:添加字符串的结束标志
buffer[i] = '\0';
//2.2:将缓冲区中的内容追加到目标字符串对象上(堆区)
s += buffer;
//2.3:重置缓冲区索引
i = 0;
}
//3.读取下一个字符
ch = in.get();
}
/*---------------第四步:处理缓冲区剩余的内容---------------*/
if (i > 0)
{
//1.添加字符串的结束标志
buffer[i] = '\0';
//2.将缓冲区中的内容追加到目标字符串对象上(堆区)
s += buffer;
}
/*---------------第五步:返回输入流对象---------------*/
return in; //支持链式调用
}
}
测试文件:Test.cpp
c
#include "string.h"
using namespace std;
/*------------------------------构造函数测试------------------------------*/
void TestConstructors()
{
cout << "\n===== 构造函数测试 =====\n";
// 默认构造函数
mySpace::string s1;
cout << "默认构造函数: \"" << s1 << "\" (长度=" << s1.size()
<< ", 容量=" << s1.capacity() << ")\n";
// C风格字符串构造函数
mySpace::string s2("Hello");
assert(s2.size() == 5);
assert(strcmp(s2.c_str(), "Hello") == 0);
cout << "C风格字符串构造函数: \"" << s2 << "\"\n";
// 拷贝构造函数
mySpace::string s3(s2);
assert(s3 == s2);
cout << "拷贝构造函数: \"" << s3 << "\"\n";
}
/*------------------------------修改功能测试------------------------------*/
void TestModifiers()
{
cout << "\n===== 修改功能测试 =====\n";
mySpace::string s;
// push_back 测试
s.push_back('H');
s.push_back('i');
assert(s == "Hi");
cout << "调用push_back后: \"" << s << "\"\n";
// append 测试
s.append(" there!");
assert(s == "Hi there!");
cout << "调用append后: \"" << s << "\"\n";
// += 操作符测试
s += ' ';
s += "C++";
assert(s == "Hi there! C++");
cout << "调用+=后: \"" << s << "\"\n";
// insert 测试
s.insert(3, "awesome ");
assert(s == "Hi awesome there! C++");
cout << "调用insert后: \"" << s << "\"\n";
// erase 测试
s.erase(3, 8);
assert(s == "Hi there! C++");
cout << "调用erase后: \"" << s << "\"\n";
}
/*------------------------------访问功能测试------------------------------*/
void TestAccessors()
{
cout << "\n===== 访问功能测试 =====\n";
const mySpace::string s("Hello World");
// operator[] 测试
assert(s[0] == 'H');
assert(s[6] == 'W');
cout << "s[0] = '" << s[0] << "', s[6] = '" << s[6] << "'\n";
}
/*------------------------------操作功能测试------------------------------*/
void TestOperations()
{
cout << "\n===== 操作功能测试 =====\n";
mySpace::string s = "Hello";
// find 测试
assert(s.find('e') == 1);
assert(s.find("ll") == 2);
assert(s.find('x') == string::npos);
cout << "查找'e'的位置: " << s.find('e') << endl;
cout << "查找'll'的位置: " << s.find("ll") << endl;
// substr 测试
assert(s.substr(1, 3) == "ell");
cout << "截取子串(1,3): \"" << s.substr(1, 3) << "\"\n";
}
/*------------------------------迭代器测试------------------------------*/
void TestIterators()
{
cout << "\n===== 迭代器测试 =====\n";
mySpace::string s = "Hello";
// 正向迭代器
cout << "使用正向迭代器遍历: ";
for (auto it = s.begin(); it != s.end(); ++it)
{
*it = toupper(*it);
cout << *it;
}
cout << " -> " << s << endl;
assert(s == "HELLO");
//// 反向迭代器
//cout << "使用反向迭代器遍历: ";
//for (auto rit = s.rbegin(); rit != s.rend(); ++rit)
//{
// cout << *rit;
//}
//cout << endl;
// 范围for循环
cout << "使用范围for循环修改: ";
for (char& c : s)
{
c = tolower(c);
}
cout << s << endl;
assert(s == "hello");
}
/*------------------------------容量测试------------------------------*/
void TestCapacity()
{
cout << "\n===== 容量测试 =====\n";
mySpace::string s;
cout << "初始化状态 - 长度: " << s.size()
<< ", 容量: " << s.capacity()<< endl;
s.reserve(100);
cout << "调用reserve(100)后 - 容量: " << s.capacity() << endl;
assert(s.capacity() >= 100);
}
/*------------------------------比较运算测试------------------------------*/
void TestComparison()
{
cout << "\n===== 比较运算测试 =====\n";
mySpace::string a = "apple";
mySpace::string b = "banana";
assert(a < b);
assert(b > a);
assert(a == "apple");
assert(a != b);
cout << "\"apple\" 小于 \"banana\": " << (a < b) << endl;
cout << "\"apple\" 等于 \"apple\": " << (a == "apple") << endl;
}
/*------------------------------输入输出测试------------------------------*/
void TestIO()
{
cout << "\n===== 输入输出测试 =====\n";
mySpace::string s;
cout << "请输入一个字符串(遇到空格停止): ";
cin >> s;
cout << "你输入的字符串是: \"" << s << "\"" << endl;
}
int main()
{
cout << "=== 开始执行字符串类测试套件 ===\n";
TestConstructors();
TestModifiers();
TestAccessors();
TestOperations();
TestIterators();
TestCapacity();
TestComparison();
TestIO();
cout << "\n=== 所有测试已成功通过! ===\n";
return 0;
}
运行结果

