第27章 字符串和流
C++20新特性:字符串相关增强
C++20对字符串处理进行了多项增强,包括format库、char8_t支持等:
C++20引入了std::format库,提供了一种类型安全、灵活的字符串格式化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <format> #include <string> #include <iostream>
int main() { std::string message = std::format("Hello, {}!", "world"); std::cout << message << std::endl; std::string info = std::format("Name: {}, Age: {}", "Alice", 30); std::cout << info << std::endl; std::string number = std::format("Pi is approximately {:.2f}", 3.14159); std::cout << number << std::endl; std::string aligned = std::format("{:<10} {:>10}", "Left", "Right"); std::cout << aligned << std::endl; std::string hex = std::format("Decimal: {}, Hex: {:x}, Octal: {:o}", 42, 42, 42); std::cout << hex << std::endl; auto now = std::chrono::system_clock::now(); std::string time_str = std::format("Current time: {:%Y-%m-%d %H:%M:%S}", now); std::cout << time_str << std::endl; return 0; }
|
char8_t类型
C++20引入了char8_t类型,专门用于UTF-8编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #include <iostream> #include <string>
int main() { const char8_t* utf8_str = u8"Hello, 世界!"; std::u8string u8s = u8"Hello, 世界!"; std::cout << "Length of u8string: " << u8s.size() << std::endl; return 0; }
|
string类
string类的基本操作
string类是C++标准库中用于处理字符串的类,它提供了丰富的字符串操作功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include <string> #include <iostream>
int main() { std::string s1; std::string s2("Hello"); std::string s3(s2); std::string s4(5, 'a'); std::string s5(s2, 1, 3); s1 = "World"; s1 = s2; s1.assign("Hello World"); s1.assign("Hello World", 5); s1.assign(3, 'x'); s1 = "Hello"; s2 = "World"; std::string s6 = s1 + " " + s2; s1 += " " + s2; s1.append("!"); s1.append(" C++", 4); std::cout << "Length of s1: " << s1.length() << std::endl; std::cout << "Size of s1: " << s1.size() << std::endl; std::cout << "Empty: " << (s1.empty() ? "Yes" : "No") << std::endl; std::cout << "First character: " << s1[0] << std::endl; std::cout << "Second character: " << s1.at(1) << std::endl; s1[0] = 'h'; s1.at(1) = 'e'; size_t pos = s1.find("World"); if (pos != std::string::npos) { std::cout << "Found 'World' at position: " << pos << std::endl; } pos = s1.rfind("o"); if (pos != std::string::npos) { std::cout << "Last 'o' at position: " << pos << std::endl; } s1.replace(6, 5, "C++"); s1.insert(6, " "); s1.erase(6, 1); if (s1 == s2) { std::cout << "s1 == s2" << std::endl; } else if (s1 > s2) { std::cout << "s1 > s2" << std::endl; } else { std::cout << "s1 < s2" << std::endl; } std::string substr = s1.substr(0, 5); std::cout << "Substring: " << substr << std::endl; const char* cstr = s1.c_str(); std::cout << "C-style string: " << cstr << std::endl; std::string numStr = "12345"; int num = std::stoi(numStr); std::cout << "String to int: " << num << std::endl; double dbl = std::stod("3.14"); std::cout << "String to double: " << dbl << std::endl; std::string intStr = std::to_string(12345); std::cout << "Int to string: " << intStr << std::endl; return 0; }
|
string类的迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <string> #include <iostream>
int main() { std::string s = "Hello, World!"; std::cout << "Forward iteration: " << std::endl; for (auto it = s.begin(); it != s.end(); ++it) { std::cout << *it; } std::cout << std::endl; std::cout << "Reverse iteration: " << std::endl; for (auto it = s.rbegin(); it != s.rend(); ++it) { std::cout << *it; } std::cout << std::endl; std::cout << "Constant forward iteration: " << std::endl; for (auto it = s.cbegin(); it != s.cend(); ++it) { std::cout << *it; } std::cout << std::endl; std::cout << "Constant reverse iteration: " << std::endl; for (auto it = s.crbegin(); it != s.crend(); ++it) { std::cout << *it; } std::cout << std::endl; std::cout << "Range-based for loop: " << std::endl; for (char c : s) { std::cout << c; } std::cout << std::endl; return 0; }
|
string类的算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <string> #include <iostream> #include <algorithm>
int main() { std::string s = "Hello, World!"; std::transform(s.begin(), s.end(), s.begin(), ::toupper); std::cout << "Uppercase: " << s << std::endl; std::transform(s.begin(), s.end(), s.begin(), ::tolower); std::cout << "Lowercase: " << s << std::endl; std::reverse(s.begin(), s.end()); std::cout << "Reversed: " << s << std::endl; std::sort(s.begin(), s.end()); std::cout << "Sorted: " << s << std::endl; auto last = std::unique(s.begin(), s.end()); s.erase(last, s.end()); std::cout << "Unique: " << s << std::endl; return 0; }
|
标准模板库(STL)
STL的组成部分
STL由以下几个部分组成:
- 容器(Containers):用于存储数据的对象
- 迭代器(Iterators):用于遍历容器中的元素
- 算法(Algorithms):用于操作容器中的元素
- 函数对象(Functors):行为类似函数的对象
- 适配器(Adapters):修改其他STL组件接口的组件
- 分配器(Allocators):用于管理内存分配
容器
序列容器
序列容器按顺序存储元素,包括:
- vector:动态数组,随机访问快
- list:双向链表,插入删除快
- deque:双端队列,两端插入删除快
- array:固定大小数组(C++11+)
- forward_list:单向链表(C++11+)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #include <vector> #include <list> #include <deque> #include <array> #include <forward_list> #include <iostream>
int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; vec.push_back(6); vec.pop_back(); std::cout << "Vector: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; std::list<int> lst = {1, 2, 3, 4, 5}; lst.push_back(6); lst.push_front(0); lst.pop_back(); lst.pop_front(); std::cout << "List: " << std::endl; for (int num : lst) { std::cout << num << " "; } std::cout << std::endl; std::deque<int> dq = {1, 2, 3, 4, 5}; dq.push_back(6); dq.push_front(0); dq.pop_back(); dq.pop_front(); std::cout << "Deque: " << std::endl; for (int num : dq) { std::cout << num << " "; } std::cout << std::endl; std::array<int, 5> arr = {1, 2, 3, 4, 5}; std::cout << "Array: " << std::endl; for (int num : arr) { std::cout << num << " "; } std::cout << std::endl; std::forward_list<int> flst = {1, 2, 3, 4, 5}; flst.push_front(0); flst.pop_front(); std::cout << "Forward list: " << std::endl; for (int num : flst) { std::cout << num << " "; } std::cout << std::endl; return 0; }
|
关联容器
关联容器按键存储元素,包括:
- set:有序集合,键唯一
- multiset:有序集合,键可重复
- map:有序映射,键值对,键唯一
- multimap:有序映射,键值对,键可重复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <set> #include <map> #include <iostream>
int main() { std::set<int> s = {3, 1, 4, 1, 5, 9, 2, 6}; s.insert(7); s.erase(1); std::cout << "Set: " << std::endl; for (int num : s) { std::cout << num << " "; } std::cout << std::endl; auto it = s.find(5); if (it != s.end()) { std::cout << "Found 5" << std::endl; } std::multiset<int> ms = {3, 1, 4, 1, 5, 9, 2, 6}; ms.insert(1); std::cout << "Multiset: " << std::endl; for (int num : ms) { std::cout << num << " "; } std::cout << std::endl; std::map<std::string, int> m; m["one"] = 1; m["two"] = 2; m["three"] = 3; m.insert({"four", 4}); std::cout << "Map: " << std::endl; for (const auto& pair : m) { std::cout << pair.first << ": " << pair.second << std::endl; } auto it2 = m.find("two"); if (it2 != m.end()) { std::cout << "Found two: " << it2->second << std::endl; } std::multimap<std::string, int> mm; mm.insert({"one", 1}); mm.insert({"one", 11}); mm.insert({"two", 2}); std::cout << "Multimap: " << std::endl; for (const auto& pair : mm) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; }
|
无序容器(C++11+)
无序容器使用哈希表实现,包括:
- unordered_set:无序集合,键唯一
- unordered_multiset:无序集合,键可重复
- unordered_map:无序映射,键值对,键唯一
- unordered_multimap:无序映射,键值对,键可重复
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <unordered_set> #include <unordered_map> #include <iostream>
int main() { std::unordered_set<int> us = {3, 1, 4, 1, 5, 9, 2, 6}; us.insert(7); us.erase(1); std::cout << "Unordered set: " << std::endl; for (int num : us) { std::cout << num << " "; } std::cout << std::endl; std::unordered_map<std::string, int> um; um["one"] = 1; um["two"] = 2; um["three"] = 3; um.insert({"four", 4}); std::cout << "Unordered map: " << std::endl; for (const auto& pair : um) { std::cout << pair.first << ": " << pair.second << std::endl; } return 0; }
|
迭代器
迭代器是一种抽象的设计概念,它提供了一种方法来访问容器中的元素,而不暴露容器的底层表示。
迭代器类型
| 迭代器类型 | 功能 | 支持的操作 |
|---|
| 输入迭代器 | 只读,单向移动 | ++, *, ->, ==, != |
| 输出迭代器 | 只写,单向移动 | ++, * |
| 前向迭代器 | 读写,单向移动 | ++, *, ->, ==, != |
| 双向迭代器 | 读写,双向移动 | ++, –, *, ->, ==, != |
| 随机访问迭代器 | 读写,随机访问 | ++, –, *, ->, ==, !=, +, -, +=, -=, [] |
迭代器使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #include <vector> #include <list> #include <iostream>
int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; std::cout << "Vector: " << std::endl; for (auto it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; std::cout << "Third element: " << *(vec.begin() + 2) << std::endl; std::cout << "Using []: " << vec[2] << std::endl; std::list<int> lst = {1, 2, 3, 4, 5}; std::cout << "List: " << std::endl; for (auto it = lst.begin(); it != lst.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; auto it = lst.end(); --it; std::cout << "Last element: " << *it << std::endl; return 0; }
|
算法
STL提供了大量的算法,用于操作容器中的元素。这些算法定义在<algorithm>头文件中。
常用算法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <vector> #include <algorithm> #include <iostream>
int main() { std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7, 4, 6}; std::sort(vec.begin(), vec.end()); std::cout << "Sorted: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; std::reverse(vec.begin(), vec.end()); std::cout << "Reversed: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; auto it = std::find(vec.begin(), vec.end(), 5); if (it != vec.end()) { std::cout << "Found 5 at position: " << std::distance(vec.begin(), it) << std::endl; } int count = std::count(vec.begin(), vec.end(), 5); std::cout << "Count of 5: " << count << std::endl; auto maxIt = std::max_element(vec.begin(), vec.end()); std::cout << "Max element: " << *maxIt << std::endl; auto minIt = std::min_element(vec.begin(), vec.end()); std::cout << "Min element: " << *minIt << std::endl; int sum = std::accumulate(vec.begin(), vec.end(), 0); std::cout << "Sum: " << sum << std::endl; std::fill(vec.begin(), vec.begin() + 3, 0); std::cout << "After fill: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; std::replace(vec.begin(), vec.end(), 0, 10); std::cout << "After replace: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; std::vector<int> vec2(vec.size()); std::copy(vec.begin(), vec.end(), vec2.begin()); std::cout << "Copied vector: " << std::endl; for (int num : vec2) { std::cout << num << " "; } std::cout << std::endl; std::swap(vec[0], vec[1]); std::cout << "After swap: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; return 0; }
|
谓词和函数对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <vector> #include <algorithm> #include <iostream>
class GreaterThan { private: int value;
public: GreaterThan(int v) : value(v) {} bool operator()(int num) const { return num > value; } };
int main() { std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7, 4, 6}; int threshold = 5; GreaterThan gt(threshold); int count = std::count_if(vec.begin(), vec.end(), gt); std::cout << "Count of elements greater than " << threshold << ": " << count << std::endl; count = std::count_if(vec.begin(), vec.end(), [threshold](int num) { return num > threshold; }); std::cout << "Count using lambda: " << count << std::endl; std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; }); std::cout << "Sorted in descending order: " << std::endl; for (int num : vec) { std::cout << num << " "; } std::cout << std::endl; auto it = std::find_if(vec.begin(), vec.end(), [threshold](int num) { return num > threshold; }); if (it != vec.end()) { std::cout << "First element greater than " << threshold << ": " << *it << std::endl; } return 0; }
|
函数对象
函数对象是重载了函数调用运算符()的类的实例,也称为仿函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| #include <vector> #include <algorithm> #include <iostream>
class Add { private: int value;
public: Add(int v) : value(v) {} int operator()(int x) const { return x + value; } };
class Multiply { private: int factor;
public: Multiply(int f) : factor(f) {} int operator()(int x) const { return x * factor; } };
int main() { Add add5(5); int result1 = add5(10); std::cout << "10 + 5 = " << result1 << std::endl; Multiply multiply3(3); int result2 = multiply3(10); std::cout << "10 * 3 = " << result2 << std::endl; std::vector<int> vec = {1, 2, 3, 4, 5}; std::vector<int> result(vec.size()); std::transform(vec.begin(), vec.end(), result.begin(), add5); std::cout << "After adding 5: " << std::endl; for (int num : result) { std::cout << num << " "; } std::cout << std::endl; std::transform(vec.begin(), vec.end(), result.begin(), multiply3); std::cout << "After multiplying by 3: " << std::endl; for (int num : result) { std::cout << num << " "; } std::cout << std::endl; return 0; }
|
适配器
STL提供了三种适配器:容器适配器、迭代器适配器和函数适配器。
容器适配器
容器适配器修改容器的接口,提供不同的使用方式:
- stack:栈,后进先出(LIFO)
- queue:队列,先进先出(FIFO)
- priority_queue:优先队列,最高优先级元素先出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <stack> #include <queue> #include <iostream>
int main() { std::stack<int> s; s.push(10); s.push(20); s.push(30); std::cout << "Stack size: " << s.size() << std::endl; std::cout << "Top element: " << s.top() << std::endl; s.pop(); std::cout << "After pop, top element: " << s.top() << std::endl; std::queue<int> q; q.push(10); q.push(20); q.push(30); std::cout << "Queue size: " << q.size() << std::endl; std::cout << "Front element: " << q.front() << std::endl; std::cout << "Back element: " << q.back() << std::endl; q.pop(); std::cout << "After pop, front element: " << q.front() << std::endl; std::priority_queue<int> pq; pq.push(30); pq.push(10); pq.push(50); pq.push(20); std::cout << "Priority queue size: " << pq.size() << std::endl; std::cout << "Top element: " << pq.top() << std::endl; pq.pop(); std::cout << "After pop, top element: " << pq.top() << std::endl; return 0; }
|
最佳实践
1. string类的使用
- 优先使用string:相比C风格字符串,string更安全、更方便
- 避免不必要的拷贝:使用引用传递字符串
- 合理使用reserve:对于大型字符串操作,使用reserve预分配空间
- 注意字符串比较:使用==运算符或compare方法,而不是strcmp
- 使用STL算法:对string使用STL算法时,注意string的特性
2. STL容器的选择
| 场景 | 推荐容器 | 原因 |
|---|
| 随机访问频繁 | vector | 随机访问时间复杂度O(1) |
| 插入删除频繁 | list | 插入删除时间复杂度O(1) |
| 两端插入删除 | deque | 两端操作时间复杂度O(1) |
| 固定大小数组 | array | 编译期大小,更高效 |
| 查找频繁 | set/map | 查找时间复杂度O(log n) |
| 无序查找 | unordered_set/unordered_map | 平均查找时间复杂度O(1) |
| 栈操作 | stack | 后进先出 |
| 队列操作 | queue | 先进先出 |
| 优先队列 | priority_queue | 最高优先级元素先出 |
3. 迭代器的使用
- 使用auto:使用auto推导迭代器类型,提高代码可读性
- 注意迭代器失效:在修改容器时,注意迭代器可能失效
- 优先使用范围for:对于简单遍历,使用范围for循环
- 使用const迭代器:对于只读操作,使用const迭代器
4. 算法的使用
- 了解算法的时间复杂度:选择合适的算法
- 使用合适的谓词:根据需要使用函数对象或lambda表达式
- 注意算法的要求:不同算法对迭代器类型有不同要求
- 优先使用STL算法:避免手写循环,提高代码可读性和效率
5. 函数对象和Lambda
- 对于复杂逻辑:使用函数对象,提高代码可读性
- 对于简单逻辑:使用Lambda表达式,简化代码
- 注意捕获列表:避免不必要的捕获,防止变量生命周期问题
- 使用mutable:需要修改捕获变量时,使用mutable关键字
常见错误和陷阱
1. string类的错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| std::string s = "Hello"; char c = s[10];
try { char c = s.at(10); } catch (const std::out_of_range& e) { std::cout << "Out of range: " << e.what() << std::endl; }
std::string s = "Hello"; s.copy(buffer, 10);
const char* cstr = s.c_str();
|
2. 容器使用错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| std::vector<int> vec = {1, 2, 3, 4, 5}; for (auto it = vec.begin(); it != vec.end(); ++it) { if (*it == 3) { vec.erase(it); } }
for (auto it = vec.begin(); it != vec.end();) { if (*it == 3) { it = vec.erase(it); } else { ++it; } }
std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = std::find(vec.begin(), vec.end(), 5);
std::set<int> s = {1, 2, 3, 4, 5}; auto it = s.find(5);
|
3. 迭代器错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = vec.begin(); vec.push_back(6); std::cout << *it << std::endl;
std::vector<int> vec = {1, 2, 3, 4, 5}; auto it = vec.begin(); vec.push_back(6); it = vec.begin(); std::cout << *it << std::endl;
std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); std::cout << *(it + 2) << std::endl;
std::list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); std::advance(it, 2); std::cout << *it << std::endl;
|
4. 算法使用错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| std::list<int> lst = {5, 2, 8, 1, 9}; std::sort(lst.begin(), lst.end());
lst.sort();
std::vector<int> src = {1, 2, 3, 4, 5}; std::vector<int> dst; std::copy(src.begin(), src.end(), dst.begin());
std::copy(src.begin(), src.end(), std::back_inserter(dst));
dst.resize(src.size()); std::copy(src.begin(), src.end(), dst.begin());
|
小结
本章介绍了C++中的string类和标准模板库(STL),包括:
- string类:基本操作、迭代器、算法
- STL的组成部分:容器、迭代器、算法、函数对象、适配器、分配器
- STL容器:序列容器、关联容器、无序容器
- STL迭代器:类型、使用方法
- STL算法:常用算法、谓词和函数对象
- STL函数对象:使用方法
- STL适配器:容器适配器
- 最佳实践:string类的使用、容器选择、迭代器使用、算法使用、函数对象和Lambda
- 常见错误和陷阱:string类错误、容器使用错误、迭代器错误、算法使用错误
string类和STL是C++标准库的重要组成部分,它们提供了丰富的工具和组件,大大提高了编程效率和代码质量。掌握这些内容对于成为一名优秀的C++程序员至关重要。在实际编程中,应根据具体情况选择合适的工具和方法,遵循最佳实践,避免常见错误。
在后续章节中,我们将学习C++的输入输出、文件操作、模板特化等内容,进一步提高C++编程能力。