🎁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 视图只能观察,不能修改底层字符串。
💻 基础使用示例 - #include <iostream>
- #include <string>
- #include <string_view>
- #include <vector>
- #include <algorithm>
- #include <cctype>
- // 基础打印函数 - 展示 string_view 的核心能力
- void print(std::string_view sv, const std::string& title = "string_view") {
- std::cout << "\n=== " << title << " ===\n";
- std::cout << "内容: '" << sv << "'\n";
- std::cout << "长度: " << sv.size() << " 字符\n";
- std::cout << "是否为空: " << (sv.empty() ? "是" : "否") << "\n";
-
- // 遍历方式1:下标访问(O(1) 随机访问)
- std::cout << "字符(下标): ";
- for (size_t i = 0; i < sv.size(); ++i) {
- std::cout << sv[i] << " ";
- }
- std::cout << "\n";
-
- // 遍历方式2:范围 for 循环(现代 C++ 风格)
- std::cout << "字符(范围): ";
- for (char ch : sv) {
- std::cout << ch << " ";
- }
- std::cout << "\n";
-
- // 遍历方式3:迭代器(算法友好)
- std::cout << "字符(迭代器): ";
- for (auto it = sv.begin(); it != sv.end(); ++it) {
- std::cout << *it << " ";
- }
- std::cout << "\n";
- }
- // 实际应用:字符串分割函数(不复制数据,高效)
- std::vector<std::string_view> split(std::string_view str, char delimiter) {
- std::vector<std::string_view> result;
- size_t start = 0;
-
- while (start < str.size()) {
- size_t end = str.find(delimiter, start);
- if (end == std::string_view::npos) {
- // 最后一部分
- result.push_back(str.substr(start));
- break;
- }
- // 添加子串(零拷贝)
- result.push_back(str.substr(start, end - start));
- start = end + 1;
- }
- return result;
- }
- // 实际应用:移除空格(延迟处理,不修改原字符串)
- std::string_view trim(std::string_view str) {
- // 移除前导空格
- while (!str.empty() && std::isspace(static_cast<unsigned char>(str.front()))) {
- str.remove_prefix(1);
- }
- // 移除尾随空格
- while (!str.empty() && std::isspace(static_cast<unsigned char>(str.back()))) {
- str.remove_suffix(1);
- }
- return str;
- }
- // 实际应用:判断是否为前缀
- bool starts_with(std::string_view str, std::string_view prefix) {
- return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
- }
- // 实际应用:判断是否为后缀
- bool ends_with(std::string_view str, std::string_view suffix) {
- return str.size() >= suffix.size() &&
- str.substr(str.size() - suffix.size()) == suffix;
- }
- // 实际应用:大小写不敏感比较
- bool iequals(std::string_view a, std::string_view b) {
- if (a.size() != b.size()) return false;
-
- auto to_lower = [](char c) {
- return std::tolower(static_cast<unsigned char>(c));
- };
-
- return std::equal(a.begin(), a.end(), b.begin(), to_lower);
- }
- // 性能对比演示
- void performance_demo() {
- std::cout << "\n=== 性能优化演示 ===\n";
-
- // ❌ 低效:不必要的字符串拷贝
- std::string huge_string(1000000, 'A');
- auto start = std::chrono::high_resolution_clock::now();
- std::string copy = huge_string.substr(100, 500); // 复制数据
- auto end = std::chrono::high_resolution_clock::now();
- auto copy_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
- std::cout << "字符串拷贝耗时: " << copy_time.count() << " 微秒\n";
-
- // ✅ 高效:string_view 零拷贝
- start = std::chrono::high_resolution_clock::now();
- std::string_view view = std::string_view(huge_string).substr(100, 500); // 不复制
- end = std::chrono::high_resolution_clock::now();
- auto view_time = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
- std::cout << "string_view 耗时: " << view_time.count() << " 微秒\n";
- std::cout << "性能提升: " << (double)copy_time.count() / view_time.count() << " 倍\n";
- }
- int main() {
- // ========== 1. 基础构造方式 ==========
- std::cout << "1. 基础构造方式\n" << std::string(50, '=') << "\n";
-
- // 从字符串字面量构造
- std::string_view sv1 = "Hello, world!";
- print(sv1, "从字面量构造");
-
- // 从 std::string 构造
- std::string str = "C++17 string_view is powerful!";
- std::string_view sv2 = str;
- print(sv2, "从 std::string 构造");
-
- // 从部分字符串构造(指针+长度)
- std::string_view sv3(str.c_str() + 6, 11); // "string_view"
- print(sv3, "从部分字符串构造");
-
- // 使用字面量后缀(需要 using namespace std::literals)
- using namespace std::literals;
- std::string_view sv4 = "Literal string_view"sv;
- print(sv4, "字面量后缀");
-
- // ========== 2. 实际应用场景 ==========
- std::cout << "\n2. 实际应用场景\n" << std::string(50, '=') << "\n";
-
- // 字符串分割
- std::string csv = "apple,banana,cherry,date";
- auto fruits = split(csv, ',');
- std::cout << "CSV 分割: ";
- for (auto fruit : fruits) {
- std::cout << "'" << fruit << "' ";
- }
- std::cout << "\n";
-
- // 移除空格
- std::string messy = " \t Hello, World! \n";
- std::string_view cleaned = trim(messy);
- std::cout << "原字符串: '" << messy << "'\n";
- std::cout << "去除空格后: '" << cleaned << "'\n";
- std::cout << "原字符串未修改: '" << messy << "' (证明零拷贝)\n";
-
- // 前缀/后缀检查
- std::string filename = "document.pdf";
- std::string_view fv = filename;
- std::cout << "文件名: " << fv << "\n";
- std::cout << "以 'doc' 开头? " << (starts_with(fv, "doc") ? "是" : "否") << "\n";
- std::cout << "以 '.pdf' 结尾? " << (ends_with(fv, ".pdf") ? "是" : "否") << "\n";
-
- // 大小写不敏感比较
- std::string_view str1 = "Hello";
- std::string_view str2 = "HELLO";
- std::cout << "忽略大小写比较: '" << str1 << "' vs '" << str2 << "' = "
- << (iequals(str1, str2) ? "相等" : "不等") << "\n";
-
- // ========== 3. 边界情况和注意事项 ==========
- std::cout << "\n3. 边界情况和注意事项\n" << std::string(50, '=') << "\n";
-
- // 空字符串视图
- std::string_view empty;
- print(empty, "空 string_view");
-
- // 临时字符串的危险用法(演示)
- std::cout << "⚠️ 警告:string_view 不拥有数据!\n";
- std::string_view dangerous;
- {
- std::string temp = "临时字符串";
- dangerous = temp; // 危险!temp 即将销毁
- std::cout << "临时对象存在时: " << dangerous << "\n";
- }
- // dangerous 现在是悬空引用!不要取消注释下一行
- // std::cout << "临时对象销毁后: " << dangerous << "\n"; // 未定义行为!
-
- // 安全做法:确保底层数据生命周期足够长
- std::string safe_str = "安全字符串";
- std::string_view safe_view = safe_str;
- std::cout << "安全使用: " << safe_view << "\n";
-
- // ========== 4. find 系列操作 ==========
- std::cout << "\n4. 查找操作\n" << std::string(50, '=') << "\n";
-
- std::string_view text = "The quick brown fox jumps over the lazy dog";
- std::cout << "文本: '" << text << "'\n";
-
- auto pos = text.find("fox");
- if (pos != std::string_view::npos) {
- std::cout << "找到 'fox' 在位置 " << pos << "\n";
- std::cout << "子串: " << text.substr(pos, 3) << "\n";
- }
-
- pos = text.find_first_of("aeiou");
- if (pos != std::string_view::npos) {
- std::cout << "第一个元音 '" << text[pos] << "' 在位置 " << pos << "\n";
- }
-
- // ========== 5. 性能演示 ==========
- performance_demo();
-
- // ========== 6. 现代 C++ 最佳实践 ==========
- std::cout << "\n5. 最佳实践建议\n" << std::string(50, '=') << "\n";
-
- // 函数参数使用 string_view
- auto process = [](std::string_view data) {
- // 不拷贝,只读取
- return data.size();
- };
-
- // 可以传字符串字面量、std::string、另一个 string_view
- std::cout << "字符串字面量长度: " << process("Hello") << "\n";
- std::cout << "std::string 长度: " << process(std::string("World")) << "\n";
-
- // 避免 string_view 悬空
- std::cout << "\n✅ 核心原则:\n";
- std::cout << "1. string_view 像指针一样,不拥有数据\n";
- std::cout << "2. 确保底层数据生命周期覆盖 string_view 的使用\n";
- std::cout << "3. 函数参数用 string_view,返回值用 string(如果需要拥有数据)\n";
- std::cout << "4. 修改原字符串会使 string_view 失效\n";
- std::cout << "5. string_view 不保证以 \\0 结尾,使用 .data() 要小心\n";
-
- return 0;
- }
复制代码
✨ 核心优势与典型场景
std::string_view 几乎是零开销,它不会复制字符串数据,构造和析构成本极低。并且提供了与 std::string 非常相似的接口,几乎提供了所有的读相关接口,并且兼容性强,它可以高效地从 std::string、字符串字面量、char* 等任何字符序列构造而来。这样在很多场景下可以减少构造和拷贝,又可以利用 string 类似的相关接口处理串。 - /**
- * @file string_view_core_demo.cpp
- * @brief std::string_view 核心优势与典型场景演示
- * @author Code Reviewer
- * @date 2026-05-12
- *
- * @details 本文件展示 std::string_view 的核心特性:
- * 1. 零拷贝(不复制字符串数据)
- * 2. 只读访问(非拥有型视图)
- * 3. 轻量级(仅存储指针+长度,16字节)
- * 4. 高效子串操作(无需复制)
- * 5. 兼容多种字符串类型(字面量、std::string、C字符串)
- *
- * 编译运行:
- * g++ -std=c++17 -O2 string_view_core_demo.cpp -o core_demo
- * ./core_demo
- */
- #include <iostream>
- #include <string>
- #include <string_view>
- #include <vector>
- #include <algorithm>
- #include <cctype>
- /**
- * @brief 打印 string_view 的基本信息(辅助函数)
- * @param sv 要打印的字符串视图
- * @param title 可选的标题
- */
- void print_info(std::string_view sv, const std::string& title = "string_view") {
- std::cout << "\n┌─────────────────────────────────────────┐\n";
- std::cout << "│ " << title << "\n";
- std::cout << "├─────────────────────────────────────────┤\n";
- std::cout << "│ 内容: '" << sv << "'\n";
- std::cout << "│ 长度: " << sv.size() << " 字符\n";
- std::cout << "│ 是否为空: " << (sv.empty() ? "是" : "否") << "\n";
- std::cout << "│ sizeof(sv): " << sizeof(sv) << " 字节\n";
- std::cout << "└─────────────────────────────────────────┘\n";
- }
- /**
- * @brief 【典型场景1】字符串分割(零拷贝)
- * @param str 要分割的字符串视图
- * @param delimiter 分隔符
- * @return 分割后的子串视图列表(不复制任何字符数据)
- *
- * @note 这是 string_view 最经典的用法之一
- * 传统方式:会为每个子串分配内存并复制字符
- * 本方式:只记录每个子串在原字符串中的位置和长度
- */
- std::vector<std::string_view> split(std::string_view str, char delimiter) {
- std::vector<std::string_view> result;
- size_t start = 0;
- while (start < str.size()) {
- // 查找下一个分隔符
- size_t end = str.find(delimiter, start);
- if (end == std::string_view::npos) {
- // 最后一部分(或没有分隔符)
- result.push_back(str.substr(start));
- break;
- }
- // 添加子串视图(零拷贝!只记录位置和长度)
- result.push_back(str.substr(start, end - start));
- start = end + 1;
- }
- return result;
- }
- /**
- * @brief 【典型场景2】去除首尾空格(非破坏性)
- * @param str 需要处理的字符串视图
- * @return 去除首尾空格后的新视图
- *
- * @note 不修改原字符串,不复制数据,只是调整视图的边界
- * 时间复杂度 O(n),但空间复杂度 O(1)
- */
- std::string_view trim(std::string_view str) {
- // 移除前导空白字符(空格、制表符、换行等)
- while (!str.empty() && std::isspace(static_cast<unsigned char>(str.front()))) {
- str.remove_prefix(1); // 将视图起始位置右移1
- }
- // 移除尾随空白字符
- while (!str.empty() && std::isspace(static_cast<unsigned char>(str.back()))) {
- str.remove_suffix(1); // 将视图结束位置左移1
- }
- return str;
- }
- /**
- * @brief 【典型场景3】检查字符串前缀
- * @param str 待检查的字符串
- * @param prefix 可能的前缀
- * @return 是否是前缀
- */
- bool starts_with(std::string_view str, std::string_view prefix) {
- // 首先确保原字符串长度足够
- if (str.size() < prefix.size()) {
- return false;
- }
- // substr 是 O(1) 操作,不会复制数据
- return str.substr(0, prefix.size()) == prefix;
- }
- /**
- * @brief 【典型场景4】检查字符串后缀
- * @param str 待检查的字符串
- * @param suffix 可能的后缀
- * @return 是否是后缀
- */
- bool ends_with(std::string_view str, std::string_view suffix) {
- if (str.size() < suffix.size()) {
- return false;
- }
- // 从尾部取子串进行对比,仍然是 O(1)
- return str.substr(str.size() - suffix.size()) == suffix;
- }
- /**
- * @brief 【典型场景5】大小写不敏感的比较
- * @param a 字符串A
- * @param b 字符串B
- * @return 是否相等(忽略大小写)
- */
- bool iequals(std::string_view a, std::string_view b) {
- if (a.size() != b.size()) {
- return false;
- }
- // 双参数 lambda:比较两个字符是否相等(忽略大小写)
- auto char_iequal = [](char c1, char c2) {
- return std::tolower(static_cast<unsigned char>(c1)) ==
- std::tolower(static_cast<unsigned char>(c2));
- };
- return std::equal(a.begin(), a.end(), b.begin(), char_iequal);
- }
- /**
- * @brief 【典型场景6】提取文件扩展名
- * @param filename 文件名
- * @return 扩展名视图(不含点号)
- */
- std::string_view get_extension(std::string_view filename) {
- // 查找最后一个点号
- size_t dot_pos = filename.rfind('.');
- if (dot_pos == std::string_view::npos) {
- return {}; // 没有扩展名,返回空视图
- }
- // 返回点号之后的部分(零拷贝)
- return filename.substr(dot_pos + 1);
- }
- /**
- * @brief 【典型场景7】解析键值对
- * @param kv_pair 格式为 "key=value" 的字符串
- * @param key 输出参数:存储键的视图
- * @param value 输出参数:存储值的视图
- * @return 是否解析成功
- */
- bool parse_key_value(std::string_view kv_pair, std::string_view& key, std::string_view& value) {
- size_t eq_pos = kv_pair.find('=');
- if (eq_pos == std::string_view::npos) {
- return false;
- }
- // 直接使用视图,不复制数据
- key = trim(kv_pair.substr(0, eq_pos));
- value = trim(kv_pair.substr(eq_pos + 1));
- return !key.empty(); // 键不能为空
- }
- /**
- * @brief 【典型场景8】灵活的函数参数
- * @param data 可以接受任何字符串类型(字面量、std::string、C字符串)
- * @return 字符串长度
- *
- * @note 使用 string_view 作为函数参数是最佳实践:
- * 1. 不需要重载多个版本
- * 2. 避免不必要的字符串拷贝
- * 3. 调用者无需修改代码
- */
- size_t get_length(std::string_view data) {
- // 这里只读取,不修改,完美发挥 string_view 的优势
- return data.size();
- }
- // ==================== 主函数 ====================
- int main() {
- std::cout << "\n";
- std::cout << "╔══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ std::string_view 核心优势与典型场景演示 ║\n";
- std::cout << "╚══════════════════════════════════════════════════════════════╝\n";
- // ==================== 一、核心优势演示 ====================
- std::cout << "\n【一、核心优势】\n";
- std::cout << "─────────────────────────────────────────────────────────────\n";
- // 优势1:极轻量(只占16字节)
- std::cout << "\n✓ 优势1:轻量级(仅16字节)\n";
- std::cout << " sizeof(std::string_view) = " << sizeof(std::string_view) << " 字节\n";
- std::cout << " sizeof(std::string) = " << sizeof(std::string) << " 字节\n";
- // 优势2:零拷贝构造
- std::cout << "\n✓ 优势2:零拷贝构造\n";
- std::string original = "这是一个很长的字符串,构造视图时不会复制任何字符数据";
- std::string_view view = original; // 只复制指针和长度,不复制字符
- std::cout << " 原字符串地址: " << (void*)original.data() << "\n";
- std::cout << " 视图存储地址: " << (void*)view.data() << " (指向同一块内存)\n";
- // 优势3:零拷贝子串
- std::cout << "\n✓ 优势3:零拷贝子串操作\n";
- std::string_view text = "The quick brown fox jumps over the lazy dog";
- std::string_view sub = text.substr(10, 9); // "brown fox"
- std::cout << " 原字符串: '" << text << "'\n";
- std::cout << " 子串视图: '" << sub << "'\n";
- std::cout << " 地址比较: 原:" << (void*)text.data()
- << " vs 子:" << (void*)sub.data() << " (相同)\n";
- std::cout << " 说明: substr 只记录了新的起始位置,没有复制数据\n";
- // ==================== 二、典型应用场景 ====================
- std::cout << "\n【二、典型应用场景】\n";
- std::cout << "─────────────────────────────────────────────────────────────\n";
- // 场景1:字符串分割
- std::cout << "\n✓ 场景1:高效字符串分割\n";
- std::string csv = "apple,banana,cherry,date,elderberry";
- std::cout << " CSV 数据: " << csv << "\n";
- auto fruits = split(csv, ',');
- std::cout << " 分割结果(零拷贝视图): ";
- for (size_t i = 0; i < fruits.size(); ++i) {
- std::cout << "'" << fruits[i] << "'";
- if (i < fruits.size() - 1) std::cout << ", ";
- }
- std::cout << "\n";
- std::cout << " 注意:所有子串视图都直接指向原字符串内存\n";
- // 场景2:去除首尾空格
- std::cout << "\n✓ 场景2:非破坏性空格去除\n";
- std::string messy = " \t Hello, World! \n\r";
- std::string_view trimmed = trim(messy);
- std::cout << " 原字符串: '" << messy << "'\n";
- std::cout << " 去除空格: '" << trimmed << "'\n";
- std::cout << " 原字符串未被修改: '" << messy << "'\n";
- // 场景3:前缀/后缀检查
- std::cout << "\n✓ 场景3:高效前缀/后缀检查\n";
- std::string filename = "document.pdf";
- std::string_view fv = filename;
- std::cout << " 文件名: " << fv << "\n";
- std::cout << " 以 'doc' 开头? " << (starts_with(fv, "doc") ? "✓ 是" : "✗ 否") << "\n";
- std::cout << " 以 '.pdf' 结尾? " << (ends_with(fv, ".pdf") ? "✓ 是" : "✗ 否") << "\n";
- // 场景4:文件扩展名提取
- std::cout << "\n✓ 场景4:文件扩展名提取\n";
- std::vector<std::string> files = { "image.jpg", "document.pdf", "script.py", "Makefile" };
- for (const auto& file : files) {
- std::string_view ext = get_extension(file);
- std::cout << " " << file << " → 扩展名: " << (ext.empty() ? "(无)" : ext.data()) << "\n";
- }
- // 场景5:解析键值对
- std::cout << "\n✓ 场景5:解析键值对\n";
- std::vector<std::string> kv_strings = {
- "name=John Doe",
- " age = 25 ",
- "email=john@example.com"
- };
- for (const auto& kv : kv_strings) {
- std::string_view key, value;
- if (parse_key_value(kv, key, value)) {
- std::cout << " 解析: |" << key << "| → |" << value << "|\n";
- }
- }
- // 场景6:大小写不敏感比较
- std::cout << "\n✓ 场景6:大小写不敏感比较\n";
- std::string_view hello1 = "Hello";
- std::string_view hello2 = "HELLO";
- std::string_view hello3 = "World";
- std::cout << " 'Hello' vs 'HELLO': " << (iequals(hello1, hello2) ? "相等" : "不等") << "\n";
- std::cout << " 'Hello' vs 'World': " << (iequals(hello1, hello3) ? "相等" : "不等") << "\n";
- // 场景7:灵活的字符串类型兼容性
- std::cout << "\n✓ 场景7:统一处理多种字符串类型\n";
- const char* c_str = "C字符串";
- std::string cpp_str = "C++字符串";
- std::string_view view_str = "字符串视图";
- // 同一个函数可以接受所有类型
- std::cout << " C字符串长度: " << get_length(c_str) << "\n";
- std::cout << " C++字符串长度: " << get_length(cpp_str) << "\n";
- std::cout << " 字符串视图长度: " << get_length(view_str) << "\n";
- // ==================== 三、输出格式演示 ====================
- std::cout << "\n【三、多种遍历方式】\n";
- std::cout << "─────────────────────────────────────────────────────────────\n";
- std::string_view demo = "C++17";
- print_info(demo, "遍历示例");
- std::cout << "\n 遍历方式1(下标): ";
- for (size_t i = 0; i < demo.size(); ++i) {
- std::cout << demo[i] << " ";
- }
- std::cout << "\n 遍历方式2(范围for): ";
- for (char ch : demo) {
- std::cout << ch << " ";
- }
- std::cout << "\n 遍历方式3(迭代器): ";
- for (auto it = demo.begin(); it != demo.end(); ++it) {
- std::cout << *it << " ";
- }
- std::cout << "\n";
- std::cout << "\n═══════════════════════════════════════════════════════════════\n";
- std::cout << "✅ 演示完成!string_view 的核心优势:\n";
- std::cout << " • 零拷贝(不分配内存)\n";
- std::cout << " • 轻量级(只占16字节)\n";
- std::cout << " • 兼容多种字符串类型\n";
- std::cout << " • 高效子串操作\n";
- std::cout << " • 作为函数参数的最佳实践\n";
- std::cout << "═══════════════════════════════════════════════════════════════\n";
- return 0;
- }
复制代码- ╔══════════════════════════════════════════════════════════════╗
- ║ std::string_view 核心优势与典型场景演示 ║
- ╚══════════════════════════════════════════════════════════════╝
- 【一、核心优势】
- ─────────────────────────────────────────────────────────────
- ✓ 优势1:轻量级(仅16字节)
- sizeof(std::string_view) = 16 字节
- sizeof(std::string) = 40 字节
- ✓ 优势2:零拷贝构造
- 原字符串地址: 000001E96A6DBE30
- 视图存储地址: 000001E96A6DBE30 (指向同一块内存)
- ✓ 优势3:零拷贝子串操作
- 原字符串: 'The quick brown fox jumps over the lazy dog'
- 子串视图: 'brown fox'
- 地址比较: 原:00007FF6F8803950 vs 子:00007FF6F880395A (相同)
- 说明: substr 只记录了新的起始位置,没有复制数据
- 【二、典型应用场景】
- ─────────────────────────────────────────────────────────────
- ✓ 场景1:高效字符串分割
- CSV 数据: apple,banana,cherry,date,elderberry
- 分割结果(零拷贝视图): 'apple', 'banana', 'cherry', 'date', 'elderberry'
- 注意:所有子串视图都直接指向原字符串内存
- ✓ 场景2:非破坏性空格去除
- 原字符串: ' Hello, World!
- '
- 去除空格: 'Hello, World!'
- 原字符串未被修改: ' Hello, World!
- '
- ✓ 场景3:高效前缀/后缀检查
- 文件名: document.pdf
- 以 'doc' 开头? ✓ 是
- 以 '.pdf' 结尾? ✓ 是
- ✓ 场景4:文件扩展名提取
- image.jpg → 扩展名: jpg
- document.pdf → 扩展名: pdf
- script.py → 扩展名: py
- Makefile → 扩展名: (无)
- ✓ 场景5:解析键值对
- 解析: |name| → |John Doe|
- 解析: |age| → |25|
- 解析: |email| → |john@example.com|
- ✓ 场景6:大小写不敏感比较
- 'Hello' vs 'HELLO': 相等
- 'Hello' vs 'World': 不等
- ✓ 场景7:统一处理多种字符串类型
- C字符串长度: 10
- C++字符串长度: 12
- 字符串视图长度: 15
- 【三、多种遍历方式】
- ─────────────────────────────────────────────────────────────
- ┌─────────────────────────────────────────┐
- │ 遍历示例
- ├─────────────────────────────────────────┤
- │ 内容: 'C++17'
- │ 长度: 5 字符
- │ 是否为空: 否
- │ sizeof(sv): 16 字节
- └─────────────────────────────────────────┘
- 遍历方式1(下标): C + + 1 7
- 遍历方式2(范围for): C + + 1 7
- 遍历方式3(迭代器): C + + 1 7
- ═══════════════════════════════════════════════════════════════
- ✅ 演示完成!string_view 的核心优势:
- • 零拷贝(不分配内存)
- • 轻量级(只占16字节)
- • 兼容多种字符串类型
- • 高效子串操作
- • 作为函数参数的最佳实践
- ═══════════════════════════════════════════════════════════════
- E:\myLearn\StringView\x64\Debug\StringView.exe (进程 25764)已退出,代码为 0 (0x0)。
- 按任意键关闭此窗口. . .
复制代码
⚡ 性能对比测试 - /**
- * @file string_view_performance_demo.cpp
- * @brief std::string_view 性能对比测试
- * @author Code Reviewer
- * @date 2026-05-12
- *
- * @details 本文件通过基准测试对比 string_view 和传统 string 的性能差异:
- * 1. 构造性能对比(拷贝 vs 视图)
- * 2. 子串操作性能对比
- * 3. 函数参数传递性能对比
- * 4. 字符串分割性能对比
- * 5. 大规模数据处理测试
- *
- * 编译运行:
- * g++ -std=c++17 -O2 string_view_performance_demo.cpp -o perf_demo
- * ./perf_demo
- */
- #include <iostream>
- #include <string>
- #include <string_view>
- #include <vector>
- #include <chrono>
- #include <algorithm>
- #include <numeric>
- #include <iomanip>
- /**
- * @brief 高性能计时器类
- * @details 用于精确测量代码块执行时间
- */
- class Timer {
- private:
- std::chrono::high_resolution_clock::time_point start_time;
- std::string name;
-
- public:
- /**
- * @brief 构造函数,开始计时
- * @param test_name 测试名称
- */
- Timer(const std::string& test_name) : name(test_name) {
- start_time = std::chrono::high_resolution_clock::now();
- }
-
- /**
- * @brief 析构函数,自动输出耗时
- */
- ~Timer() {
- auto end_time = std::chrono::high_resolution_clock::now();
- auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
- std::cout << " ⏱️ " << name << ": " << std::setw(8) << duration.count() << " 微秒\n";
- }
-
- /**
- * @brief 手动获取耗时(不结束计时)
- */
- long long elapsed() const {
- auto now = std::chrono::high_resolution_clock::now();
- return std::chrono::duration_cast<std::chrono::microseconds>(now - start_time).count();
- }
- };
- /**
- * @brief 使用 std::string 进行字符串分割(传统方式)
- * @param str 字符串
- * @param delimiter 分隔符
- * @return 分割后的子串列表(每个子串都有独立的内存)
- *
- * @note 这种方式会为每个子串分配内存并复制字符数据
- */
- std::vector<std::string> split_string(const std::string& str, char delimiter) {
- std::vector<std::string> result;
- size_t start = 0;
-
- while (start < str.size()) {
- size_t end = str.find(delimiter, start);
- if (end == std::string::npos) {
- result.push_back(str.substr(start)); // 复制数据
- break;
- }
- result.push_back(str.substr(start, end - start)); // 复制数据
- start = end + 1;
- }
- return result;
- }
- /**
- * @brief 使用 std::string_view 进行字符串分割(现代方式)
- * @param str 字符串视图
- * @param delimiter 分隔符
- * @return 分割后的子串视图列表(零拷贝)
- *
- * @note 这种方式不复制任何字符数据,只记录位置和长度
- */
- std::vector<std::string_view> split_string_view(std::string_view str, char delimiter) {
- std::vector<std::string_view> result;
- size_t start = 0;
-
- while (start < str.size()) {
- size_t end = str.find(delimiter, start);
- if (end == std::string_view::npos) {
- result.push_back(str.substr(start)); // 零拷贝
- break;
- }
- result.push_back(str.substr(start, end - start)); // 零拷贝
- start = end + 1;
- }
- return result;
- }
- /**
- * @brief 性能测试1:构造性能对比
- * @param iterations 迭代次数
- *
- * @details 对比创建 1000 个字符串子串的耗时差异
- * 字符串视图的构造只是复制指针,远快于实际复制字符数据
- */
- void test_construction_performance(int iterations = 1000) {
- std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
- std::cout << "│ 测试1:构造性能对比(创建 " << iterations << " 个子串) │\n";
- std::cout << "└─────────────────────────────────────────────────────────────┘\n";
-
- // 准备一个长字符串
- std::string long_string(10000, 'A');
- for (int i = 0; i < 100; ++i) {
- long_string[i * 100] = ','; // 添加分隔符
- }
-
- // 测试 std::string 构造(会复制字符)
- {
- Timer timer("std::string 构造(复制 " + std::to_string(iterations) + " 次)");
- for (int i = 0; i < iterations; ++i) {
- std::string copy = long_string; // 复制整个字符串
- (void)copy;
- }
- }
-
- // 测试 std::string_view 构造(只复制指针)
- {
- Timer timer("std::string_view 构造(" + std::to_string(iterations) + " 次)");
- for (int i = 0; i < iterations; ++i) {
- std::string_view view = long_string; // 只复制16字节
- (void)view;
- }
- }
- }
- /**
- * @brief 性能测试2:子串操作性能对比
- * @param iterations 迭代次数
- *
- * @details 对比提取 10000 个子串的耗时
- * string::substr 会复制数据,string_view::substr 只调整视图边界
- */
- void test_substr_performance(int iterations = 10000) {
- std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
- std::cout << "│ 测试2:子串操作性能对比(提取 " << iterations << " 个子串) │\n";
- std::cout << "└─────────────────────────────────────────────────────────────┘\n";
-
- std::string data = "https://www.example.com/very/long/path/to/resource";
-
- // 测试 std::string::substr(复制数据)
- {
- Timer timer("std::string::substr(复制数据)");
- for (int i = 0; i < iterations; ++i) {
- std::string sub = data.substr(8, 10); // 复制 "www.exampl"
- (void)sub;
- }
- }
-
- // 测试 std::string_view::substr(零拷贝)
- {
- Timer timer("std::string_view::substr(零拷贝)");
- std::string_view view = data;
- for (int i = 0; i < iterations; ++i) {
- std::string_view sub = view.substr(8, 10); // 只调整边界
- (void)sub;
- }
- }
- }
- /**
- * @brief 测试3:字符串分割性能对比
- * @param iterations 重复测试次数
- *
- * @details 对比传统方式和视图方式在字符串分割上的性能差异
- * 处理 1MB 的 CSV 数据,分割成约 200,000 个字段
- */
- void test_split_performance(int iterations = 10) {
- std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
- std::cout << "│ 测试3:字符串分割性能对比(1MB CSV 数据,重复 " << iterations << " 次) │\n";
- std::cout << "└─────────────────────────────────────────────────────────────┘\n";
-
- // 生成测试数据:1MB 的 CSV 格式字符串
- std::string csv_data;
- csv_data.reserve(1024 * 1024);
- for (int i = 0; i < 100000; ++i) {
- csv_data += "field" + std::to_string(i) + ",";
- }
- csv_data.back() = '\n'; // 替换最后一个逗号
-
- // 测试 std::string 分割(会复制每个字段)
- {
- long long total_time = 0;
- for (int i = 0; i < iterations; ++i) {
- auto start = std::chrono::high_resolution_clock::now();
- auto result = split_string(csv_data, ',');
- auto end = std::chrono::high_resolution_clock::now();
- total_time += std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
-
- // 验证结果
- if (i == 0) {
- std::cout << " 📊 std::string 分割: " << result.size() << " 个字段\n";
- }
- }
- std::cout << " ⏱️ std::string 分割(平均): "
- << std::setw(8) << total_time / iterations << " 微秒\n";
- }
-
- // 测试 std::string_view 分割(零拷贝)
- {
- long long total_time = 0;
- for (int i = 0; i < iterations; ++i) {
- auto start = std::chrono::high_resolution_clock::now();
- auto result = split_string_view(csv_data, ',');
- auto end = std::chrono::high_resolution_clock::now();
- total_time += std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
-
- if (i == 0) {
- std::cout << " 📊 string_view 分割: " << result.size() << " 个视图\n";
- }
- }
- std::cout << " ⏱️ string_view 分割(平均): "
- << std::setw(8) << total_time / iterations << " 微秒\n";
- }
- }
- /**
- * @brief 测试4:函数参数传递性能对比
- * @param iterations 迭代次数
- *
- * @details 对比传递 string 引用 vs string_view 的性能
- * string_view 更轻量,且不会引起不必要的分配
- */
- void test_parameter_passing(int iterations = 1000000) {
- std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
- std::cout << "│ 测试4:函数参数传递性能对比(调用 " << iterations << " 次) │\n";
- std::cout << "└─────────────────────────────────────────────────────────────┘\n";
-
- std::string data = "这是一个测试字符串,用于性能对比";
-
- // 定义测试函数
- auto process_by_const_ref = [](const std::string& s) -> size_t {
- return s.size();
- };
-
- auto process_by_string_view = [](std::string_view sv) -> size_t {
- return sv.size();
- };
-
- // 测试 const std::string& 传递
- {
- Timer timer("const std::string& 传递");
- size_t sum = 0;
- for (int i = 0; i < iterations; ++i) {
- sum += process_by_const_ref(data);
- }
- (void)sum;
- }
-
- // 测试 std::string_view 传递
- {
- Timer timer("std::string_view 传递");
- size_t sum = 0;
- for (int i = 0; i < iterations; ++i) {
- sum += process_by_string_view(data);
- }
- (void)sum;
- }
-
- // 测试从字面量传递的情况(string_view 优势更明显)
- {
- Timer timer("string_view + 字面量(避免临时 string)");
- size_t sum = 0;
- for (int i = 0; i < iterations; ++i) {
- sum += process_by_string_view("Hello, World!");
- }
- (void)sum;
- }
- }
- /**
- * @brief 测试5:大规模数据处理性能对比
- * @param data_size_mb 数据大小(MB)
- *
- * @details 处理 10MB 的日志数据,模拟实际场景
- * 统计以 'ERROR' 开头的行数
- */
- void test_large_data_processing(int data_size_mb = 10) {
- std::cout << "\n┌─────────────────────────────────────────────────────────────┐\n";
- std::cout << "│ 测试5:大规模数据处理(" << data_size_mb << " MB 日志数据) │\n";
- std::cout << "└─────────────────────────────────────────────────────────────┘\n";
-
- // 生成测试日志数据
- std::string log_data;
- log_data.reserve(data_size_mb * 1024 * 1024);
-
- for (int i = 0; i < 500000; ++i) {
- if (i % 100 == 0) {
- log_data += "ERROR: Something went wrong at step " + std::to_string(i) + "\n";
- } else {
- log_data += "INFO: Processing step " + std::to_string(i) + "\n";
- }
- }
-
- // 使用 std::string 的方式(复制并分割)
- {
- Timer timer("std::string 方式(复制行数据)");
- int error_count = 0;
- size_t start = 0;
- std::string data_copy = log_data; // 先复制整个数据
-
- while (start < data_copy.size()) {
- size_t end = data_copy.find('\n', start);
- if (end == std::string::npos) break;
-
- std::string line = data_copy.substr(start, end - start); // 复制每一行
- if (line.size() >= 5 && line.substr(0, 5) == "ERROR") {
- ++error_count;
- }
- start = end + 1;
- }
- std::cout << " 📊 找到 " << error_count << " 条 ERROR 日志\n";
- }
-
- // 使用 std::string_view 的方式(只读视图)
- {
- Timer timer("string_view 方式(零拷贝)");
- int error_count = 0;
- size_t start = 0;
- std::string_view view = log_data; // 不复制
-
- while (start < view.size()) {
- size_t end = view.find('\n', start);
- if (end == std::string_view::npos) break;
-
- std::string_view line = view.substr(start, end - start); // 零拷贝视图
- if (line.size() >= 5 && line.substr(0, 5) == "ERROR") {
- ++error_count;
- }
- start = end + 1;
- }
- std::cout << " 📊 找到 " << error_count << " 条 ERROR 日志\n";
- }
- }
- /**
- * @brief 显示速度对比总结
- */
- void show_summary() {
- std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 性能测试总结 ║\n";
- std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
- std::cout << "║ std::string_view 的核心性能优势: ║\n";
- std::cout << "║ ║\n";
- std::cout << "║ 1. 零拷贝构造 → 只复制16字节,与字符串长度无关 ║\n";
- std::cout << "║ 2. 零拷贝子串 → O(1) 时间,不随子串长度增加 ║\n";
- std::cout << "║ 3. 减少内存分配 → 避免频繁的 malloc/free ║\n";
- std::cout << "║ 4. 更好的缓存局部性 → 数据在原位置,不产生内存碎片 ║\n";
- std::cout << "║ 5. 无额外内存开销 → 适合嵌入式/高性能环境 ║\n";
- std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
- std::cout << "║ 适用场景: ║\n";
- std::cout << "║ • 函数参数(避免不必要的拷贝) ║\n";
- std::cout << "║ • 字符串解析(配置文件、日志、网络协议) ║\n";
- std::cout << "║ • 大规模文本处理(日志分析、数据清洗) ║\n";
- std::cout << "║ • 只读访问场景(查找、比较、过滤) ║\n";
- std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
- std::cout << "║ 注意事项: ║\n";
- std::cout << "║ • 不保证以 '\\0' 结尾,使用 .data() 需谨慎 ║\n";
- std::cout << "║ • 不拥有数据,需确保底层数据生命周期更长 ║\n";
- std::cout << "║ • 修改原字符串会使视图失效 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- }
- // ==================== 主函数 ====================
- int main() {
- std::cout << "\n";
- std::cout << "╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ std::string_view 性能对比测试 ║\n";
- std::cout << "║ 编译优化: -O2 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
-
- // 运行所有性能测试
- test_construction_performance(1000);
- test_substr_performance(10000);
- test_split_performance(10);
- test_parameter_passing(1000000);
- test_large_data_processing(5);
-
- // 显示总结
- show_summary();
-
- return 0;
- }
复制代码- ╔═══════════════════════════════════════════════════════════════╗
- ║ std::string_view 性能对比测试 ║
- ║ 编译优化: -O2 ║
- ╚═══════════════════════════════════════════════════════════════╝
- ┌─────────────────────────────────────────────────────────────┐
- │ 测试1:构造性能对比(创建 1000 个子串) │
- └─────────────────────────────────────────────────────────────┘
- ⏱️ std::string 构造(复制 1000 次): 813 微秒
- ⏱️ std::string_view 构造(1000 次): 16 微秒
- ┌─────────────────────────────────────────────────────────────┐
- │ 测试2:子串操作性能对比(提取 10000 个子串) │
- └─────────────────────────────────────────────────────────────┘
- ⏱️ std::string::substr(复制数据): 3824 微秒
- ⏱️ std::string_view::substr(零拷贝): 342 微秒
- ┌─────────────────────────────────────────────────────────────┐
- │ 测试3:字符串分割性能对比(1MB CSV 数据,重复 10 次) │
- └─────────────────────────────────────────────────────────────┘
- 📊 std::string 分割: 100000 个字段
- ⏱️ std::string 分割(平均): 188841 微秒
- 📊 string_view 分割: 100000 个视图
- ⏱️ string_view 分割(平均): 11620 微秒
- ┌─────────────────────────────────────────────────────────────┐
- │ 测试4:函数参数传递性能对比(调用 1000000 次) │
- └─────────────────────────────────────────────────────────────┘
- ⏱️ const std::string& 传递: 3061 微秒
- ⏱️ std::string_view 传递: 18145 微秒
- ⏱️ string_view + 字面量(避免临时 string): 13493 微秒
- ┌─────────────────────────────────────────────────────────────┐
- │ 测试5:大规模数据处理(5 MB 日志数据) │
- └─────────────────────────────────────────────────────────────┘
- 📊 找到 5000 条 ERROR 日志
- ⏱️ std::string 方式(复制行数据): 589907 微秒
- 📊 找到 5000 条 ERROR 日志
- ⏱️ string_view 方式(零拷贝): 54217 微秒
- ╔═══════════════════════════════════════════════════════════════╗
- ║ 性能测试总结 ║
- ╠═══════════════════════════════════════════════════════════════╣
- ║ std::string_view 的核心性能优势: ║
- ║ ║
- ║ 1. 零拷贝构造 → 只复制16字节,与字符串长度无关 ║
- ║ 2. 零拷贝子串 → O(1) 时间,不随子串长度增加 ║
- ║ 3. 减少内存分配 → 避免频繁的 malloc/free ║
- ║ 4. 更好的缓存局部性 → 数据在原位置,不产生内存碎片 ║
- ║ 5. 无额外内存开销 → 适合嵌入式/高性能环境 ║
- ╠═══════════════════════════════════════════════════════════════╣
- ║ 适用场景: ║
- ║ • 函数参数(避免不必要的拷贝) ║
- ║ • 字符串解析(配置文件、日志、网络协议) ║
- ║ • 大规模文本处理(日志分析、数据清洗) ║
- ║ • 只读访问场景(查找、比较、过滤) ║
- ╠═══════════════════════════════════════════════════════════════╣
- ║ 注意事项: ║
- ║ • 不保证以 '\0' 结尾,使用 .data() 需谨慎 ║
- ║ • 不拥有数据,需确保底层数据生命周期更长 ║
- ║ • 修改原字符串会使视图失效 ║
- ╚═══════════════════════════════════════════════════════════════╝
- E:\myLearn\StringView\x64\Debug\StringView.exe (进程 24704)已退出,代码为 0 (0x0)。
- 按任意键关闭此窗口. . .
复制代码
⚠️ 注意事项
- 生命周期管理:std::string_view 不管理底层字符串的生命周期,string_view 对象的生命周期不能超过指向的字符串。如果你需要传递一个 string_view,必须确保它指向的字符串在整个使用期间都有效。
- \0 结尾问题:string_view 不一定以 \0 结尾,如果你需要传递给一个以 \0 结尾的 C API(如 fopen),必须确保它是 \0 结尾的,或者手动创建一个以 \0 结尾的副本(例如使用 std::string(sv))。
- 底层字符串修改:如果底层字符串被修改或销毁,string_view 会变成 “悬空” 状态,访问它会导致未定义行为。
- #define _CRT_SECURE_NO_WARNINGS
- /**
- * @file string_view_lifetime_demo.cpp
- * @brief std::string_view 生命周期管理、\0结尾、底层修改 三大注意事项验证
- * @author Code Reviewer
- * @date 2026-05-12
- *
- * @details 本文件演示 std::string_view 的三个关键陷阱:
- * 1. 生命周期管理:string_view 不拥有数据,不能比源字符串活得久
- * 2. \0 结尾问题:string_view 不一定以 \0 结尾,传给 C API 需小心
- * 3. 底层字符串修改:修改原字符串会使 string_view 失效
- *
- * 编译运行:
- * g++ -std=c++17 -O2 string_view_lifetime_demo.cpp -o demo
- * ./demo
- */
- #include <iostream>
- #include <string>
- #include <string_view>
- #include <vector>
- #include <cstring>
- #include <fstream>
- // ==================== 辅助函数 ====================
- /**
- * @brief 安全打印 string_view(带边界标识)
- */
- void safe_print(std::string_view sv, const std::string& title = "") {
- if (!title.empty()) {
- std::cout << "\n【" << title << "】\n";
- }
- std::cout << " 内容: '" << sv << "'\n";
- std::cout << " 长度: " << sv.size() << "\n";
- std::cout << " 是否以\\0结尾: " << (sv.data()[sv.size()] == '\0' ? "是" : "否") << "\n";
- }
- // ==================== 注意事项1:生命周期管理 ====================
- /**
- * @brief 危险示例:返回局部字符串的 string_view
- * @return 悬空的 string_view(未定义行为)
- *
- * ⚠️ 这是典型的错误用法!
- * 局部字符串在函数返回时被销毁,返回的 string_view 变成悬空引用。
- */
- std::string_view dangerous_return() {
- std::string local = "这个字符串马上会被销毁";
- std::string_view sv = local;
- std::cout << " [函数内部] sv 有效: '" << sv << "'\n";
- // ⚠️ 返回 sv,但 local 即将被销毁
- return sv; // 危险!返回悬空引用
- }
- /**
- * @brief 正确示例:返回拥有所有权的字符串
- * @return 安全的 std::string
- *
- * ✅ 如果需要返回字符串,应该返回 std::string 而不是 string_view
- */
- std::string safe_return() {
- std::string local = "这个字符串会安全返回";
- return local; // 移动语义,高效且安全
- }
- /**
- * @brief 正确示例:从已有数据创建 string_view
- * @param data 生命周期足够长的数据
- * @return 安全的 string_view
- *
- * ✅ string_view 只作为观察者,不拥有数据
- */
- std::string_view safe_parameter(const std::string& data) {
- return data; // 安全:调用者保证 data 生命周期足够长
- }
- /**
- * @brief 验证生命周期管理的正确与错误用法
- */
- void verify_lifetime() {
- std::cout << "\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 注意事项1:生命周期管理 ║\n";
- std::cout << "║ string_view 不管理生命周期,不能比源字符串活得久 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- // ✅ 正确用法1:string_view 生命周期在源字符串内
- std::cout << "\n✅ 正确示例:生命周期内使用\n";
- {
- std::string data = "持久存在的字符串";
- std::string_view view = data; // view 指向 data
- std::cout << " view: '" << view << "'\n";
- // data 和 view 都在作用域内,安全
- std::cout << " → 安全:view 和 data 在同一作用域\n";
- }
- // ✅ 正确用法2:作为函数参数
- std::cout << "\n✅ 正确示例:作为函数参数\n";
- {
- std::string data = "作为参数传递";
- auto process = [](std::string_view sv) {
- std::cout << " 函数内接收到: '" << sv << "'\n";
- };
- process(data); // 调用者保证 data 在函数执行期间有效
- std::cout << " → 安全:调用者保证生命周期\n";
- }
- // ❌ 危险示例:返回局部字符串的视图
- std::cout << "\n❌ 危险示例:返回局部变量的 view\n";
- std::cout << " ⚠️ 下面的代码会触发未定义行为,已被注释\n";
- // std::string_view dangling = dangerous_return(); // 未定义行为!
- // std::cout << dangling << "\n"; // 可能崩溃或输出乱码
- std::cout << " → 危险:view 指向已销毁的内存,访问会导致未定义行为\n";
- // ✅ 正确示例:如果需要返回,返回 std::string
- std::cout << "\n✅ 正确做法:需要返回时使用 std::string\n";
- {
- std::string result = safe_return();
- std::cout << " 返回的字符串: '" << result << "'\n";
- std::cout << " → 安全:返回拥有所有权的 std::string\n";
- }
- // 可视化生命周期问题
- std::cout << "\n📊 生命周期图解:\n";
- std::cout << " ┌─────────────────────────────────────────────────────────┐\n";
- std::cout << " │ std::string data = "Hello"; // data 拥有内存 │\n";
- std::cout << " │ std::string_view sv = data; // sv 指向 data │\n";
- std::cout << " │ // data 和 sv 都在作用域内 // ✅ 安全 │\n";
- std::cout << " │ } // data 被销毁 // ❌ sv 变成悬空 │\n";
- std::cout << " └─────────────────────────────────────────────────────────┘\n";
- }
- // ==================== 注意事项2:\0 结尾问题 ====================
- /**
- * @brief 演示 string_view 不一定以 \0 结尾
- *
- * string_view 只存储指针和长度,不保证结尾有 null 终止符。
- * 这对需要 \0 结尾的 C API(如 fopen, strcmp, printf %s)是危险的。
- */
- void verify_null_termination() {
- std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 注意事项2:\\0 结尾问题 ║\n";
- std::cout << "║ string_view 不保证以 \\0 结尾 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- // 情况1:从字符串字面量构造(有 \0 结尾)
- std::cout << "\n📌 情况1:从字符串字面量构造\n";
- std::string_view sv1 = "Hello";
- safe_print(sv1, "字符串字面量");
- std::cout << " 说明:字面量在编译期自动添加 \\0,所以有结尾符\n";
- // 情况2:从 std::string 构造(有 \0 结尾,C++11 起保证)
- std::cout << "\n📌 情况2:从 std::string 构造\n";
- std::string str = "World";
- std::string_view sv2 = str;
- safe_print(sv2, "std::string");
- std::cout << " 说明:std::string 保证 \\0 结尾(C++11 起)\n";
- // 情况3:从子串构造(⚠️ 不一定有 \0 结尾!)
- std::cout << "\n📌 情况3:从子串构造(⚠️ 危险场景)\n";
- std::string long_str = "Hello, World!";
- std::string_view sv3 = long_str.substr(0, 5); // 取 "Hello"
- safe_print(sv3, "substr 子串");
- std::cout << " ⚠️ 危险:substr 创建的视图不一定有 \\0 结尾!\n";
- std::cout << " 📌 原因:substr 只调整指针和长度,不复制数据\n";
- // 情况4:从字符数组的中间位置
- std::cout << "\n📌 情况4:指向字符数组中间\n";
- const char* cstr = "Programming";
- std::string_view sv4(cstr + 3, 6); // 从索引3开始,取6个字符 → "grammi"
- safe_print(sv4, "指向中间");
- std::cout << " ⚠️ 危险:view.data()[view.size()] 访问越界!\n";
- std::cout << " 📌 原因:view 指向字符串内部,后面没有 \\0\n";
- // ❌ 危险操作:传给需要 \0 结尾的 C API
- std::cout << "\n❌ 危险示例:传给 C API\n";
- std::cout << " ⚠️ 以下代码会导致未定义行为,已被注释\n";
- // std::string_view filename = "test.txt"; // 这个没问题
- // FILE* f = fopen(filename.data(), "r"); // 危险!如果 view 没有 \0 结尾会崩溃
- std::cout << " → 问题:C API 期望 \\0 结尾,但 view 不保证\n";
- // ✅ 安全做法:需要 \0 结尾时转换为 std::string
- std::cout << "\n✅ 安全做法:传给 C API 前转换为 std::string\n";
- {
- std::string_view sv = "temp/test.txt";
- // 方式1:显式转换为 string
- std::string filename(sv); // 会复制并添加 \0
- std::cout << " 方式1:std::string(sv) → 会复制并添加 \\0\n";
- // 方式2:临时构造(用于函数调用)
- auto call_c_api = [](const char* path) {
- std::cout << " 调用 C API,路径: " << path << "\n";
- };
- call_c_api(std::string(sv).c_str()); // 临时 string 保证 \0 结尾
- std::cout << " 方式2:std::string(sv).c_str() → 适合单次调用\n";
- }
- // 检测 string_view 是否有 \0 结尾的工具函数
- std::cout << "\n📊 检测 string_view 是否有 \\0 结尾:\n";
- std::cout << " bool is_null_terminated(std::string_view sv) {\n";
- std::cout << " return sv.data()[sv.size()] == '\\0';\n";
- std::cout << " }\n";
- std::cout << " ⚠️ 注意:即使检测为 false,访问也是未定义行为!\n";
- }
- // ==================== 注意事项3:底层字符串修改 ====================
- /**
- * @brief 演示底层字符串修改对 string_view 的影响
- *
- * string_view 只是观察者,不拥有数据。
- * 如果底层字符串被修改或重新分配,string_view 会变成悬空状态。
- */
- void verify_underlying_modification() {
- std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 注意事项3:底层字符串修改 ║\n";
- std::cout << "║ 修改原字符串会使 string_view 失效 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- // 场景1:通过原字符串修改内容
- std::cout << "\n📌 场景1:修改原字符串内容\n";
- {
- std::string data = "Original Data";
- std::string_view view = data;
- std::cout << " 初始 data: '" << data << "'\n";
- std::cout << " 初始 view: '" << view << "'\n";
- // 修改原字符串
- data[0] = 'M';
- data[1] = 'o';
- data[2] = 'd';
- std::cout << " 修改后 data: '" << data << "'\n";
- std::cout << " 修改后 view: '" << view << "'\n";
- std::cout << " → view 反映了修改(指向同一内存)\n";
- std::cout << " ⚠️ 注意:这可能不是期望的行为!\n";
- }
- // 场景2:字符串重新分配(最危险!)
- std::cout << "\n📌 场景2:字符串重新分配(悬空引用)\n";
- {
- std::string data = "Short string"; // SSO 可能不分配堆内存
- // 强制使用堆内存
- data = "This is a very long string that will definitely allocate heap memory";
- std::string_view view = data;
- std::cout << " 初始 view: '" << view << "'\n";
- std::cout << " 原字符串地址: " << (void*)view.data() << "\n";
- // ⚠️ 追加内容可能导致重新分配
- data += " and some more data to trigger reallocation!";
- std::cout << " ⚠️ 重新分配后 string_view 变成悬空!\n";
- std::cout << " 新字符串地址: " << (void*)data.data() << "\n";
- std::cout << " view 仍指向旧地址: " << (void*)view.data() << "\n";
- // std::cout << " view: '" << view << "'\n"; // 未定义行为!
- std::cout << " → 危险:访问悬空 view 会导致未定义行为\n";
- }
- // 场景3:字符串销毁
- std::cout << "\n📌 场景3:字符串销毁(经典陷阱)\n";
- {
- std::string_view view;
- {
- std::string data = "临时数据";
- view = data; // view 指向 data
- std::cout << " 作用域内 view: '" << view << "'\n";
- } // data 在这里被销毁
- // std::cout << view << "\n"; // 未定义行为!
- std::cout << " → data 已销毁,view 变成悬空引用\n";
- std::cout << " ⚠️ 访问 view 会导致缓冲区溢出或崩溃\n";
- }
- // 场景4:字符数组被修改
- std::cout << "\n📌 场景4:栈上字符数组被修改\n";
- {
- char buffer[] = "Initial value";
- std::string_view view = buffer;
- std::cout << " 初始 view: '" << view << "'\n";
- // 修改原始数组
- strcpy(buffer, "Modified!");
- std::cout << " 修改后 view: '" << view << "'\n";
- std::cout << " → view 反映了修改(指向同一内存)\n";
- }
- // 安全实践
- std::cout << "\n✅ 安全实践:\n";
- std::cout << " 1. 确保 string_view 不拥有数据,只作为临时观察者\n";
- std::cout << " 2. 不要在 string_view 存活期间修改/重分配底层字符串\n";
- std::cout << " 3. 如果需要长期保存,使用 std::string 而不是 string_view\n";
- std::cout << " 4. 函数参数用 string_view,函数返回值用 string\n";
- }
- // ==================== 综合示例:正确使用 string_view ====================
- /**
- * @brief 展示 string_view 的正确使用模式
- */
- class LogParser {
- private:
- std::string log_data_; // 拥有数据
- std::vector<std::string_view> lines_; // 视图,不拥有数据
- public:
- /**
- * @brief 构造函数:接收数据并创建视图
- * @param data 日志数据(会被复制)
- */
- explicit LogParser(const std::string& data) : log_data_(data) {
- // 安全:log_data_ 拥有数据,lines_ 只是视图
- size_t start = 0;
- while (start < log_data_.size()) {
- size_t end = log_data_.find('\n', start);
- if (end == std::string::npos) {
- lines_.push_back(std::string_view(log_data_.c_str() + start));
- break;
- }
- lines_.push_back(std::string_view(log_data_.c_str() + start, end - start));
- start = end + 1;
- }
- }
- /**
- * @brief 获取行数
- */
- size_t line_count() const { return lines_.size(); }
- /**
- * @brief 获取指定行(安全:数据生命周期由类保证)
- */
- std::string_view get_line(size_t index) const {
- if (index >= lines_.size()) {
- return {};
- }
- return lines_[index];
- }
- /**
- * @brief 查找包含特定字符串的行
- */
- std::vector<std::string_view> find_lines(std::string_view keyword) const {
- std::vector<std::string_view> result;
- for (auto line : lines_) {
- if (line.find(keyword) != std::string_view::npos) {
- result.push_back(line);
- }
- }
- return result;
- }
- };
- /**
- * @brief 演示综合示例
- */
- void demonstrate_best_practice() {
- std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 最佳实践示例:LogParser 类 ║\n";
- std::cout << "║ 正确使用 string_view 管理生命周期 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- // 创建测试数据
- std::string log_data =
- "[INFO] Application started\n"
- "[ERROR] Failed to open config file\n"
- "[WARN] Deprecated API used\n"
- "[INFO] Connection established\n"
- "[ERROR] Timeout occurred\n";
- // LogParser 拥有数据,内部视图安全
- LogParser parser(log_data);
- std::cout << "\n📊 日志解析结果:\n";
- std::cout << " 总行数: " << parser.line_count() << "\n";
- std::cout << "\n 查找包含 '[ERROR]' 的行:\n";
- auto error_lines = parser.find_lines("[ERROR]");
- for (auto line : error_lines) {
- std::cout << " • " << line << "\n";
- }
- std::cout << "\n ✅ 关键点:\n";
- std::cout << " 1. LogParser 拥有 log_data_ 的所有权\n";
- std::cout << " 2. lines_ 中的 string_view 指向 log_data_\n";
- std::cout << " 3. 返回 string_view 是安全的(数据生命周期由类保证)\n";
- std::cout << " 4. 不会发生悬空引用或内存泄漏\n";
- }
- // ==================== 主函数 ====================
- int main() {
- std::cout << "\n";
- std::cout << "╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ std::string_view 三大注意事项验证程序 ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- // 运行所有验证
- verify_lifetime();
- verify_null_termination();
- verify_underlying_modification();
- demonstrate_best_practice();
- // 最终总结
- std::cout << "\n\n╔═══════════════════════════════════════════════════════════════╗\n";
- std::cout << "║ 总结与检查清单 ║\n";
- std::cout << "╠═══════════════════════════════════════════════════════════════╣\n";
- std::cout << "║ □ 生命周期:确保 string_view 不超出源字符串的作用域 ║\n";
- std::cout << "║ □ \\0 结尾:传给 C API 前确认或转换为 std::string ║\n";
- std::cout << "║ □ 底层修改:view 存活期间不修改/重分配底层字符串 ║\n";
- std::cout << "║ □ 返回值:需要返回时用 std::string,不要返回 string_view ║\n";
- std::cout << "║ □ 参数:函数参数用 string_view(临时观察者) ║\n";
- std::cout << "║ □ 成员:类成员用 std::string(拥有数据) ║\n";
- std::cout << "╚═══════════════════════════════════════════════════════════════╝\n";
- return 0;
- }
复制代码
|