目录
[3.1 operator[]](#3.1 operator[])
7.1opraetor+=,append,push_back

简单理解就是有关字符的东西我们学完就会感觉到用这里提供的东西会比C语言舒服很多。而且我们要联系我们前面学习的日期类的内容去学习string。在这方面的学习我们要学习去看一些文档,没办法这是我们学习过程中必须学习的一项技能,因为学习这部分时我们在这里不可能给大家介绍完它的所有接口的功能或者用法,在直接使用时可能需要边查找边使用。
(文档:https://cplusplus.com/reference/string/string/?kw=string#google_vignette)
一、介绍
C++ 中的 std::string 是 标准库提供的字符串类(头文件 <string>),比 C 语言的字符数组(char[ ])更安全、易用,核心优势是自动管理内存、支持丰富的成员函数和运算符重载。
我们在使用string类的时候必须包含#include头文件和using namespace std.
我们接下来会重点说明我们常用string类中的一些方法,我会贴上对应的文档图片帮助大家阅读。我们这里基本上说的是C++11版本的。
二、实例化相关
2.1构造函数(constructor)

我们这里可以看到string类里的构造函数为我们提供了很多接口,但我们只需要记住常用的就可以解决日常使用的问题了。1,创造空字符串对象。2,拷贝创造。3,从str的pos位置截取len个字符创造。4,字符串创造。5,字符串前n个创造。6,n个字符c创造。
其他的初始化列表构造,迭代器范围构造,移动构造只需要了解。
#include <iostream>
using namespace std;
void test1()
{
string s1;
string s2(s1);
string s4("hello world");
string s3(s4, 6, 5);
string s5(s4, 5);
string s6(6, 'c');
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
cout << s5 << endl;
cout << s6 << endl;
}
2.2析构函数(destructor)

析构函数就没有什么特别的,我们能想到对于string这个结构体它内部应该是
class string
{
char* _str;
size_t _size;
size_t capacity;
};
所以它的析构也就是改释放的释放,该归零的归零。
2.3赋值运算符(operator=)

1,把一个string对象给当前对象。2,把字符串给当前对象。3,把字符给当前对象。而且我们可以注意到这些赋值接口返回的都是string&说明可以支持链式赋值(s1 = s2 = s3)
其他的字符初始化列表赋值,移动赋值只需要了解。
void test2()
{
string s1("cqw");
string s2;
s2 = s1;
string s3 = "cqw";
string s4;
s4 = 'c';
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
}
三、遍历方法
3.1 operator[]
当我们想实现遍历时一定绕不开string类中重载的[ ]这个操作符,它可以让我们直接通过下标访问字符,就像我们数组中的那样。
void test3()
{
string s1("hello world");
//cout << s1[0] << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i];
}
}
3.2迭代器(Iterators)
迭代器(iterator)是STL中一个很重要的模块,是连接容器与算法的桥梁,本质是封装了指针的对象,提供统一接口遍历容器元素,但不同容器(数组/链表/树)的底层存储差异。我们可以在string的时候把它当作指针来理解,但它绝对不可能是指针因为迭代器是可以代替数组下标,支持++,*等操作,如果它就是指针在这里还说得过去,但在双向链表的时候就不可能是指针。
我们要学习这些迭代器的函数

3.2.1begin和end
begin():返回指向字符串首个字符的普通迭代器(可读写)。
end():返回指向字符串末尾(最后一个字符的下一位)的普通迭代器(可读写)。

void test4()
{
string s1("hello world");
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
it++;
}
}
3.2.2rbegin和rend
rbegin():返回指向字符串最后一个字符的反向迭代器(可读写,遍历方向从后往前)。
rend():返回指向字符串首个字符前一位的反向迭代器(可读写)。
我们要注意这里移动是还是++,但移动方向是从后往前,这里也可以看出迭代器并不是指针,指针无法解释这种情况。

void test4()
{
string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit;
++rit;
}
}
3.2.3cbegin和cend
cbegin():返回指向字符串首个字符的常量迭代器(只读)。
cend():返回指向字符串末尾的常量迭代器(只读)。
这里只要注意它俩只能读这个特点,然后我们阅读文档的时候可以看到begin有两种可以用的接口。我们这里使用cbegin的时候也可以用begin。cend也是一样


