【C++】string_view:高效字符串处理指南

文章目录

      • [一、`string_view` 出现的意义](#一、string_view 出现的意义)
      • [二、`string_view` 的应用场景](#二、string_view 的应用场景)
        • [1. 函数参数(最核心场景)](#1. 函数参数(最核心场景))
        • [2. 字符串切片/子串操作](#2. 字符串切片/子串操作)
        • [3. 容器中存储只读字符串的"引用"](#3. 容器中存储只读字符串的“引用”)
      • [三、`string_view` 的注意事项(核心坑点)](#三、string_view 的注意事项(核心坑点))
        • [1. 生命周期问题(最容易踩坑)](#1. 生命周期问题(最容易踩坑))
        • [2. 不保证以 '\0' 结尾](#2. 不保证以 '\0' 结尾)
        • [3. 不可修改字符串](#3. 不可修改字符串)
        • [4. 赋值/拷贝的浅语义](#4. 赋值/拷贝的浅语义)
      • 总结

你想深入了解 C++ 中的 string_view,包括它被引入的意义、适用场景以及使用时需要注意的问题,我会从新手能理解的角度,由浅入深地为你讲解。

一、string_view 出现的意义

string_view (C++17 引入) 出现之前,处理字符串"只读访问"的场景主要依赖 const std::string&const char*,但这两种方式都有明显的缺陷:

  1. const std::string& 的问题 :如果传入的是 const char*(比如字面量 "hello"),会隐式构造一个 std::string 对象,带来内存分配、拷贝的开销,哪怕只是临时只读访问,也会产生不必要的性能损耗。
  2. const char* 的问题 :只保存字符串起始地址,丢失了长度信息,每次获取长度都要调用 strlen()(遍历字符串直到 '\0'),且无法安全处理非空终止的字符序列(比如内存中一段连续的字符但没有 '\0')。

string_view 的核心意义就是解决这些问题:

  • 零拷贝 :它本质是一个"视图",只保存字符串起始地址长度,不持有内存、不拷贝数据,仅提供只读访问,彻底消除不必要的内存分配和拷贝。
  • 统一接口 :兼容 std::stringconst char*、非空终止的字符序列,提供和 std::string 类似的成员函数(如 substrfindsize 等),简化只读字符串的处理逻辑。
  • 更高效率 :长度直接存储,无需调用 strlen(),访问长度的时间复杂度是 O(1)。

二、string_view 的应用场景

string_view 仅适用于只读访问字符串 的场景,核心原则是:不修改、不负责内存生命周期,常见场景包括:

1. 函数参数(最核心场景)

当函数只需要"读"字符串,不需要修改、不需要持有字符串时,用 string_view 替代 const std::string&const char*,避免隐式拷贝。

cpp 复制代码
#include <iostream>
#include <string_view>
#include <string>

// 旧写法:传入 const char* 会隐式构造 string,有拷贝开销
void print_old(const std::string& s) {
    std::cout << s << std::endl;
}

// 新写法:零拷贝,兼容 string/const char*/字符数组
void print_new(std::string_view s) {
    std::cout << s << std::endl;
}

int main() {
    const char* c_str = "hello world";
    std::string std_str = "cpp string";
    char char_arr[] = {'a', 'b', 'c'}; // 无 '\0' 的字符数组

    // 旧写法:传入 c_str 会构造临时 string,传入 char_arr 会越界(因为找 '\0')
    print_old(c_str); // 有拷贝开销
    // print_old(char_arr); // 危险:越界访问

    // 新写法:零拷贝,且能处理非空终止的字符序列
    print_new(c_str); // 无拷贝
    print_new(std_str); // 无拷贝
    print_new(std::string_view(char_arr, 3)); // 安全访问前3个字符,输出 "abc"
    return 0;
}
2. 字符串切片/子串操作

std::stringsubstr() 会返回新的 string(拷贝数据),而 string_viewsubstr() 仅返回新的视图(仅调整起始地址和长度),效率极高。

cpp 复制代码
#include <iostream>
#include <string_view>

int main() {
    std::string_view sv = "1234567890";
    // 切片:取第2到第5个字符(索引从0开始),无数据拷贝
    std::string_view sub_sv = sv.substr(1, 4); 
    std::cout << sub_sv << std::endl; // 输出 "2345"
    return 0;
}
3. 容器中存储只读字符串的"引用"

如果容器只需要保存字符串的只读视图(不管理内存),用 vector<string_view> 替代 vector<string>,大幅减少内存占用。

注意:必须保证原字符串的生命周期长于 string_view

三、string_view 的注意事项(核心坑点)

string_view 是"视图"而非"持有者",这是它所有注意事项的根源,新手务必牢记:

1. 生命周期问题(最容易踩坑)

string_view 不持有内存,仅指向外部字符串的内存区域。如果原字符串被销毁(比如局部变量出作用域、动态内存被释放),string_view 会变成野指针,访问时会导致未定义行为。

cpp 复制代码
#include <iostream>
#include <string_view>

std::string_view bad_function() {
    std::string temp_str = "temporary string"; // 局部变量,函数结束后销毁
    return std::string_view(temp_str); // 返回的是指向已销毁内存的视图
}

int main() {
    std::string_view sv = bad_function();
    std::cout << sv << std::endl; // 未定义行为:访问已释放的内存
    return 0;
}

解决方案 :确保 string_view 的生命周期 ≤ 原字符串的生命周期。

2. 不保证以 '\0' 结尾

string_view 是按"长度"管理字符串的,而非依赖 '\0' 终止符。如果原字符串没有 '\0'(比如字符数组),直接将 string_view 传给需要 '\0' 终止的函数(如 strlen()printf("%s"))会导致越界。

cpp 复制代码
#include <iostream>
#include <string_view>
#include <cstdio>

int main() {
    char arr[] = {'x', 'y', 'z'}; // 无 '\0'
    std::string_view sv(arr, 3);

    // 错误用法:printf("%s") 期望 '\0' 终止的字符串,会越界
    // printf("%s\n", sv.data()); 

    // 正确用法1:用 string_view 的接口访问
    std::cout << sv << std::endl; // 正确,输出 "xyz"

    // 正确用法2:转为 std::string(会自动补 '\0')
    std::string s = sv.data(); // 错误:直接取 data() 无 '\0'
    std::string s2(sv); // 正确:构造 string 时会补 '\0'
    printf("%s\n", s2.c_str()); // 正确,输出 "xyz"
    return 0;
}
3. 不可修改字符串

string_view 的设计目标是"只读",它没有提供任何修改字符串的接口(如 push_backreplace)。如果需要修改,必须先转为 std::string

cpp 复制代码
#include <iostream>
#include <string_view>
#include <string>

int main() {
    std::string_view sv = "hello";
    // sv[0] = 'H'; // 编译错误:string_view 的元素是 const char

    // 正确:转为 string 后修改
    std::string s = sv;
    s[0] = 'H';
    std::cout << s << std::endl; // 输出 "Hello"
    return 0;
}
4. 赋值/拷贝的浅语义

string_view 的拷贝、赋值都是"浅拷贝"(仅拷贝地址和长度),而非 std::string 的"深拷贝"(拷贝数据)。如果多个 string_view 指向同一个原字符串,原字符串修改(如果可修改)会影响所有视图:

cpp 复制代码
#include <iostream>
#include <string_view>
#include <string>

int main() {
    std::string s = "test";
    std::string_view sv1 = s;
    std::string_view sv2 = sv1;

    s[0] = 'T'; // 修改原字符串
    std::cout << sv1 << std::endl; // 输出 "Test"(视图也变了)
    std::cout << sv2 << std::endl; // 输出 "Test"
    return 0;
}

总结

  1. 核心意义string_view 是 C++17 为解决"只读字符串访问"的性能问题而生,核心是零拷贝、统一接口、高效访问
  2. 核心场景:只读的函数参数、字符串切片操作、存储只读字符串视图的容器,核心原则是"只读、不管理内存"。
  3. 核心注意事项:必须保证原字符串生命周期更长、不依赖 '\0' 终止符、不可修改字符串、拷贝是浅语义。

简单来说,使用 string_view 的口诀是:只读用它,修改转 string;生命周期是底线,\0 终止要留心

相关推荐
玄同7655 小时前
我的 Trae Skill 实践|使用 UV 工具一键搭建 Python 项目开发环境
开发语言·人工智能·python·langchain·uv·trae·vibe coding
Word码5 小时前
[C++语法] 继承 (用法详解)
java·jvm·c++
Yorlen_Zhang5 小时前
Python Tkinter Text 控件完全指南:从基础编辑器到富文本应用
开发语言·python·c#
lxl13075 小时前
C++算法(1)双指针
开发语言·c++
淀粉肠kk5 小时前
C++11列表初始化:{}的革命性进化
c++
不绝1915 小时前
C#进阶:预处理指令/反射,Gettype,Typeof/关键类
开发语言·c#
无小道5 小时前
Qt-qrc机制简单介绍
开发语言·qt
zhooyu5 小时前
C++和OpenGL手搓3D游戏编程(20160207进展和效果)
开发语言·c++·游戏·3d·opengl
HAPPY酷5 小时前
C++ 和 Python 的“容器”对决:从万金油到核武器
开发语言·c++·python