C++的string_view从基础到通天!

C++ std::string_view 全面讲解

文章目录

std::string_view 可以概括为:

一个不拥有字符串内容、只保存"字符地址 + 字符数量"的只读字符串窗口。

它在 C++17 中加入标准库,定义在 <string_view> 中。标准将它描述为"对连续只读字符序列的引用";典型实现内部只需要一个指针和一个长度。(Cppreference)

cpp 复制代码
#include <string_view>

std::string_view sv = "hello";

可以把它想象成:

text 复制代码
底层字符:
[ h ][ e ][ l ][ l ][ o ][ \0 ]
  ^
  |
sv.data()

sv.size() == 5

它只是观察这些字符:

  • 不负责分配内存;
  • 不负责释放内存;
  • 通常不复制字符;
  • 不能通过 string_view 修改字符;
  • 必须保证底层字符活得比 string_view 久。

最后一点------生命周期 ------是 string_view 最重要、也最容易出错的地方。


一、最基本的使用

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

void print(std::string_view text)
{
    std::cout << text << '\n';
}

int main()
{
    std::string s = "hello";

    print(s);                  // std::string
    print("world");            // 字符串字面量
    print(std::string_view{"C++"});
}

std::stringconst char* 都可以隐式转换为 std::string_view,所以一个接收 string_view 的函数可以同时处理字符串对象、字符串字面量和字符指针。标准也明确把这种统一字符串参数的能力作为其设计目的之一。(Eel.is)


二、它和 std::string 有什么区别?

std::string 拥有字符

cpp 复制代码
std::string s = "hello";

大致可以理解为:

text 复制代码
std::string
    |
    +-- 拥有一块内存
        [h][e][l][l][o][\0]

std::string 负责:

  • 保存字符;
  • 分配和释放内存;
  • 扩容;
  • 修改内容;
  • 管理生命周期。

std::string_view 不拥有字符

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

大致是:

text 复制代码
std::string s
    |
    +-- [h][e][l][l][o][\0]
         ^
         |
     string_view sv
     pointer + length

sv 只指向 s 的字符。它不会延长 s 的生命周期,也不会保存一份副本。

对比表

特性 std::string std::string_view
是否拥有字符
是否通常分配内存 可能
是否复制字符 可能 通常不复制
能否修改字符 可以 不能通过 view 修改
能否保存结果 可以 要保证底层生命周期
子串操作 通常复制字符 通常只调整指针和长度
是否保证结尾有 \0 c_str() 保证 不保证
适合做只读参数 可以 通常更灵活
适合长期存储 需要非常谨慎

三、string_view 的本质:指针和长度

可以用下面这个简化结构理解它:

cpp 复制代码
class simplified_string_view
{
    const char* data_;
    std::size_t size_;
};

这不是标准要求的实际实现代码,但标准的说明性定义确实表现为一个 const charT* 和一个 size_type。因此复制一个 string_view,通常只是复制地址和长度,而不是复制整个字符串。(Eel.is)

例如:

cpp 复制代码
std::string_view sv = "hello world";

std::string_view sub = sv.substr(6, 5);

内存大致是:

text 复制代码
[h][e][l][l][o][ ][w][o][r][l][d][\0]
 ^                 ^
 |                 |
 sv.data()         sub.data()

sv.size()  == 11
sub.size() == 5

sub 并没有创建一个新的 "world" 字符串,只是指向了原字符序列中间的位置。substr() 返回视图,复杂度为常数级。(Cppreference)


四、创建 string_view 的各种方式

1. 从字符串字面量创建

cpp 复制代码
std::string_view sv = "hello";

这里使用接收 const char* 的构造函数。这个构造函数需要查找结尾的 '\0',因此构造复杂度是 O(n)。(Eel.is)


2. 从 std::string 创建

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

此时 sv 指向 s 的内部字符。

cpp 复制代码
std::cout << sv;  // hello

但是必须保证 s 仍然存在,而且没有执行可能使内部地址失效的操作。


