深度解读C++17中的std::string_view:解锁字符串处理的新境界

深入研究C++17中的std::string_view:解锁字符串处理的新境界

  • 一、简介
  • 二、std::string_view的基础知识
    • 2.1、构造函数
    • 2.2、成员函数
  • 三、std::string_view为什么性能高?
  • 四、std::string_view的使用陷阱
  • 五、std::string_view源码解析
  • 六、总结

一、简介

C++中有两类字符串,即C风格字符串(字符串字面值、字符数组、字符串指针)和std::string对象两大类。

C风格字符串:

#include <string.h>int main()
{//C风格字符串初始化方式char* arr = "LionLong";char arr[] = "LionLong";char arr[] = { 'L', 'i', 'o', 'n', 'L', 'o','n', 'g', '\0' }; //结尾必须有\0结束符//C风格字符串函数strlen(arr);strcmp(arr1, arr2);strcat(arr1, arr2);strcpy(arr1, arr2);return 0;
}

C++ std::string对象:

#include <string>//初始化方式
std::string s1;
std::string s2(s1);
std::string s3 = s1;
std::string s4("LionLong");
std::string s4 = "LionLong";
std::string s5 = std::string("LionLong");
std::string s6(6, 'L'); //LLLLLL//对象操作
s1.empty();
s1.size();
s[n];
s.substr(3, 5);

当需要将字符串作为参数传递给函数时,往往会伴随字符串的拷贝。当数据占用较大内存时,减少数据的拷贝显得尤为重要。

在C++17之前,可以通过C风格字符串指针作为函数形参,也可以通过std::string字符串引用类型 作为函数形参。但是这并不完美,从实践上看,存在以下问题:

  • C风格字符串的传递仍会进行拷贝。字符数组、字符串字面量和字符串指针是可以隐式转换为std::string对象的,当函数的形参是std::string,而传递的实参是C风格字符串时,编译器会做一次隐式转换,生成一个临时的std::string对象,再让形参指向这个对象。字符串字面值一般较小,性能消耗可以忽略不计;但是字符数组和字符串指针往往较大,频繁的数据拷贝就会造成较大的性能消耗,不得不重视。
  • substr()的复杂度是O(N)。std::string提供了一个返回字符串子串的函数,但是每次返回的都是一个新的对象,也需要进行构造。

那么有没有办法在原始字符串的基础上进行操作呢?答案是std::string_view

在C++17中引入的std::string_view是一种轻量级的字符串视图类型,类似于Golang的slice。它的出现主要是为了提供一种非拥有性的字符串引用机制,用于处理字符串的读取和操作,而无需进行内存拷贝或分配新的字符串对象。

std::string_view并不会真正分配存储空间,而只是原始数据的一个只读窗口,可以认为它是一个内存的观察者。std::string_view的结构非常简单,只会保持原始字符串的起始指针以及字符串的长度,这个结构不会占用太多内存,开销非常小。

std::string_view的出现意义和重要性:

  1. 减少内存拷贝:使用std::string_view可以避免不必要的字符串拷贝操作,特别是在函数参数传递和返回值返回时,可以显著提高性能和效率。

  2. std::string_view提供了类似std::string的接口,可以方便地进行字符串的访问和操作,例如查找子串、比较字符串、截取子串等,而无需额外的内存分配和释放。现有的基于std::string的代码可以无缝地迁移到使用std::string_view的代码。

  3. std::string_view不仅可以用于处理std::string类型的字符串,还可以用于处理其他字符序列,包括字符数组、字符指针等。

二、std::string_view的基础知识

std::string_view是对字符串的一种非拥有式(non-owning)表示,意味着它不拥有字符串的内存,而是通过指针和长度来引用现有的字符串数据。

std::string_view定义于C++标准库头文件<string_view>中,std::string_view的定义如下:

