前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >c++ string_view

c++ string_view

作者头像
jasong
发布于 2023-06-26 11:23:25
发布于 2023-06-26 11:23:25
41100
代码可运行
举报
文章被收录于专栏:ClickHouseClickHouse
运行总次数:0
代码可运行

转载

https://www.geeksforgeeks.org/class-stdstring_view-in-cpp-17/

一、背景

在日常C/C++编程中,我们常进行数据的传递操作,比如,将数据传给函数。当数据占用的内存较大时,减少数据的拷贝可以有效提高程序的性能。在C中指针是完成这一目的的标准数据结构,而C++引入了安全性更高的引用类型。所以在C++中若传递的数据仅仅只读,const string&成了C++的天然的方式。但这并非完美,从实践来看,它至少有以下几方面问题:

  1. 字符串字面值、字符数组、字符串指针的传递仍要数据拷贝 这三类低级数据类型与string类型不同,传入时,编译器需要做隐式转换,即需要拷贝这些数据生成string临时对象。const string&指向的实际上是这个临时对象。通常字符串字面值较小,性能损耗可以忽略不计;但字符串指针和字符数组某些情况下可能会比较大(比如读取文件的内容),此时会引起频繁的内存分配和数据拷贝,会严重影响程序的性能。
  2. substr O(n)复杂度 这是一个特别常用的函数,好在std::string提供了这个函数,美中不足的是其每次都返回一个新生成的子串,很容易引起性能热点。实际上我们本意并不是要改变原字符串,为什么不在原字符串基础上返回呢?

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

二、std::string_view

从名字出发,我们可以类比数据库视图,view表示该类型不会为数据分配存储空间,而且该数据类型只能用来读。该数据类型可通过{数据的起始指针,数据的长度}两个元素表示,实际上该数据类型的实例不会具体存储原数据,仅仅存储指向的数据的起始指针和长度,所以这个开销是非常小的。

要使用字符串视图,需要引入<string_view>,下面介绍该数据类型主要的API。这些API基本上都有constexpr修饰,所以能在编译时很好地处理字符串字面值,从而提高程序效率。

2.1 构造函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
constexpr string_view() noexcept;
constexpr string_view(const string_view& other) noexcept = default;
constexpr string_view(const CharT* s, size_type count);
constexpr string_view(const CharT* s);

基本上都是自解释的,唯一需要说明的是:为什么我们代码string_view foo(string("abc"))可以编译通过,但为什么没有对应的构造函数?

实际上这是因为string类重载了stringstring_view的转换操作符: operator std::basic_string_view<CharT, Traits>() const noexcept;

所以,string_view foo(string("abc"))实际执行了两步操作:

  1. string("abc")转换为string_view对象a
  2. string_view使用对象本篇文章从string_view引入的背景,

2.2 自定义字面量

自定义字面量也是C++17新增的特性,提高了常量的易读。 下面的代码取值cppreference,能很好地说明自定义字面值和字符串语义的差异。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#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";
}

输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
s1: 3 "abc"
s2: 8 "abc^@^@def"

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

2.3 成员函数

下面列举其成员函数:忽略了函数的返回值,若函数有重载,括号内用...填充。这样可以对其有个整体轮廓。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 迭代器
begin()
end()
cbegin()
cend()
rbegin()
rend()
crbegin()
crend()
 
// 容量
size()
length()
max_size()
empty()
 
// 元素访问
operator[](size_type pos)
at(size_type pos)
front()
back()
data()
 
// 修改器
remove_prefix(size_type n)
remove_suffix(size_type n)
swap(basic_string_view& s)
 
copy(charT* s, size_type n, size_type pos = 0)
string_view substr(size_type pos = 0, size_type n = npos)
compare(...)
starts_with(...)
ends_with(...)
find(...)
rfind(...)
find_first_of(...)
find_last_of(...) 
find_first_not_of(...)
find_last_not_of(...)

从函数列表来看,几乎跟string的只读函数一致,使用string_view的方式跟string基本一致。有几个地方需要特别说明:

  1. string_viewsubstr函数的时间复杂度是O(1),解决了背景部分的第二个问题。
  2. 修改器中的三个函数仅会修改string_view的数据指向,不会修改指向的数据。