3. 使用指针和长度创建

cpp 复制代码
const char buffer[] = {'a', 'b', '\0', 'c', 'd'};

std::string_view sv(buffer, 5);

此时:

cpp 复制代码
sv.size() == 5;

string_view 可以包含中间的 '\0',因为它依靠长度,而不是依靠空字符判断结束。

指针加长度的构造不需要扫描字符串,只记录指针和长度。(Eel.is)


4. 使用 sv 字面量

cpp 复制代码
#include <string_view>

using namespace std::string_view_literals;

std::string_view sv = "hello"sv;

也可以使用:

cpp 复制代码
using namespace std::literals;

auto sv = "hello"sv;

"..."sv 会直接获得编译器提供的字符数量,特别适合处理内嵌 '\0'。标准库为不同字符类型提供了对应的 sv 后缀。(Eel.is)


五、内嵌 '\0' 的重要区别

来看这个例子:

cpp 复制代码
using namespace std::string_view_literals;

std::string_view a = "ab\0cd";
std::string_view b = "ab\0cd"sv;

结果是:

cpp 复制代码
a.size() == 2;
b.size() == 5;

为什么?

普通形式

cpp 复制代码
std::string_view a = "ab\0cd";

调用的是:

cpp 复制代码
string_view(const char* str);

它使用类似 char_traits<char>::length(str) 的逻辑,在第一个 '\0' 处停止。标准规定这个构造函数会计算空字符终止字符串的长度。(Eel.is)

sv 字面量形式

cpp 复制代码
std::string_view b = "ab\0cd"sv;

字面量运算符同时收到:

cpp 复制代码
const char* pointer;
std::size_t length;

因此能保留完整长度。

处理二进制数据或可能包含 '\0' 的文本时,应该使用显式长度。


六、最重要的问题:生命周期

string_view 不管理内存,所以它可能变成悬空视图,也就是指向已经失效的内存。

错误 1:指向临时 std::string

cpp 复制代码
std::string_view sv = std::string("hello");

// 临时 std::string 已经销毁
std::cout << sv;  // 未定义行为

执行完初始化语句后,临时 std::string 被销毁,而 sv 仍然保存着原来的地址。

这和下面的情况类似:

cpp 复制代码
const char* p = std::string("hello").data();

指针不会自动延长原对象的生命周期。

正确方式

需要长期保存时,让拥有者活得足够久:

cpp 复制代码
std::string owner = "hello";
std::string_view sv = owner;

std::cout << sv;  // 正确

或者直接保存一份字符串:

cpp 复制代码
std::string result = std::string("hello");

错误 2:函数返回指向局部字符串的视图

cpp 复制代码
std::string_view make_name()
{
    std::string name = "Alice";
    return name;  // 错误
}

函数结束时 name 被销毁,返回的视图立即悬空。

正确方式是返回拥有数据的 std::string

cpp 复制代码
std::string make_name()
{
    std::string name = "Alice";
    return name;
}

现代 C++ 的返回值优化和移动语义通常会让这种写法非常高效。


正确返回 string_view 的情况

返回字符串字面量

cpp 复制代码
std::string_view get_name()
{
    return "Alice";
}

字符串字面量具有静态存储期,程序结束前一直有效,所以这是安全的。

返回参数的一部分

cpp 复制代码
std::string_view get_extension(std::string_view filename)
{
    auto pos = filename.rfind('.');

    if (pos == std::string_view::npos)
        return {};

    return filename.substr(pos + 1);
}

调用:

cpp 复制代码
std::string file = "report.pdf";
std::string_view ext = get_extension(file);

只要 file 仍然有效,ext 就有效。

但下面仍然危险:

cpp 复制代码
std::string_view ext = get_extension(std::string("report.pdf"));

临时字符串在该语句结束后销毁,ext 会悬空。


七、原字符串修改后,视图可能失效

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

s += " a very long suffix";

std::cout << sv;  // 可能未定义行为

std::string 扩容时可能重新分配内存。重新分配后,sv 仍指向旧地址。