namespace std {template<class charT, class traits = std::char_traits<charT>>class basic_string_view {public:// 构造函数constexpr basic_string_view() noexcept;constexpr basic_string_view(const charT* str);constexpr basic_string_view(const charT* str, size_t len);// 成员函数constexpr const charT* data() const noexcept;constexpr size_t size() const noexcept;constexpr bool empty() const noexcept;constexpr charT operator[](size_t pos) const;constexpr charT front() const;constexpr charT back() const;constexpr basic_string_view substr(size_t pos, size_t count = npos) const;constexpr int compare(basic_string_view other) const noexcept;constexpr size_t find(basic_string_view str, size_t pos = 0) const noexcept;// ...};// 类型别名using string_view = basic_string_view<char>;using wstring_view = basic_string_view<wchar_t>;using u16string_view = basic_string_view<char16_t>;using u32string_view = basic_string_view<char32_t>;
}

std::string_view实际上是一种模板类basic_string_view的一种实现。与之类似的还有wstring_viewu8string_viewu16string_viewu32string_view

std::string_view的特点:

  1. 轻量级:std::string_view本身只包含一个指向字符串数据的指针和一个长度,因此它的大小非常小。
  2. 非拥有式:std::string_view不拥有字符串数据的内存,它只是对现有字符串数据的引用。这意味着它可以安全地引用临时字符串、字符串字面量或其他字符串对象,而无需复制数据。
  3. 零拷贝:由于std::string_view不拥有字符串数据,它可以在不进行数据复制的情况下对字符串进行操作。
  4. 不可变性:std::string_view是只读的,它提供了一系列成员函数来访问和操作字符串数据,但不能修改字符串的内容。
  5. 字符串操作支持:std::string_view提供了一组成员函数,例如data()、size()、empty()、substr()、compare()和find()等,使得对字符串数据的常见操作变得方便和高效。

通过使用std::string_view,可以在不引入额外的内存开销的情况下,对字符串进行查看和操作,这在许多情况下都是非常有用的。

相比传统的字符串类型(如std::string或C风格的字符串),传统的字符串类型(如std::string或C风格的字符串)需要进行内存分配和拷贝操作,导致额外的开销和性能损失。而std::string_view则更加轻量级和高效,适用于对字符串进行读取和操作,特别是在函数参数传递、字符串处理和性能敏感的场景下。

需要注意的是,由于std::string_view只是对字符串的引用,使用时需要确保字符串的生命周期长于std::string_view的使用范围,以避免悬空引用或访问已释放的内存。

std::string_view是C++17中引入的一种轻量级字符串视图类型,用于以非拥有(non-owning)的方式引用字符串数据。它提供了一种有效的方式来访问字符串,而无需进行复制或拥有内存。

2.1、构造函数

//默认构造函数
constexpr basic_string_view() noexcept;
//拷贝构造函数
constexpr basic_string_view(const string_view& other) noexcept = default;
//直接构造,构造一个从s所指向的字符数组开始的前count个字符的视图
constexpr basic_string_view(const CharT* s, size_type count);
//直接构造,构造一个从s所指向的字符数组开始,到\0之前为止的视图,不包含空字符
constexpr basic_string_view(const CharT* s);

std::string_view的构造方法:

  • 默认构造方法:std::string_view(),创建一个空的string_view。
  • 字符串指针构造方法:std::string_view(const char* str),创建一个string_view,指向以null结尾的C风格字符串。
  • 字符串指针和长度构造方法:std::string_view(const char* str, size_t len),创建一个string_view,指向给定长度的字符序列。
  • std::string构造方法:std::string_view(const std::string& str),创建一个string_view,指向std::string对象的字符序列。
  • 字符串迭代器构造方法:std::string_view(InputIt first, InputIt last),创建一个string_view,指向[first, last)区间内的字符序列。

std::string类重载了从stringstring_view的转换操作符:

operator std::basic_string_view<CharT, Traits>() const noexcept;

因此可以通过std::string来构造一个std::string_view:

std::string_view foo(std::string("LionLong"));

这个过程其实包含三步:

