5.8 std::string_view 介绍

请考虑以下程序:

#include <iostream>

int main()
{
    int x { 5 }; // x makes a copy of its initializer
    std::cout << x << '\n';

    return 0;
}

当执行变量x的定义时,初始化值5会被复制到为整型变量x分配的内存中。对于基本类型而言,变量的初始化和复制过程非常迅速。

现在考虑这个类似的程序:

#include <iostream>
#include <string>

int main()
{
    std::string s{ "Hello, world!" }; // s makes a copy of its initializer
    std::cout << s << '\n';

    return 0;
}

初始化字符串变量 s 时,C 风格字符串常量 “Hello, world!” 被复制到为 std::string s 分配的内存中。与基本类型不同,初始化并复制 std::string 效率较低。

在上面的程序中,我们对 s 的所有操作只是将值打印到控制台,然后 s 就被销毁了。我们本质上是复制了 “Hello, world!” 只为打印并销毁该副本。这很低效。

在这个示例中我们看到类似的情况:

#include <iostream>
#include <string>

void printString(std::string str) // str makes a copy of its initializer
{
    std::cout << str << '\n';
}

int main()
{
    std::string s{ "Hello, world!" }; // s makes a copy of its initializer
    printString(s);

    return 0;
}

这个示例对C风格字符串“Hello, world!”进行了两次复制:一次是在main()中初始化s时,另一次是在printString()中初始化参数str时。仅仅为了打印一个字符串就进行了大量不必要的复制!


std::string_view(C++17)

为解决std::string初始化(或复制)成本高昂的问题,C++17引入了std::string_view(位于<string_view>头文件中)。std::string_view 提供对现有字符串(C 风格字符串、std::string 或另一个 std::string_view)的只读访问,且不进行复制。只读Read-only意味着我们可以访问和使用被查看的值,但无法修改它。

以下示例与前例完全相同,只是将 std::string 替换为 std::string_view。

#include <iostream>
#include <string_view> // C++17

// str provides read-only access to whatever argument is passed in
void printSV(std::string_view str) // now a std::string_view
{
    std::cout << str << '\n';
}

int main()
{
    std::string_view s{ "Hello, world!" }; // now a std::string_view
    printSV(s);

    return 0;
}

该程序与前一个程序输出相同,但不会复制字符串“Hello, world!”。

当我们用C风格字符串字面量“Hello, world!”初始化std::string_view s时,s提供对“Hello, world!”的只读访问,而不会复制该字符串。当我们将 s 传递给 printSV() 时,形参 str 由 s 初始化。这使我们能够通过 str 访问“Hello, world!”,同样无需复制字符串。

最佳实践
需要只读字符串时(尤其是函数形参),应优先选择 std::string_view 而非 std::string。


std::string_view 可以使用多种不同类型的字符串进行初始化

std::string_view 的一个亮点在于其灵活性。一个 std::string_view 对象可以使用 C 风格字符串、std::string 或另一个 std::string_view 进行初始化:

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

int main()
{
    std::string_view s1 { "Hello, world!" }; // initialize with C-style string literal
    std::cout << s1 << '\n';

    std::string s{ "Hello, world!" };
    std::string_view s2 { s };  // initialize with std::string
    std::cout << s2 << '\n';

    std::string_view s3 { s2 }; // initialize with std::string_view
    std::cout << s3 << '\n';

    return 0;
}

std::string_view 形参可接受多种不同类型的字符串实参

C 风格字符串和 std::string 都会隐式转换为 std::string_view。因此,std::string_view 形参可接受 C 风格字符串、std::string 或 std::string_view 类型的实参:

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

void printSV(std::string_view str)
{
    std::cout << str << '\n';
}

int main()
{
    printSV("Hello, world!"); // call with C-style string literal

    std::string s2{ "Hello, world!" };
    printSV(s2); // call with std::string

    std::string_view s3 { s2 };
    printSV(s3); // call with std::string_view

    return 0;
}

std::string_view 不会隐式转换为 std::string

由于 std::string 会复制其初始化表达式(此操作成本较高),C++ 不允许将 std::string_view 隐式转换为 std::string。此设计旨在防止误将 std::string_view 实参传递给 std::string 形参,从而避免在无需复制的情况下产生高成本的复制操作。

若需实现此转换,可采用两种方案:

  1. 显式使用 std::string_view 初始化器创建 std::string(此操作允许,因极少发生意外情况)
  2. 使用 static_cast 将现有 std::string_view 转换为 std::string

以下示例展示两种方案:

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

void printString(std::string str)
{
	std::cout << str << '\n';
}

int main()
{
	std::string_view sv{ "Hello, world!" };

	// printString(sv);   // compile error: won't implicitly convert std::string_view to a std::string

	std::string s{ sv }; // okay: we can create std::string using std::string_view initializer
	printString(s);      // and call the function with the std::string

	printString(static_cast<std::string>(sv)); // okay: we can explicitly cast a std::string_view to a std::string

	return 0;
}

赋值操作会改变 std::string_view 所指向的内容

将新字符串赋值给 std::string_view 时,该视图将指向新字符串。此操作不会以任何方式修改原先指向的字符串。

以下示例说明了这一点:

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

int main()
{
    std::string name { "Alex" };
    std::string_view sv { name }; // sv is now viewing name
    std::cout << sv << '\n'; // prints Alex

    sv = "John"; // sv is now viewing "John" (does not change name)
    std::cout << sv << '\n'; // prints John

    std::cout << name << '\n'; // prints Alex

    return 0;
}

image

在上例中,sv = “John” 使 sv 现在显示字符串 ‘John’。它不会改变 name 持有的值(该值仍为 “Alex”)。


std::string_view 的字面量

双引号字符串字面量默认为 C 风格字符串字面量。我们可以在双引号字符串字面量后添加 sv 后缀来创建类型为 std::string_view 的字符串字面量。sv 必须为小写。

#include <iostream>
#include <string>      // for std::string
#include <string_view> // for std::string_view

int main()
{
    using namespace std::string_literals;      // access the s suffix
    using namespace std::string_view_literals; // access the sv suffix

    std::cout << "foo\n";   // no suffix is a C-style string literal
    std::cout << "goo\n"s;  // s suffix is a std::string literal
    std::cout << "moo\n"sv; // sv suffix is a std::string_view literal

    return 0;
}

相关内容
我们在第5.7节——std::string介绍中讨论了这种命名空间的使用方式。此处同样适用相同建议。

使用C风格字符串常量初始化std::string_view对象是可行的(无需使用std::string_view常量进行初始化)。

不过,使用std::string_view常量初始化std::string_view也不会引发问题(因为这类常量本质上是伪装的C风格字符串常量)。


constexpr std::string_view

与 std::string 不同,std::string_view 完全支持 constexpr:

#include <iostream>
#include <string_view>

int main()
{
    constexpr std::string_view s{ "Hello, world!" }; // s is a string symbolic constant
    std::cout << s << '\n'; // s will be replaced with "Hello, world!" at compile-time

    return 0;
}

image

这使得当需要字符串符号常量时,constexpr std::string_view 成为首选方案。

我们将在下一课继续探讨 std::string_view。

posted @ 2026-02-17 14:57  游翔  阅读(0)  评论(0)    收藏  举报