[C.C++] C++ 17 详细特性解析(6)

385 0
Honkers 2026-5-14 15:28:40 来自手机 | 显示全部楼层 |阅读模式

🎁std::string_view

std::string_view 是 C++17 标准库中引入的非拥有(non-owning)的字符视图类。它提供了一种轻量级的方式来 “查看” 一个已有的字符串(或字符数组),而无需复制其内容。你可以把它想象成一个指向现有字符串的 “望远镜” 或 “观察窗口”,它本身不分配或管理内存。

🔗 参考:https://en.cppreference.com/w/cpp/header/string_view.html

std::string_view 提供和 std::string 完全类似的接口,它的设计思路是非拥有的视图,只保存原始位置的指针和长度;无所有权,不负责字符串的生命周期管理,const 视图只能观察,不能修改底层字符串。


💻 基础使用示例

  1. #include <iostream>
  2. #include <string>
  3. #include <string_view>
  4. #include <vector>
  5. #include <algorithm>
  6. #include <cctype>
  7. // 基础打印函数 - 展示 string_view 的核心能力
  8. void print(std::string_view sv, const std::string& title = "string_view") {
  9. std::cout << "\n=== " << title << " ===\n";
  10. std::cout << "内容: '" << sv << "'\n";
  11. std::cout << "长度: " << sv.size() << " 字符\n";
  12. std::cout << "是否为空: " << (sv.empty() ? "是" : "否") << "\n";
  13. // 遍历方式1:下标访问(O(1) 随机访问)
  14. std::cout << "字符(下标): ";
  15. for (size_t i = 0; i < sv.size(); ++i) {
  16. std::cout << sv[i] << " ";
  17. }
  18. std::cout << "\n";
  19. // 遍历方式2:范围 for 循环(现代 C++ 风格)
  20. std::cout << "字符(范围): ";
  21. for (char ch : sv) {
  22. std::cout << ch << " ";
  23. }
  24. std::cout << "\n";
  25. // 遍历方式3:迭代器(算法友好)
  26. std::cout << "字符(迭代器): ";
  27. for (auto it = sv.begin(); it != sv.end(); ++it) {
  28. std::cout << *it << " ";
  29. }
  30. std::cout << "\n";
  31. }
  32. // 实际应用:字符串分割函数(不复制数据,高效)
  33. std::vector<std::string_view> split(std::string_view str, char delimiter) {
  34. std::vector<std::string_view> result;
  35. size_t start = 0;
  36. while (start < str.size()) {
  37. size_t end = str.find(delimiter, start);
  38. if (end == std::string_view::npos) {
  39. // 最后一部分
  40. result.push_back(str.substr(start));
  41. break;
  42. }
  43. // 添加子串(零拷贝)
  44. result.push_back(str.substr(start, end - start));
  45. start = end + 1;
  46. }
  47. return result;
  48. }
  49. // 实际应用:移除空格(延迟处理,不修改原字符串)
  50. std::string_view trim(std::string_view str) {
  51. // 移除前导空格
  52. while (!str.empty() && std::isspace(static_cast<unsigned char>(str.front()))) {
  53. str.remove_prefix(1);
  54. }
  55. // 移除尾随空格
  56. while (!str.empty() && std::isspace(static_cast<unsigned char>(str.back()))) {
  57. str.remove_suffix(1);
  58. }
  59. return str;
  60. }
  61. // 实际应用:判断是否为前缀
  62. bool starts_with(std::string_view str, std::string_view prefix) {
  63. return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
  64. }
  65. // 实际应用:判断是否为后缀
  66. bool ends_with(std::string_view str, std::string_view suffix) {
  67. return str.size() >= suffix.size() &&
  68. str.substr(str.size() - suffix.size()) == suffix;
  69. }
  70. // 实际应用:大小写不敏感比较
  71. bool iequals(std::string_view a, std::string_view b) {
  72. if (a.size() != b.size()) return false;
  73. auto to_lower = [](char c) {
  74. return std::tolower(static_cast<unsigned char>(c));
  75. };
  76. return std::equal(a.begin(), a.end(), b.begin(), to_lower);
  77. }
  78. // 性能对比演示
  79. void performance_demo() {
  80. std::cout << "\n=== 性能优化演示 ===\n";
  81. // ❌ 低效:不必要的字符串拷贝
  82. std::string huge_string(1000000, 'A');
  83. auto start = std::chrono::high_resolution_clock::now();
  84. std::string copy = huge_string.substr(100, 500); // 复制数据
  85. auto end = std::chrono::high_resolution_clock::now();
  86. auto copy_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
  87. std::cout << "字符串拷贝耗时: " << copy_time.count() << " 微秒\n";
  88. // ✅ 高效:string_view 零拷贝
  89. start = std::chrono::high_resolution_clock::now();
  90. std::string_view view = std::string_view(huge_string).substr(100, 500); // 不复制
  91. end = std::chrono::high_resolution_clock::now();
  92. auto view_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
  93. std::cout << "string_view 耗时: " << view_time.count() << " 微秒\n";
  94. std::cout << "性能提升: " << (double)copy_time.count() / view_time.count() << " 倍\n";
  95. }
  96. int main() {
  97. // ========== 1. 基础构造方式 ==========
  98. std::cout << "1. 基础构造方式\n" << std::string(50, '=') << "\n";
  99. // 从字符串字面量构造
  100. std::string_view sv1 = "Hello, world!";
  101. print(sv1, "从字面量构造");
  102. // 从 std::string 构造
  103. std::string str = "C++17 string_view is powerful!";
  104. std::string_view sv2 = str;
  105. print(sv2, "从 std::string 构造");
  106. // 从部分字符串构造(指针+长度)
  107. std::string_view sv3(str.c_str() + 6, 11); // "string_view"
  108. print(sv3, "从部分字符串构造");
  109. // 使用字面量后缀(需要 using namespace std::literals)
  110. using namespace std::literals;
  111. std::string_view sv4 = "Literal string_view"sv;
  112. print(sv4, "字面量后缀");
  113. // ========== 2. 实际应用场景 ==========
  114. std::cout << "\n2. 实际应用场景\n" << std::string(50, '=') << "\n";
  115. // 字符串分割
  116. std::string csv = "apple,banana,cherry,date";
  117. auto fruits = split(csv, ',');
  118. std::cout << "CSV 分割: ";
  119. for (auto fruit : fruits) {
  120. std::cout << "'" << fruit << "' ";
  121. }
  122. std::cout << "\n";
  123. // 移除空格
  124. std::string messy = " \t Hello, World! \n";
  125. std::string_view cleaned = trim(messy);
  126. std::cout << "原字符串: '" << messy << "'\n";
  127. std::cout << "去除空格后: '" << cleaned << "'\n";
  128. std::cout << "原字符串未修改: '" << messy << "' (证明零拷贝)\n";
  129. // 前缀/后缀检查
  130. std::string filename = "document.pdf";
  131. std::string_view fv = filename;
  132. std::cout << "文件名: " << fv << "\n";
  133. std::cout << "以 'doc' 开头? " << (starts_with(fv, "doc") ? "是" : "否") << "\n";
  134. std::cout << "以 '.pdf' 结尾? " << (ends_with(fv, ".pdf") ? "是" : "否") << "\n";
  135. // 大小写不敏感比较
  136. std::string_view str1 = "Hello";
  137. std::string_view str2 = "HELLO";
  138. std::cout << "忽略大小写比较: '" << str1 << "' vs '" << str2 << "' = "
  139. << (iequals(str1, str2) ? "相等" : "不等") << "\n";
  140. // ========== 3. 边界情况和注意事项 ==========
  141. std::cout << "\n3. 边界情况和注意事项\n" << std::string(50, '=') << "\n";
  142. // 空字符串视图
  143. std::string_view empty;
  144. print(empty, "空 string_view");
  145. // 临时字符串的危险用法(演示)
  146. std::cout << "⚠️ 警告:string_view 不拥有数据!\n";
  147. std::string_view dangerous;
  148. {
  149. std::string temp = "临时字符串";
  150. dangerous = temp; // 危险!temp 即将销毁
  151. std::cout << "临时对象存在时: " << dangerous << "\n";
  152. }
  153. // dangerous 现在是悬空引用!不要取消注释下一行
  154. // std::cout << "临时对象销毁后: " << dangerous << "\n"; // 未定义行为!
  155. // 安全做法:确保底层数据生命周期足够长
  156. std::string safe_str = "安全字符串";
  157. std::string_view safe_view = safe_str;
  158. std::cout << "安全使用: " << safe_view << "\n";
  159. // ========== 4. find 系列操作 ==========
  160. std::cout << "\n4. 查找操作\n" << std::string(50, '=') << "\n";
  161. std::string_view text = "The quick brown fox jumps over the lazy dog";
  162. std::cout << "文本: '" << text << "'\n";
  163. auto pos = text.find("fox");
  164. if (pos != std::string_view::npos) {
  165. std::cout << "找到 'fox' 在位置 " << pos << "\n";
  166. std::cout << "子串: " << text.substr(pos, 3) << "\n";
  167. }
  168. pos = text.find_first_of("aeiou");
  169. if (pos != std::string_view::npos) {
  170. std::cout << "第一个元音 '" << text[pos] << "' 在位置 " << pos << "\n";
  171. }
  172. // ========== 5. 性能演示 ==========
  173. performance_demo();
  174. // ========== 6. 现代 C++ 最佳实践 ==========
  175. std::cout << "\n5. 最佳实践建议\n" << std::string(50, '=') << "\n";
  176. // 函数参数使用 string_view
  177. auto process = [](std::string_view data) {
  178. // 不拷贝,只读取
  179. return data.size();
  180. };
  181. // 可以传字符串字面量、std::string、另一个 string_view
  182. std::cout << "字符串字面量长度: " << process("Hello") << "\n";
  183. std::cout << "std::string 长度: " << process(std::string("World")) << "\n";
  184. // 避免 string_view 悬空
  185. std::cout << "\n✅ 核心原则:\n";
  186. std::cout << "1. string_view 像指针一样,不拥有数据\n";
  187. std::cout << "2. 确保底层数据生命周期覆盖 string_view 的使用\n";
  188. std::cout << "3. 函数参数用 string_view,返回值用 string(如果需要拥有数据)\n";
  189. std::cout << "4. 修改原字符串会使 string_view 失效\n";
  190. std::cout << "5. string_view 不保证以 \\0 结尾,使用 .data() 要小心\n";
  191. return 0;
  192. }
