什么是string_view

当你创建一个将(常量)字符串作为参数的函数时,你有四个选择,你可能知道两个,但不知道另外两个:

void TakesCharStar(const char* s);             // C convention
void TakesString(const string& s);             // Old Standard C++ convention
void TakesStringView(absl::string_view s);     // Abseil C++ convention
void TakesStringView(std::string_view s);      // C++17 C++ convention

当调用者已经有已提供的格式的字符串时即提供的字符串类型完全匹配时(对于第1个函数,调用者提供的字符串类型为:const char*,如:const char *p = "test";  对于第2个函数,调用者提供的字符串类型为const string,如: const string p = "test"; ),前两者各自对应的方法最有效,但是当需要进行转换(如从const char *到string 或 string到char *)时发生什么呢?

调用者需要将字符串转换为const char *时,需要用(高效但不方便)c_str()函数:

void AlreadyHasString(const string& s) {TakesCharStar(s.c_str());               // explicit conversion
}

调用者需要将const char *转换为字符串时,不需要做任何其他操作(这是好消息);但是将创建临时字符串(方便但效率低),并复制该字符串的内容(这是坏消息)。

string有什么缺点?

本节内容主要摘自博客【现代C++】性能控的工具箱之string_view。

在数据传递中减少拷贝是提高性能的最常用办法。在C中指针是完成这一目的的标准数据结构,而在C++中引入了安全性更高的引用类型。所以在C++中若传递的数据仅仅可读,const string&成了C++天然的方式。但这并非完美,从实践上来看,它至少有以下几方面问题:

  • 字符串字面值、字符数组、字符串指针的传递依然要数据拷贝

这三类低级数据类型与string类型不同,传入时编译器要做隐式转换,即需要拷贝这些数据生成string临时对象。const string&指向的实际上是这个临时对象。通常字符串字面值较小,性能损失可以忽略不计;但字符串指针和字符数组某些情况下可能会比较大(比如读取文件的内容),此时会引起频繁的内存分配和数据拷贝,影响程序性能。

  • substr O(n)复杂度

substr是个常用的函数,好在std::string提供了这个函数,美中不足的时每次都要返回一个新生成的子串,很容易引起性能热点。实际上我们本意不是要改变原字符串,为什么不在原字符串基础上返回呢?

怎么办

C++17中引入了string_view,能很好的解决以上两个问题。

std::string_view是C++ 17标准中新加入的类,正如其名,它提供一个字符串的视图,即可以通过这个类以各种方法“观测”字符串,但不允许修改字符串。由于它只读的特性,它并不真正持有这个字符串的拷贝,而是与相对应的字符串共享这一空间。即——构造时不发生字符串的复制(具体请参考《详解C++17下的string_view》)。同时,你也可以自由的移动这个视图,移动视图并不会移动原定的字符串。

  • 通过调用 string_view 构造器可将字符串转换为 string_view 对象。string 可隐式转换为 string_view。
  • string_view 是只读的轻量对象,它对所指向的字符串没有所有权。
  • string_view通常用于函数参数类型,可用来取代 const char* 和 const string&。string_view 代替 const string&,可以避免不必要的内存分配。
  • string_view的成员函数即对外接口与 string 相类似,但只包含读取字符串内容的部分。
    string_view::substr()的返回值类型是string_view,不产生新的字符串,不会进行内存分配。string::substr()的返回值类型是string,产生新的字符串,会进行内存分配。
  • string_view字面量的后缀是 sv。(string字面量的后缀是 s)
#include <string_view>
#include <iostream>int main()
{using namespace std::literals;std::string_view s1 = "abc\0\0def";std::string_view s2 = "abc\0\0def"sv;std::cout << "s1: " << s1.size() << " \"" << s1 << "\"\n";std::cout << "s2: " << s2.size() << " \"" << s2 << "\"\n";
}

输出:

s1: 3 "abc"
s2: 8 "abc^@^@def"

以上例子能很好看清二者的语义区别,\0对于字符串而言,有其特殊的意义,即表示字符串的结束,字符串视图根本不care,它关心实际的字符个数。

Google首选通过stringview接受这样的字符串参数。这是C++17的“pre-adopted”类型,在C++17的构建中,您应该使用std::string_view,在任何不依赖C++17的代码中,您应该使用absl::string_view(Abseil是Google开源的C++库)。

string_view类的实例可以看作是现有字符串缓冲区的“视图”。具体来说,string_view仅由一个指针和一个长度组成,用于标记不是string _view拥有且不能被该视图修改的字符串数据部分。所以,复制string_view是一项浅层的操作:不复制任何字符串数据。

string_view有来自const char * 和 const string&的隐式转换构造函数,并且由于string_view不拷贝,因此进行浅拷贝不产生O(n)内存损失。在传递cosnt string&的情况下,构造函数在O(1)时间进行。在传递const char*的情况下,构造函数会自动调用strlen()(或者你可以使用具有两个参数的string_view构造函数)。

void AlreadyHasString(const string& s) {TakesStringView(s); // no explicit conversion; convenient!
}void AlreadyHasCharStar(const char* s) {TakesStringView(s); // no copy; efficient!
}

因为string_view不拥有数据,所以string_view所指的任何字符串必须具有已知的生命周期,并且必须比string_view本身生命周期更长。这意味着使用string_view进行存储通常是有问题的:你需要一些证据证明基础数据的生命周期将超过string_view。