  1. 构造std::string的临时对象a
  2. 通过转换操作符将临时对象a转换为string_view类型的临时对象b
  3. 调用std::string_view的拷贝构造函数。

2.2、成员函数

std::string_view的成员函数和操作符:

  • data():返回string_view所指向的字符序列的指针。
  • size()、length():返回string_view所指向的字符序列的长度。
  • max_size():返回可以容纳的最大长度。
  • empty():检查string_view是否为空,即长度是否为0。
  • operator[]():访问string_view中指定位置的字符。
  • at():以安全的方式访问string_view中指定位置的字符,会进行边界检查。
  • front():返回string_view中第一个字符。
  • find():返回首次出现给定子串的位置。
  • back():返回string_view中最后一个字符。
  • begin():返回指向string_view中第一个字符的迭代器。
  • end():返回指向string_view末尾的迭代器。
  • cbegin():返回指向string_view中第一个字符的const迭代器。
  • cend():返回指向string_view末尾的const迭代器。
  • substr():返回一个新的string_view,包含原始string_view的子字符串。不同于std::string::substr()的时间复杂度O(n),它的时间复杂度是O(1)。
  • remove_prefix():移除前缀,将string_view的起始位置向后移动指定数量的字符。
  • remove_suffix():移除后缀,将string_view的结束位置向前移动指定数量的字符。
  • swap():交换两个string_view的内容。
  • compare():比较两个视图是否相等。
  • starts_with() :C++20新增,判断视图是否以以给定的前缀开始。
  • ends_with():C++20新增,判断视图是否以给定的后缀结尾。
  • contains():C++23新增,判断视图是否包含给定的子串。

这些成员函数与std::basic_string的相同成员函数完全兼容,可以认为是对其调用的一层封装。不同于std::basic_string::data()和字符串字面量,data()可以返回指向非空终止的缓冲区的指针。

data()示例:

#include <string_view>using namespace std::string_view_literals;int main() {std::string_view sv("hello, LionLong");std::cout << "sv = " << sv<< ", size() = " << sv.size()<< ", data() = " << sv.data() << std::endl;std::string_view sv2 = sv.substr(0, 5);std::cout << "sv2 = " << sv2<< ", size() = " << sv2.size()<< ", data() = " << sv2.data() << std::endl;std::string_view sv3 = "hello\0 LionLong"sv;//std::string_view sv4("hello\0 LionLong"sv)std::cout << "sv3 = " << sv3<< ", size() = " << sv3.size()<< ", data() = " << sv3.data() << std::endl;std::string_view sv4("hello\0 LionLong");std::cout << "sv4 = " << sv4<< ", size() = " << sv4.size()<< ", data() = " << sv4.data() << std::endl;
}

输出:

sv = hello, LionLong, size() = 14, data() = hello, LionLong
sv2 = hello, size() = 5, data() = hello, LionLong
sv3 = hello LionLong, size() = 14, data() = hello
sv4 = hello, size() = 5, data() = hello

可以看到data()会返回的是起始位置的字符指针(const char*),以data()返回值进行打印会一直输出直到遇到空字符。因此使用data()需要非常小心。

max_size()示例:

std::string_view sv;
std::cout << sv.max_size() << std::endl; //4611686018427387899

remove_prefix()示例:视图的起始位置向后移动n位,收缩视图的大小。

std::string str = "   hello";
std::string_view v = str;
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
std::cout << "String: '" << str << "', View  : '" << v << << "'" << std::endl; 
//输出
// String: '   hello', View  : 'hello'

三、std::string_view为什么性能高?

  • std::string_view采用享元设计模式,通常以ptrlength的结构来实现,非常轻便。

  • std::string_view上的字符串操作具有和std::string同类操作一致的复杂度。

  • std::string_view中的字符串操作大多数是constexpr的,都可在编译器执行,省去了运行时的复杂度。