复制代码

✨ 核心优势与典型场景

std::string_view 几乎是零开销,它不会复制字符串数据,构造和析构成本极低。并且提供了与 std::string 非常相似的接口,几乎提供了所有的读相关接口,并且兼容性强,它可以高效地从 std::string、字符串字面量、char* 等任何字符序列构造而来。这样在很多场景下可以减少构造和拷贝,又可以利用 string 类似的相关接口处理串。

  1. /**
  2. * @file string_view_core_demo.cpp
  3. * @brief std::string_view 核心优势与典型场景演示
  4. * @author Code Reviewer
  5. * @date 2026-05-12
  6. *
  7. * @details 本文件展示 std::string_view 的核心特性:
  8. * 1. 零拷贝(不复制字符串数据)
  9. * 2. 只读访问(非拥有型视图)
  10. * 3. 轻量级(仅存储指针+长度,16字节)
  11. * 4. 高效子串操作(无需复制)
  12. * 5. 兼容多种字符串类型(字面量、std::string、C字符串)
  13. *
  14. * 编译运行:
  15. * g++ -std=c++17 -O2 string_view_core_demo.cpp -o core_demo
  16. * ./core_demo
  17. */
  18. #include <iostream>
  19. #include <string>
  20. #include <string_view>
  21. #include <vector>
  22. #include <algorithm>
  23. #include <cctype>
  24. /**
  25. * @brief 打印 string_view 的基本信息(辅助函数)
  26. * @param sv 要打印的字符串视图
  27. * @param title 可选的标题
  28. */
  29. void print_info(std::string_view sv, const std::string& title = "string_view") {
  30. std::cout << "\n┌─────────────────────────────────────────┐\n";
  31. std::cout << "│ " << title << "\n";
  32. std::cout << "├─────────────────────────────────────────┤\n";
  33. std::cout << "│ 内容: '" << sv << "'\n";
  34. std::cout << "│ 长度: " << sv.size() << " 字符\n";
  35. std::cout << "│ 是否为空: " << (sv.empty() ? "是" : "否") << "\n";
  36. std::cout << "│ sizeof(sv): " << sizeof(sv) << " 字节\n";
  37. std::cout << "└─────────────────────────────────────────┘\n";
  38. }
  39. /**
  40. * @brief 【典型场景1】字符串分割(零拷贝)
  41. * @param str 要分割的字符串视图
  42. * @param delimiter 分隔符
  43. * @return 分割后的子串视图列表(不复制任何字符数据)
  44. *
  45. * @note 这是 string_view 最经典的用法之一
  46. * 传统方式:会为每个子串分配内存并复制字符
  47. * 本方式:只记录每个子串在原字符串中的位置和长度
  48. */
  49. std::vector<std::string_view> split(std::string_view str, char delimiter) {
  50. std::vector<std::string_view> result;
  51. size_t start = 0;
  52. while (start < str.size()) {
  53. // 查找下一个分隔符
  54. size_t end = str.find(delimiter, start);
  55. if (end == std::string_view::npos) {
  56. // 最后一部分(或没有分隔符)
  57. result.push_back(str.substr(start));
  58. break;
  59. }
  60. // 添加子串视图(零拷贝!只记录位置和长度)
  61. result.push_back(str.substr(start, end - start));
  62. start = end + 1;
  63. }
  64. return result;
  65. }
  66. /**
  67. * @brief 【典型场景2】去除首尾空格(非破坏性)
  68. * @param str 需要处理的字符串视图
  69. * @return 去除首尾空格后的新视图
  70. *
  71. * @note 不修改原字符串,不复制数据,只是调整视图的边界
  72. * 时间复杂度 O(n),但空间复杂度 O(1)
  73. */
  74. std::string_view trim(std::string_view str) {
  75. // 移除前导空白字符(空格、制表符、换行等)
  76. while (!str.empty() && std::isspace(static_cast<unsigned char>(str.front()))) {
  77. str.remove_prefix(1); // 将视图起始位置右移1
  78. }
  79. // 移除尾随空白字符
  80. while (!str.empty() && std::isspace(static_cast<unsigned char>(str.back()))) {
  81. str.remove_suffix(1); // 将视图结束位置左移1
  82. }
  83. return str;
  84. }
  85. /**
  86. * @brief 【典型场景3】检查字符串前缀
  87. * @param str 待检查的字符串
  88. * @param prefix 可能的前缀
  89. * @return 是否是前缀
  90. */
  91. bool starts_with(std::string_view str, std::string_view prefix) {
  92. // 首先确保原字符串长度足够
  93. if (str.size() < prefix.size()) {
  94. return false;
  95. }
  96. // substr 是 O(1) 操作,不会复制数据
  97. return str.substr(0, prefix.size()) == prefix;
  98. }
  99. /**
  100. * @brief 【典型场景4】检查字符串后缀
  101. * @param str 待检查的字符串
  102. * @param suffix 可能的后缀
  103. * @return 是否是后缀
  104. */
  105. bool ends_with(std::string_view str, std::string_view suffix) {
  106. if (str.size() < suffix.size()) {
  107. return false;
  108. }
  109. // 从尾部取子串进行对比,仍然是 O(1)
  110. return str.substr(str.size() - suffix.size()) == suffix;
  111. }
  112. /**
  113. * @brief 【典型场景5】大小写不敏感的比较
  114. * @param a 字符串A
  115. * @param b 字符串B
  116. * @return 是否相等(忽略大小写)
  117. */
  118. bool iequals(std::string_view a, std::string_view b) {
  119. if (a.size() != b.size()) {
  120. return false;
  121. }
  122. // 双参数 lambda:比较两个字符是否相等(忽略大小写)
  123. auto char_iequal = [](char c1, char c2) {
  124. return std::tolower(static_cast<unsigned char>(c1)) ==
  125. std::tolower(static_cast<unsigned char>(c2));
  126. };
  127. return std::equal(a.begin(), a.end(), b.begin(), char_iequal);
  128. }
  129. /**
  130. * @brief 【典型场景6】提取文件扩展名
  131. * @param filename 文件名
  132. * @return 扩展名视图(不含点号)
  133. */
  134. std::string_view get_extension(std::string_view filename) {
  135. // 查找最后一个点号
  136. size_t dot_pos = filename.rfind('.');
  137. if (dot_pos == std::string_view::npos) {
  138. return {}; // 没有扩展名,返回空视图
  139. }
  140. // 返回点号之后的部分(零拷贝)
  141. return filename.substr(dot_pos + 1);
  142. }
  143. /**
  144. * @brief 【典型场景7】解析键值对
  145. * @param kv_pair 格式为 "key=value" 的字符串
  146. * @param key 输出参数:存储键的视图
  147. * @param value 输出参数:存储值的视图
  148. * @return 是否解析成功
  149. */
  150. bool parse_key_value(std::string_view kv_pair, std::string_view& key, std::string_view& value) {
  151. size_t eq_pos = kv_pair.find('=');
  152. if (eq_pos == std::string_view::npos) {
  153. return false;
  154. }
  155. // 直接使用视图,不复制数据
  156. key = trim(kv_pair.substr(0, eq_pos));
  157. value = trim(kv_pair.substr(eq_pos + 1));
  158. return !key.empty(); // 键不能为空
  159. }
  160. /**
  161. * @brief 【典型场景8】灵活的函数参数
  162. * @param data 可以接受任何字符串类型(字面量、std::string、C字符串)
  163. * @return 字符串长度
  164. *
  165. * @note 使用 string_view 作为函数参数是最佳实践:
  166. * 1. 不需要重载多个版本
  167. * 2. 避免不必要的字符串拷贝
  168. * 3. 调用者无需修改代码
  169. */
  170. size_t get_length(std::string_view data) {
  171. // 这里只读取,不修改,完美发挥 string_view 的优势
  172. return data.size();
  173. }
  174. // ==================== 主函数 ====================
  175. int main() {
  176. std::cout << "\n";
  177. std::cout << "╔══════════════════════════════════════════════════════════════╗\n";
  178. std::cout << "║ std::string_view 核心优势与典型场景演示 ║\n";
  179. std::cout << "╚══════════════════════════════════════════════════════════════╝\n";
  180. // ==================== 一、核心优势演示 ====================
  181. std::cout << "\n【一、核心优势】\n";
  182. std::cout << "─────────────────────────────────────────────────────────────\n";
  183. // 优势1:极轻量(只占16字节)
  184. std::cout << "\n✓ 优势1:轻量级(仅16字节)\n";
  185. std::cout << " sizeof(std::string_view) = " << sizeof(std::string_view) << " 字节\n";
  186. std::cout << " sizeof(std::string) = " << sizeof(std::string) << " 字节\n";
  187. // 优势2:零拷贝构造
  188. std::cout << "\n✓ 优势2:零拷贝构造\n";
  189. std::string original = "这是一个很长的字符串,构造视图时不会复制任何字符数据";
  190. std::string_view view = original; // 只复制指针和长度,不复制字符
  191. std::cout << " 原字符串地址: " << (void*)original.data() << "\n";
  192. std::cout << " 视图存储地址: " << (void*)view.data() << " (指向同一块内存)\n";
  193. // 优势3:零拷贝子串
  194. std::cout << "\n✓ 优势3:零拷贝子串操作\n";
  195. std::string_view text = "The quick brown fox jumps over the lazy dog";
  196. std::string_view sub = text.substr(10, 9); // "brown fox"
  197. std::cout << " 原字符串: '" << text << "'\n";
  198. std::cout << " 子串视图: '" << sub << "'\n";
  199. std::cout << " 地址比较: 原:" << (void*)text.data()
  200. << " vs 子:" << (void*)sub.data() << " (相同)\n";
  201. std::cout << " 说明: substr 只记录了新的起始位置,没有复制数据\n";
  202. // ==================== 二、典型应用场景 ====================
  203. std::cout << "\n【二、典型应用场景】\n";
  204. std::cout << "─────────────────────────────────────────────────────────────\n";
  205. // 场景1:字符串分割
  206. std::cout << "\n✓ 场景1:高效字符串分割\n";
  207. std::string csv = "apple,banana,cherry,date,elderberry";
  208. std::cout << " CSV 数据: " << csv << "\n";
  209. auto fruits = split(csv, ',');
  210. std::cout << " 分割结果(零拷贝视图): ";
  211. for (size_t i = 0; i < fruits.size(); ++i) {
  212. std::cout << "'" << fruits[i] << "'";
  213. if (i < fruits.size() - 1) std::cout << ", ";
  214. }
  215. std::cout << "\n";
  216. std::cout << " 注意:所有子串视图都直接指向原字符串内存\n";
  217. // 场景2:去除首尾空格
  218. std::cout << "\n✓ 场景2:非破坏性空格去除\n";
  219. std::string messy = " \t Hello, World! \n\r";
  220. std::string_view trimmed = trim(messy);
  221. std::cout << " 原字符串: '" << messy << "'\n";
  222. std::cout << " 去除空格: '" << trimmed << "'\n";
  223. std::cout << " 原字符串未被修改: '" << messy << "'\n";
  224. // 场景3:前缀/后缀检查
  225. std::cout << "\n✓ 场景3:高效前缀/后缀检查\n";
  226. std::string filename = "document.pdf";
  227. std::string_view fv = filename;
  228. std::cout << " 文件名: " << fv << "\n";
  229. std::cout << " 以 'doc' 开头? " << (starts_with(fv, "doc") ? "✓ 是" : "✗ 否") << "\n";
  230. std::cout << " 以 '.pdf' 结尾? " << (ends_with(fv, ".pdf") ? "✓ 是" : "✗ 否") << "\n";
  231. // 场景4:文件扩展名提取
  232. std::cout << "\n✓ 场景4:文件扩展名提取\n";
  233. std::vector<std::string> files = { "image.jpg", "document.pdf", "script.py", "Makefile" };
  234. for (const auto& file : files) {
  235. std::string_view ext = get_extension(file);
  236. std::cout << " " << file << " → 扩展名: " << (ext.empty() ? "(无)" : ext.data()) << "\n";
  237. }
  238. // 场景5:解析键值对
  239. std::cout << "\n✓ 场景5:解析键值对\n";
  240. std::vector<std::string> kv_strings = {
  241. "name=John Doe",
  242. " age = 25 ",
  243. "email=john@example.com"
  244. };
  245. for (const auto& kv : kv_strings) {
  246. std::string_view key, value;
  247. if (parse_key_value(kv, key, value)) {
  248. std::cout << " 解析: |" << key << "| → |" << value << "|\n";
  249. }
  250. }
  251. // 场景6:大小写不敏感比较
  252. std::cout << "\n✓ 场景6:大小写不敏感比较\n";
  253. std::string_view hello1 = "Hello";
  254. std::string_view hello2 = "HELLO";
  255. std::string_view hello3 = "World";
  256. std::cout << " 'Hello' vs 'HELLO': " << (iequals(hello1, hello2) ? "相等" : "不等") << "\n";
  257. std::cout << " 'Hello' vs 'World': " << (iequals(hello1, hello3) ? "相等" : "不等") << "\n";
  258. // 场景7:灵活的字符串类型兼容性
  259. std::cout << "\n✓ 场景7:统一处理多种字符串类型\n";
  260. const char* c_str = "C字符串";
  261. std::string cpp_str = "C++字符串";
  262. std::string_view view_str = "字符串视图";
  263. // 同一个函数可以接受所有类型
  264. std::cout << " C字符串长度: " << get_length(c_str) << "\n";
  265. std::cout << " C++字符串长度: " << get_length(cpp_str) << "\n";
  266. std::cout << " 字符串视图长度: " << get_length(view_str) << "\n";
  267. // ==================== 三、输出格式演示 ====================
  268. std::cout << "\n【三、多种遍历方式】\n";
  269. std::cout << "─────────────────────────────────────────────────────────────\n";
  270. std::string_view demo = "C++17";
  271. print_info(demo, "遍历示例");
  272. std::cout << "\n 遍历方式1(下标): ";
  273. for (size_t i = 0; i < demo.size(); ++i) {
  274. std::cout << demo[i] << " ";
  275. }
  276. std::cout << "\n 遍历方式2(范围for): ";
  277. for (char ch : demo) {
  278. std::cout << ch << " ";
  279. }
  280. std::cout << "\n 遍历方式3(迭代器): ";
  281. for (auto it = demo.begin(); it != demo.end(); ++it) {
  282. std::cout << *it << " ";
  283. }
  284. std::cout << "\n";
  285. std::cout << "\n═══════════════════════════════════════════════════════════════\n";
  286. std::cout << "✅ 演示完成!string_view 的核心优势:\n";
  287. std::cout << " • 零拷贝(不分配内存)\n";
  288. std::cout << " • 轻量级(只占16字节)\n";
  289. std::cout << " • 兼容多种字符串类型\n";
  290. std::cout << " • 高效子串操作\n";
  291. std::cout << " • 作为函数参数的最佳实践\n";
  292. std::cout << "═══════════════════════════════════════════════════════════════\n";
  293. return 0;
  294. }
