深入探索 c++ stl 中的 std::string
在 c++ 编程中,字符串处理是一个常见的任务,而 c++ 标准模板库(stl)中的 std::string 类为我们提供了强大的功能来简化这一过程。
一、std::string 的基本概念
std::string 是 c++ stl 中的一个类,用于表示和操作字符串。它属于 <string> 头文件,是基于模板的 std::basic_string 类的一个特化版本,专门用于处理字符类型为 char 的字符串。与传统的 c 风格字符串(以空字符结束的字符数组)相比,std::string 提供了更高的安全性和更丰富的功能。
1. 内存管理
std::string 内部会动态管理内存,这意味着你不需要手动分配和释放内存。
当你对字符串进行修改时,std::string 会自动调整内存大小以适应新的内容。
- 例如:
std::string str = "hello"; str += ", world!"; // 内部自动调整内存大小
2. 安全性
std::string 不会像 c 风格字符串那样容易出现越界访问或内存泄漏的问题。
它提供了边界检查和异常处理机制,确保字符串操作的安全性。
二、std::string 的构造与初始化
std::string 提供了多种构造方式,可以根据不同的需求选择合适的构造函数。
1. 默认构造
std::string str; // 创建一个空字符串
2. 从 c 风格字符串构造
const char* c_str = "hello"; std::string str(c_str); // 使用 c 风格字符串初始化
3. 从字符串的一部分构造
std::string str1 = "hello world"; std::string str2(str1, 6, 5); // 从 str1 的第 6 个字符开始,取 5 个字符 // str2 的内容为 "world"
4. 使用重复字符构造
std::string str(5, 'a'); // 创建一个包含 5 个 'a' 的字符串 // str 的内容为 "aaaaa"
三、std::string 的常用操作
1. 字符串拼接
std::string 提供了多种方式来拼接字符串,包括使用 + 运算符和 += 运算符。
std::string str1 = "hello"; std::string str2 = "world"; std::string str3 = str1 + ", " + str2 + "!"; // 使用 + 拼接 str1 += " "; // 使用 += 拼接 str1 += str2; // str1 的内容为 "hello world"
2. 字符串比较
std::string 支持使用比较运算符(如 ==、!=、<、> 等)来比较字符串内容。
std::string str1 = "apple";
std::string str2 = "banana";
if (str1 < str2) {
std::cout << "apple is less than banana" << std::endl;
}3. 字符串查找
std::string 提供了 find 方法,用于查找子字符串的位置。
std::string str = "hello world";
size_t pos = str.find("world");
if (pos != std::string::npos) {
std::cout << "found 'world' at position " << pos << std::endl;
}4. 字符串替换
std::string 的 replace 方法可以用来替换字符串中的部分内容。
std::string str = "hello world"; str.replace(6, 5, "universe"); // str 的内容变为 "hello universe"
5. 字符串分割
虽然 std::string 没有直接提供分割字符串的方法,但可以通过循环和 find 方法实现。
std::string str = "apple,banana,orange";
std::string delimiter = ",";
size_t pos = 0;
std::string token;
while ((pos = str.find(delimiter)) != std::string::npos) {
token = str.substr(0, pos);
std::cout << token << std::endl;
str.erase(0, pos + delimiter.length());
}
std::cout << str << std::endl; // 输出最后一个子字符串6. 字符串大小和容量
std::string 提供了 size 和 capacity 方法来获取字符串的长度和当前分配的内存容量。
std::string str = "hello"; std::cout << "size: " << str.size() << std::endl; // 输出 5 std::cout << "capacity: " << str.capacity() << std::endl; // 输出实际分配的容量
7. 字符访问
可以通过下标运算符 [] 或 at 方法访问字符串中的字符。
std::string str = "hello"; char c = str[0]; // 使用下标访问 char d = str.at(1); // 使用 at 方法访问
四.string类对象的访问及遍历操作
std::string的容量相关概念
在深入了解容量操作之前,我们需要明确几个与std::string容量相关的重要概念:
1. size(或length)
size函数返回字符串中实际存储的字符数量,不包括末尾的空字符'\0'。length函数与size功能完全相同,只是名字不同。
std::string str = "hello, world!"; std::cout << "size: " << str.size() << std::endl; // 输出:size: 13
2. capacity
capacity函数返回字符串当前分配的内存容量,单位是字符数。
capacity通常大于或等于size,因为std::string会预留一些额外的内存,以便在字符串扩展时减少内存分配的次数。
std::string str = "hello, world!"; std::cout << "capacity: " << str.capacity() << std::endl; // 输出:capacity: 15(具体值可能因实现而异)
3. max_size
max_size函数返回std::string能够存储的最大字符数。
这个值通常取决于系统的内存限制和std::string的实现。
std::string str; std::cout << "max size: " << str.max_size() << std::endl; // 输出:max size: 18446744073709551615(64位系统)
reserve:预留内存
reserve函数用于显式地为std::string预留一定量的内存。这可以减少在字符串频繁扩展时的内存分配次数,从而提高性能。
使用场景
当你知道字符串将要存储大量字符时,可以使用reserve预先分配足够的内存。
例如,当你从文件中读取大量文本并拼接到字符串中时,reserve可以显著提高效率。
示例代码
std::string str;
str.reserve(1000); // 预留1000个字符的内存
for (int i = 0; i < 1000; ++i) {
str += 'a';
}
std::cout << "size: " << str.size() << std::endl; // 输出:size: 1000
std::cout << "capacity: " << str.capacity() << std::endl; // 输出:capacity: 1000注意事项
reserve不会改变字符串的内容,只是改变其容量。- 如果
reserve的参数小于当前capacity,std::string的容量不会减少。 reserve是一个非强制性操作,具体行为可能因标准库的实现而异。
shrink_to_fit:收缩内存
shrink_to_fit函数用于收缩std::string的容量,使其尽可能接近实际的size。
这可以减少不必要的内存占用,但需要注意的是,shrink_to_fit是一个非强制性操作,具体行为可能因标准库的实现而异。
使用场景
当你完成了一个字符串的频繁修改操作后,可以使用shrink_to_fit来释放多余的内存。
例如,在字符串拼接完成后,调用shrink_to_fit可以优化内存使用。
示例代码
std::string str = "hello"; str += "world"; str.shrink_to_fit(); std::cout << "size: " << str.size() << std::endl; // 输出:size: 10 std::cout << "capacity: " << str.capacity() << std::endl; // 输出:capacity: 10(具体值可能因实现而异)
注意事项
shrink_to_fit不会改变字符串的内容,只是尝试减少其容量。shrink_to_fit是一个非强制性操作,可能不会立即生效。
resize:调整字符串大小
resize函数用于调整字符串的大小。如果新的大小大于当前size,则会用指定的字符填充新分配的部分;如果新的大小小于当前size,则会截断字符串。
示例代码
std::string str = "hello, world!"; str.resize(5); // 截断字符串 std::cout << "resized string: " << str << std::endl; // 输出:hello str.resize(10, 'x'); // 扩展字符串并用'x'填充 std::cout << "resized string: " << str << std::endl; // 输出:helloxxxxx
注意事项
resize会改变字符串的内容,而reserve和shrink_to_fit不会。resize的第二个参数是可选的,默认填充字符为空字符'\0'。
容量操作的性能优化
合理使用std::string的容量操作可以显著提高程序的性能。
以下是一些优化建议:
1. 预留足够的内存
如果你知道字符串将要存储大量字符,使用reserve预先分配足够的内存可以减少内存分配的次数。
2. 及时收缩内存
在完成字符串的频繁修改操作后,使用shrink_to_fit可以释放多余的内存,优化内存使用。
3. 避免不必要的拷贝
std::string的拷贝构造和赋值操作可能会导致不必要的内存分配和拷贝。如果可能,尽量使用引用或std::move来避免拷贝。
五.string类对象的修改操作
std::string的修改操作概述
std::string提供了多种方法来修改字符串的内容,包括插入、删除、替换、直接修改字符等。
这些操作使得字符串的编辑变得非常灵活,同时也避免了c语言中直接操作字符数组时可能出现的内存错误。
直接修改字符
std::string支持通过下标操作符[]或at函数直接修改字符串中的字符。
这两种方式的主要区别在于,[]操作符不进行边界检查,而at函数会进行边界检查并抛出异常。
示例代码
std::string str = "hello, world!"; str[0] = 'h'; // 将第一个字符修改为小写的'h' str.at(7) = 'w'; // 将第8个字符(索引为7)修改为小写的'w' std::cout << str << std::endl; // 输出:hello, world!
注意事项
- 使用
[]操作符时,如果索引超出字符串范围,行为是未定义的。 - 使用
at函数时,如果索引超出范围,会抛出std::out_of_range异常。
插入操作
std::string提供了多种插入操作,允许你在字符串的任意位置插入字符、字符串或字符数组。
1. 插入单个字符
std::string str = "hello, world!"; str.insert(5, "x"); // 在索引5的位置插入字符'x' std::cout << str << std::endl; // 输出:hellox, world!
2. 插入字符串
std::string str = "hello, world!"; str.insert(7, "c++"); // 在索引7的位置插入字符串"c++" std::cout << str << std::endl; // 输出:hello, c++world!
3. 插入字符数组
std::string str = "hello, world!"; char arr[] = "python"; str.insert(7, arr); // 在索引7的位置插入字符数组"python" std::cout << str << std::endl; // 输出:hello, pythonworld!
注意事项
- 插入操作会改变字符串的
size和capacity。 - 如果插入位置超出字符串范围,行为是未定义的。
删除操作
std::string提供了erase函数,用于删除字符串中的一部分内容。
示例代码
std::string str = "hello, world!"; str.erase(7, 5); // 从索引7开始删除5个字符 std::cout << str << std::endl; // 输出:hello, !
参数说明
- 第一个参数是删除的起始位置(索引)。
- 第二个参数是删除的字符数。
注意事项
- 如果删除的起始位置超出字符串范围,行为是未定义的。
- 删除操作会改变字符串的
size,但不会改变capacity。
替换操作
std::string提供了replace函数,用于替换字符串中的一部分内容。
示例代码
std::string str = "hello, world!"; str.replace(7, 5, "c++"); // 从索引7开始,替换5个字符为"c++" std::cout << str << std::endl; // 输出:hello, c++!
参数说明
- 第一个参数是替换的起始位置(索引)。
- 第二个参数是替换的字符数。
- 第三个参数是新的内容。
注意事项
- 替换操作会改变字符串的
size,但不会改变capacity。 - 如果替换的起始位置超出字符串范围,行为是未定义的。
追加操作
std::string提供了多种追加操作,允许你在字符串的末尾添加字符、字符串或字符数组。
1. 追加单个字符
std::string str = "hello";
str.push_back(' '); // 追加一个空格
str += 'w'; // 追加字符'w'
std::cout << str << std::endl; // 输出:hello w2. 追加字符串
std::string str = "hello "; str += "world"; // 追加字符串"world" std::cout << str << std::endl; // 输出:hello world
3. 追加字符数组
std::string str = "hello "; char arr[] = "c++"; str.append(arr); // 追加字符数组"c++" std::cout << str << std::endl; // 输出:hello c++
注意事项
- 追加操作会改变字符串的
size,但不会改变capacity。 - 如果
capacity不足,std::string会自动分配更大的内存。
清空字符串
std::string提供了clear函数,用于清空字符串的内容,但不会释放内存。
示例代码
std::string str = "hello, world!"; str.clear(); std::cout << "size: " << str.size() << std::endl; // 输出:size: 0 std::cout << "capacity: " << str.capacity() << std::endl; // 输出:capacity: 15(具体值可能因实现而异)
注意事项
clear函数只会清空字符串的内容,不会释放内存。- 如果需要释放内存,可以结合
shrink_to_fit使用。
使用std::stringstream进行复杂修改
对于复杂的字符串修改操作,可以使用std::stringstream。
std::stringstream是一个非常灵活的工具,可以方便地进行字符串的拼接、格式化和提取。
示例代码
#include <sstream> std::string str = "hello, world!"; std::stringstream ss(str); std::string word; ss >> word; // 提取第一个单词 ss << "c++"; // 替换为"c++" ss >> word; // 提取第二个单词 ss << "programming"; // 替换为"programming" str = ss.str(); // 将修改后的内容赋值回str std::cout << str << std::endl; // 输出:hello, c++programming
六.string类非成员函数
什么是std::string的非成员函数?
std::string的非成员函数是指那些不属于std::string类的成员函数,但专门用于操作std::string对象的函数。
这些函数通常定义在<string>头文件中,或者通过标准库的其他部分提供。
非成员函数的优势在于它们可以提供更通用的操作方式,同时避免了对std::string类本身的过度扩展。
常见的std::string非成员函数
1. std::swap
std::swap用于交换两个std::string对象的内容。
它是一个非成员函数,可以高效地交换两个字符串的内容,而不需要逐个字符复制。
示例代码
#include <iostream>
#include <string>
#include <algorithm> // 包含std::swap
int main() {
std::string str1 = "hello";
std::string str2 = "world";
std::swap(str1, str2);
std::cout << "str1: " << str1 << std::endl; // 输出:str1: world
std::cout << "str2: " << str2 << std::endl; // 输出:str2: hello
}2. std::to_string
std::to_string是一个非常有用的非成员函数,用于将数值类型(如int、float、double等)转换为std::string。
它提供了一种简单且类型安全的方式来实现数值与字符串之间的转换。
示例代码
#include <iostream>
#include <string>
int main() {
int num = 42;
double pi = 3.14159;
std::string numstr = std::to_string(num);
std::string pistr = std::to_string(pi);
std::cout << "number as string: " << numstr << std::endl; // 输出:number as string: 42
std::cout << "pi as string: " << pistr << std::endl; // 输出:pi as string: 3.141590
}3. std::stoi、std::stod、std::stof等
这些函数用于将std::string对象转换为数值类型。
std::stoi将字符串转换为int,std::stod将字符串转换为double,std::stof将字符串转换为float等。
这些函数提供了一种简单且安全的方式来解析字符串中的数值。
示例代码
#include <iostream>
#include <string>
int main() {
std::string numstr = "42";
std::string pistr = "3.14159";
int num = std::stoi(numstr);
double pi = std::stod(pistr);
std::cout << "number from string: " << num << std::endl; // 输出:number from string: 42
std::cout << "pi from string: " << pi << std::endl; // 输出:pi from string: 3.14159
}4. std::getline
std::getline是一个非成员函数,用于从输入流(如std::cin或std::ifstream)中读取一行内容并存储到std::string对象中。它支持按分隔符读取,例如按逗号、空格等分隔。
示例代码
#include <iostream>
#include <string>
#include <sstream>
int main() {
std::string line = "hello, world!";
std::istringstream ss(line);
std::string word;
while (std::getline(ss, word, ',')) {
std::cout << word << std::endl; // 输出:hello 和 world!
}
}5. std::operator<< 和 std::operator>>
这些操作符重载允许你直接使用std::cin和std::cout来输入和输出std::string对象。
它们是c++标准库提供的非成员函数,使得字符串的输入输出变得非常方便。
示例代码
#include <iostream>
#include <string>
int main() {
std::string str;
std::cout << "enter a string: ";
std::cin >> str;
std::cout << "you entered: " << str << std::endl;
}6. std::operator+ 和 std::operator+=
虽然这些操作符是std::string的成员函数,但它们也可以作为非成员函数使用。
std::operator+用于拼接两个字符串,而std::operator+=用于将一个字符串追加到另一个字符串的末尾。
这些操作符使得字符串拼接变得非常直观。
示例代码
#include <iostream>
#include <string>
int main() {
std::string str1 = "hello";
std::string str2 = "world";
std::string result = str1 + " " + str2; // 使用std::operator+
str1 += " "; // 使用std::operator+=
str1 += str2;
std::cout << "result: " << result << std::endl; // 输出:result: hello world
std::cout << "modified str1: " << str1 << std::endl; // 输出:modified str1: hello world
}7. std::operator==、std::operator!=、std::operator<等
这些操作符重载允许你直接比较两个std::string对象。它们按照字典序进行比较,使得字符串的比较变得非常方便。
示例代码
#include <iostream>
#include <string>
int main() {
std::string str1 = "hello";
std::string str2 = "world";
if (str1 == str2) {
std::cout << "strings are equal." << std::endl;
} else {
std::cout << "strings are not equal." << std::endl; // 输出:strings are not equal.
}
if (str1 < str2) {
std::cout << "str1 is less than str2." << std::endl; // 输出:str1 is less than str2.
}
}总结
std::string 是 c++ stl 中一个非常强大且灵活的工具,它提供了丰富的功能来处理字符串,同时避免了许多传统字符串操作的常见问题。
通过掌握 std::string 的构造、操作、性能优化以及高级用法,你可以更高效地编写字符串处理相关的代码。
在实际开发中,合理使用 std::string 能够大大提高代码的可读性和安全性。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论