标准规定,多种非 const 字符串操作可能使指向 std::string 元素的指针、引用和迭代器失效;string_view 本质上保存的也是这样的地址。(Eel.is)

需要特别警惕:

cpp 复制代码
s.append(...);
s += ...;
s.push_back(...);
s.insert(...);
s.erase(...);
s.replace(...);
s.resize(...);
s.reserve(...);
s.clear();
s = ...;

稳妥的工程规则是:

std::string 创建视图以后,在视图使用完之前,不要对原字符串进行可能改变其内容、大小或存储位置的操作。

即使操作没有重新分配内存,也可能改变视图所观察的字符:

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

s[0] = 'H';

std::cout << sv;  // Hello

string_view 是"通过自身只读",并不意味着底层数据永远不会变化。


八、data() 不保证以 '\0' 结尾

这是另一个非常大的坑。

cpp 复制代码
std::string_view sv = "hello world";
std::string_view sub = sv.substr(0, 5);

sub 表示 "hello",但它的底层内存实际可能是:

text 复制代码
[h][e][l][l][o][ ][w][o][r][l][d][\0]
 ^             ^
 data()        data() + size()

sub.data() 指向 h,但第五个字符之后是空格,不是 '\0'

因此:

cpp 复制代码
printf("%s\n", sub.data());

可能输出:

text 复制代码
hello world

而不是:

text 复制代码
hello

甚至可能读取越界。标准只保证 [data(), data() + size()) 是有效范围,不保证 data() + size() 所在位置存放空字符。(Cppreference)

正确输出方式

C++ 流可以识别长度:

cpp 复制代码
std::cout << sub;

写入文件或二进制流时:

cpp 复制代码
std::fwrite(sub.data(), 1, sub.size(), stdout);

必须调用要求 C 风格空结尾字符串的接口时,可以创建拥有的字符串:

cpp 复制代码
std::string temp(sub);
some_c_api(temp.c_str());

string_view 没有 c_str()

这是有意的:一个视图并不保证末尾有 '\0',所以不能安全地提供 c_str()


九、常用成员函数

1. 长度和判空

cpp 复制代码
std::string_view sv = "hello";

sv.size();    // 5
sv.length();  // 5
sv.empty();   // false

size()length() 含义相同。


2. 访问字符

cpp 复制代码
char a = sv[0];
char b = sv.at(1);
char c = sv.front();
char d = sv.back();

区别:

cpp 复制代码
sv[index]

要求下标合法,不检查越界。

cpp 复制代码
sv.at(index)

会检查越界,越界时抛出 std::out_of_range。(Cppreference)

注意空视图不能调用:

cpp 复制代码
sv.front();
sv.back();

3. 取子串

cpp 复制代码
std::string_view sv = "hello world";

auto first = sv.substr(0, 5);  // hello
auto last  = sv.substr(6);     // world

substr() 返回的仍然是 string_view,不会复制字符,复杂度为常数级。(Cppreference)

如果:

cpp 复制代码
pos > sv.size()

则抛出 std::out_of_range

如果长度过大:

cpp 复制代码
sv.substr(6, 1000);

不会越界,而是截取到原视图末尾。


4. 查找

cpp 复制代码
std::string_view sv = "hello world";

auto pos1 = sv.find("world");  // 6
auto pos2 = sv.find('o');      // 4
auto pos3 = sv.rfind('o');     // 7

没找到时返回:

cpp 复制代码
std::string_view::npos

常见写法:

cpp 复制代码
if (sv.find("world") != std::string_view::npos) {
    // 找到了
}

还有:

cpp 复制代码
find_first_of()
find_last_of()
find_first_not_of()
find_last_not_of()

标准对这些搜索操作给出的最坏复杂度上界通常是 O(size × pattern_size),同时鼓励实现采用更好的算法。(Eel.is)


5. 前缀和后缀

C++20:

cpp 复制代码
std::string_view path = "/usr/bin";

path.starts_with("/");     // true
path.ends_with("bin");     // true