四、std::string_view的使用陷阱

  1. 前面介绍data()函数的时候有提到过,data()会返回的是起始位置的字符指针,若以其返回值进行输出打印,会一直输出直到遇到\0结束符。
  2. std::string_view不持有所指向内容的所有权,所以如果把std::string_view局部变量作为函数返回值,则在函数返回后,内存会被释放,将出现悬垂指针或悬垂引用。
  3. 由于std::string_view只是字符串数据的视图,并不拥有字符串数据,它不能用于修改原始字符串的内容。如果尝试修改std::string_view所引用的字符串数据,将导致未定义行为。如果需要修改字符串数据,应该使用std::string而不是std::string_view
  4. 当使用std::string_view时,需要注意空指针的风险。如果将一个空指针传递给std::string_view,它的行为是未定义的。在使用std::string_view之前,应该检查字符串指针是否为空,以避免潜在的问题。
std::string_view foo() {std::string s { "hello, LionLong" };return std::string_view { s };
}int main() {std::cout << foo() << std::endl; //可能的输出:=�;Vreturn 0;
}

五、std::string_view源码解析

//<string_view>
template<typename _CharT, typename _Traits = std::char_traits<_CharT>>
class basic_string_view
{
public:// typesusing traits_type = _Traits;using value_type = _CharT;using pointer = value_type*;using const_pointer = const value_type*;using reference = value_type&;using const_reference = const value_type&;using const_iterator = const value_type*;using iterator = const_iterator;using const_reverse_iterator = std::reverse_iterator<const_iterator>;using reverse_iterator = const_reverse_iterator;using size_type = size_t;using difference_type = ptrdiff_t;static constexpr size_type npos = size_type(-1);constexpr basic_string_view() noexcept: _M_len{0}, _M_str{nullptr}{ }constexpr basic_string_view(const basic_string_view&) noexcept = default;constexpr basic_string_view(const _CharT* __str) noexcept: _M_len{traits_type::length(__str)}, _M_str{__str}{ }constexpr basic_string_view(const _CharT* __str, size_type __len) noexcept: _M_len{__len}, _M_str{__str}{ }//...private:size_t _M_len;const _CharT* _M_str;
};

std::string_view的实现并不复杂,在底层其实是一个非常简单的结构。std::string_view通常由两个成员变量组成:

  1. 指向字符串数据的指针(通常是const char*)。
  2. 字符串数据的长度。

构造函数只是对这两个成员变量进行初始化。这两个成员变量使得std::string_view能够表示字符串的范围,而不需要复制字符串数据。因此,它的创建和销毁成本非常低。

这两个成员变量使得std::string_view能够表示字符串的范围,而不需要复制字符串数据。因此,它的创建和销毁成本非常低。

看一下std::string_view的几个成员函数实现:

//<string_view> class basic_string_view
constexpr const_pointer data() const noexcept
{return this->_M_str;
}constexpr void remove_prefix(size_type __n) noexcept
{__glibcxx_assert(this->_M_len >= __n);this->_M_str += __n;this->_M_len -= __n;
}constexpr void remove_suffix(size_type __n) noexcept
{this->_M_len -= __n;
}constexpr basic_string_view substr(size_type __pos = 0, size_type __n = npos) const noexcept(false)
{__pos = std::__sv_check(size(), __pos, "basic_string_view::substr");const size_type __rlen = std::min(__n, _M_len - __pos);return basic_string_view{_M_str + __pos, __rlen};
}

内部实现方面,std::string_view的成员函数和操作符通常是非常轻量级的。底层实现原理相对简单,主要围绕着对指针和长度的操作展开。

六、总结

std::string_view是C++17引入的一个非拥有的字符串视图类型,它提供了一种轻量级的方式来访问现有字符串数据。std::string_view通过避免字符串复制和内存分配,它可以显著提高程序性能,并提供方便的字符串处理能力。但是,在使用过程中需要注意正确管理原始字符串的生命周期,以确保使用的字符串数据有效和安全。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/304428.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【开源社区】openEuler、openGauss、openHiTLS、MindSpore