除此之外,函数名基本是自解释的。

2.4 示例

Haskell中有一个常用函数lines,会将字符串切割成行存储在容器里。下面我们用C++来实现

string-版本

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>

void lines(std::vector<std::string> &lines, const std::string &str) {
    auto sep{"\n"};
    size_t start{str.find_first_not_of(sep)};
    size_t end{};

    while (start != std::string::npos) {
        end = str.find_first_of(sep, start + 1);
        if (end == std::string::npos)
            end = str.length();

        lines.push_back(str.substr(start, end - start));
        start = str.find_first_not_of(sep, end + 1);
    }
}

上面我们用const std::string &类型接收待分割的字符串,若我们传入指向较大内存的字符指针时,会影响程序效率。

使用std::string_view可以避免这种情况: string_view-版本

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <sstream>
#include <string_view>

void lines(std::vector<std::string> &lines, std::string_view str) {
    auto sep{"\n"};
    size_t start{str.find_first_not_of(sep)};
    size_t end{};

    while (start != std::string_view::npos) {
        end = str.find_first_of(sep, start + 1);
        if (end == std::string_view::npos)
            end = str.length();

        lines.push_back(std::string{str.substr(start, end - start)});
        start = str.find_first_not_of(sep, end + 1);
    }
}

上面的例子仅仅是把string类型修改成了string_view就获得了性能上的提升。一般情况下,将程序中的string换成string_view的过程是比较直观的,这得益于两者的成员函数的相似性。但并不是所有的“翻译”过程都是这样的,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void lines(std::vector<std::string> &lines, const std::string& str) {
    std::stringstream ss(str);
    std::string line;

    while (std::getline(ss, line, '\n')) {
        lines.push_back(line);
    }
}

这个版本使用stringstream实现lines函数。由于stringstream没有相应的构造函数接收string_view类型参数,所以没法采用直接替换的方式,所以翻译过程要复杂点。

三、使用陷阱

世上没有免费的午餐。不恰当的使用string_view也会带来一系列的问题。

  1. string_view范围内的字符可能不包含\0

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <iostream>
#include <string_view>

int main() {
    std::string_view str{"abc", 1};

    std::cout << str.data() << std::endl;

    return 0;
}

本来是要打印a,但输出了abc。这是因为字符串相关的函数都有一条兼容C的约定:\0代表字符串的结尾。上面的程序打印从开始到字符串结束的所有字符,虽然str包含的有效字符是a,但cout\0。好在这块内存空间有合法的字符串结尾符,如果str指向的是一个没有\0的字符数组,程序很有可能会出现内存问题,所以我们在将string_view类型的数据传入接收字符串的函数时要非常小心。

2.从[const] char*构造string_view对象时间复杂度O(n) 这是因为获取字符串的长度需要从头开始遍历。如果对[const] char*类型仅仅是一些O(1)的操作,相比直接使用[const] char*,转为string_view是没有性能优势的。只不过是相比const string&string_view少了拷贝的损耗。实际上我们完全可以用[const] char*接收所有的字符串,但这个类型太底层了,不便使用。在某些情况下,我们转为string_view可能仅仅是想用其中的一些函数,比如substr