starts_with()ends_with() 从 C++20 开始提供。(Cppreference)


6. contains

C++23:

cpp 复制代码
std::string_view text = "hello world";

text.contains("world");  // true
text.contains('x');      // false

contains() 本质上相当于:

cpp 复制代码
text.find(target) != std::string_view::npos;

它从 C++23 开始提供。(Cppreference)


7. 删除视图前缀和后缀

cpp 复制代码
std::string_view sv = "  hello  ";

sv.remove_prefix(2);  // "hello  "
sv.remove_suffix(2);  // "hello"

它们不会删除底层字符,只会调整视图的起始地址或长度。

大致相当于:

cpp 复制代码
// remove_prefix(n)
data_ += n;
size_ -= n;

// remove_suffix(n)
size_ -= n;

要求 n <= size();超过当前长度会违反前置条件。(Cppreference)


十、一个实用例子:去除两端空格

cpp 复制代码
#include <string_view>

std::string_view trim(std::string_view text)
{
    while (!text.empty() && text.front() == ' ') {
        text.remove_prefix(1);
    }

    while (!text.empty() && text.back() == ' ') {
        text.remove_suffix(1);
    }

    return text;
}

使用:

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

int main()
{
    std::string input = "   hello world   ";

    std::string_view result = trim(input);

    std::cout << '[' << result << "]\n";
}

输出:

text 复制代码
[hello world]

这个操作:

  • 不修改 input
  • 不创建新的字符缓冲区;
  • 不复制 "hello world"
  • 只是改变视图的起点和长度。

十一、一个实用例子:解析键值对

输入:

text 复制代码
name=Alice

解析函数:

cpp 复制代码
#include <optional>
#include <string_view>
#include <utility>

using KeyValue =
    std::pair<std::string_view, std::string_view>;

std::optional<KeyValue> parse_key_value(std::string_view text)
{
    const auto pos = text.find('=');

    if (pos == std::string_view::npos) {
        return std::nullopt;
    }

    std::string_view key   = text.substr(0, pos);
    std::string_view value = text.substr(pos + 1);

    return KeyValue{key, value};
}

使用:

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

int main()
{
    std::string line = "name=Alice";

    auto result = parse_key_value(line);

    if (result) {
        std::cout << "key: " << result->first << '\n';
        std::cout << "value: " << result->second << '\n';
    }
}

这里没有复制 "name""Alice";两个结果视图都指向 line 内部。因此 line 必须比结果活得更久。


十二、函数参数应该怎么选择?

场景 1:函数只读取文本,不保存

优先考虑:

cpp 复制代码
void log(std::string_view message);

它可以接受:

cpp 复制代码
log("hello");

std::string s = "world";
log(s);

std::string_view sv = "C++";
log(sv);

这种接口很灵活,且调用时通常不需要创建新的字符串。


场景 2:函数要保存字符串

应该接收或构造 std::string

cpp 复制代码
class User
{
public:
    explicit User(std::string name)
        : name_(std::move(name))
    {
    }

private:
    std::string name_;
};

因为对象需要拥有名字。

也可以接收视图后复制:

cpp 复制代码
class User
{
public:
    explicit User(std::string_view name)
        : name_(name)
    {
    }

private:
    std::string name_;
};

但这意味着无论调用者传左值还是右值,构造时都会复制字符。

按值接收 std::string 往往更能利用移动:

cpp 复制代码
User(std::string name)
    : name_(std::move(name))
{
}

场景 3:函数要修改调用者的字符串

使用:

cpp 复制代码
void normalize(std::string& text);

而不是 string_view


场景 4:参数必须以 '\0' 结尾

不要仅接收 string_view,因为它不提供这个保证。

可以接收:

cpp 复制代码
void call_c_api(const std::string& text);

然后:

cpp 复制代码
some_c_api(text.c_str());

或者让接口明确接收拥有的字符串。


string_view 应按值传递

通常写:

cpp 复制代码
void process(std::string_view text);

而不是:

cpp 复制代码
void process(const std::string_view& text);

string_view 本身是轻量视图,按值传递还能让函数自由使用:

cpp 复制代码
text.remove_prefix(...);

而不会修改调用者持有的那个视图对象。

注意,这里修改的只是函数内部视图副本,并不是底层字符串。


十三、为什么 const std::string& 还没有被淘汰?

下面两个接口都可能合理:

cpp 复制代码
void f(const std::string& text);
void g(std::string_view text);

string_view 的优势

cpp 复制代码
g("hello");

无需为了参数创建 std::string

它还可以自然表示:

cpp 复制代码
g(std::string_view(buffer, length));

包括不是空字符结尾的字符范围。

const std::string& 的优势

它明确要求调用者提供一个 std::string,因此函数内部可以使用:

cpp 复制代码
text.c_str();
text.capacity();
text.get_allocator();

并且知道字符串对象提供标准 std::string 的完整语义。

所以并不是看到所有 const std::string& 都应该机械替换成 string_view。只有当函数真正只需要"一个只读字符序列"时,string_view 才更符合抽象。


十四、不要轻易把 string_view 作为成员变量

危险设计:

cpp 复制代码
class User
{
public:
    explicit User(std::string_view name)
        : name_(name)
    {
    }

private:
    std::string_view name_;
};

调用:

cpp 复制代码
User user(std::string("Alice"));

构造完成后临时字符串销毁,name_ 立即悬空。

安全设计:

cpp 复制代码
class User
{
public:
    explicit User(std::string name)
        : name_(std::move(name))
    {
    }

private:
    std::string name_;
};

只有在生命周期关系非常明确时,才适合存储视图,例如视图始终指向对象内另一个稳定成员:

cpp 复制代码
class ParsedLine
{
public:
    explicit ParsedLine(std::string text)
        : storage_(std::move(text))
    {
        auto pos = storage_.find('=');

        if (pos != std::string::npos) {
            key_ = std::string_view(storage_).substr(0, pos);
            value_ = std::string_view(storage_).substr(pos + 1);
        }
    }

private:
    std::string storage_;
    std::string_view key_;
    std::string_view value_;
};

但这种类还要谨慎处理复制和移动:对象移动后,视图是否仍指向正确位置可能取决于字符串存储是否改变。工程上经常使用"保存偏移量"或自定义复制、移动逻辑来避免问题。


十五、不要把不稳定的 string_view 当哈希表键

下面的类型本身可以编译:

cpp 复制代码
std::unordered_map<std::string_view, int> table;

标准库提供了 string_view 的哈希支持。(Eel.is)

但是这样使用很危险:

cpp 复制代码
std::string key = "hello";

table[key] = 42;

key = "something else";

问题有两个:

  1. 修改或重新分配可能让视图悬空;
  2. 即使地址没失效,字符内容发生变化也会改变键的哈希值。

哈希容器要求键在存储期间保持稳定。字符发生变化后,元素可能位于与新哈希值不匹配的桶中,破坏容器查找逻辑。

长期保存键通常应该使用:

cpp 复制代码
std::unordered_map<std::string, int>

十六、空 string_view 的细节

cpp 复制代码
std::string_view a;
std::string_view b = "";

两者都满足:

cpp 复制代码
a.empty();
b.empty();

但:

cpp 复制代码
a.data() == nullptr;  // 默认构造的当前标准语义

b.data() 通常指向字符串字面量中的终止字符,不一定是空指针。默认构造视图的标准后置条件是长度为 0、内部地址为空。(Eel.is)

因此判断空视图应该使用:

cpp 复制代码
sv.empty()

不要使用:

cpp 复制代码
sv.data() == nullptr

"长度为零"和"指针为空"不是同一个概念。


十七、不要向它传入空指针

错误:

cpp 复制代码
const char* p = nullptr;
std::string_view sv(p);

接收 const char* 的构造函数需要对指针计算字符串长度,因此要求它指向有效的空字符结尾字符序列。当前标准草案还显式删除了 nullptr_t 构造形式。(Eel.is)

