【c++】STL详解(一):string类的使用
C++标准模板库(STL)是C++编程语言的重要组成部分,他提供了一系列模板化的通用类和函数,用于实现常见的数据结构和算法。
在STL中,string类作为处理字符串的基础设施,提供了丰富的功能来支持字符串的操作。本文将深入探讨string类的使用,包括其构造函数、容量操作、字符遍历、修改、以及与其他非成员函数的交互。
目录
- 1. STL
- 2. basic_string
- 2.1 编码标准理解
- 3. string构造函数详解
- 3.1 无参(默认)构造函数
- 3.2 带参构造函数
- 4. 容量操作
- 4.1 获取长度和容量
- 4.2 扩容操作
- 4.3 调整长度
- 4.4 收缩到拟合
- 5. 遍历与访问
- 4.1 下标访问
- 4.2 范围for循环
- 4.3 迭代器遍历
- 4.4 const迭代器遍历
- 5. 字符串修改操作
- 5.1 插入操作
- 5.1.1 尾插操作
- 使用push_back()尾插单个字符
- 使用append()尾插字符串
- 使用operator+=尾插字符串
- 5.1.2 任意位置插入
- 在指定位置插入字符串
- 在指定位置插入多个相同字符
- 5.2 删除操作
- 使用erase()方法删除部分字符
- 5.3 替换操作
- 使用replace()方法
- 6. 字符串查找与子串
- 6.1 查找字符或字符串
- 代码示例
- 运行结果
- 代码示例
- 运行结果
- 6.2 截取子串
- 代码示例
- 运行结果
- 7. 非成员函数与string交互
- 7.1 流操作
- 7.2 字符串比较
- 8. 总结
1. STL
STL
(标准模板库)是C++中不可或缺的一部分,它由六大组件组成:伪函数、空间配置器、算法、容器、迭代器和配接器。这些组件中,各种容器对我们的编程工作提供了极大的帮助。以今天我们要探讨的string
为例,有了string容器的支持,我们在处理字符串
时可以更加流畅自如。
下图为STL组件:
2. basic_string
basic_string
是 C++ 标准库中提供的一个模板类,其中今天要学习的string也只是basic_string模板的一份实例。用于表示和处理字符串。它能够根据不同的字符类型提供不同版本的字符串类,以适应各种编码和处理需求
。
string
: 常规字符串类,使用 char 类型表示每个字符,通常每个字符占用 1 byte。wstring
: 宽字符串类,使用 wchar_t 类型表示每个字符,其大小在不同平台上有所不同(Windows 下为 2 bytes,而 Linux 下为 4 bytes)。u16string
: 用于表示UTF-16
编码的字符串,每个字符固定占用 2 bytes(C++11 引入)。u32string
: 用于表示UTF-32
编码的字符串,每个字符固定占用 4 bytes(C++11 引入)。
不难看出,这么多的变体的原因:
随着全球化的发展,软件需要支持多种语言和字符集,这就要求字符串处理能力必须跨越ASCII编码的限制,支持宽字符和多字节字符集。因此需要使用不同的 string 来匹配输出自己国家的字符。
2.1 编码标准理解
ASCII
: 美国信息交换标准代码,是最早的编码系统之一,使用 1 byte 表示一个字符,足以覆盖英语字母、数字和一些符号。
UTF-8
: 一种变长的编码方式,能够根据字符的不同选择不同长度的表示方式。它向下兼容 ASCII,但能够表示全球几乎所有的字符和符号。
随着 Unicode
的出现(也称“全球码”),他提供了一个统一的方式来表示世界上大多数的文字系统。UTF-16
和 UTF-32
是 Unicode 提出的两种编码方案,分别由 u16string 和 u32string 在 C++ 中实现。这两种类型的引入,标志着 C++ 对全球化支持的加强。
3. string构造函数详解
构造函数是string
类初始化的基础,它定义了如何创建一个string
对象。以下是string
构造函数的详细解析和代码示例。
3.1 无参(默认)构造函数
无参构造函数创建一个空的string
对象,不包含任何字符。
std::string s;
std::cout << "The string is empty: \"" << s << "\"" << std::endl;
输出:
The string is empty: ""
3.2 带参构造函数
string
提供了多种带参构造函数,用于根据不同的需求初始化字符串。
- 3.2.1 从C字符串构造
可以使用C风格的字符串(即以null终止的字符数组)初始化string
对象。
const char* c_str = "Hello, C++!";
std::string s(c_str);
std::cout << "Initialized from C string: " << s << std::endl;
输出:
Initialized from C string: Hello, C++!
- 3.2.2 重复字符构造
此构造函数创建一个包含重复字符的字符串。
std::string s(5, 'A');
std::cout << "String with repeated characters: " << s << std::endl;
输出:
String with repeated characters: AAAAA
- 3.2.3 从另一个string对象拷贝构造
可以使用一个已存在的string
对象来初始化另一个string
对象。
std::string original("Original string");
std::string copy(original);
std::cout << "Copied string: " << copy << std::endl;
输出:
Copied string: Original string
- 3.2.4 子串构造函数
此构造函数从另一个string
对象的指定位置开始,拷贝指定长度的字符来创建一个新的string
对象。
std::string original("Hello, World!");
std::string substring(original, 7, 5); // 从位置7开始,拷贝5个字符
std::cout << "Substring: " << substring << std::endl;
输出:
Substring: World
- 3.2.5 从初始化列表构造
C++11引入了从初始化列表构造string
的能力,允许直接使用字符列表初始化string
对象。
std::string s({'H', 'e', 'l', 'l', 'o'});
std::cout << "Initialized from initializer list: " << s << std::endl;
输出:
Initialized from initializer list: Hello
- 3.3 移动构造函数(C++11)
移动构造函数允许从临时string
对象“窃取”资源,而不是拷贝,这可以提高性能。
std::string temp("I am temporary");
std::string moved(std::move(temp));
std::cout << "Moved string: " << moved << std::endl;
输出:
Moved string: I am temporary
注意:移动后,原始对象temp
变成了未定义状态,不应再使用。
通过这些构造函数,string
类提供了灵活的方式来创建和初始化字符串对象,满足不同场景的需求。
4. 容量操作
容量操作是string
类管理字符串存储空间的重要手段,允许程序员有效控制内存使用。以下是对容量操作的详细解析和代码示例。
4.1 获取长度和容量
string
提供了size()
,length()
和capacity()
成员函数,用于获取字符串的长度和容量。
- 长度(Length):字符串当前的字符数。
- 容量(Capacity):在需要重新分配之前,字符串可以包含的字符数。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World!";
std::cout << "Length: " << s.size() << std::endl; // 输出字符串长度
std::cout << "Length: " << s.length() << std::endl; // 输出字符串长度
std::cout << "Capacity: " << s.capacity() << std::endl; // 输出字符串容量
return 0;
}
4.2 扩容操作
当向string
对象添加字符,使其大小超过当前容量时,string
会自动增加其容量以容纳更多的字符。reserve()
方法允许手动增加string
的容量。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string s;
std::cout << "Default capacity: " << s.capacity() << std::endl; // 默认容量
s.reserve(100); // 手动扩容至少100个字符
std::cout << "New capacity after reserve: " << s.capacity() << std::endl; // 扩容后的容量
return 0;
}
reserve(100)并不一定扩容100,例如:VS下扩容往往会多一点。
s.reserve(100); // 手动扩容至少100个字符
即便我们不手动扩容,string识别到容量不够时,也会自动扩容
但是不同平台下,扩容策略也不相同:
1.VS中的string扩容策略:
- 初始容量:默认给一个大小为15的数组来存储数据。
- 数组与指针的使用:当数组容量够用时,使用数组;容量不足时,改用指针,并首先将容量翻倍至30。
- 后续扩容:之后的扩容操作中,
每次扩容都是1.5倍
。此外,还会预分配一些额外的空间
。
2.Linux中的string扩容策略:
- 初始容量:默认大小为0的空间。
- 首次扩容:当第一次扩容时,会先将容量扩至1。
- 后续扩容:
每次扩容都是2倍的扩容法
。与VS中的策略不同,Linux的策略不会预先分配过多的空间
。
4.3 调整长度
resize()
方法用于调整string
的长度。如果新长度大于当前长度,新位置将被初始化为指定的字符。如果新长度小于当前长度,多余的字符将被删除。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World!";
std::cout << "Original string: " << s << " | Length: " << s.size() << std::endl;
s.resize(5); // 调整长度为5
std::cout << "After resize down: " << s << " | Length: " << s.size() << std::endl;
s.resize(10, 'X'); // 扩展长度至10,新字符用'X'填充
std::cout << "After resize up: " << s << " | Length: " << s.size() << std::endl;
return 0;
}
有两种情况:
- 1.如果调整后
长度比原空间大
,此时相当于扩容reserve()
- 2.如果调整后
空间比原空间小
,也就是将_size
调整至目标空间,而_capapcity
不变,此时我们也无法访问到_size
之外的数据
4.4 收缩到拟合
C++11引入了shrink_to_fit()
方法,建议string减少其容量以适应当前大小
,尽管这不是一个强制性的操作,实现可能不会改变容量。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World!";
s.reserve(100); // 扩容至100
std::cout << "Capacity after reserve: " << s.capacity() << std::endl;
s.shrink_to_fit(); // 收缩到拟合
std::cout << "Capacity after shrink_to_fit: " << s.capacity() << std::endl;
return 0;
}
通过上述代码示例,我们可以看到string
类提供的容量操作方法不仅允许查询字符串的当前状态,还允许对其进行有效的内存管理,这对于优化程序性能和减少不必要的内存使用非常关键。
5. 遍历与访问
在C++中,string
类提供了多种方法来遍历和访问字符串中的字符。这些方法包括使用下标、范围for
循环、以及迭代器。下面通过代码示例详细介绍这些方法,并展示它们的运行结果。
4.1 下标访问
使用下标访问是最直接的遍历字符串的方法。通过string
对象的operator[]
,我们可以获取到字符串中特定位置的字符。
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World!";
for(size_t i = 0; i < s.size(); ++i) {
std::cout << s[i] << " ";
}
return 0;
}
运行结果:
H e l l o , W o r l d !
4.2 范围for循环
C++11引入的范围for
循环(range-based for loop)提供了一种更简洁的遍历容器的方法。使用这种方法遍历string
时,可以直接获取到字符串中的每个字符。
#include <iostream>
#include <string>
int main() {
std::string s = "C++ STL";
for(char c : s) {
std::cout << c << "-";
}
return 0;
}
运行结果:
C-+-+S-T-L-
4.3 迭代器遍历
迭代器提供了一种通用的遍历容器元素的方法。通过使用string
的迭代器,我们可以遍历字符串中的所有字符。
代码示例:
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World!";
std::cout << "Using iterator: ";
for(std::string::iterator it = s.begin(); it != s.end(); ++it) {
std::cout << *it;
}
std::cout << std::endl;
std::cout << "Using reverse_iterator: ";
for(std::string::reverse_iterator rit = s.rbegin(); rit != s.rend(); ++rit) {
std::cout << *rit;
}
std::cout << std::endl;
return 0;
}
注:
- 迭代器 iterator
begin()
获取第一个字符,end()
获取最后一个字符的下一个字符,即'\0'
- 反向迭代器 reverse_iterator
rbegin()
获取最后一个字符,rend()
获取第一个字符的前一个字符
迭代遍历区间都是左闭右开
运行结果:
Using iterator: Hello, World!
Using reverse_iterator: !dlroW ,olleH
4.4 const迭代器遍历
当我们不需要修改字符串中的字符,而只是想要读取它们时,可以使用const_iterator
来遍历字符串。
#include <iostream>
#include <string>
int main() {
const std::string s = "Constant Iterator";
for(std::string::const_iterator it = s.cbegin(); it != s.cend(); ++it) {
std::cout << *it << "_";
}
return 0;
}
运行结果:
C_o_n_s_t_a_n_t_ _I_t_e_r_a_t_o_r_
以上示例展示了使用不同方法遍历和访问string
对象中的字符。每种方法都有其适用场景,选择合适的方法可以使代码更加简洁、高效。
5. 字符串修改操作
5.1 插入操作
5.1.1 尾插操作
尾插是指在字符串的末尾添加一个字符或另一个字符串。string
类提供了push_back()
,append()
和operator+=
方法来实现尾插操作。
使用push_back()尾插单个字符
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
// 尾插字符'!'
str.push_back('!');
std::cout << str << std::endl; // 输出: Hello!
return 0;
}
运行结果:
Hello!
使用append()尾插字符串
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
// 尾插字符串" World"
str.append(" World");
std::cout << str << std::endl; // 输出: Hello World
return 0;
}
运行结果:
Hello World
使用operator+=尾插字符串
#include <iostream>
#include <string>
int main() {
std::string str = "Hello";
// 尾插字符串" World"
str += "world";
std::cout << str << std::endl; // 输出: Hello World
return 0;
}
运行结果:
Hello World
5.1.2 任意位置插入
任意位置插入是指在字符串的指定位置插入一个字符或另一个字符串。string
类的insert()
方法可以实现这一操作。
在指定位置插入字符串
#include <iostream>
#include <string>
int main() {
std::string str = "Hello World";
// 在位置6插入字符串"Beautiful "
str.insert(6, "Beautiful ");
std::cout << str << std::endl; // 输出: Hello Beautiful World
return 0;
}
运行结果:
Hello Beautiful World
在指定位置插入多个相同字符
#include <iostream>
#include <string>
int main() {
std::string str = "Hello World";
// 在位置5插入3个'!'字符
str.insert(5, 3, '!');
std::cout << str << std::endl; // 输出: Hello!!! World
return 0;
}
运行结果:
Hello!!! World
5.2 删除操作
string
类的erase()
方法允许删除字符串中的一部分或全部字符。
erase()//使用这个删除全部字符
下面着重删除部分字符的情况。
erase()
是一个全缺省参数的函数,参数1默认为 0
,参数2为 npos
,这是无符号整型中的 -1
,为无符号整型最大值,默认删除全部
使用erase()方法删除部分字符
#include <iostream>
#include <string>
int main() {
std::string str = "Hello Beautiful World";
// 从位置6开始删除13个字符("Beautiful ")
str.erase(6, 13);
std::cout << str << std::endl;
return 0;
}
运行结果:
Hello World
使用erase()方法删除单个字符
#include <iostream>
#include <string>
int main() {
std::string str = "Hello World";
// 删除位置5的字符(空格)
str.erase(5, 1);
std::cout << str << std::endl;
return 0;
}
运行结果:
HelloWorld
5.3 替换操作
string
的replace()
方法允许替换字符串中的一部分为另一个字符串。
使用replace()方法
#include <iostream>
#include <string>
int main() {
std::string str = "Hello World";
// 从位置6开始,替换5个字符为"Universe"
str.replace(6, 5, "Universe");
std::cout << str << std::endl;
return 0;
}
运行结果:
Hello Universe
6. 字符串查找与子串
在C++中,string
类提供了多种方法来查找字符或字符串以及截取子串。这些操作对于文本处理非常重要,允许我们定位特定数据并从更大的字符串中提取信息。
6.1 查找字符或字符串
string
类的find
方法可以用来查找一个字符或字符串首次出现的位置。如果找到,它返回字符或子字符串首次出现的索引;如果未找到,它返回string::npos
。
代码示例
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World! Welcome to C++ programming.";
// 查找字符'W'首次出现的位置
size_t pos = s.find('W');
if(pos != std::string::npos) {
std::cout << "'W' found at position: " << pos << std::endl;
} else {
std::cout << "'W' not found." << std::endl;
}
// 查找子字符串"Welcome"首次出现的位置
pos = s.find("Welcome");
if(pos != std::string::npos) {
std::cout << "\"Welcome\" found at position: " << pos << std::endl;
} else {
std::cout << "\"Welcome\" not found." << std::endl;
}
return 0;
}
运行结果
'W' found at position: 7
"Welcome" found at position: 14
此外:find()
还有几种形式:
rfind()
方法从字符串的末尾开始搜索,返回最后一个匹配字符的索引。find_first_of()
方法从指定位置(默认为开始)搜索字符串中任意一个给定字符首次出现的位置。find_last_of()
方法从字符串的末尾开始搜索,返回任意一个给定字符最后一次出现的位置。find_first_not_of()
方法从字符串的开始搜索,返回第一个不在给定字符串中的字符的索引。find_last_not_of()
方法从字符串的末尾开始搜索,返回最后一个不在给定字符串中的字符的索引。
代码示例
#include <iostream>
#include <string>
int main() {
std::string s = "Example sentence for demonstration.";
// rfind() 从后往前找
size_t pos = s.rfind('e');
std::cout << "Last 'e' found at position: " << pos << std::endl;
// find_first_of() 从pos位置往后,找str中出现的任意字符
pos = s.find_first_of("aeiou", 0);
std::cout << "First vowel found at position: " << pos << std::endl;
// find_last_of() 从npos位置往前,找str中出现的任意字符
pos = s.find_last_of("aeiou");
std::cout << "Last vowel found at position: " << pos << std::endl;
// find_first_not_of() 反向查找
pos = s.find_first_not_of("Example ");
std::cout << "First character not in 'Example ' found at position: " << pos << std::endl;
// find_last_not_of() 反向查找
pos = s.find_last_not_of(" .");
std::cout << "Last character not ' .' found at position: " << pos << std::endl;
return 0;
}
运行结果
Last 'e' found at position: 22
First vowel found at position: 2
Last vowel found at position: 32
First character not in 'Example ' found at position: 8
Last character not ' .' found at position: 33
这些示例展示了string
类提供的不同查找方法的用法:
6.2 截取子串
substr
方法允许从字符串中截取一部分内容。它接受两个参数:起始位置和子串长度。如果不指定长度,它将从起始位置截取到字符串的末尾。
代码示例
#include <iostream>
#include <string>
int main() {
std::string s = "Hello, World! Welcome to C++ programming.";
// 从位置13开始截取10个字符
std::string sub = s.substr(13, 10);
std::cout << "Substring: " << sub << std::endl;
// 从位置13开始截取到字符串末尾
sub = s.substr(13);
std::cout << "Substring from position 13 to end: " << sub << std::endl;
return 0;
}
运行结果
Substring: Welcome t
Substring from position 13 to end: Welcome to C++ programming.
7. 非成员函数与string交互
非成员函数提供了一种方式来处理string
对象与其他类型的交互,如通过输入输出流进行读写操作,以及使用比较函数来对字符串进行比较。以下是一些具体的示例和解释,包括代码示例及其预期的运行结果。
7.1 流操作
流操作允许string
对象与标准输入输出流(cin
、cout
)进行交互,这在读取用户输入和输出字符串到控制台时非常有用。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string inputString;
std::cout << "请输入一段文本:" << std::endl;
std::getline(std::cin, inputString); // 使用getline读取一行文本
std::cout << "你输入的文本是:" << inputString << std::endl;
return 0;
}
运行结果:
请输入一段文本:
Hello, World!
你输入的文本是:Hello, World!
7.2 字符串比较
string
类提供了compare
函数来比较两个字符串。此外,可以直接使用比较运算符(如==
、!=
、<
、>
等)来比较两个string
对象的字典序。
示例代码:
#include <iostream>
#include <string>
int main() {
std::string str1 = "Apple";
std::string str2 = "Banana";
std::string str3 = "Apple";
// 使用compare函数
if (str1.compare(str2) < 0) {
std::cout << str1 << " 在字典序上小于 " << str2 << std::endl;
}
// 使用比较运算符
if (str1 == str3) {
std::cout << str1 << " 和 " << str3 << " 相等" << std::endl;
}
if (str1 < str2) {
std::cout << str1 << " 在字典序上小于 " << str2 << std::endl;
}
return 0;
}
运行结果:
Apple 在字典序上小于 Banana
Apple 和 Apple 相等
Apple 在字典序上小于 Banana
8. 总结
string
类不仅是C++标准库中用于处理字符串的基础设施,它的设计和实现也体现了C++对效率和灵活性的追求。通过实际的代码示例,我们可以更深入地理解string类的应用和效果。