复制代码
  1. ╔══════════════════════════════════════════════════════════════╗
  2. ║ std::string_view 核心优势与典型场景演示 ║
  3. ╚══════════════════════════════════════════════════════════════╝
  4. 【一、核心优势】
  5. ─────────────────────────────────────────────────────────────
  6. ✓ 优势1:轻量级(仅16字节)
  7. sizeof(std::string_view) = 16 字节
  8. sizeof(std::string) = 40 字节
  9. ✓ 优势2:零拷贝构造
  10. 原字符串地址: 000001E96A6DBE30
  11. 视图存储地址: 000001E96A6DBE30 (指向同一块内存)
  12. ✓ 优势3:零拷贝子串操作
  13. 原字符串: 'The quick brown fox jumps over the lazy dog'
  14. 子串视图: 'brown fox'
  15. 地址比较: 原:00007FF6F8803950 vs 子:00007FF6F880395A (相同)
  16. 说明: substr 只记录了新的起始位置,没有复制数据
  17. 【二、典型应用场景】
  18. ─────────────────────────────────────────────────────────────
  19. ✓ 场景1:高效字符串分割
  20. CSV 数据: apple,banana,cherry,date,elderberry
  21. 分割结果(零拷贝视图): 'apple', 'banana', 'cherry', 'date', 'elderberry'
  22. 注意:所有子串视图都直接指向原字符串内存
  23. ✓ 场景2:非破坏性空格去除
  24. 原字符串: ' Hello, World!
  25. '
  26. 去除空格: 'Hello, World!'
  27. 原字符串未被修改: ' Hello, World!
  28. '
  29. ✓ 场景3:高效前缀/后缀检查
  30. 文件名: document.pdf
  31. 以 'doc' 开头? ✓ 是
  32. 以 '.pdf' 结尾? ✓ 是
  33. ✓ 场景4:文件扩展名提取
  34. image.jpg → 扩展名: jpg
  35. document.pdf → 扩展名: pdf
  36. script.py → 扩展名: py
  37. Makefile → 扩展名: (无)
  38. ✓ 场景5:解析键值对
  39. 解析: |name| → |John Doe|
  40. 解析: |age| → |25|
  41. 解析: |email| → |john@example.com|
  42. ✓ 场景6:大小写不敏感比较
  43. 'Hello' vs 'HELLO': 相等
  44. 'Hello' vs 'World': 不等
  45. ✓ 场景7:统一处理多种字符串类型
  46. C字符串长度: 10
  47. C++字符串长度: 12
  48. 字符串视图长度: 15
  49. 【三、多种遍历方式】
  50. ─────────────────────────────────────────────────────────────
  51. ┌─────────────────────────────────────────┐
  52. │ 遍历示例
  53. ├─────────────────────────────────────────┤
  54. │ 内容: 'C++17'
  55. │ 长度: 5 字符
  56. │ 是否为空: 否
  57. │ sizeof(sv): 16 字节
  58. └─────────────────────────────────────────┘
  59. 遍历方式1(下标): C + + 1 7
  60. 遍历方式2(范围for): C + + 1 7
  61. 遍历方式3(迭代器): C + + 1 7
  62. ═══════════════════════════════════════════════════════════════
  63. ✅ 演示完成!string_view 的核心优势:
  64. • 零拷贝(不分配内存)
  65. • 轻量级(只占16字节)
  66. • 兼容多种字符串类型
  67. • 高效子串操作
  68. • 作为函数参数的最佳实践
  69. ═══════════════════════════════════════════════════════════════
  70. E:\myLearn\StringView\x64\Debug\StringView.exe (进程 25764)已退出,代码为 0 (0x0)。
  71. 按任意键关闭此窗口. . .