需要表示"没有内容"时使用:

cpp 复制代码
std::string_view sv{};

需要区分"没有值"和"存在但为空"时,可以用:

cpp 复制代码
std::optional<std::string_view>

例如:

cpp 复制代码
std::optional<std::string_view> find_value(...);

十八、转换回 std::string

当需要拥有字符时:

cpp 复制代码
std::string_view sv = "hello";

std::string s(sv);

这会复制 sv.size() 个字符。

也可以:

cpp 复制代码
std::string s{sv};

转换后:

cpp 复制代码
s

拥有独立存储,不再依赖 sv 原本观察的对象。


十九、不能直接修改字符

cpp 复制代码
std::string_view sv = "hello";

sv[0] = 'H';  // 编译错误

因为字符访问返回只读引用。标准中 iteratorconst_iterator 也是同一种只读迭代器类型。(Eel.is)

但是可以修改视图范围:

cpp 复制代码
sv.remove_prefix(1);
sv.remove_suffix(1);
sv = sv.substr(1, 2);

这只是在修改:

text 复制代码
从哪里开始看
看多少字符

而不是修改字符本身。


二十、没有自然的字符串拼接能力

下面通常不能表示你想要的结果:

cpp 复制代码
std::string_view a = "hello";
std::string_view b = "world";

// auto c = a + b;  // 没有独立拥有拼接结果的 string_view

原因是拼接后的字符必须存储在某个地方,而 string_view 不负责存储。

正确做法:

cpp 复制代码
std::string result;
result.reserve(a.size() + b.size());

result.append(a);
result.append(b);

或者:

cpp 复制代码
std::string result(a);
result += b;

较新的标准工作草案改善了 std::stringstring_view 的混合拼接接口,但核心原则不变:拼接结果需要由 std::string 一类拥有对象保存。 (Eel.is)


二十一、性能分析

string_view 并不是"所有字符串操作都会自动变快",它主要优化的是:

  1. 参数传递;
  2. 子串表示;
  3. 避免不必要的分配;
  4. 避免不必要的字符复制。

常见复杂度

操作 典型复杂度
默认构造 O(1)
复制视图 O(1)
从指针和长度构造 O(1)
const char* 构造 O(n)
size()empty()data() O(1)
remove_prefix() O(1)
remove_suffix() O(1)
substr() O(1)
比较两个字符串 与比较字符数量有关
find() 与文本和模式长度有关

从 C 字符串构造之所以不是 O(1),是因为必须查找结尾 '\0';标准明确规定了该构造函数的线性复杂度。(Eel.is)

例如下面的代码可能反复扫描:

cpp 复制代码
void process(std::string_view);

const char* p = get_c_string();

for (int i = 0; i < 1000; ++i) {
    process(p);  // 每次构造 view 都可能重新计算 strlen
}

可以改为:

cpp 复制代码
std::string_view view(p);

for (int i = 0; i < 1000; ++i) {
    process(view);
}

二十二、string_view 不理解 Unicode 字符

cpp 复制代码
std::string_view text = "你好";

在 UTF-8 环境中:

cpp 复制代码
text.size()

返回的是 char 代码单元数量,通常也就是 UTF-8 字节数,不是人眼看到的汉字数量。

string_view 只知道它观察的是一个连续的 charchar8_tchar16_tchar32_twchar_t 序列;它不负责 Unicode 解码、字素簇计算或规范化。标准为这些字符类型提供了对应的 string_view 别名。(Eel.is)

例如:

cpp 复制代码
std::u8string_view utf8;
std::u16string_view utf16;
std::u32string_view utf32;
std::wstring_view wide;

即使使用 u8string_view,它表示的仍然是 UTF-8 代码单元视图,不等于"Unicode 字符视图"。


二十三、版本相关功能

标准版本 相关功能
C++17 std::string_viewsubstr、查找、比较、remove_prefix
C++20 starts_withends_with,以及更完整的字符类型支持
C++23 contains
更新标准草案 继续补充与 string、范围及子视图相关的接口