如果你的API仅需在一次调用中引用字符串数据,而无需修改数据,则接受string_view就足够了。如果以后需要引用数据或需要修改数据,则可以使用string(my_string_view)显式转换为C ++字符串对象。

将string_view添加到现有代码库中并非总是正确的答案:更改参数以通过string_view传递可能效率不高,如果这些参数随后传递给需要字符串或以NUL终止的const char *的函数。最好从实用程序代码开始向上使用string_view,或者在启动新项目时保持完全一致。

其他事项

  • 与其他字符串类型不同,你应该按值传递string_view,就像int或double一样,因为string_view是一个很小的值。
  • string_view不一定是NUL终止的。因此,编写以下内容并不安全:
printf("%s\n", sv.data()); // DON’T DO THIS

以下这篇文章很好说明了上例的不安全:

《C++ string_view 的坑》

但是,下面是好的代码:

printf("%.*s\n", static_cast<int>(sv.size()), sv.data());
  • 你可以输出string_view,就像输出字符串或const char*一样:
std::cout << "Took '" << s << "'";

大多数情况下,你可以将接受const string&或NUL终止的const char*的现有例程安全的转换为string_view。在执行此操作时遇到的唯一危险是,如果已获取函数的地址,则将导致编译中断,因为生成的函数指针类型将有所不同。

string_View理解与用法(一)相关推荐

  1. string_view理解与用法(二)

    以前写了<string_View理解与用法(一)>和<详解C++17下的string_view>,请参考. 本篇文章从string_view引入的背景出发,依次介绍了其相关的知 ...

  2. python模块之HTMLParser之穆雪峰的案例(理解其用法原理)

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #python模块之HTMLParser之穆雪峰的案例(理解其用法原理) #http://www.cnblog ...

  3. Promise async/await的理解和用法

    Promise && async/await的理解和用法 为什么需要promise(承诺)这个东西 在之前我们处理异步函数都是用回调这个方法,回调嵌套的时候会发现 阅读性 和 调试 的 ...

  4. php yield 个人小解_PHP5.5新特性之yield理解与用法实例分析

    本文实例讲述了PHP5.5新特性之yield理解与用法.分享给大家供大家参考,具体如下: yield生成器是php5.5之后出现的,yield提供了一种更容易的方法来实现简单的迭代对象,相比较定义类实 ...

  5. 对于EnterCriticalSection和LeaveCriticalSection的理解和用法

     对于EnterCriticalSection和LeaveCriticalSection的理解和用法 2015年08月26日 11:58:08 阅读数:4966  线程锁的概念函数EnterC ...

  6. bind()、call()、apply()理解及用法

    apply和call都是为了改变某个函数运行时的上下文而存在的(就是为了改变函数内部this的指向),Function对象的方法,每个函数都能调用: 使用apply或call方法,其运行的上下文指向第 ...

  7. C# ManualResetEvent的理解和用法

    ManualResetEvent是C#中一个比较常用的工具,可用于线程间通信,实现一种类似信号量的功能(不知道我这样描述是否恰当,有可能不是"类似",而"就是" ...

  8. Pytorch forward()的简单理解与用法

    1.基本用法 在pytorch中,使用torch.nn包来构建神经网络,我们定义的网络继承自nn.Module类.而一个nn.Module包含神经网络的各个层(放在__init__里面)和前向传播方式 ...

  9. 汇编jmp指令的理解与用法

    这篇文章是在学习过程中对于转移地址偏移地址的理解以及网上缺乏帮助理解计算转移地址偏移地址的前提下诞生的.初次写知识点分享,如有误,请指出多包含. JMP是在编写程序中经常使用的指令,它的功能是转移到指 ...

最新文章

  1. python的用途-Python运算符的作用与意义
  2. 博客园7月底至8月初51Aspx源码发布详情
  3. Java -- 内部类(一)
  4. CentOS下面ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)
  5. 【OpenPose-Windows】运行OpenposeDemo.exe 如何保存图像运行结果及关节点信息
  6. js进阶 11-6 jquery如何获取和设置元素的宽高(jquery多方法)
  7. MySQL高可用--MGR入门(4)异常恢复
  8. unity2020 for Mac 安装下载详解
  9. 球缺体积和球冠表面积的计算公式及应用
  10. 雨课堂计算机网络答案,运行雨课堂进行网络综合布线实训教学
  11. Ubuntu 无法连接网络
  12. 模拟电子经典200问
  13. 地图API公交线路查询
  14. HTML5定稿了 为什么原生App世界将被颠覆
  15. 国人函数概念,稀里糊涂,误人子弟
  16. 计算机的信息表示(进制的转换)
  17. Spark开发实用技巧-从入门到爱不释手
  18. 算法中的一些数学问题分享,ICG游戏
  19. 微信小程序转二维码方法分享
  20. java 获取文件夹下所有文件

热门文章

  1. 仅靠一杯奶茶钱8.8元,你就能转到人工智能专业?
  2. java ftp限速_为什么Java FTP客户端的传输速率存在很大差异
  3. 用不同显卡训练gan的区别_面霜质地这么多,到底哪一种最好用?不同质地面霜有什么区别?...
  4. C/S简易UI框架开发总结(2)
  5. Spring Boot log4j2 configuration example
  6. CentOS6.x 下 /etc/security/limits.conf 被改错的故障经历
  7. C#(.Net)中调用Sql sever汉字字符串显示为?问号
  8. RISC-V评估系列
  9. 远程登录-出现身份验证错误[可能是由于CredSSP加密Oracle修正]
  10. Linux服务器上的oracle数据导入和导出