void test4()
{
string s1("hello world");
/*string::const_iterator cit = s1.cbegin();
while (cit != s1.cend())
{
cout << *cit;
++cit;
}*/
string::const_iterator cit = s1.begin();
while (cit != s1.end())
{
cout << *cit;
++cit;
}
}
3.2.4crbegin和crend
crbegin():返回指向字符串最后一个字符的常量反向迭代器(只读,遍历方向从后往前)。
crend():返回指向字符串首个字符前一位的常量反向迭代器(只读)。
这里只要注意它俩只能读这个特点,然后我们阅读文档的时候可以看到rbegin有两种可以用的接口。我们这里使用crbegin的时候也可以用rbegin。crend也是一样


void test4()
{
string s1("hello world");
/*string::const_reverse_iterator crit = s1.crbegin();
while (crit != s1.crend())
{
cout << *crit;
++crit;
}*/
string::const_reverse_iterator crit = s1.rbegin();
while (crit != s1.rend())
{
cout << *crit;
++crit;
}
}
3.3auto和范围for
auto是 C++11 引入的自动类型推导关键字,核心作用是让编译器根据变量初始化表达式自动推断其数据类型,简化代码并提升灵活性。
auto的使用也要注意,必须初始化,如果要推导引用类型必须要用auto&,它会把数组推导成指针。但不要把模板T和它混为一谈,因为auto不能作参数但可以做返回值。
void test5()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = func1();
//auto e;//err
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 = 1.0;//err
}
范围for:对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。for循环后的括号由冒号" :"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。所以说这种方法的本质就是迭代器。而且我们还可以直接改变值(auto&),不改变原字符串(auto)。
void test6()
{
string s1("hello world");
for (auto& ch : s1)
{
ch -= 2;
cout << ch;
}
cout << endl;
cout << s1 << endl;
}
五、容量管理相关成员函数

5.1size和length

这两个都是获取字符串的长度。按理说是有一个size就够用了,但这里就要说到SLT是一个时代的产物,它的时间跨度已经比较长了,string又是很早出现的,所以在实现的一些功能上就有一些冗余。同时我们要注意size可能不同于capacity,而且它受编码(后面会重点说明)的影响。
5.2max_size

获取字符串理论上的最大长度。我们在string里的使用并不是很频繁,因为我们使用时会发现它的值是2147483647,因为max_size返回的是当前系统 / 编译器下,string能容纳的理论最大字符数,这个值由内部存储字符的类型(通常是char)和系统的地址空间决定。
我这里使用的是VS219,32位系统,char占1个字节,32位系统的有符号整数最大值是2^31-1=2147483647。当然实际中受物理内存限制,几乎不可能达到这么大的字符串。

void test7()
{
string s1("hello world");
int sz = s1.max_size();
cout << sz << endl;
}
5.3capacity

获取已经分配的内存容量。不包含存储字符串末尾隐式 \0的空间。
5.4resize

调整字符串为n。 如果 n 小于当前字符串的长度 ,则当前值会被截取为前 n 个字符,超出第 n 个位置的字符将被删除。如果 n 大于当前字符串的长度 ,字符串会被扩展,新增的字符默认填充 '\0'(第一种重载),或填充指定字符 c(第二种重载)。
void test8()
{
string s1("hello world");
s1.resize(5);
cout << s1 << endl;
string s2("hello world");
s2.resize(5,'c');
cout << s2 << endl;
string s3("hello world");
s3.resize(20);
cout << s3 << endl;
string s4("hello world");
s4.resize(20,'c');
cout << s4 << endl;
}
5.5reserve

预分配n的内存容量。当n>capacoty时 ,函数会强制扩容,将容量调整到至少n(看编译器的扩容策略,编译器可能分配更大的空间,比如按 2 倍扩容策略),避免后续频繁的内存分配。当n<=capacity时, 编译器可以选择不缩减容量(出于性能优化,比如避免频繁释放 / 重新分配内存),最终容量可能仍大于n。
这里我可以带大家看一些VS2019对于这部分的处理
void test9()
{
string s1("hello world");
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
s1.reserve(20);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
s1.reserve(28);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
s1.reserve(40);
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl << endl;
//打印迭代器类型
cout << typeid(string::iterator).name() << endl;
cout << typeid(string::reverse_iterator).name() << endl;
}

5.6clear