3.string_view指向的内容的生命周期可能比其本身短 string_view并不拥有其指向内容的所有权,用Rust的术语来说,它仅仅是暂时borrow(借用)了它。如果拥有者提前释放了,你还在使用这些内容,那会出现内存问题,这跟悬挂指针(dangling pointer)或悬挂引用(dangling references)很像。Rust专门有套机制在编译时分析变量的生命期,保证borrow的资源在使用期间不会被释放,但C++没有这样的检查,需要人工保证。下面列出一些典型的问题情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
std::string_view sv = std::string{"hello world"}; 
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
string_view foo() {
    std::string s{"hello world"};
    return string_view{s};
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
auto id(std::string_view sv) { return sv; }

int main() {
    std::string s = "hello";
    auto sv = id(s + " world"); 
}

四、总结

string_view解决了一些痛点,但同时也引入了指针和引用的一些老问题。C++标准并没有对这个类型做太多的约束,这引来的问题是我们可以像平常的变量一样以多种方式使用它,如,可以传参,可以作为函数返回值,可以做普遍变量,甚至我们可以放到容器里。随着使用场景的复杂,人工是很难保证指向的内容的生命周期足够长。所以,推荐的使用方式:仅仅作为函数参数,因为如果该参数仅仅在函数体内使用而不传递出去,这样使用是安全的。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
不要以自己的怀疑,认定他人的思想,不要猜疑他人,否则只会影响彼此间的情谊
有时候我们低头,是为了看准自己走的路,很多人认为,自己已经过得还可以,不愿意去尝试新鲜的事物,很多东西都放不下,拉不下这个脸,最终死在面子上。
李才哥
2019/07/10
1.7K0
不要以自己的怀疑,认定他人的思想,不要猜疑他人,否则只会影响彼此间的情谊
前端MVC学习总结(三)——AngularJS服务、路由、内置API、jQueryLite
一、服务 AngularJS功能最基本的组件之一是服务(Service)。服务为你的应用提供基于任务的功能。服务可以被视为重复使用的执行一个或多个相关任务的代码块。 AngularJS服务是单例对象,
张果
2018/01/04
6.4K0
前端MVC学习总结(三)——AngularJS服务、路由、内置API、jQueryLite
2017移动前端的一些总结web前端 —— 移动端知识的一些总结一.css部分二.js部分
web前端 —— 移动端知识的一些总结 个人在移动端的一些总结归纳,有新的知识点会一直更新 一.css部分 1.meta标签       <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"/> 移动端加上这个标签才是真正的自适应,不加的话,假如你把一个980px宽度(手机端常规是980)的PC网页 放在手机上显示,倒也能正常显示不出现滚动条,不过是移动设备对页面 做了缩小优化,所以字体等都相应
windseek
2018/06/14
3.7K0
AngularJS快速入门
记得第一次听说AngularJS这项很赞的Web的前端技术,那时还是2014年,年中时我们我的一个大牛兄弟当时去面试时,被问到了是否熟悉该技术,当时他了解和使用的技术比较多。我们询问他面试情况时,他给俺这个菜菜科普了该技术,印象比较深的是该技术支持前端MVC架构,可以完成大部分原有的后台工作,当时就觉得很神奇,但由于自身技术基础比较薄弱,没有太多时间和积累去学习新的技术,因而搁置了。在2016新年初始,正好有一些富余时间,正好学习下这个被称为就是“”两个大括号“”的前端框架(当前已经非常成熟,国内大部分公司
用户1216676
2018/01/24
2.7K0
AngularJS快速入门
AngularJs ng-route路由详解
本篇基于ng-route来讲下angular中的路由,路由功能主要是 $routeProvider服务 与 ng-view 实现。 ng-view的实现原理,是根据路由的切换,动态编译html模板——$compile(html)(scope)。 更多内容参考:Angularjs总结 前提 首先需要在页面引入angular和angular-route,注意要在angular-route之前引入angular <script src="../../bower_components/angular/an
用户1154259
2018/01/17
2K0
Angularjs中UI Router超级详细的教程{{上}}
这篇文章主要介绍了Angularjs中UI Router全攻略,涉及到angularjs ui router的基本用法,需要的朋友参考下吧 首先给大家介绍angular-ui-router的基本用法。 如何引用依赖angular-ui-router angular.module('app',["ui.router"]) .config(function($stateProvider){ $stateProvider.state(stateName, stateCofig); }) $stateProvid
前朝楚水
2018/04/02
5.4K0
AngularJS一些简单处理得到性能提升
谈起angular的脏检查机制(dirty-checking), 常见的误解就是认为: ng是定时轮询去检查model是否变更。 其实,ng只有在指定事件触发后,才进入$digest cycle:
javascript.shop
2019/09/04
1.8K0
AngularJS一些简单处理得到性能提升
Angularjs基础(八)
AngularJS Bootstrap     AngularJS 的首选样式表是 Twitter Bootstrap ,Twitter Bootstrap 是目前最受欢迎的前端框架 Bootstrap     你可以在你的 AngularJS 应用中加入 Twitter Bootstrap,你可以在你的 <head>元素中添加如下代码:     <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/boo
用户1197315
2018/01/19
3.1K0
基于h5+ angularjs页面拖拽实现
第四步:定义函数,修改图片数组顺序(angualr最好用的地方之一就是双向绑定。所以只需要操作图片数组的顺序就可以实现页面上交换图片位置)。
用户5166330
2019/04/16
1.6K0
基于h5+ angularjs页面拖拽实现
angularjs自定义指令实现分页插件
由于最近的一个项目使用的是angularjs1.0的版本,涉及到分页查询数据的功能,后来自己就用自定义指令实现了该功能。现在单独做了个简易的小demo,主要是为了分享自己写的分页功能。注:本实例调用的
用户1174387
2018/01/17
3.2K0
angularjs自定义指令实现分页插件
iframe自适应高度_html页面自适应
为什么需要使用iframe自适应高度呢?其实就是为了美观,要不然iframe和窗口长短大小不一,看起来总是不那么舒服,特别是对于我们这些编程的来说,如鲠在喉的感觉。在页面中通过iframe嵌入了另外一个页面后,如何使得页面的这块区域随着iframe的高度自动适应而不会出现蹩脚的上下左右滚动条呢?下面这个办法就是使用javascript实现iframe高度自适应的,这个可是兼容所有浏览器的,ie,firefox,chrome,opera,safari这些浏览器都能够实现iframe高度自适应的,具体的js代码如下:function dyniframesize(down){
全栈程序员站长
2022/11/04
4K0
移动端开发中遇到的坑点及总结(持续更新)
本文主要是记录自己在移动端开发中遇到的一些坑点或者总结(持续更新,有新的坑点会总结进来)
全栈程序员站长
2022/09/12
1K0
pc 和 ipad 端网站适配
方法一:设置fontsize 按照iphone 5的适配 1em=10px 适配320
公众号---人生代码
2020/11/11
3K0
前端学习资料整理
HTML&CSS: 对Web标准的理解、浏览器内核差异、兼容性、hack、CSS基本功:布局、盒子模型、选择器优先级、 HTML5、CSS3、Flexbox
江一铭
2022/06/16
3.6K0
前端学习资料整理
11-angular 实例学习-2
一些重要的 demo dropList <div ng-app="myApp" ng-controller="myCtrl" > <select ng-model="adStyle" ng-change="look();" > <option value="0">全部广告类型option> <option value="1">图片广告option> <option value="2">图音视广告op
西南_张家辉
2021/02/02
2.3K0
安卓/ios兼容问题及处理(小程序/H5)
问题:然后利用new Date() 转换时间戳时,使用微信开发工具、安卓都没问题,ios中无法展示并报错 “invalid date”。
小唐同学.
2022/03/15
7.9K1
AngularJS进阶(十一)AngularJS实现表格数据的编辑,更新和删除[通俗易懂]
我们来看其中一个标签,<edit>,这里呢,我们用ng-Model来绑定employee这个对象。
全栈程序员站长
2022/09/15
4.9K0
AngularJS进阶(十一)AngularJS实现表格数据的编辑,更新和删除[通俗易懂]
移动端H5页面开发坑点指南
前言 在平时的H5移动端开发时,我们难免会遇到各种各样的坑点,这篇文章就带着大家来看看怎么解决,文章较长,建议收藏方便以后查阅!
Javanx
2019/10/28
3.2K0
(4)Angular的开发
angular框架,库,是一款非常优秀的前端高级JS框架,有了这个框架就可以轻松构建SPA应用程序,通过指令宽展了HTML,通过表达式绑定数据到HTML。
达达前端
2019/07/22
3.3K0
带你走近AngularJS - 体验指令实例
带你走近AngularJS系列: 带你走近AngularJS - 基本功能介绍 带你走近AngularJS - 体验指令实例 带你走近AngularJS - 创建自定义指令 ------------------------------------------------------------------------------------------------ 之前我们已经介绍了所有的AngularJS 基础知识,下面让我们通过实例来加深记忆,体验自定义指令的乐趣。 手风琴指令 我们展示的第一个例子是手
葡萄城控件
2018/01/10
2.7K0
带你走近AngularJS - 体验指令实例
推荐阅读
相关推荐
不要以自己的怀疑,认定他人的思想,不要猜疑他人,否则只会影响彼此间的情谊
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档