C++——string的了解和使用
引言
为了简化字符串的处理,C++标准库引入了string类。C++ 中的 std::string 是标准库提供的字符串类,用于处理可变长度的字符串。它封装了字符数组的操作,提供了安全、便捷的字符串处理方式。以下是关于 std::string 的详细介绍和使用指南。
一、基本概念
std::string 是 basic_string<char> 的 typedef,定义在 <string> 头文件中。它具有以下特点:
- 动态管理内存:自动处理内存分配和释放,避免内存泄漏。
- 安全的操作:边界检查、自动扩容等机制减少错误。
- 丰富的接口:支持字符串拼接、查找、替换等常见操作。
- 与 C 风格字符串兼容:可与 const char* 相互转换。
下面是一个string的简单使用:
int main()
{string s1;string s2("hello world");string s3(s2);cout << s1 << endl;cout << s2 << endl;cout << s3 << endl;string s4(s2, 6, 15);cout << s4 << endl;string s5(s2, 6);cout << s5 << endl;string s6("hello world", 5);cout << s6 << endl;string s7(10, 'x');cout << s7 << endl;return 0;
}
开始之前我们先学习一下
auto和范围for
在学习string类之前,我们先来学习一下auto和范围for,方便我们后面的学习。
C++11 引入的 auto 关键字和范围 for 循环(Range-based for loop)是两个非常实用的特性,它们共同提升了代码的简洁性和可读性。下面分别介绍这两个特性的用法和原理。
一、auto 关键字
1. 基本用法
auto 用于自动推导变量的类型,由编译器根据初始化表达式的类型确定变量类型:
auto x = 42; // x 为 int 类型
auto d = 3.14; // d 为 double 类型
auto s = "hello"; // s 为 const char* 类型
auto lst = {1, 2, 3}; // lst 为 std::initializer_list<int> 类型
2. 配合复杂类型
简化冗长的类型声明,例如迭代器:
#include <vector>std::vector<int> vec = {1, 2, 3};// 传统写法
std::vector<int>::iterator it = vec.begin();// 使用 auto
auto it = vec.begin(); // it 为 std::vector<int>::iterator 类型
3. 配合引用和常量
const std::vector<int>& ref = vec;auto copy = ref; // copy 是 std::vector<int>(顶层 const 被丢弃)
auto& ref2 = ref; // ref2 是 const std::vector<int>&
const auto& cref = ref; // cref 是 const std::vector<int>&
auto* ptr = &vec; // ptr 是 std::vector<int>*
4. 函数返回值推导
C++14 允许 auto 作为函数返回类型(需有明确的返回语句):
auto add(int a, int b) {return a + b; // 返回类型推导为 int
}
5. 注意事项
- 必须初始化:auto 变量必须在定义时初始化。
- 类型可能被调整:例如数组会退化为指针,顶层 const 可能被丢弃。
- 避免滥用:在类型明确的场景(如 int x = 42)中无需使用 auto。
二、范围 for 循环
1. 基本语法
遍历可迭代对象(如数组、容器)的每个元素:
for (declaration : expression) {// 循环体
}
- expression:表示可迭代对象(如 std::vector、数组)。
- declaration:表示迭代变量,通常用 auto 声明。
2. 使用示例
#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 遍历并打印每个元素(值拷贝)for (auto x : vec) {std::cout << x << " ";}// 遍历并修改元素(使用引用)for (auto& x : vec) {x *= 2;}// 常量引用(只读访问)for (const auto& x : vec) {std::cout << x << " ";}// 遍历 C 风格数组int arr[] = {10, 20, 30};for (auto x : arr) {std::cout << x << " ";}// 遍历字符串std::string s = "hello";for (auto c : s) {std::cout << c << " ";}return 0;
}
3. 原理
范围 for 循环本质上是语法糖,等价于:
auto&& __range = expression;
for (auto __it = std::begin(__range); __it != std::end(__range); ++__it) {declaration = *__it;// 循环体
}
- 要求 expression 支持 std::begin() 和 std::end()(或成员函数 begin()/end())。
三、auto 与范围 for 的结合使用
1. 自动推导元素类型
std::vector<std::pair<int, std::string>> vec = {{1, "one"}, {2, "two"}};// 使用 auto 自动推导 pair 类型
for (const auto& p : vec) {std::cout << p.first << ": " << p.second << std::endl;
}// C++17 结构化绑定
for (const auto& [num, str] : vec) {std::cout << num << ": " << str << std::endl;
}
2. 处理不同容器类型
#include <map>std::map<int, std::string> m = {{1, "one"}, {2, "two"}};// 遍历 map(键值对)
for (const auto& pair : m) {std::cout << pair.first << ": " << pair.second << std::endl;
}// C++17 结构化绑定
for (const auto& [key, value] : m) {std::cout << key << ": " << value << std::endl;
}
四、注意事项
1. 避免意外拷贝
使用 auto& 或 const auto& 避免值拷贝:
// 低效(每次迭代拷贝元素)
for (auto elem : large_vector) { ... }// 高效(引用避免拷贝)
for (const auto& elem : large_vector) { ... }
2. 迭代器失效问题
在范围 for 中修改容器结构(如添加 / 删除元素)可能导致迭代器失效:
std::vector<int> vec = {1, 2, 3};// 错误:循环中删除元素会导致迭代器失效
for (auto& x : vec) {if (x == 2) {vec.erase(...); // 危险!}
}
总结
- auto 关键字:自动推导变量类型,简化复杂类型声明,提高代码可读性。
- 范围 for 循环:简洁地遍历可迭代对象,避免手动管理迭代器。
- 最佳实践:结合 auto 和引用(auto&/const auto&)高效遍历容器。
标准库中的string类
我们Cplusplus的文档来辅助我们学习string:
string类的接口汇总
1.string类的迭代器
C++ 的 std::string 类提供了迭代器(Iterator)来遍历字符串中的每个字符。迭代器是一种抽象的指针,允许以统一的方式访问容器(如 string、vector 等)中的元素。下面详细介绍 std::string 的迭代器。
一、迭代器类型 std::string 支持以下几种迭代器:
1.正向迭代器:
- string::iterator:可读写迭代器
- string::const_iterator:只读迭代器(用于常量字符串)
2.反向迭代器:
- string::reverse_iterator:可读写反向迭代器
- string::const_reverse_iterator:只读反向迭代器
二、获取迭代器
通过以下成员函数获取迭代器:
函数 | 描述 | 返回类型 |
begin() | 指向第一个字符 | iterator/const_iterator |
end() | 指向字符串末尾的下一个位置(空字符 '\0' 之后) | iterator /const_iterator |
rbegin() | 指向最后一个字符(反向开始) | everse_iterator/const_reverse_iterator |
rend() | 指向第一个字符的前一个位置(反向末尾) | reverse_iterator/const_reverse_iterator |
三、迭代器操作
迭代器支持以下基本操作:
- 解引用:*it 获取当前字符
- 递增 / 递减:++it、--it 移动迭代器
- 比较:it1 == it2、it1 != it2 判断迭代器是否相等
四、使用示例
1. 正向遍历字符串
#include <string>
#include <iostream>int main() {std::string s = "hello";// 使用普通迭代器(可修改)for (std::string::iterator it = s.begin(); it != s.end(); ++it) {*it = std::toupper(*it); // 转为大写}// 使用常量迭代器(只读,推荐)for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) {std::cout << *it; // 输出: HELLO}// C++11 自动类型推导for (auto it = s.cbegin(); it != s.cend(); ++it) {std::cout << *it; // 输出: HELLO}return 0;
}
输出结果:
2. 反向遍历字符串
std::string s = "hello";// 反向迭代器
for (std::string::reverse_iterator rit = s.rbegin(); rit != s.rend(); ++rit) {std::cout << *rit; // 输出: olleh
}// C++11 自动类型推导
for (auto rit = s.rbegin(); rit != s.rend(); ++rit) {std::cout << *rit; // 输出: olleh
}
输出结果:
3. 使用范围 for 循环(更简洁)
std::string s = "hello";// 范围 for 本质上使用迭代器
for (char c : s) {std::cout << c; // 输出: hello
}// 修改字符(使用引用)
for (char& c : s) {c = std::toupper(c); // 转为大写
}
五、迭代器有效性
- 插入 / 删除操作:在字符串中间插入或删除字符可能导致迭代器失效。
- 重新分配内存:如果字符串容量不足,插入操作可能触发内存重新分配,导致所有迭代器失效。
示例(危险操作):
std::string s = "abc";
auto it = s.begin();s.insert(it, 'x'); // 插入后 it 可能失效
++it; // 未定义行为!
总结
- 迭代器类型:begin()/end()(正向)、rbegin()/rend()(反向),分 const 和非 const 版本。
- 推荐写法:优先使用 auto 和范围 for 循环,减少手动管理迭代器。
- 注意事项:避免在迭代过程中修改字符串结构,防止迭代器失效。
2.string类的初始化和销毁
string 是一个类,因此我们在初始化时肯定会调用其构造函数初始化。
1.默认初始化
std::string str; // 生成空字符串
2 .用字符串字面量初始化
std::string str = "Hello"; // 拷贝初始化
std::string str("Hello"); // 直接初始化
3.指定重复字符
std::string str(5, 'a'); // 结果为 "aaaaa"
4.用已有字符串初始化
std::string original = "World";
std::string copy(original); // 拷贝构造
5.用部分字符串初始化
std::string str("HelloWorld", 5); // 取前5个字符,结果是 "Hello"
6.从迭代器范围初始化
std::string source = "0123456789";
std::string str(source.begin() + 2, source.begin() + 5); // 结果为 "234"
7.用初始化列表初始化
std::string str = {'H', 'e', 'l', 'l', 'o'};
在C++中,std::string 是一个类模板的实例化,代表了一个可变的字符序列。由于它是一个类,当 std::string 对象的生命周期结束时(即离开其作用域),编译器会自动调用该对象的析构函数来清理其分配的资源。
3.string类的容量操作
在 C++ 中,std::string 的容量操作涉及内存管理和性能优化。理解这些操作可以帮助你避免不必要的内存重新分配,提高代码效率。以下是关于 std::string 容量操作的详细介绍:
以下是比较常见的容量操作:
函数名称 | 功能 |
size | 返回字符串的有效长度 |
length | 返回字符串的有效长度 |
capacity | 返回字符串的容量大小 |
max size | 返回字符串的最大长度 |
clear | 清空字符串 |
empty | 检查是否为空串,是则返回true,否则返回false |
reserve | 请求改变字符串的容量 |
resize | 重新设置有效字符的数量,超过原来有效长度则用c字符填充 |
1.容量管理函数
reserve(size_t n)
- 功能:请求字符串容量至少为 n 个字符,若当前容量不足则重新分配内存。
- 用途:提前分配足够内存,避免多次小容量扩展导致的性能开销。
std::string str;
str.reserve(100); // 预先分配100个字符的容量
resize(size_t n, char c = '\0')
功能:调整字符串长度为 n:
- 若 n > 当前长度,则追加字符 c(默认补 '\0')。
- 若 n < 当前长度,则截断字符串。
注意:可能触发容量扩展。
std::string str = "Hello";
str.resize(10, '!'); // str变为 "Hello!!!"
2.自动扩容机制
- 当向字符串追加字符且长度超过当前容量时,std::string 会自动重新分配更大的内存空间。
- 扩容策略通常是指数增长(如翻倍),以减少重新分配的频率。
std::string str;
for (int i = 0; i < 100; ++i) {str += 'a';std::cout << "Length: " << str.length() << ", Capacity: " << str.capacity() << std::endl;
}
// 容量可能按15、31、63、127...的规律增长
3.容量与性能
- 提前预留容量:若你预先知道字符串的大致长度,使用 reserve() 可避免多次扩容。
std::string str;
str.reserve(1000); // 避免循环中多次扩容
for (int i = 0; i < 1000; ++i) {str += 'a';
}
- 避免过度扩容:频繁 resize() 或 += 可能导致多次内存分配,影响性能。
常见误区
1.混淆长度和容量:
std::string str(10, 'a'); // 长度为10,容量至少为10
str.reserve(20); // 长度仍为10,但容量变为20
2.误用 resize() 初始化:
std::string str;
str.resize(5, 'a'); // 正确:初始化为 "aaaaa"
// 错误写法:std::string str(5, 'a'); // 这是构造函数,效果相同但语义不同
总结
- 使用 reserve() 预分配内存以优化性能。
- 使用 resize() 调整字符串长度,同时可能改变容量。
- 自动扩容机制能减少分配次数,但可能导致内存浪费。
4.string类的访问操作
string类的访问操作通常有如下几种:
函数名称 | 功能 |
operator[] | 返回指定位置的字符,越界则报错 |
at | 返回指定位置的字符,越界则抛异常 |
back() | 返回字符串最后一个字符(不是"0”) |
front() | 返回字符串第一个字符 |
substr() | 提取长度为 len 的子串。 |
1. 索引访问
operator[]
- 功能:通过下标访问指定位置的字符,不进行边界检查。
- 返回值:char&(可修改)或 const char&(常量字符串)。
std::string str = "Hello";
str[0] = 'J'; // 修改首字符为 'J'
std::cout << str[1]; // 输出 'e'
at(size_t pos)
- 功能:通过下标访问指定位置的字符,进行边界检查(越界时抛出 std::out_of_range 异常)。
- 返回值:同 operator[]。
try {std::cout << str.at(10); // 抛出异常
} catch (const std::out_of_range& e) {std::cerr << "Error: " << e.what() << std::endl;
}
2. 迭代器访问
正向迭代器
- 类型:iterator(可修改)和 const_iterator(只读)。
- 示例:
std::string str = "Hello";
for (auto it = str.begin(); it != str.end(); ++it) {std::cout << *it; // 输出 'H', 'e', 'l', 'l', 'o'
}
反向迭代器
- 类型:reverse_iterator 和 const_reverse_iterator。
- 示例:
for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {std::cout << *rit; // 输出 'o', 'l', 'l', 'e', 'H'
}
3. 访问首尾字符
front() 和 back()
- 功能:直接访问字符串的第一个和最后一个字符(C++11 起)。
- 返回值:char& 或 const char&。
std::string str = "Hello";
str.front() = 'A'; // 修改首字符为 'A'
str.back() = '!'; // 修改尾字符为 '!'
4. 子串提取
substr(size_t pos = 0, size_t len = npos)
- 功能:从位置 pos 开始提取长度为 len 的子串。
- 返回值:新的 std::string 对象。
std::string str = "HelloWorld";
std::string sub = str.substr(6, 5); // 提取 "World"
std::string all = str.substr(); // 提取整个字符串
5. 范围 for 循环(C++11 起)
功能:遍历字符串中的每个字符。
for (char c : str) {std::cout << c; // 输出 'H', 'e', 'l', 'l', 'o'
}// 修改字符(需使用引用)
for (char& c : str) {c = std::toupper(c); // 转为大写
}
5.string类的修改操作
在 C++ 中,std::string 提供了丰富的修改操作,包括追加、插入、删除、替换等。以下是详细介绍:
1. 追加操作
operator+=
- 功能:追加字符串、字符或初始化列表。
std::string str = "Hello";
str += " World"; // 追加字符串
str += '!'; // 追加字符
str += {' ', 'C', '+'}; // 追加初始化列表
append()
- 功能:追加多种类型的数据(字符串、子串、字符重复等)。
str.append("!!!"); // 追加字符串
str.append("abc", 2); // 追加 "abc" 的前2个字符
str.append(3, 'X'); // 追加3个 'X'
str.append({"a", "b", "c"}); // 追加初始化列表
push_back(char c)
- 功能:在字符串末尾添加单个字符。
str.push_back('!'); // 等价于 str += '!';
2. 插入操作
insert()
- 功能:在指定位置插入字符串、子串、字符等。
std::string str = "HelloWorld";
str.insert(5, " "); // 在位置5插入空格,结果:"Hello World"
str.insert(0, "Hi, "); // 在开头插入字符串
str.insert(10, 3, '!'); // 在位置10插入3个 '!'
3. 删除操作
erase()
- 功能:删除指定位置的字符或子串。
str.erase(5, 1); // 删除位置5的1个字符(空格)
str.erase(str.begin() + 5); // 删除迭代器指向的字符
str.erase(str.begin(), str.begin() + 5); // 删除前5个字符
pop_back()
- 功能:删除字符串的最后一个字符(C++11 起)。
str.pop_back(); // 移除 '!'
clear()
- 功能:清空字符串,使其长度为 0。
str.clear(); // str 变为 ""
4. 替换操作
replace()
- 功能:替换指定位置的子串。
std::string str = "HelloWorld";
str.replace(5, 5, " C++"); // 从位置5开始的5个字符替换为 " C++"
str.replace(str.begin(), str.begin() + 5, "Hi"); // 前5个字符替换为 "Hi"
5. 大小写转换
- 需结合 <cctype> 中的函数:
#include <cctype>// 转为大写
for (char& c : str) {c = std::toupper(c);
}// 转为小写
for (char& c : str) {c = std::tolower(c);
}
6. 交换操作
swap()
- 功能:交换两个字符串的内容,效率高(常数时间)。
7. 修改注意事项
- 迭代器 / 引用失效:插入或删除操作可能导致迭代器、指针或引用失效。
auto it = str.begin(); str.insert(it, 'A'); // it 失效,不应继续使用
- 内存重新分配:追加或插入可能触发容量扩展,导致内存重新分配。
str.reserve(100); // 预先分配足够容量,避免频繁扩容
- 边界检查:确保插入或删除位置合法,否则可能导致未定义行为。
if (pos < str.size()) {str.erase(pos, 1); }
总结
操作类型 | 常用函数 |
追加 | +=, append(), push_back() |
插入 | insert() |
删除 | erase(), pop_back(), clear() |
替换 | replace() |
大小写转换 | std::toupper(), std::tolower() |
交换 | swap() |