【c++】STL-string容器的使用
hello~ 很高兴见到大家! 这次带来的是C++中关于STL-string容器的使用这部分的一些知识点,如果对你有所帮助的话,可否留下你的三连呢?
个 人 主 页: 默|笙
文章目录
- 一、STL
- 1.1 什么是STL
- 1.2 STL的版本
- 1.3 STL的六大组件
- 二、string类及其使用
- 2.1 成员常量
- 2.2 可以默认生成的特殊成员函数
- 1. 构造函数
- 2. 析构函数
- 3. 赋值重载函数
- 2.3 迭代器成员函数
- 1.正向迭代器(begin从前往后遍历):
- 2.反向(reverse)迭代器(rbegin从后往前遍历):
- 2.4 遍历
- 2.4.1 下标 + []
- 2.4.2 迭代器
- 2.4.3 范围 for(c++11)
- 1. auto关键字
- 2.遍历
- 2.5 容量相关成员函数
- 7. *shrink_to_fit():
- 8. resize(n, c):
- 9. reserve(n):
- 2.6 元素访问相关成员函数
- 1. operator[]:
- 2. *at(pos):
- 2.7 修改相关成员函数
- 1. operator+=:
- 2. *append():
- 3. *push_back():
- 4.*assign():
- 5.*insert():
- 6.*erase():
- 7.*replace:
- 8.swap():
- 9.pop_back():
- 2.8 字符串操作相关成员函数
- 1. c_str():
- 2.*data():
- 3.get_allocator():
- 4.*copy(s, len, pos):
- 5.substr(pos, len):
- 6.find():
- 7.*rfind():
- 8.*find_first_of(s/c, pos):
- 9.*find_last_of(s/c, pos):
- 10.*find_first_not_of(s/c, pos):
- 11.*find_last_not_of(s/c, pos):
- 12.*compare():
- 2.9 非成员函数
- 1.*operator+:
- 2.relational operators:
- 3.swap(x, y):
- 4.operator>> 与 operator<<:
- 5.getline(string)
一、STL
1.1 什么是STL
STL(Standard Template Library,标准模板库) 是 C++ 标准库的重要组成部分,提供了通用的模板类和函数,用于实现数据结构和算法。它的核心思想是泛型编程,通过模板(Template)让代码具备高度复用性。
1.2 STL的版本
- HP版本:STL首个实现版本,开源,其他版本通常都是基于此版本来实现的。
- P.J.版本:继承自HP版本,不开源,可读性低,符号命名比较怪异,被Windows Visual C++采用。
- RW版本:继承自HP版本,不开源,可读性一般,被C+ + Builder 采用。
- SGI版本:继承自HP版本,开源,可读性高,被GCC(Linux)采用。
1.3 STL的六大组件
- 容器:用于存储和管理数据的类模板。
- 算法:一系列用于处理容器中元素的函数模板。
- 迭代器:与指针类似,用于遍历容器中的元素,是容器与算法之间沟通的桥梁。
- 仿函数:通过重载函数调用运算符 () 实现,可像函数一样调用的类或结构体(不是真函数,它是对象,但行为像函数),能用于自定义算法的行为。
- 配接器:也叫适配器,用于修改容器、迭代器或仿函数的接口,使其符合特定需求 。
- 空间配置器:负责容器的内存分配和释放,管理容器的内存资源。
二、string类及其使用
string:
定义: std :: string是c++标准库提供的动态字符串类(位于头文件 < string> 中),它封装了对字符序列的底层操作,以成员函数和运算符重载为用户提供了一套安全、便捷的接口。
- 可以将其理解为一个管理字符数组的顺序表。
- string 实际上是 basic_string< char>此类型的别名,是basic_string模板char类型的实例化版本。
- 使用的时候包含头文件< iostream>,后进行命名空间的展开 using namespace std; 。
- 有很多接口都不常用,不需要我们去记忆,在需要使用的时候动手去查一查就行,目录里标注星号的一般没那么重要。string<—
2.1 成员常量
- 它是一个静态成员常量值,npos 的值被设置为无符号整数(size_t)的最大值。
- 当此值用作 string 成员函数中的 len(长度参数) 时,表示直至这个字符串的末尾。
- 作为返回值,它通常用于表示未找到匹配项。
- 这个常量被定义为数值 -1,但由于 size_t 是无符号整数类型,因此该值实际上是该类型所能表示的最大可能值。
- -1 的补码被无符号整数类型解析为最大值。
2.2 可以默认生成的特殊成员函数
1. 构造函数
- 默认构造函数:构造一个长度为0个字符的空字符串。
- 拷贝构造函数:以 str 为蓝本,拷贝构造一个字符串。
- *子串构造函数:以 str 为蓝本,从索引为 pos 的字符起,拷贝 len 个字符,如果没有传实参给 len ,将会使用默认值 npos。
- 若 len 的值超过剩余长度(比如默认值 npos),则截断至末尾。
- 若 pos 越界时,构造函数会抛出异常,不会静默处理。
- c风格字符串构造:复制指针 s 指向的以’\0’ 结尾的字符串序列(遇到 ‘\0’ 停止)。
- *缓冲区构造:复制指针 s 指向的字符串里的前 n 个字符(无论是否有 ‘\0’。
- *填充构造函数:用字符 c 的连续 n 个副本填充字符串。
- *范围构造函数:按顺序复制迭代器区间 [first, last)(左闭右开)内的所有字符。关于迭代器下文将会讲到,它类似指针但不是指针。
//默认构造
string s1;
cout << "s1:" << s1 << endl;
s1 = "abcdefg";
//拷贝构造
string s2 = s1;//string s2(s1);
//子串构造
string s3(s1, 2);
//c风格
string s4("abc\0defg");
//从缓冲区
string s5("abc\0defg", 6);
//填充
string s6(5, 'a');
//范围
string s7(s2.begin(), s2.end());
cout << "s2:"<< s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;
执行结果:
- 在 std :: string 中,一般情况下不会显式存储’\0’,但在与c风格函数交互时会根据需要进行存储。
2. 析构函数
析构函数会在 std::string 对象声明周期结束时(如离开作用域,被显式删除),会自动调用析构函数,无需手动干预,仅作了解即可。
3. 赋值重载函数
string s1("abcdefg");
string s2;
cout << "s2:" << s2 << endl;
//string,自赋值是安全的(s2 = s2)
s2 = s1;
cout << "string:"<< "s2:" << s2 << endl;
//c_string
s2 = "abc\0defg";
cout << "string_c:" << "s2:" << s2 << endl;
//character
s2 = 'a';
cout << "character:" << "s2:" << s2 << endl;
执行结果:
- s2 首先得存在,若 s2 不存在就进行赋值操作,编译器会强制调用构造函数,赋值操作将变为初始化操作:string s2 = s1。
2.3 迭代器成员函数
迭代器:在 C++ 中,迭代器(Iterator) 是一种行为类似于指针的对象,它提供了一种统一的方式来遍历和操作容器(如 std::vector、std::list、std::map 等)中的元素,而无需暴露容器的底层实现细节。
意义:
- 统一且类似的方式修改容器。在之后的遍历操作中会详细讲解。
- 算法脱离具体底层结构,和底层结构解耦。(降低耦合,降低关联性,不用担心底层如何存储,实现)
1.正向迭代器(begin从前往后遍历):
- begin():返回一个指向字符串首字符(第一个元素) 的非常量迭代器。
- end():返回一个指向字符串最后一个字符的下一个位置的非常量迭代器。
- *cbegin()和*cend():用于返回正向常量迭代器,相比于begin()/end() 加 const 修饰返回正向常量迭代器,它的语义更明确(好区分),避免意外修改。
string s1 = "abcdefg";
string::iterator it1 = s1.begin();
string::const_iterator it1 = s1.begin();
string::const_iterator it1 = s1.cbegin();
2.反向(reverse)迭代器(rbegin从后往前遍历):
- rbegin():返回一个指向字符串最后一个字符的非常量迭代器(反向开头)。
- rend():返回一个指向字符串首字符的前一个位置的非常量迭代器。
- *crbegin()和 *crend():用于返回反向常量迭代器,比 rbegin()/rend() 加 const 修饰返回反向常量迭代器更清晰(好区分)安全。
string::reverse_iterator it3 = s1.rbegin();
string::const_reverse_iterator it3 = s1.rbegin();
string::const_reverse_iterator it3 = s1.crbegin();
关于常量迭代器:关于新的类型const_iterator与const_reverse_iterator,它们限制只能对“指向”的元素进行只读访问,而不是指它本身不可修改(const iterator 是本身不可修改)。它们能够很好的保护数据。
//正向
cout << "正向:" << endl;
string::iterator it1 = s1.begin();
while (it1 != s1.end())
{cout << *it1 << " ";it1++;
}
cout << endl;
//反向
cout << "反向" << endl;
string::reverse_iterator it2 = s1.rbegin();
while (it2 != s1.rend())
{cout << *it2 << " ";it2++;
}
执行结果:
2.4 遍历
2.4.1 下标 + []
string s1 = "abcdefg";
//下标 + []
for (size_t i = 0; i < s1.size(); i++)
{cout << s1[i] << endl;
}
2.4.2 迭代器
string s1 = "abcdefg";//迭代器//[begin(), end())string::iterator it1 = s1.begin();while (it1 != s1.end()){(*it1)++;++it1;}cout << s1 << endl;
对于 string 而言,下标 + [] 的确更加直白简单,但是对于其他容器如链表(空间一般不连续),下标 + [] 不再适用,而迭代器适用于一切容器,是容器的主流遍历方式。
2.4.3 范围 for(c++11)
1. auto关键字
在c/c++早期,auto的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量。后来在 c++11中,它被赋予新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量的类型必须由编译器在编译时期经过推导而得。
- auto能够自动推导类型。
- auto 和 auto*声明指针类型没有什么不同,但是在声明引用类型时,必须添加 ‘&’。
int x = 10;
int& y = x;
auto z1 = y;//auto推演的是y本身的int类型,而非int&类型
z1++;
cout << y << endl;
auto& z2 = y;//在声明引用类型时,要加上&
z2++;
cout << y << endl;
执行结果:
- 推导过程顺序独立进行:同一行申明多个变量时,这些变量类型必须相同,否则编译器会报错,因为编译器实际只会对第一个变量进行推导,然后用推导出来的类型定义其他变量。
auto x = 5, y = 10; // 合法:x 和 y 均为 int
auto a = 1, b = 3.14; // 错误:int 和 double 类型不匹配,编译报错
- auto不能作为函数的参数,编译报错,但是可以作返回值。谨慎使用。
- auto不能直接用来声明数组,编译报错。
2.遍历
//范围 for
for (auto e : s1)//e是容器元素的别名,可以随便换,但一般用e
{e++;cout << e;
}
- 范围for循环的核心机制:
- 通过容器的begin()/end()获得迭代器范围。
- 自动迭代(递增迭代器)。
- 自动解引用:自动取容器数据赋值给e。
- 自动判断是否结束。
- 跟迭代器一样,所有容器都可以用它来进行遍历,非常方便,但它底层是迭代器。
2.5 容量相关成员函数
- size():返回字符串的大小。
- length():返回字符串的长度。
- *max_size():返回字符串可以达到的最大理论长度。实际往往比理论要小,意义不大。
- capacity():返回已分配存储空间的大小。
vs里面,除了第一次,后续都是 1.5 倍扩容。
gcc里面,2倍扩容。
- clear():清空字符串里的所有字符。
- empty():判断是否还有字符,是否为空。是为 true,不是为 false。
7. *shrink_to_fit():
请求字符串减少其容量以适应其实际大小。
缩容代价很大,需重新分配内存:它无法直接缩小原有空间,只能去申请一块适应字符串大小的新的空间,然后将原内容拷贝到新的空间中,再释放原有空间,最后让string的内部指针指向新的空间。是一种用时间换空间的做法,一般不会这样做。
8. resize(n, c):
将字符串调整为n个字符的长度。
- 如果 n 小于字符串的长度,字符串会被截断为前 n 个字符,后面的字符将被删除。
- 如果 n 大于字符串的长度,那么会在字符串末尾插入所需数量的字符来达到 n 的大小。如果指定了 c,将会以 c 的副本作为新元素填充,未指定时,则用值初始化字符(‘\0’)进行填充。
- 容量够用时不会改变,不够用时会自动扩容。
string s1 = "abcdefg";
//初始值:
cout << "初始值:" << endl;
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;//改变大小(增)且不指定:
cout << "改变大小(增)且不指定:" << endl;
s1.resize(100);
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;//改变大小(减):
cout << "改变大小(减):" << endl;
s1.resize(4);
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;//补充:改变大小(增)且指定:
cout << "补充:改变大小(增)且指定:" << endl;
s1.resize(10, '#');
cout << "s1:" << s1 << endl;
cout << "capacity:" << s1.capacity() << endl;
cout << "size:" << s1.size() << endl;
执行结果:
9. reserve(n):
请求调整字符串容量,用于预先分配内存,以避免后续操作频繁分配内存,从而提高性能。扩容靠谱,缩容不靠谱。
它会将容量调整为至少 n 个字符,但不会改变字符串的长度。
- 若 n 大于当前容量,则必须扩容到 n 个字符或者更大的容量(考虑内存对齐等问题)。
- 若 n 小于等于当前容量,则不保证(非约束性)执行任何操作(容量可能保持不变,或按实现策略优化)。
2.6 元素访问相关成员函数
1. operator[]:
返回字符串 pos 索引处字符的引用(非 const 限定的字符串可对其进行修改)。
- 若 pos == 字符串长度,则函数会返回对空字符’\0’的引用。
- operator[]不进行任何边界检查,但在某些编译器如 vs 下面,一旦越界,运行时会断言报错。
2. *at(pos):
功能与 operator[] 一样,不过它在处理越界时会抛异常。
void stirng_test06()
{string s1 = "abcdefg";s1.at(25);
}
int main()
{try{stirng_test06();}catch (const exception& e){cout << e.what() << endl;}return 0;
}
结果:
- *back():返回字符串结束位置的字符。
- *front():返回字符串首字符。
2.7 修改相关成员函数
1. operator+=:
通过在当前字符串的末尾添加其他字符来拓展字符串。
//string
string s0 = "abcdefg";
string s1 = "###";
s0 += s1;
cout << s0 << endl;//c-string
string s2 = "abcdefg";
const char* str = "###";
s2 += str;
cout << s2 << endl;//character
string s3 = "abcdefg";
s3 += '#';
cout << s3 << endl;
结果:
2. *append():
追加内容,相比于operator +=,功能更加丰富,但其实平常用得不多。
3. *push_back():
在尾部插入一个字符。
4.*assign():
整体替换内容,相比于operator =,功能更加丰富,但其实平常用得不多。
5.*insert():
在索引为 pos 的值前插入字符或字符串。一般记住一个插入字符一个插入字符串的就行了。
string s1 = "abcdefg";
//字符串
s1.insert(0, "###");
cout << s1 << endl;
//字符
s1.insert(0, 1, '!');
cout << s1 << endl;
s1.insert(s1.begin(), '$');
cout << s1 << endl;
结果:
insert谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。
6.*erase():
删除部分字符串,减少字符串长度。
string s1 = "abcdefg";
//从索引为1的字符开始删,删一个
s1.erase(1, 1);
cout << s1 << endl;
//从索引为1的字符开始删,删完为止
s1.erase(1);
cout << s1 << endl;
erase:谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。
7.*replace:
用来替换字符串的一部分。
string s1 = "abcdefg";
s1.replace(1, 3, "#");
cout << s1 << endl;
结果:
replace:谨慎使用,涉及底层数据移动,效率低下,时间复杂度可达O(N)。
8.swap():
整体替换,替换为 str,要注意 str 类型是 string& 而不是 const string&。
在之后的博客里会详细讲解。
9.pop_back():
删掉字符串最后一个字符,使长度减 1。
2.8 字符串操作相关成员函数
1. c_str():
返回一个指向数组的指针,该数组包含一个以’\0’结尾的字符序列,即c字符串。
- c_str()可以拿到底层的_str(内部指针),即指向c风格字符串的指针。
- 有些函数只提供c的接口(比如 fopen()等),而没有c++的接口,这种情况下,c_str()可以很好的解决这个问题。
string filename("Test.cpp");
FILE* fout = fopen(filename.c_str(), "r");
if (fout == nullptr)
{cout << "fopen fail" << endl;return;
}
2.*data():
c++11以后,功能与c_str()完全相同。
3.get_allocator():
用于获取容器当前使用的内存分配器。
4.*copy(s, len, pos):
拷贝当前对象的一部分到 s 所指向的数组中,从 pos(起始位置) 开始拷贝,最多拷贝 len 个字符,返回值是实际拷贝的字符数。
一般使用之后substr()来进行拷贝,这个用得很少。
5.substr(pos, len):
从当前字符串对象中提取子串,并通过拷贝构造返回一个新的 string 对象。
- 从 pos(当前位置)开始拷贝,最多拷贝 len 个字符,若 len > 剩余字符数量,则拷贝到字符串末尾。
- pos 默认值是0,显式传参时不能越界,否则运行时会抛异常,len 默认值是 npos,无符号整数最大值,表示“直到字符串末尾”。
6.find():
查找当前字符串对象的字符或者是子字符串。
- buffer(3):s 是指向c风格字符串的指针,size_t n 中的n指的是从 s 中取前 n 个字符进行查找。
- 返回值:找到则返回第一次找到位置的索引;未找到则返回 npos。
7.*rfind():
查找当前字符串对象的字符或者是子字符串。相比于find(),用法差不多,不过它是倒着从后往前找。
8.*find_first_of(s/c, pos):
给定一个字符串或单个字符参考集合 s 或 c,在当前字符串对象里查找第一个在参考集合里面的字符,并返回它的索引。
9.*find_last_of(s/c, pos):
功能和 find_first_of(s/c, pos)差不多,不过它是倒着从后往前查找。
10.*find_first_not_of(s/c, pos):
给定一个字符串或单个字符参考集合 s 或 c,在当前字符串对象里面查找第一个不在参考集合里面的字符,并返回它的索引。
11.*find_last_not_of(s/c, pos):
功能和 find_first_not_of(s/c, pos)差不多,不过与之相比,它是倒着从后往前查找。
12.*compare():
用来比较字符串,不过很少用到它,因为非成员函数里重载了用来比较的运算符,了解一下。
2.9 非成员函数
1.*operator+:
连接字符串并返回新的字符串,并且不会改变字符串彼此的值。
- 尽量少用,传值返回,深拷贝效率低下。
- 其不跟 operator+= 一同重载为成员函数的原因:
成员函数的都有一个隐藏的参数:const string& this,为了支持第一个参数为非string类型(如const char*),operator+需要被重载为非成员函数。之后的关系运算符重载函数重载为非成员函数也是这个原因。
2.relational operators:
其重载了所有的关系运算符。
3.swap(x, y):
交换两个字符串的值。
4.operator>> 与 operator<<:
让 string类对象可以像内置类型一样输入输出。
5.getline(string)
从输入流中读取一行文本(遇到’\n’停止),并将其存储到字符串对象 str 中,类似cin。使用依赖<string>头文件。
它与 cin 的区别:
- cin 读取字符串的时候,如果字符串中间有空格,它便会停止读取。cin以空白字符作为分隔符(空格、制表符、换行符)。
- getline() 则会无视空格,一直读下去,直到遇到’\n’。默认getline以换行符’\n’作为分隔符,它也可以自己定义分隔符(delim)。
string s1;
getline(cin, s1);
cout << "s1:" << s1 << endl;
string s2;
cin >> s2;
cout << "s2:" << s2 << endl;
今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
如果可以, 那就让我们共同努力, 一起走下去!