基础类型和主要 API 可以从 C++17 开始使用;contains() 等接口要根据项目使用的语言标准选择。(Cppreference)

编译时例如:

bash 复制代码
g++ -std=c++17 main.cpp

或者:

bash 复制代码
g++ -std=c++20 main.cpp
g++ -std=c++23 main.cpp

二十四、常见错误总结

错误:保存临时字符串的视图

cpp 复制代码
std::string_view sv = std::string("hello");

应改为:

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

或者直接拥有内容:

cpp 复制代码
std::string s = "hello";

错误:返回局部字符串的视图

cpp 复制代码
std::string_view f()
{
    std::string s = "hello";
    return s;
}

应返回:

cpp 复制代码
std::string f()
{
    return "hello";
}

错误:把 data() 当作 C 字符串

cpp 复制代码
printf("%s", sv.data());

应使用长度感知接口,或先复制成 std::string


错误:原字符串变化后继续使用视图

cpp 复制代码
std::string s = "hello";
std::string_view sv = s;

s += " world";

use(sv);  // 可能失效

应在修改前用完视图,或重新创建视图。


错误:把 view 当作拥有者

cpp 复制代码
struct Config
{
    std::string_view name;
};

除非生命周期契约非常清楚,否则应该保存:

cpp 复制代码
struct Config
{
    std::string name;
};

错误:通过 data() 判断空

cpp 复制代码
if (sv.data() == nullptr) {
}

应使用:

cpp 复制代码
if (sv.empty()) {
}

二十五、工程中的选择口诀

可以记住下面几条:

只读并且只在函数调用期间使用

cpp 复制代码
void process(std::string_view text);

要修改调用者的字符串

cpp 复制代码
void process(std::string& text);

要取得所有权或长期保存

cpp 复制代码
void process(std::string text);

或者成员中保存:

cpp 复制代码
std::string text_;

要与要求 '\0' 的 C API 交互

使用:

cpp 复制代码
std::string

并调用:

cpp 复制代码
c_str()

返回 string_view

只有在能明确保证:

text 复制代码
返回视图所指向的数据,比返回值活得更久

时才这样做。


最核心的理解

std::string_view 的优势并不是"它是一种更快的字符串",而是:

它把字符串内容的所有权与字符串内容的观察分开了。

它特别适合:

  • 只读函数参数;
  • 零复制解析;
  • 子串切分;
  • 日志和格式处理;
  • 配置解析;
  • 协议解析;
  • 编译期字符串操作。

它最大的风险则是:

视图还活着,但它所指向的字符已经不存在或已经改变。

只要始终问自己一句话,绝大多数问题都能避免:

这个 string_view 指向谁?那个对象还能活多久?

相关推荐
ting94520004 小时前
Humalike X Hermes 深度技术剖析:单指令注入群聊社交智能的底层架构、算法与跨 IM 平台实现
人工智能·算法·架构
豆沙沙包?4 小时前
C++~~~stack容器、queue容器、list容器(p45-P56)
c++·windows·list
Data_Journal4 小时前
用于网页抓取的 Node-unblocker
大数据·开发语言·数据库·python·scrapy
吴声子夜歌4 小时前
Java面试——算法
java·算法·面试
豆角焖肉4 小时前
SSM 聚合项目搭建:多 Maven 模块项目
开发语言·python·pycharm
Frank_refuel4 小时前
【C++八股】面向对象
开发语言·c++
h_a_o777oah5 小时前
【图论】Tarjan 缩点:解决有向图中环的问题
c++·算法·图论·acm·强连通分量·缩点·tarjan
Tisfy5 小时前
LeetCode 1386.安排电影院座位:哈希表+位运算
算法·leetcode·散列表·题解·哈希表
Nil2085 小时前
leetcode 199二叉树的右视图
算法·leetcode·深度优先
m0_380743876 小时前
为 OpenAI 兼容接口配置教程
开发语言·python·node.js