复制代码

⚡ 性能对比测试

  1. /**
  2. * @file string_view_performance_demo.cpp
  3. * @brief std::string_view 性能对比测试
  4. * @author Code Reviewer
  5. * @date 2026-05-12
  6. *
  7. * @details 本文件通过基准测试对比 string_view 和传统 string 的性能差异:
  8. * 1. 构造性能对比(拷贝 vs 视图)
  9. * 2. 子串操作性能对比
  10. * 3. 函数参数传递性能对比
  11. * 4. 字符串分割性能对比
  12. * 5. 大规模数据处理测试
  13. *
  14. * 编译运行:
  15. * g++ -std=c++17 -O2 string_view_performance_demo.cpp -o perf_demo
  16. * ./perf_demo
  17. */
  18. #include <iostream>
  19. #include <string>
  20. #include <string_view>
  21. #include <vector>
  22. #include <chrono>
  23. #include <algorithm>
  24. #include <numeric>
  25. #include <iomanip>
  26. /**
  27. * @brief 高性能计时器类
  28. * @details 用于精确测量代码块执行时间
  29. */
  30. class Timer {
  31. private:
  32. std::chrono::high_resolution_clock::time_point start_time;
  33. std::string name;
  34. public:
  35. /**
  36. * @brief 构造函数,开始计时
  37. * @param test_name 测试名称
  38. */
  39. Timer(const std::string& test_name) : name(test_name) {
  40. start_time = std::chrono::high_resolution_clock::now();
  41. }
  42. /**
  43. * @brief 析构函数,自动输出耗时
  44. */
  45. ~Timer() {
  46. auto end_time = std::chrono::high_resolution_clock::now();
  47. auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
  48. std::cout << " ⏱️ " << name << ": " << std::setw(8) << duration.count() << " 微秒\n";
  49. }
  50. /**
  51. * @brief 手动获取耗时(不结束计时)
  52. */
  53. long long elapsed() const {
  54. auto now = std::chrono::high_resolution_clock::now();
  55. return std::chrono::duration_cast<std::chrono::microseconds>(now - start_time).count();
  56. }
  57. };
  58. /**
  59. * @brief 使用 std::string 进行字符串分割(传统方式)
  60. * @param str 字符串
  61. * @param delimiter 分隔符
  62. * @return 分割后的子串列表(每个子串都有独立的内存)
  63. *
  64. * @note 这种方式会为每个子串分配内存并复制字符数据
  65. */
  66. std::vector<std::string> split_string(const std::string& str, char delimiter) {
  67. std::vector<std::string> result;
  68. size_t start = 0;
  69. while (start < str.size()) {
  70. size_t end = str.find(delimiter, start);
  71. if (end == std::string::npos) {
  72. result.push_back(str.substr(start)); // 复制数据
  73. break;
  74. }
  75. result.push_back(str.substr(start, end - start)); // 复制数据
  76. start = end + 1;
  77. }
  78. return result;
  79. }
  80. /**
  81. * @brief 使用 std::string_view 进行字符串分割(现代方式)
  82. * @param str 字符串视图
  83. * @param delimiter 分隔符
  84. * @return 分割后的子串视图列表(零拷贝)
  85. *
  86. * @note 这种方式不复制任何字符数据,只记录位置和长度
  87. */
  88. std::vector<std::string_view> split_string_view(std::string_view str, char delimiter) {
  89. std::vector<std::string_view> result;
  90. size_t start = 0;
  91. while (start < str.size()) {
  92. size_t end = str.find(delimiter, start);
  93. if (end == std::string_view::npos) {
  94. result.push_back(str.substr(start)); // 零拷贝
  95. break;
  96. }
  97. result.push_back(str.substr(start, end - start)); // 零拷贝
  98. start = end + 1;
  99. }
  100. return result;
  101. }
  102. /**
  103. * @brief 性能测试1:构造性能对比
  104. * @param iterations 迭代次数
  105. *
  106. * @details 对比创建 1000 个字符串子串的耗时差异
  107. * 字符串视图的构造只是复制指针,远快于实际复制字符数据
  108. */
  109. void test_construction_performance(int iterations = 1000) {
  110. std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
  111. std::cout << "│ 测试1:构造性能对比(创建 " << iterations << " 个子串) │\n";
  112. std::cout << "└─────────────────────────────────────────────────────────────┘\n";
  113. // 准备一个长字符串
  114. std::string long_string(10000, 'A');
  115. for (int i = 0; i < 100; ++i) {
  116. long_string[i * 100] = ','; // 添加分隔符
  117. }
  118. // 测试 std::string 构造(会复制字符)
  119. {
  120. Timer timer("std::string 构造(复制 " + std::to_string(iterations) + " 次)");
  121. for (int i = 0; i < iterations; ++i) {
  122. std::string copy = long_string; // 复制整个字符串
  123. (void)copy;
  124. }
  125. }
  126. // 测试 std::string_view 构造(只复制指针)
  127. {
  128. Timer timer("std::string_view 构造(" + std::to_string(iterations) + " 次)");
  129. for (int i = 0; i < iterations; ++i) {
  130. std::string_view view = long_string; // 只复制16字节
  131. (void)view;
  132. }
  133. }
  134. }
  135. /**
  136. * @brief 性能测试2:子串操作性能对比
  137. * @param iterations 迭代次数
  138. *
  139. * @details 对比提取 10000 个子串的耗时
  140. * string::substr 会复制数据,string_view::substr 只调整视图边界
  141. */
  142. void test_substr_performance(int iterations = 10000) {
  143. std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
  144. std::cout << "│ 测试2:子串操作性能对比(提取 " << iterations << " 个子串) │\n";
  145. std::cout << "└─────────────────────────────────────────────────────────────┘\n";
  146. std::string data = "https://www.example.com/very/long/path/to/resource";
  147. // 测试 std::string::substr(复制数据)
  148. {
  149. Timer timer("std::string::substr(复制数据)");
  150. for (int i = 0; i < iterations; ++i) {
  151. std::string sub = data.substr(8, 10); // 复制 "www.exampl"
  152. (void)sub;
  153. }
  154. }
  155. // 测试 std::string_view::substr(零拷贝)
  156. {
  157. Timer timer("std::string_view::substr(零拷贝)");
  158. std::string_view view = data;
  159. for (int i = 0; i < iterations; ++i) {
  160. std::string_view sub = view.substr(8, 10); // 只调整边界
  161. (void)sub;
  162. }
  163. }
  164. }
  165. /**
  166. * @brief 测试3:字符串分割性能对比
  167. * @param iterations 重复测试次数
  168. *
  169. * @details 对比传统方式和视图方式在字符串分割上的性能差异
  170. * 处理 1MB 的 CSV 数据,分割成约 200,000 个字段
  171. */
  172. void test_split_performance(int iterations = 10) {
  173. std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
  174. std::cout << "│ 测试3:字符串分割性能对比(1MB CSV 数据,重复 " << iterations << " 次) │\n";
  175. std::cout << "└─────────────────────────────────────────────────────────────┘\n";
  176. // 生成测试数据:1MB 的 CSV 格式字符串
  177. std::string csv_data;
  178. csv_data.reserve(1024 * 1024);
  179. for (int i = 0; i < 100000; ++i) {
  180. csv_data += "field" + std::to_string(i) + ",";
  181. }
  182. csv_data.back() = '\n'; // 替换最后一个逗号
  183. // 测试 std::string 分割(会复制每个字段)
  184. {
  185. long long total_time = 0;
  186. for (int i = 0; i < iterations; ++i) {
  187. auto start = std::chrono::high_resolution_clock::now();
  188. auto result = split_string(csv_data, ',');
  189. auto end = std::chrono::high_resolution_clock::now();
  190. total_time += std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
  191. // 验证结果
  192. if (i == 0) {
  193. std::cout << " 📊 std::string 分割: " << result.size() << " 个字段\n";
  194. }
  195. }
  196. std::cout << " ⏱️ std::string 分割(平均): "
  197. << std::setw(8) << total_time / iterations << " 微秒\n";
  198. }
  199. // 测试 std::string_view 分割(零拷贝)
  200. {
  201. long long total_time = 0;
  202. for (int i = 0; i < iterations; ++i) {
  203. auto start = std::chrono::high_resolution_clock::now();
  204. auto result = split_string_view(csv_data, ',');
  205. auto end = std::chrono::high_resolution_clock::now();
  206. total_time += std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
  207. if (i == 0) {
  208. std::cout << " 📊 string_view 分割: " << result.size() << " 个视图\n";
  209. }
  210. }
  211. std::cout << " ⏱️ string_view 分割(平均): "
  212. << std::setw(8) << total_time / iterations << " 微秒\n";
  213. }
  214. }
  215. /**
  216. * @brief 测试4:函数参数传递性能对比
  217. * @param iterations 迭代次数
  218. *
  219. * @details 对比传递 string 引用 vs string_view 的性能
  220. * string_view 更轻量,且不会引起不必要的分配
  221. */
  222. void test_parameter_passing(int iterations = 1000000) {
  223. std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
  224. std::cout << "│ 测试4:函数参数传递性能对比(调用 " << iterations << " 次) │\n";
  225. std::cout << "└─────────────────────────────────────────────────────────────┘\n";
  226. std::string data = "这是一个测试字符串,用于性能对比";
  227. // 定义测试函数
  228. auto process_by_const_ref = [](const std::string& s) -> size_t {
  229. return s.size();
  230. };
  231. auto process_by_string_view = [](std::string_view sv) -> size_t {
  232. return sv.size();
  233. };
  234. // 测试 const std::string& 传递
  235. {
  236. Timer timer("const std::string& 传递");
  237. size_t sum = 0;
  238. for (int i = 0; i < iterations; ++i) {
  239. sum += process_by_const_ref(data);
  240. }
  241. (void)sum;
  242. }
  243. // 测试 std::string_view 传递
  244. {
  245. Timer timer("std::string_view 传递");
  246. size_t sum = 0;
  247. for (int i = 0; i < iterations; ++i) {
  248. sum += process_by_string_view(data);
  249. }
  250. (void)sum;
  251. }
  252. // 测试从字面量传递的情况(string_view 优势更明显)
  253. {
  254. Timer timer("string_view + 字面量(避免临时 string)");
  255. size_t sum = 0;
  256. for (int i = 0; i < iterations; ++i) {
  257. sum += process_by_string_view("Hello, World!");
  258. }
  259. (void)sum;
  260. }
  261. }
  262. /**
  263. * @brief 测试5:大规模数据处理性能对比
  264. * @param data_size_mb 数据大小(MB)
  265. *
  266. * @details 处理 10MB 的日志数据,模拟实际场景
  267. * 统计以 'ERROR' 开头的行数
  268. */
  269. void test_large_data_processing(int data_size_mb = 10) {
  270. std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
  271. std::cout << "│ 测试5:大规模数据处理(" << data_size_mb << " MB 日志数据) │\n";
  272. std::cout << "└─────────────────────────────────────────────────────────────┘\n";
  273. // 生成测试日志数据
  274. std::string log_data;
  275. log_data.reserve(data_size_mb * 1024 * 1024);
  276. for (int i = 0; i < 500000; ++i) {
  277. if (i % 100 == 0) {
  278. log_data += "ERROR: Something went wrong at step " + std::to_string(i) + "\n";
  279. } else {
  280. log_data += "INFO: Processing step " + std::to_string(i) + "\n";
  281. }
  282. }
  283. // 使用 std::string 的方式(复制并分割)
  284. {
  285. Timer timer("std::string 方式(复制行数据)");
  286. int error_count = 0;
  287. size_t start = 0;
  288. std::string data_copy = log_data; // 先复制整个数据
  289. while (start < data_copy.size()) {
  290. size_t end = data_copy.find('\n', start);
  291. if (end == std::string::npos) break;
  292. std::string line = data_copy.substr(start, end - start); // 复制每一行
  293. if (line.size() >= 5 && line.substr(0, 5) == "ERROR") {
  294. ++error_count;
  295. }
  296. start = end + 1;
  297. }
  298. std::cout << " 📊 找到 " << error_count << " 条 ERROR 日志\n";
  299. }
  300. // 使用 std::string_view 的方式(只读视图)
  301. {
  302. Timer timer("string_view 方式(零拷贝)");
  303. int error_count = 0;
  304. size_t start = 0;
  305. std::string_view view = log_data; // 不复制
  306. while (start < view.size()) {
  307. size_t end = view.find('\n', start);
  308. if (end == std::string_view::npos) break;
  309. std::string_view line = view.substr(start, end - start); // 零拷贝视图
  310. if (line.size() >= 5 && line.substr(0, 5) == "ERROR") {
  311. ++error_count;
  312. }
  313. start = end + 1;
  314. }
  315. std::cout << " 📊 找到 " << error_count << " 条 ERROR 日志\n";
  316. }
  317. }
  318. /**
  319. * @brief 显示速度对比总结
  320. */
  321. void show_summary() {
  322. std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
  323. std::cout << "║ 性能测试总结 ║\n";
  324. std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
  325. std::cout << "║ std::string_view 的核心性能优势: ║\n";
  326. std::cout << "║ ║\n";
  327. std::cout << "║ 1. 零拷贝构造 → 只复制16字节,与字符串长度无关 ║\n";
  328. std::cout << "║ 2. 零拷贝子串 → O(1) 时间,不随子串长度增加 ║\n";
  329. std::cout << "║ 3. 减少内存分配 → 避免频繁的 malloc/free ║\n";
  330. std::cout << "║ 4. 更好的缓存局部性 → 数据在原位置,不产生内存碎片 ║\n";
  331. std::cout << "║ 5. 无额外内存开销 → 适合嵌入式/高性能环境 ║\n";
  332. std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
  333. std::cout << "║ 适用场景: ║\n";
  334. std::cout << "║ • 函数参数(避免不必要的拷贝) ║\n";
  335. std::cout << "║ • 字符串解析(配置文件、日志、网络协议) ║\n";
  336. std::cout << "║ • 大规模文本处理(日志分析、数据清洗) ║\n";
  337. std::cout << "║ • 只读访问场景(查找、比较、过滤) ║\n";
  338. std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
  339. std::cout << "║ 注意事项: ║\n";
  340. std::cout << "║ • 不保证以 '\\0' 结尾,使用 .data() 需谨慎 ║\n";
  341. std::cout << "║ • 不拥有数据,需确保底层数据生命周期更长 ║\n";
  342. std::cout << "║ • 修改原字符串会使视图失效 ║\n";
  343. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  344. }
  345. // ==================== 主函数 ====================
  346. int main() {
  347. std::cout << "\n";
  348. std::cout << "╔═══════════════════════════════════════════════════════════════╗\n";
  349. std::cout << "║ std::string_view 性能对比测试 ║\n";
  350. std::cout << "║ 编译优化: -O2 ║\n";
  351. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  352. // 运行所有性能测试
  353. test_construction_performance(1000);
  354. test_substr_performance(10000);
  355. test_split_performance(10);
  356. test_parameter_passing(1000000);
  357. test_large_data_processing(5);
  358. // 显示总结
  359. show_summary();
  360. return 0;
  361. }
