string和string_view的区别

以下是四个代码示例,阐述string和string_view的区别(千问生成)


✅ 示例1:所有权与生命周期(悬空陷阱)

#include <iostream>
#include <string>
#include <string_view>

std::string_view dangerous() {
    std::string local = "Temporary Data";
    return std::string_view(local); // ❌ 返回指向已销毁内存的视图!
} // local 析构,视图悬空

std::string safe() {
    return std::string("Permanent Data"); // ✅ 返回新分配的字符串副本
}

int main() {
    // ⚠️ 危险操作(实际运行可能崩溃/乱码,此处仅演示逻辑)
    // std::string_view sv = dangerous(); 
    // std::cout << sv << "\n"; // 未定义行为!(注释掉避免实际崩溃)

    // ✅ 安全操作
    std::string s = safe();
    std::cout << "Safe return: " << s << "\n"; // Safe return: Permanent Data
    
    // 💡 正确使用 string_view 的方式:确保底层数据存活
    std::string keeper = "I am alive!";
    std::string_view valid_view(keeper); // ✅ keeper 生命周期覆盖 valid_view
    std::cout << "Valid view: " << valid_view << "\n"; // Valid view: I am alive!
}

输出

Safe return: Permanent Data
Valid view: I am alive!

🔑 核心string_view 是“借来的指针”,必须确保“出借方”(底层数据)在使用期间存活!


✅ 示例2:可变性对比(彻底修正逻辑错误!)

#include <iostream>
#include <string>
#include <string_view>

int main() {
    // ✅ std::string:真正修改内存内容
    std::string s = "Hello";
    s[0] = 'J';          // s 变为 "Jello"
    s.append(" World");  // s 变为 "Jello World"
    std::cout << "Modified string: " << s << "\n"; // Modified string: Jello World

    // ✅ std::string_view:只读 + C++20 视图范围调整(不改原数据!)
    std::string_view sv = s; // sv 视图整个 "Jello World"
    // sv[0] = 'X';      // ❌ 编译错误!C++17/20 均禁止修改底层字符
    
    // C++20 允许调整视图范围(仅改变 sv 自身的起始/长度,s 不变!)
    sv.remove_prefix(1); // sv 现在视图 "ello World"(跳过首字符)
    sv.remove_suffix(6); // sv 现在视图 "ello"(再跳过后6字符)
    
    std::cout << "Original string (unchanged): " << s << "\n"; // Jello World
    std::cout << "Adjusted view: [" << sv << "]\n";            // Adjusted view: [ello]
    
    // 验证:底层数据未被 string_view 操作影响
    std::cout << "s[0] still 'J'? " << (s[0] == 'J' ? "YES" : "NO") << "\n"; // YES
}

输出

Modified string: Jello World
Original string (unchanged): Jello World
Adjusted view: [ello]
s[0] still 'J'? YES

🔑 核心

  • string 修改 = 真改内存
  • string_view “修改” = 仅调整自己的窗口范围(C++20),绝不碰原始数据

✅ 示例3:空终止陷阱(彻底修正初始化方式!)

#include <iostream>
#include <string>
#include <string_view>
#include <cstring>

int main() {
    // ✅ 正确初始化:显式指定长度,强制包含中间 \0 和后续字符
    const char raw[] = "C:\\file\0name.txt"; // 编译期保留全部16字节(含\0)
    std::string path(raw, 16); // 关键!带长度构造,保留二进制数据
    
    std::cout << "path.length(): " << path.length() << "\n"; // 16
    std::cout << "path.c_str() output: [" << path.c_str() << "]\n"; // [C:\file](遇\0截断)
    
    std::string_view sv(path.data(), path.length());
    std::cout << "sv.length(): " << sv.length() << "\n"; // 16
    
    // ❌ 危险:直接输出 sv.data() 会被 \0 截断
    std::cout << "sv.data() output: [" << sv.data() << "]\n"; // [C:\file]
    
    // ✅ 安全验证:用 memcmp 比较完整16字节
    if (memcmp(sv.data(), raw, 16) == 0) {
        std::cout << "✅ sv 完整保留了二进制数据!\n"; // 会输出!
    }
    
    // ✅ 安全输出含 \0 的内容(使用 write + length)
    std::cout << "Full content (hex): ";
    for (size_t i = 0; i < sv.length(); ++i) {
        printf("%02X ", static_cast<unsigned char>(sv[i]));
    }
    std::cout << "\n";
    
    // 🌰 您原代码风险演示
    // LOG_INFO << folder.data(); // 若 folder 含 \0,日志可能只显示 "C:\\file"
}

输出

path.length(): 16
path.c_str() output: [C:\file]
sv.length(): 16
sv.data() output: [C:\file]
✅ sv 完整保留了二进制数据!
Full content (hex): 43 3A 5C 66 69 6C 65 00 6E 61 6D 65 2E 74 78 74 

🔑 核心

  • \\ → 编译后 1字节 \
  • \0 → 编译后 1字节 空字符
  • 必须用 (buf, len) 构造才能保留 \0 后的数据!
  • 输出含 \0 数据:禁用 <<,改用 write() 或十六进制打印!

✅ 示例4:性能对比(高频调用场景)

#include <iostream>
#include <string>
#include <string_view>
#include <chrono>

void process_string(std::string s) {}      // 每次深拷贝
void process_view(std::string_view sv) {}  // 仅拷贝指针+长度(16字节)

int main() {
    std::string big(10000, 'A'); // 10KB 字符串
    
    // 测试 string 拷贝开销
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; ++i) process_string(big);
    auto mid = std::chrono::high_resolution_clock::now();
    
    // 测试 string_view 零拷贝
    for (int i = 0; i < 10000; ++i) process_view(big);
    auto end = std::chrono::high_resolution_clock::now();
    
    auto str_us = std::chrono::duration_cast<std::chrono::microseconds>(mid - start).count();
    auto view_us = std::chrono::duration_cast<std::chrono::microseconds>(end - mid).count();
    
    std::cout << "std::string  耗时: " << str_us << " μs (深拷贝 100MB)\n";
    std::cout << "string_view 耗时: " << view_us << " μs (仅拷贝 160KB)\n";
    std::cout << "性能提升: " << (str_us * 1.0 / view_us) << " 倍\n";
}

典型输出(环境不同数值有差异):

std::string  耗时: 15200 μs (深拷贝 100MB)
string_view 耗时: 85 μs (仅拷贝 160KB)
性能提升: 178.8 倍

🔑 核心:高频只读场景,string_view 避免无谓拷贝,性能碾压!


📌 终极使用口诀(结合修正后示例)

场景 用谁 为什么 口诀
函数只读参数 string_view 零拷贝、泛化接口 “看数据,用视图”
需修改/存储 std::string 拥有内存,安全可控 “要改动,必拥有”
\0 二进制 string_view + 显式长度 避免截断 “有 \0,带长度;输出时,用 write"
返回临时字符串 std::string 避免悬空 “返回值,拷贝走”
与 C API 交互 std::string c_str() 保证 \0 结尾 “交 C 库,用 c_str()"

posted on 2026-03-18 16:27  四季萌芽V  阅读(4)  评论(0)    收藏  举报

导航