【开源社区】openEuler、openGauss、openHiTLS、MindSpore 写在最前面开源社区参与和贡献的一般方式开源技术的需求和贡献方向 openEuler 社区&#xff1a;开源系统官方网站官方介绍贡献攻略开源技术需求 openGauss 社区&#xff1a;开源数据库官方网站官方介绍贡献攻略开源技术…

数字乡村:科技引领新时代农村发展

随着信息技术的迅猛发展和数字化浪潮的推进&#xff0c;数字乡村作为新时代农村发展的重要战略&#xff0c;正日益成为引领农村现代化的强大引擎。数字乡村不仅代表着农村信息化建设的新高度&#xff0c;更是农村经济社会发展的重要支撑。通过数字技术的深入应用&#xff0c;农…

vue2创建项目的两种方式,配置路由vue-router,引入element-ui

提示&#xff1a;vue2依赖node版本8.0以上 文章目录 前言一、创建项目基于vue-cli二、创建项目基于vue/cli三、对吧两种创建方式四、安装Element ui并引入五、配置路由跳转四、效果五、参考文档总结 前言 使用vue/cli脚手架vue create创建 使用vue-cli脚手架vue init webpack创…

✌2024/4/6—力扣—最长公共前缀✌

代码实现&#xff1a; char *longestCommonPrefix(char **strs, int strsSize) {if (strsSize 0) {return "";}for (int i 0; i < strlen(strs[0]); i) { // 列for (int j 1; j < strsSize; j) { // 行if (strs[0][i] ! strs[j][i]) { // 如果比较字符串的第…

Java特性之设计模式【外观模式】

一、外观模式 概述 外观模式&#xff08;Facade Pattern&#xff09;隐藏系统的复杂性&#xff0c;并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式&#xff0c;它向现有的系统添加一个接口&#xff0c;来隐藏系统的复杂性 这种模式涉及到一…

uniapp中页面滚动锚点位置及滚动到对应高度显示对应按钮

可以把页面代码和组件代码放自己项目里跑一下 页面代码 <template><view class"Tracing-detail"><view class"title" v-for"i in 30">顶部信息</view><!-- tab按钮 --><Tab v-model"activeIndex" …

云手机解决海外社媒运营的诸多挑战

随着海外社交媒体运营的兴起&#xff0c;如何有效管理多个账户成为了一项挑战。云手机作为一种新兴的解决方案&#xff0c;为海外社媒运营带来了前所未有的便利。 云手机的基本原理是基于云计算和虚拟化技术&#xff0c;允许用户在物理手机之外创建和使用多个虚拟手机。这种创新…

Node.js 的 5 个常见服务器漏洞

Node.js 是一个强大且广泛使用的 JavaScript 运行时环境&#xff0c;用于构建服务器端应用程序。然而&#xff0c;与任何其他软件一样&#xff0c;Node.js 也有自己的一些漏洞&#xff0c;如果处理不当&#xff0c;可能会导致安全问题。请注意&#xff0c;这些漏洞并不是 Node.…

嵌入式面向对象学习 RT-Thread I/O 设备管理框架 设备驱动层 案例测试

嵌入式面向对象 RT-Thread I/O 设备管理框架 设备驱动层 注&#xff1a;本文介绍性内容转载于《RT-Thread记录&#xff08;十、全面认识 RT-Thread I/O 设备模型&#xff09;》 注&#xff1a; 本次使用的开发板 &#xff1a; ​ 兆易创新GD32F407VET6开发板 ​ 雅特力科技…

nginx配置证书和私钥进行SSL通信验证