复制代码
  1. ╔═══════════════════════════════════════════════════════════════╗
  2. ║ std::string_view 性能对比测试 ║
  3. ║ 编译优化: -O2 ║
  4. ╚═══════════════════════════════════════════════════════════════╝
  5. ┌─────────────────────────────────────────────────────────────┐
  6. │ 测试1:构造性能对比(创建 1000 个子串) │
  7. └─────────────────────────────────────────────────────────────┘
  8. ⏱️ std::string 构造(复制 1000 次): 813 微秒
  9. ⏱️ std::string_view 构造(1000 次): 16 微秒
  10. ┌─────────────────────────────────────────────────────────────┐
  11. │ 测试2:子串操作性能对比(提取 10000 个子串) │
  12. └─────────────────────────────────────────────────────────────┘
  13. ⏱️ std::string::substr(复制数据): 3824 微秒
  14. ⏱️ std::string_view::substr(零拷贝): 342 微秒
  15. ┌─────────────────────────────────────────────────────────────┐
  16. │ 测试3:字符串分割性能对比(1MB CSV 数据,重复 10 次) │
  17. └─────────────────────────────────────────────────────────────┘
  18. 📊 std::string 分割: 100000 个字段
  19. ⏱️ std::string 分割(平均): 188841 微秒
  20. 📊 string_view 分割: 100000 个视图
  21. ⏱️ string_view 分割(平均): 11620 微秒
  22. ┌─────────────────────────────────────────────────────────────┐
  23. │ 测试4:函数参数传递性能对比(调用 1000000 次) │
  24. └─────────────────────────────────────────────────────────────┘
  25. ⏱️ const std::string& 传递: 3061 微秒
  26. ⏱️ std::string_view 传递: 18145 微秒
  27. ⏱️ string_view + 字面量(避免临时 string): 13493 微秒
  28. ┌─────────────────────────────────────────────────────────────┐
  29. │ 测试5:大规模数据处理(5 MB 日志数据) │
  30. └─────────────────────────────────────────────────────────────┘
  31. 📊 找到 5000 条 ERROR 日志
  32. ⏱️ std::string 方式(复制行数据): 589907 微秒
  33. 📊 找到 5000 条 ERROR 日志
  34. ⏱️ string_view 方式(零拷贝): 54217 微秒
  35. ╔═══════════════════════════════════════════════════════════════╗
  36. ║ 性能测试总结 ║
  37. ╠═══════════════════════════════════════════════════════════════╣
  38. ║ std::string_view 的核心性能优势: ║
  39. ║ ║
  40. ║ 1. 零拷贝构造 → 只复制16字节,与字符串长度无关 ║
  41. ║ 2. 零拷贝子串 → O(1) 时间,不随子串长度增加 ║
  42. ║ 3. 减少内存分配 → 避免频繁的 malloc/free ║
  43. ║ 4. 更好的缓存局部性 → 数据在原位置,不产生内存碎片 ║
  44. ║ 5. 无额外内存开销 → 适合嵌入式/高性能环境 ║
  45. ╠═══════════════════════════════════════════════════════════════╣
  46. ║ 适用场景: ║
  47. ║ • 函数参数(避免不必要的拷贝) ║
  48. ║ • 字符串解析(配置文件、日志、网络协议) ║
  49. ║ • 大规模文本处理(日志分析、数据清洗) ║
  50. ║ • 只读访问场景(查找、比较、过滤) ║
  51. ╠═══════════════════════════════════════════════════════════════╣
  52. ║ 注意事项: ║
  53. ║ • 不保证以 '\0' 结尾,使用 .data() 需谨慎 ║
  54. ║ • 不拥有数据,需确保底层数据生命周期更长 ║
  55. ║ • 修改原字符串会使视图失效 ║
  56. ╚═══════════════════════════════════════════════════════════════╝
  57. E:\myLearn\StringView\x64\Debug\StringView.exe (进程 24704)已退出,代码为 0 (0x0)。
  58. 按任意键关闭此窗口. . .
