章节目录

std::string_view简介

本节阅读量:

来看下面这段程序:

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    int x { 5 }; // x 拷贝了初始值 5
    std::cout << x << '\n';

    return 0;
}

当执行 x 的定义时,初始值 5 会被复制到为变量 int x 分配的那块内存中。对于基本类型,初始化和复制变量都是非常快的。

现在再看一个类似的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
#include <string>

int main()
{
    std::string s{ "Hello, world!" }; // s 拷贝了初始值
    std::cout << s << '\n';

    return 0;
}

在初始化 s 时,C 风格的字符串字面值 “Hello, world!” 会被复制到为 std::string s 分配的内存中。与基本类型不同,std::string 的初始化和复制是比较慢的。

在上面的程序中,我们对 s 所做的全部操作其实只是把它的值打印到控制台上,然后销毁 s。我们复制了一份 “Hello, world!” 的副本,却仅仅是为了打印之后再把它销毁。这显然是低效的。

在下面这个例子中也能看到类似的情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
#include <string>

void printString(std::string str) // str 拷贝了传入的实参
{
    std::cout << str << '\n';
}

int main()
{
    std::string s{ "Hello, world!" }; // s 拷贝了初始值
    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)的只读访问能力,而不需要复制字符串。“只读”的意思是我们可以访问和使用它所查看的值,但不能修改它。

下面的示例与前面的示例相同,只是我们把 std::string 替换成了 std::string_view。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>
#include <string_view>

// str 提供了对传入实参的只读访问能力
void printSV(std::string_view str) // str 现在是 std::string_view
{
    std::cout << str << '\n';
}

int main()
{
    std::string_view s{ "Hello, world!" }; // s 现在是 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_view 的一个优点是它非常灵活。它可以使用 C 风格字符串、std::string 或另一个 std::string_view 来初始化。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>
#include <string_view>

int main()
{
    std::string_view s1 { "Hello, world!" }; // 使用 C 风格字符串初始化
    std::cout << s1 << '\n';

    std::string s{ "Hello, world!" };
    std::string_view s2 { s };  // 使用 std::string 初始化
    std::cout << s2 << '\n';

    std::string_view s3 { s2 }; // 使用 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 类型的实参。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>
#include <string_view>

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

int main()
{
    printSV("Hello, world!"); // 使用 C 风格字符串调用

    std::string s2{ "Hello, world!" };
    printSV(s2); // 使用 std::string 调用

    std::string_view s3 { s2 };
    printSV(s3); // 使用 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,并用 std::string_view 来初始化它。
  2. 使用 static_cast 进行转换。

下面的示例展示了这两种方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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);   // 编译失败: 不能将 std::string_view 隐式转换为 std::string

	std::string s{ sv }; // okay: 可以用 std::string_view 来初始化 std::string 
	printString(s);      // 然后用这个 std::string 来调用函数

	printString(static_cast<std::string>(sv)); // okay: 可以进行显式转换

	return 0;
}

修改 std::string_view

给 std::string_view 赋一个新的字符串,会让 std::string_view 转而指向新的字符串,但不会对原来的字符串做任何修改。

下面的示例说明了这一点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <string>
#include <string_view>

int main()
{
    std::string name { "Alex" };
    std::string_view sv { name }; // sv 现在查看的是 name
    std::cout << sv << '\n'; // 打印 Alex

    sv = "John"; // sv 现在查看 "John" (不会修改 name)
    std::cout << sv << '\n'; // 打印 John

    std::cout << name << '\n'; // 打印 Alex

    return 0;
}

在上面的示例中,sv = “John” 让 sv 转而查看字符串 “John”。它不会修改 name 变量所持有的值(仍然是 “Alex”)。


std::string_view 的字面值

默认情况下,双引号括起的字符串是 C 风格字符串。我们可以在双引号字符串之后加上 sv 后缀,来创建类型为 std::string_view 的字符串字面值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <iostream>
#include <string>      // for std::string
#include <string_view> // for std::string_view

int main()
{
    using namespace std::string_literals;      // 允许使用 s 后缀
    using namespace std::string_view_literals; // 允许使用 sv 后缀

    std::cout << "foo\n";   // 无后缀,C 风格字符串字面值
    std::cout << "goo\n"s;  // s 后缀,std::string 字面值
    std::cout << "moo\n"sv; // sv 后缀,std::string_view 字面值

    return 0;
};

constexpr std::string_view

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
#include <string_view>

int main()
{
    constexpr std::string_view s{ "Hello, world!" }; // s 是一个字符串常量
    std::cout << s << '\n'; // s 在编译时会被替换为 "Hello, world!"

    return 0;
}

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

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


5.8 std::string简介

上一节

5.10 std::string_view(第2部分)

下一节