C++标准模板库(STL)是C++编程语言的重要组成部分,他提供了一系列模板化的通用类和函数,用于实现常见的数据结构和算法。
在STL中,string类作为处理字符串的基础设施,提供了丰富的功能来支持字符串的操作。本文将深入探讨string类的使用,包括其构造函数、容量操作、字符遍历、修改、以及与其他非成员函数的交互。
STL
(标准模板库)是C++中不可或缺的一部分,它由六大组件组成:伪函数、空间配置器、算法、容器、迭代器和配接器。这些组件中,各种容器对我们的编程工作提供了极大的帮助。以今天我们要探讨的string
为例,有了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 来匹配输出自己国家的字符。
ASCII
: 美国信息交换标准代码,是最早的编码系统之一,使用 1 byte 表示一个字符,足以覆盖英语字母、数字和一些符号。
UTF-8
: 一种变长的编码方式,能够根据字符的不同选择不同长度的表示方式。它向下兼容 ASCII,但能够表示全球几乎所有的字符和符号。
随着 Unicode
的出现(也称“全球码”),他提供了一个统一的方式来表示世界上大多数的文字系统。UTF-16
和 UTF-32
是 Unicode 提出的两种编码方案,分别由 u16string 和 u32string 在 C++ 中实现。这两种类型的引入,标志着 C++ 对全球化支持的加强。
构造函数是string
类初始化的基础,它定义了如何创建一个string
对象。以下是string
构造函数的详细解析和代码示例。
无参构造函数创建一个空的string
对象,不包含任何字符。
std::string s;
std::cout << "The string is empty: \"" << s << "\"" << std::endl;
输出:
The string is empty: ""
string
提供了多种带参构造函数,用于根据不同的需求初始化字符串。
可以使用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++!
此构造函数创建一个包含重复字符的字符串。
std::string s(5, 'A');
std::cout << "String with repeated characters: " << s << std::endl;
输出:
String with repeated characters: AAAAA
可以使用一个已存在的string
对象来初始化另一个string
对象。
std::string original("Original string");
std::string copy(original);
std::cout << "Copied string: " << copy << std::endl;
输出:
Copied string: Original string
此构造函数从另一个string
对象的指定位置开始,拷贝指定长度的字符来创建一个新的string
对象。
std::string original("Hello, World!");
std::string substring(original, 7, 5); // 从位置7开始,拷贝5个字符
std::cout << "Substring: " << substring << std::endl;
输出:
Substring: World
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
移动构造函数允许从临时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
类提供了灵活的方式来创建和初始化字符串对象,满足不同场景的需求。
容量操作是string
类管理字符串存储空间的重要手段,允许程序员有效控制内存使用。以下是对容量操作的详细解析和代码示例。
string
提供了size()
,length()
和capacity()
成员函数,用于获取字符串的长度和容量。
示例代码:
#include
#include
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;
}
当向string
对象添加字符,使其大小超过当前容量时,string
会自动增加其容量以容纳更多的字符。reserve()
方法允许手动增加string
的容量。
示例代码:
#include
#include
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扩容策略:
每次扩容都是1.5倍
。此外,还会预分配一些额外的空间
。2.Linux中的string扩容策略:
每次扩容都是2倍的扩容法
。与VS中的策略不同,Linux的策略不会预先分配过多的空间
。resize()
方法用于调整string
的长度。如果新长度大于当前长度,新位置将被初始化为指定的字符。如果新长度小于当前长度,多余的字符将被删除。
示例代码:
#include
#include
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;
}
有两种情况:
长度比原空间大
,此时相当于扩容reserve()
空间比原空间小
,也就是将 _size
调整至目标空间,而 _capapcity
不变,此时我们也无法访问到 _size
之外的数据C++11引入了shrink_to_fit()
方法,建议string减少其容量以适应当前大小
,尽管这不是一个强制性的操作,实现可能不会改变容量。
示例代码:
#include
#include
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
类提供的容量操作方法不仅允许查询字符串的当前状态,还允许对其进行有效的内存管理,这对于优化程序性能和减少不必要的内存使用非常关键。
在C++中,string
类提供了多种方法来遍历和访问字符串中的字符。这些方法包括使用下标、范围for
循环、以及迭代器。下面通过代码示例详细介绍这些方法,并展示它们的运行结果。
使用下标访问是最直接的遍历字符串的方法。通过string
对象的operator[]
,我们可以获取到字符串中特定位置的字符。
#include
#include
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 !
C++11引入的范围for
循环(range-based for loop)提供了一种更简洁的遍历容器的方法。使用这种方法遍历string
时,可以直接获取到字符串中的每个字符。
#include
#include
int main() {
std::string s = "C++ STL";
for(char c : s) {
std::cout << c << "-";
}
return 0;
}
运行结果:
C-+-+S-T-L-
迭代器提供了一种通用的遍历容器元素的方法。通过使用string
的迭代器,我们可以遍历字符串中的所有字符。
代码示例:
#include
#include
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;
}
注:
begin()
获取第一个字符,end()
获取最后一个字符的下一个字符,即 '\0'
rbegin()
获取最后一个字符,rend()
获取第一个字符的前一个字符迭代遍历区间都是左闭右开
运行结果:
Using iterator: Hello, World!
Using reverse_iterator: !dlroW ,olleH
当我们不需要修改字符串中的字符,而只是想要读取它们时,可以使用const_iterator
来遍历字符串。
#include
#include
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
对象中的字符。每种方法都有其适用场景,选择合适的方法可以使代码更加简洁、高效。
尾插是指在字符串的末尾添加一个字符或另一个字符串。string
类提供了push_back()
,append()
和operator+=
方法来实现尾插操作。
#include
#include
int main() {
std::string str = "Hello";
// 尾插字符'!'
str.push_back('!');
std::cout << str << std::endl; // 输出: Hello!
return 0;
}
运行结果:
Hello!
#include
#include
int main() {
std::string str = "Hello";
// 尾插字符串" World"
str.append(" World");
std::cout << str << std::endl; // 输出: Hello World
return 0;
}
运行结果:
Hello World
#include
#include
int main() {
std::string str = "Hello";
// 尾插字符串" World"
str += "world";
std::cout << str << std::endl; // 输出: Hello World
return 0;
}
运行结果:
Hello World
任意位置插入是指在字符串的指定位置插入一个字符或另一个字符串。string
类的insert()
方法可以实现这一操作。
#include
#include
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
#include
int main() {
std::string str = "Hello World";
// 在位置5插入3个'!'字符
str.insert(5, 3, '!');
std::cout << str << std::endl; // 输出: Hello!!! World
return 0;
}
运行结果:
Hello!!! World
string
类的erase()
方法允许删除字符串中的一部分或全部字符。
erase()//使用这个删除全部字符
下面着重删除部分字符的情况。
erase()
是一个全缺省参数的函数,参数1默认为 0
,参数2为 npos
,这是无符号整型中的 -1
,为无符号整型最大值,默认删除全部
#include
#include
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
#include
int main() {
std::string str = "Hello World";
// 删除位置5的字符(空格)
str.erase(5, 1);
std::cout << str << std::endl;
return 0;
}
运行结果:
HelloWorld
string
的replace()
方法允许替换字符串中的一部分为另一个字符串。
#include
#include
int main() {
std::string str = "Hello World";
// 从位置6开始,替换5个字符为"Universe"
str.replace(6, 5, "Universe");
std::cout << str << std::endl;
return 0;
}
运行结果:
Hello Universe
在C++中,string
类提供了多种方法来查找字符或字符串以及截取子串。这些操作对于文本处理非常重要,允许我们定位特定数据并从更大的字符串中提取信息。
string
类的find
方法可以用来查找一个字符或字符串首次出现的位置。如果找到,它返回字符或子字符串首次出现的索引;如果未找到,它返回string::npos
。
#include
#include
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
#include
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
类提供的不同查找方法的用法:
substr
方法允许从字符串中截取一部分内容。它接受两个参数:起始位置和子串长度。如果不指定长度,它将从起始位置截取到字符串的末尾。
#include
#include
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.
非成员函数提供了一种方式来处理string
对象与其他类型的交互,如通过输入输出流进行读写操作,以及使用比较函数来对字符串进行比较。以下是一些具体的示例和解释,包括代码示例及其预期的运行结果。
流操作允许string
对象与标准输入输出流(cin
、cout
)进行交互,这在读取用户输入和输出字符串到控制台时非常有用。
示例代码:
#include
#include
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!
string
类提供了compare
函数来比较两个字符串。此外,可以直接使用比较运算符(如==
、!=
、<
、>
等)来比较两个string
对象的字典序。
示例代码:
#include
#include
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
string
类不仅是C++标准库中用于处理字符串的基础设施,它的设计和实现也体现了C++对效率和灵活性的追求。通过实际的代码示例,我们可以更深入地理解string类的应用和效果。