清空字符串内容。执行后,字符串的size(实际长度)会直接变为0;但注意:clear不会改变capacity(已分配的内存容量)------ 之前分配的内存会保留,不会释放。
关于这一点我们也可以从这里看出来

5.7empty

判断字符串是否为空。只读操作不会改变size和capacity,比size()==0更高效。
void test10()
{
string s1;
string s2 = "test";
string s3;
s3.clear();
cout << "s1是否为空:" << (s1.empty() ? "是" : "否") << endl;
cout << "s2是否为空:" << (s2.empty() ? "是" : "否") << endl;
cout << "s3是否为空:" << (s3.empty() ? "是" : "否") << endl;
}
六、访问元素相关

operator[ ]通过下标运算符访问元素,at通过成员函数访问元素并且会做越界检查,back获取容器的最后一个元素,front获取容器的第一个元素。
void test11()
{
string s1("hello world");
cout << s1[2] << endl;
cout << s1.back() << endl;
cout << s1.front() << endl;
}
七、修改相关

7.1opraetor+=,append,push_back
是用于在字符串末尾添加字符或字符串。

void test12()
{
string s1("hello world");
s1 += 'c';
s1 += "qw";
cout << s1 << endl;
string s2("study");
s1.append(s2, 0, 5);
s1.append(3, '?');
s1.append("yes");
cout << s1 << endl;
s1.push_back('!');
cout << s1 << endl;
}
7.2insert

在字符串的指定位置插入字符 / 字符串。pos超出size时,插入位置自动调整为字符串末尾(不会抛异常)。插入后字符串长度会增加(原位置及后续字符向后移动)。
void test13()
{
string s1("hello world");
s1.insert(5, "my");
cout << s1 << endl;
s1.insert(0, 3, '!');
cout << s1 << endl;
string s2("**************");
s1.insert(16, s2, 4, 4);
cout << s1 << endl;
}
7.3erase

删除字符串中指定范围的字符。npos是string类的静态常量,表示 "直到字符串末尾"(si'ze_t类型的最大值)而且迭代器需是有效的(不能指向字符串外)。
void test14()
{
string s1("hello world");
s1.erase(6, 5);
cout << s1 << endl;
s1.erase(4);
cout << s1 << endl;
s1.erase(s1.begin());
cout << s1 << endl;
s1.erase(s1.begin(), s1.begin()+2);
cout << s1 << endl;
}
7.4replace

用新字符 / 字符串替换原字符串中指定范围的内容
void test15()
{
string s = "I like apple";
s.replace(7, 1, "But");
cout << s << endl;
s.replace(1, 9, "HH");
cout << s << endl;
s.replace(2, 4, 3, '*');
cout << s << endl;
}
八、补充
8.1substr

截取字符串的子串。子串的起始位置(默认是 0,即从字符串开头开始),pos不能超过原字符串的长度,否则会触发未定义行为。子串的长度(默认是npos,表示截取到原字符串的末尾)。如果pos+len超过原字符串的长度,会自动截取到原字符串末尾。返回一个新的string对象。
int main() {
string s = "Hello, World!";
string sub1 = s.substr(0, 5);
cout << sub1 << endl;
string sub2 = s.substr(7);
cout << sub2 << endl;
string sub3 = s.substr(2, 10);
cout << sub3 << endl;
return 0;
}
8.2查找内容

find从字符串开头开始,查找子串 / 字符第一次出现的位置。
rfind从字符串末尾开始,查找子串 / 字符最后一次出现的位置。
find_first_of查找 "目标字符集合中任意一个字符" 第一次出现的位置。
find_last_of查找 "目标字符集合中任意一个字符" 最后一次出现的位置。
find_first_not_of查找 "不在目标字符集合中的字符" 第一次出现的位置。
find_last_not_of查找 "不在目标字符集合中的字符" 最后一次出现的位置。
8.3getline

可以读取带空格的字符串。我们在使用cin<<string读取字符串的时候遇到空格就会停止,这个时候getline就很好使用。
我们来看下面这道题(链接:https://www.nowcoder.com/share/jump/8889611841764899726177)

#include <iostream>
#include <string>
using namespace std;
int main() {
string str;
getline(cin,str);
size_t n = str.find_last_of(' ');
cout << str.size()-1-n << endl;
return 0;
}