文章目录 一、背景1.1 秘钥和证书是两个东西吗&#xff1f;1.2 介绍下nginx配置文件中参数ssl_certificate和ssl_certificate_key1.3介绍下nginx支持的证书类型1.4 目前nginx支持哪种证书格式&#xff1f;1.5 nginx修改配置文件目前方式也会有所不同1.6 介绍下不通格式的证书哪…

微服务-4 Nacos

目录 一、注册中心 二、配置管理 1. 添加配置 2. 配置自动刷新 3. 多环境配置共享​编辑 一、注册中心 服务列表&#xff1a; 服务详情&#xff1a; 二、配置管理 1. 添加配置 (1). 在 nacos 界面中添加配置文件&#xff1a; 配置列表&#xff1a; 配置详情&#xff1a;…

Qt之QSS样式表

QSS简介 QSS&#xff08;Qt Style Sheet&#xff09;样式表是一种用于描述图形用户界面&#xff08;GUI&#xff09;样式的语言。它允许开发者为应用程序的控件定义视觉外观&#xff0c;例如颜色、字体、尺寸和布局等。 QSS 样式表的主要目的是提供一种简洁而灵活的方式来美化…

【优选算法专栏】专题四:前缀和(一)

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

DRF 常用功能

文章目录 一、主流认证方式Session认证Token认证JWT认证 二、DRF认证与权限Session认证所有视图&#xff08;全局&#xff09;启用认证视图级别启用认证 Token认证[推荐]安装APP启用Token认证生成数据库表(因为token要存储到数据库&#xff09;配置Token认证接口URL获取token使…

迭代器模式【行为模式C++】

1.简介 迭代器模式是一种行为设计模式&#xff0c; 让你能在不暴露集合&#xff08;聚合对象&#xff09;底层表现形式 &#xff08;列表、 栈和树等&#xff09; 的情况下遍历集合&#xff08;聚合对象&#xff09;中所有的元素。 迭代器的意义就是将这个行为抽离封装起来&a…

STM32F4 IAP跳转APP问题及STM32基于Ymodem协议IAP升级笔记

STM32F4 IAP 跳转 APP问题 ST官网IAP例程Chapter1 STM32F4 IAP 跳转 APP问题1. 概念2. 程序2.1 Bootloader 程序 问题现象2.2. APP程序 3. 代码4. 其他问题 Chapter2 STM32-IAP基本原理及应用 | ICP、IAP程序下载流程 | 程序执行流程 | 配置IAP到STM32F4xxxChapter3 STM32基于Y…

SQLite数据库在Linux系统上的使用

SQLite是一个轻量级的数据库解决方案&#xff0c;它是一个嵌入式的数据库管理系统。SQLite的特点是无需独立的服务器进程&#xff0c;可以直接嵌入到使用它的应用程序中。由于其配置简单、支持跨平台、服务器零管理&#xff0c;以及不需要复杂的设置和操作&#xff0c;SQLite非…

python如何输入多行

Python中的Input()函数在输入时&#xff0c;遇到回车符&#xff0c;那么一次输入就结束了。这不能满足输入多行文本并且行数也不确定的情形&#xff0c;当然输入空行也是允许的。 方法1&#xff1a;利用异常处理机制实现 lines[] while True:try:lines.append(input())except:…

达梦数据库清理归档日志的方法

达梦数据库清理归档日志的方法 在达梦数据库&#xff08;DM数据库&#xff09;中&#xff0c;归档日志文件是数据库运行过程中产生的&#xff0c;用于记录所有对数据库修改的详细信息。这些日志对于数据库的恢复非常关键&#xff0c;尤其是在进行灾难恢复或数据恢复时。然而&a…

关于Linux下的进程等待(进程篇)

目录 为什么存在进程等待&#xff1f;进程等待是在做什么&#xff1f; 怎样去执行进程等待&#xff1f; status options 为什么存在进程等待&#xff1f;进程等待是在做什么&#xff1f; 代码示例&#xff1a;模仿僵尸进程 #include <stdio.h> #include <unistd.…