复制代码

⚠️ 注意事项

  • 生命周期管理:std::string_view 不管理底层字符串的生命周期,string_view 对象的生命周期不能超过指向的字符串。如果你需要传递一个 string_view,必须确保它指向的字符串在整个使用期间都有效。
  • \0 结尾问题:string_view 不一定以 \0 结尾,如果你需要传递给一个以 \0 结尾的 C API(如 fopen),必须确保它是 \0 结尾的,或者手动创建一个以 \0 结尾的副本(例如使用 std::string(sv))。
  • 底层字符串修改:如果底层字符串被修改或销毁,string_view 会变成 “悬空” 状态,访问它会导致未定义行为。
  1. #define _CRT_SECURE_NO_WARNINGS
  2. /**
  3. * @file string_view_lifetime_demo.cpp
  4. * @brief std::string_view 生命周期管理、\0结尾、底层修改 三大注意事项验证
  5. * @author Code Reviewer
  6. * @date 2026-05-12
  7. *
  8. * @details 本文件演示 std::string_view 的三个关键陷阱:
  9. * 1. 生命周期管理:string_view 不拥有数据,不能比源字符串活得久
  10. * 2. \0 结尾问题:string_view 不一定以 \0 结尾,传给 C API 需小心
  11. * 3. 底层字符串修改:修改原字符串会使 string_view 失效
  12. *
  13. * 编译运行:
  14. * g++ -std=c++17 -O2 string_view_lifetime_demo.cpp -o demo
  15. * ./demo
  16. */
  17. #include <iostream>
  18. #include <string>
  19. #include <string_view>
  20. #include <vector>
  21. #include <cstring>
  22. #include <fstream>
  23. // ==================== 辅助函数 ====================
  24. /**
  25. * @brief 安全打印 string_view(带边界标识)
  26. */
  27. void safe_print(std::string_view sv, const std::string& title = "") {
  28. if (!title.empty()) {
  29. std::cout << "\n【" << title << "】\n";
  30. }
  31. std::cout << " 内容: '" << sv << "'\n";
  32. std::cout << " 长度: " << sv.size() << "\n";
  33. std::cout << " 是否以\\0结尾: " << (sv.data()[sv.size()] == '\0' ? "是" : "否") << "\n";
  34. }
  35. // ==================== 注意事项1:生命周期管理 ====================
  36. /**
  37. * @brief 危险示例:返回局部字符串的 string_view
  38. * @return 悬空的 string_view(未定义行为)
  39. *
  40. * ⚠️ 这是典型的错误用法!
  41. * 局部字符串在函数返回时被销毁,返回的 string_view 变成悬空引用。
  42. */
  43. std::string_view dangerous_return() {
  44. std::string local = "这个字符串马上会被销毁";
  45. std::string_view sv = local;
  46. std::cout << " [函数内部] sv 有效: '" << sv << "'\n";
  47. // ⚠️ 返回 sv,但 local 即将被销毁
  48. return sv; // 危险!返回悬空引用
  49. }
  50. /**
  51. * @brief 正确示例:返回拥有所有权的字符串
  52. * @return 安全的 std::string
  53. *
  54. * ✅ 如果需要返回字符串,应该返回 std::string 而不是 string_view
  55. */
  56. std::string safe_return() {
  57. std::string local = "这个字符串会安全返回";
  58. return local; // 移动语义,高效且安全
  59. }
  60. /**
  61. * @brief 正确示例:从已有数据创建 string_view
  62. * @param data 生命周期足够长的数据
  63. * @return 安全的 string_view
  64. *
  65. * ✅ string_view 只作为观察者,不拥有数据
  66. */
  67. std::string_view safe_parameter(const std::string& data) {
  68. return data; // 安全:调用者保证 data 生命周期足够长
  69. }
  70. /**
  71. * @brief 验证生命周期管理的正确与错误用法
  72. */
  73. void verify_lifetime() {
  74. std::cout << "\n╔═══════════════════════════════════════════════════════════════╗\n";
  75. std::cout << "║ 注意事项1:生命周期管理 ║\n";
  76. std::cout << "║ string_view 不管理生命周期,不能比源字符串活得久 ║\n";
  77. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  78. // ✅ 正确用法1:string_view 生命周期在源字符串内
  79. std::cout << "\n✅ 正确示例:生命周期内使用\n";
  80. {
  81. std::string data = "持久存在的字符串";
  82. std::string_view view = data; // view 指向 data
  83. std::cout << " view: '" << view << "'\n";
  84. // data 和 view 都在作用域内,安全
  85. std::cout << " → 安全:view 和 data 在同一作用域\n";
  86. }
  87. // ✅ 正确用法2:作为函数参数
  88. std::cout << "\n✅ 正确示例:作为函数参数\n";
  89. {
  90. std::string data = "作为参数传递";
  91. auto process = [](std::string_view sv) {
  92. std::cout << " 函数内接收到: '" << sv << "'\n";
  93. };
  94. process(data); // 调用者保证 data 在函数执行期间有效
  95. std::cout << " → 安全:调用者保证生命周期\n";
  96. }
  97. // ❌ 危险示例:返回局部字符串的视图
  98. std::cout << "\n❌ 危险示例:返回局部变量的 view\n";
  99. std::cout << " ⚠️ 下面的代码会触发未定义行为,已被注释\n";
  100. // std::string_view dangling = dangerous_return(); // 未定义行为!
  101. // std::cout << dangling << "\n"; // 可能崩溃或输出乱码
  102. std::cout << " → 危险:view 指向已销毁的内存,访问会导致未定义行为\n";
  103. // ✅ 正确示例:如果需要返回,返回 std::string
  104. std::cout << "\n✅ 正确做法:需要返回时使用 std::string\n";
  105. {
  106. std::string result = safe_return();
  107. std::cout << " 返回的字符串: '" << result << "'\n";
  108. std::cout << " → 安全:返回拥有所有权的 std::string\n";
  109. }
  110. // 可视化生命周期问题
  111. std::cout << "\n📊 生命周期图解:\n";
  112. std::cout << " ┌─────────────────────────────────────────────────────────┐\n";
  113. std::cout << " │ std::string data = "Hello"; // data 拥有内存 │\n";
  114. std::cout << " │ std::string_view sv = data; // sv 指向 data │\n";
  115. std::cout << " │ // data 和 sv 都在作用域内 // ✅ 安全 │\n";
  116. std::cout << " │ } // data 被销毁 // ❌ sv 变成悬空 │\n";
  117. std::cout << " └─────────────────────────────────────────────────────────┘\n";
  118. }
  119. // ==================== 注意事项2:\0 结尾问题 ====================
  120. /**
  121. * @brief 演示 string_view 不一定以 \0 结尾
  122. *
  123. * string_view 只存储指针和长度,不保证结尾有 null 终止符。
  124. * 这对需要 \0 结尾的 C API(如 fopen, strcmp, printf %s)是危险的。
  125. */
  126. void verify_null_termination() {
  127. std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
  128. std::cout << "║ 注意事项2:\\0 结尾问题 ║\n";
  129. std::cout << "║ string_view 不保证以 \\0 结尾 ║\n";
  130. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  131. // 情况1:从字符串字面量构造(有 \0 结尾)
  132. std::cout << "\n📌 情况1:从字符串字面量构造\n";
  133. std::string_view sv1 = "Hello";
  134. safe_print(sv1, "字符串字面量");
  135. std::cout << " 说明:字面量在编译期自动添加 \\0,所以有结尾符\n";
  136. // 情况2:从 std::string 构造(有 \0 结尾,C++11 起保证)
  137. std::cout << "\n📌 情况2:从 std::string 构造\n";
  138. std::string str = "World";
  139. std::string_view sv2 = str;
  140. safe_print(sv2, "std::string");
  141. std::cout << " 说明:std::string 保证 \\0 结尾(C++11 起)\n";
  142. // 情况3:从子串构造(⚠️ 不一定有 \0 结尾!)
  143. std::cout << "\n📌 情况3:从子串构造(⚠️ 危险场景)\n";
  144. std::string long_str = "Hello, World!";
  145. std::string_view sv3 = long_str.substr(0, 5); // 取 "Hello"
  146. safe_print(sv3, "substr 子串");
  147. std::cout << " ⚠️ 危险:substr 创建的视图不一定有 \\0 结尾!\n";
  148. std::cout << " 📌 原因:substr 只调整指针和长度,不复制数据\n";
  149. // 情况4:从字符数组的中间位置
  150. std::cout << "\n📌 情况4:指向字符数组中间\n";
  151. const char* cstr = "Programming";
  152. std::string_view sv4(cstr + 3, 6); // 从索引3开始,取6个字符 → "grammi"
  153. safe_print(sv4, "指向中间");
  154. std::cout << " ⚠️ 危险:view.data()[view.size()] 访问越界!\n";
  155. std::cout << " 📌 原因:view 指向字符串内部,后面没有 \\0\n";
  156. // ❌ 危险操作:传给需要 \0 结尾的 C API
  157. std::cout << "\n❌ 危险示例:传给 C API\n";
  158. std::cout << " ⚠️ 以下代码会导致未定义行为,已被注释\n";
  159. // std::string_view filename = "test.txt"; // 这个没问题
  160. // FILE* f = fopen(filename.data(), "r"); // 危险!如果 view 没有 \0 结尾会崩溃
  161. std::cout << " → 问题:C API 期望 \\0 结尾,但 view 不保证\n";
  162. // ✅ 安全做法:需要 \0 结尾时转换为 std::string
  163. std::cout << "\n✅ 安全做法:传给 C API 前转换为 std::string\n";
  164. {
  165. std::string_view sv = "temp/test.txt";
  166. // 方式1:显式转换为 string
  167. std::string filename(sv); // 会复制并添加 \0
  168. std::cout << " 方式1:std::string(sv) → 会复制并添加 \\0\n";
  169. // 方式2:临时构造(用于函数调用)
  170. auto call_c_api = [](const char* path) {
  171. std::cout << " 调用 C API,路径: " << path << "\n";
  172. };
  173. call_c_api(std::string(sv).c_str()); // 临时 string 保证 \0 结尾
  174. std::cout << " 方式2:std::string(sv).c_str() → 适合单次调用\n";
  175. }
  176. // 检测 string_view 是否有 \0 结尾的工具函数
  177. std::cout << "\n📊 检测 string_view 是否有 \\0 结尾:\n";
  178. std::cout << " bool is_null_terminated(std::string_view sv) {\n";
  179. std::cout << " return sv.data()[sv.size()] == '\\0';\n";
  180. std::cout << " }\n";
  181. std::cout << " ⚠️ 注意:即使检测为 false,访问也是未定义行为!\n";
  182. }
  183. // ==================== 注意事项3:底层字符串修改 ====================
  184. /**
  185. * @brief 演示底层字符串修改对 string_view 的影响
  186. *
  187. * string_view 只是观察者,不拥有数据。
  188. * 如果底层字符串被修改或重新分配,string_view 会变成悬空状态。
  189. */
  190. void verify_underlying_modification() {
  191. std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
  192. std::cout << "║ 注意事项3:底层字符串修改 ║\n";
  193. std::cout << "║ 修改原字符串会使 string_view 失效 ║\n";
  194. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  195. // 场景1:通过原字符串修改内容
  196. std::cout << "\n📌 场景1:修改原字符串内容\n";
  197. {
  198. std::string data = "Original Data";
  199. std::string_view view = data;
  200. std::cout << " 初始 data: '" << data << "'\n";
  201. std::cout << " 初始 view: '" << view << "'\n";
  202. // 修改原字符串
  203. data[0] = 'M';
  204. data[1] = 'o';
  205. data[2] = 'd';
  206. std::cout << " 修改后 data: '" << data << "'\n";
  207. std::cout << " 修改后 view: '" << view << "'\n";
  208. std::cout << " → view 反映了修改(指向同一内存)\n";
  209. std::cout << " ⚠️ 注意:这可能不是期望的行为!\n";
  210. }
  211. // 场景2:字符串重新分配(最危险!)
  212. std::cout << "\n📌 场景2:字符串重新分配(悬空引用)\n";
  213. {
  214. std::string data = "Short string"; // SSO 可能不分配堆内存
  215. // 强制使用堆内存
  216. data = "This is a very long string that will definitely allocate heap memory";
  217. std::string_view view = data;
  218. std::cout << " 初始 view: '" << view << "'\n";
  219. std::cout << " 原字符串地址: " << (void*)view.data() << "\n";
  220. // ⚠️ 追加内容可能导致重新分配
  221. data += " and some more data to trigger reallocation!";
  222. std::cout << " ⚠️ 重新分配后 string_view 变成悬空!\n";
  223. std::cout << " 新字符串地址: " << (void*)data.data() << "\n";
  224. std::cout << " view 仍指向旧地址: " << (void*)view.data() << "\n";
  225. // std::cout << " view: '" << view << "'\n"; // 未定义行为!
  226. std::cout << " → 危险:访问悬空 view 会导致未定义行为\n";
  227. }
  228. // 场景3:字符串销毁
  229. std::cout << "\n📌 场景3:字符串销毁(经典陷阱)\n";
  230. {
  231. std::string_view view;
  232. {
  233. std::string data = "临时数据";
  234. view = data; // view 指向 data
  235. std::cout << " 作用域内 view: '" << view << "'\n";
  236. } // data 在这里被销毁
  237. // std::cout << view << "\n"; // 未定义行为!
  238. std::cout << " → data 已销毁,view 变成悬空引用\n";
  239. std::cout << " ⚠️ 访问 view 会导致缓冲区溢出或崩溃\n";
  240. }
  241. // 场景4:字符数组被修改
  242. std::cout << "\n📌 场景4:栈上字符数组被修改\n";
  243. {
  244. char buffer[] = "Initial value";
  245. std::string_view view = buffer;
  246. std::cout << " 初始 view: '" << view << "'\n";
  247. // 修改原始数组
  248. strcpy(buffer, "Modified!");
  249. std::cout << " 修改后 view: '" << view << "'\n";
  250. std::cout << " → view 反映了修改(指向同一内存)\n";
  251. }
  252. // 安全实践
  253. std::cout << "\n✅ 安全实践:\n";
  254. std::cout << " 1. 确保 string_view 不拥有数据,只作为临时观察者\n";
  255. std::cout << " 2. 不要在 string_view 存活期间修改/重分配底层字符串\n";
  256. std::cout << " 3. 如果需要长期保存,使用 std::string 而不是 string_view\n";
  257. std::cout << " 4. 函数参数用 string_view,函数返回值用 string\n";
  258. }
  259. // ==================== 综合示例:正确使用 string_view ====================
  260. /**
  261. * @brief 展示 string_view 的正确使用模式
  262. */
  263. class LogParser {
  264. private:
  265. std::string log_data_; // 拥有数据
  266. std::vector<std::string_view> lines_; // 视图,不拥有数据
  267. public:
  268. /**
  269. * @brief 构造函数:接收数据并创建视图
  270. * @param data 日志数据(会被复制)
  271. */
  272. explicit LogParser(const std::string& data) : log_data_(data) {
  273. // 安全:log_data_ 拥有数据,lines_ 只是视图
  274. size_t start = 0;
  275. while (start < log_data_.size()) {
  276. size_t end = log_data_.find('\n', start);
  277. if (end == std::string::npos) {
  278. lines_.push_back(std::string_view(log_data_.c_str() + start));
  279. break;
  280. }
  281. lines_.push_back(std::string_view(log_data_.c_str() + start, end - start));
  282. start = end + 1;
  283. }
  284. }
  285. /**
  286. * @brief 获取行数
  287. */
  288. size_t line_count() const { return lines_.size(); }
  289. /**
  290. * @brief 获取指定行(安全:数据生命周期由类保证)
  291. */
  292. std::string_view get_line(size_t index) const {
  293. if (index >= lines_.size()) {
  294. return {};
  295. }
  296. return lines_[index];
  297. }
  298. /**
  299. * @brief 查找包含特定字符串的行
  300. */
  301. std::vector<std::string_view> find_lines(std::string_view keyword) const {
  302. std::vector<std::string_view> result;
  303. for (auto line : lines_) {
  304. if (line.find(keyword) != std::string_view::npos) {
  305. result.push_back(line);
  306. }
  307. }
  308. return result;
  309. }
  310. };
  311. /**
  312. * @brief 演示综合示例
  313. */
  314. void demonstrate_best_practice() {
  315. std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
  316. std::cout << "║ 最佳实践示例:LogParser 类 ║\n";
  317. std::cout << "║ 正确使用 string_view 管理生命周期 ║\n";
  318. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  319. // 创建测试数据
  320. std::string log_data =
  321. "[INFO] Application started\n"
  322. "[ERROR] Failed to open config file\n"
  323. "[WARN] Deprecated API used\n"
  324. "[INFO] Connection established\n"
  325. "[ERROR] Timeout occurred\n";
  326. // LogParser 拥有数据,内部视图安全
  327. LogParser parser(log_data);
  328. std::cout << "\n📊 日志解析结果:\n";
  329. std::cout << " 总行数: " << parser.line_count() << "\n";
  330. std::cout << "\n 查找包含 '[ERROR]' 的行:\n";
  331. auto error_lines = parser.find_lines("[ERROR]");
  332. for (auto line : error_lines) {
  333. std::cout << " • " << line << "\n";
  334. }
  335. std::cout << "\n ✅ 关键点:\n";
  336. std::cout << " 1. LogParser 拥有 log_data_ 的所有权\n";
  337. std::cout << " 2. lines_ 中的 string_view 指向 log_data_\n";
  338. std::cout << " 3. 返回 string_view 是安全的(数据生命周期由类保证)\n";
  339. std::cout << " 4. 不会发生悬空引用或内存泄漏\n";
  340. }
  341. // ==================== 主函数 ====================
  342. int main() {
  343. std::cout << "\n";
  344. std::cout << "╔═══════════════════════════════════════════════════════════════╗\n";
  345. std::cout << "║ std::string_view 三大注意事项验证程序 ║\n";
  346. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  347. // 运行所有验证
  348. verify_lifetime();
  349. verify_null_termination();
  350. verify_underlying_modification();
  351. demonstrate_best_practice();
  352. // 最终总结
  353. std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
  354. std::cout << "║ 总结与检查清单 ║\n";
  355. std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
  356. std::cout << "║ □ 生命周期:确保 string_view 不超出源字符串的作用域 ║\n";
  357. std::cout << "║ □ \\0 结尾:传给 C API 前确认或转换为 std::string ║\n";
  358. std::cout << "║ □ 底层修改:view 存活期间不修改/重分配底层字符串 ║\n";
  359. std::cout << "║ □ 返回值:需要返回时用 std::string,不要返回 string_view ║\n";
  360. std::cout << "║ □ 参数:函数参数用 string_view(临时观察者) ║\n";
  361. std::cout << "║ □ 成员:类成员用 std::string(拥有数据) ║\n";
  362. std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
  363. return 0;
  364. }
复制代码
  1. ╔═══════════════════════════════════════════════════════════════╗
  2. ║ std::string_view 三大注意事项验证程序 ║
  3. ╚═══════════════════════════════════════════════════════════════╝
  4. ╔═══════════════════════════════════════════════════════════════╗
  5. ║ 注意事项1:生命周期管理 ║
  6. ║ string_view 不管理生命周期,不能比源字符串活得久 ║
  7. ╚═══════════════════════════════════════════════════════════════╝
  8. ✅ 正确示例:生命周期内使用
  9. view: '持久存在的字符串'
  10. → 安全:view 和 data 在同一作用域
  11. ✅ 正确示例:作为函数参数
  12. 函数内接收到: '作为参数传递'
  13. → 安全:调用者保证生命周期
  14. ❌ 危险示例:返回局部变量的 view
  15. ⚠️ 下面的代码会触发未定义行为,已被注释
  16. → 危险:view 指向已销毁的内存,访问会导致未定义行为
  17. ✅ 正确做法:需要返回时使用 std::string
  18. 返回的字符串: '这个字符串会安全返回'
  19. → 安全:返回拥有所有权的 std::string
  20. 📊 生命周期图解:
  21. ┌─────────────────────────────────────────────────────────┐
  22. │ std::string data = "Hello"; // data 拥有内存 │
  23. │ std::string_view sv = data; // sv 指向 data │
  24. │ // data 和 sv 都在作用域内 // ✅ 安全 │
  25. │ } // data 被销毁 // ❌ sv 变成悬空 │
  26. └─────────────────────────────────────────────────────────┘
  27. ╔═══════════════════════════════════════════════════════════════╗
  28. ║ 注意事项2:\0 结尾问题 ║
  29. ║ string_view 不保证以 \0 结尾 ║
  30. ╚═══════════════════════════════════════════════════════════════╝
  31. 📌 情况1:从字符串字面量构造
  32. 【字符串字面量】
  33. 内容: 'Hello'
  34. 长度: 5
  35. 是否以\0结尾: 是
  36. 说明:字面量在编译期自动添加 \0,所以有结尾符
  37. 📌 情况2:从 std::string 构造
  38. 【std::string】
  39. 内容: 'World'
  40. 长度: 5
  41. 是否以\0结尾: 是
  42. 说明:std::string 保证 \0 结尾(C++11 起)
  43. 📌 情况3:从子串构造(⚠️ 危险场景)
  44. 【substr 子串】
  45. 内容: 'ello'
  46. 长度: 5
  47. 是否以\0结尾: 是
  48. ⚠️ 危险:substr 创建的视图不一定有 \0 结尾!
  49. 📌 原因:substr 只调整指针和长度,不复制数据
  50. 📌 情况4:指向字符数组中间
  51. 【指向中间】
  52. 内容: 'grammi'
  53. 长度: 6
  54. 是否以\0结尾: 否
  55. ⚠️ 危险:view.data()[view.size()] 访问越界!
  56. 📌 原因:view 指向字符串内部,后面没有 \0
  57. ❌ 危险示例:传给 C API
  58. ⚠️ 以下代码会导致未定义行为,已被注释
  59. → 问题:C API 期望 \0 结尾,但 view 不保证
  60. ✅ 安全做法:传给 C API 前转换为 std::string
  61. 方式1:std::string(sv) → 会复制并添加 \0
  62. 调用 C API,路径: temp/test.txt
  63. 方式2:std::string(sv).c_str() → 适合单次调用
  64. 📊 检测 string_view 是否有 \0 结尾:
  65. bool is_null_terminated(std::string_view sv) {
  66. return sv.data()[sv.size()] == '\0';
  67. }
  68. ⚠️ 注意:即使检测为 false,访问也是未定义行为!
  69. ╔═══════════════════════════════════════════════════════════════╗
  70. ║ 注意事项3:底层字符串修改 ║
  71. ║ 修改原字符串会使 string_view 失效 ║
  72. ╚═══════════════════════════════════════════════════════════════╝
  73. 📌 场景1:修改原字符串内容
  74. 初始 data: 'Original Data'
  75. 初始 view: 'Original Data'
  76. 修改后 data: 'Modginal Data'
  77. 修改后 view: 'Modginal Data'
  78. → view 反映了修改(指向同一内存)
  79. ⚠️ 注意:这可能不是期望的行为!
  80. 📌 场景2:字符串重新分配(悬空引用)
  81. 初始 view: 'This is a very long string that will definitely allocate heap memory'
  82. 原字符串地址: 000001D1602F05C0
  83. ⚠️ 重新分配后 string_view 变成悬空!
  84. 新字符串地址: 000001D1602EC100
  85. view 仍指向旧地址: 000001D1602F05C0
  86. → 危险:访问悬空 view 会导致未定义行为
  87. 📌 场景3:字符串销毁(经典陷阱)
  88. 作用域内 view: '临时数据'
  89. → data 已销毁,view 变成悬空引用
  90. ⚠️ 访问 view 会导致缓冲区溢出或崩溃
  91. 📌 场景4:栈上字符数组被修改
  92. 初始 view: 'Initial value'
  93. 修改后 view: 'Modified!lue'
  94. → view 反映了修改(指向同一内存)
  95. ✅ 安全实践:
  96. 1. 确保 string_view 不拥有数据,只作为临时观察者
  97. 2. 不要在 string_view 存活期间修改/重分配底层字符串
  98. 3. 如果需要长期保存,使用 std::string 而不是 string_view
  99. 4. 函数参数用 string_view,函数返回值用 string
  100. ╔═══════════════════════════════════════════════════════════════╗
  101. ║ 最佳实践示例:LogParser 类 ║
  102. ║ 正确使用 string_view 管理生命周期 ║
  103. ╚═══════════════════════════════════════════════════════════════╝
  104. 📊 日志解析结果:
  105. 总行数: 5
  106. 查找包含 '[ERROR]' 的行:
  107. • [ERROR] Failed to open config file
  108. • [ERROR] Timeout occurred
  109. ✅ 关键点:
  110. 1. LogParser 拥有 log_data_ 的所有权
  111. 2. lines_ 中的 string_view 指向 log_data_
  112. 3. 返回 string_view 是安全的(数据生命周期由类保证)
  113. 4. 不会发生悬空引用或内存泄漏
  114. ╔═══════════════════════════════════════════════════════════════╗
  115. ║ 总结与检查清单 ║
  116. ╠═══════════════════════════════════════════════════════════════╣
  117. ║ □ 生命周期:确保 string_view 不超出源字符串的作用域 ║
  118. ║ □ \0 结尾:传给 C API 前确认或转换为 std::string ║
  119. ║ □ 底层修改:view 存活期间不修改/重分配底层字符串 ║
  120. ║ □ 返回值:需要返回时用 std::string,不要返回 string_view ║
  121. ║ □ 参数:函数参数用 string_view(临时观察者) ║
  122. ║ □ 成员:类成员用 std::string(拥有数据) ║
  123. ╚═══════════════════════════════════════════════════════════════╝
  124. E:\myLearn\StringView\x64\Debug\StringView.exe (进程 10944)已退出,代码为 0 (0x0)。
  125. 按任意键关闭此窗口. . .
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行