详解C++17下的string_view

C++ string_view全攻略:零拷贝字符串的高效处理 是C++17引入的轻量级字符串引用类,本质是一个指针+长度// 不拥有字符串,只是引用// 字面量,零拷贝// 引用str的内容// 内部实现(简化)// };return 0;操作string加速比构造分配+拷贝指针+长度~100xsubstr分配+拷贝移动指针~50x传参可能拷贝16字节~5x比较逐字符逐字符1x查找逐字符逐字符1x知识点要点本质指针+长度的轻量引用,16字节优势零拷贝、零分配,性能显著提升构造。 阅读详情

看到一个介绍 C++17 的系列博文(原文),有十来篇的样子,觉得挺好,看看有时间能不能都简单翻译一下,这是第五篇~

当字符串数据的所有权已经确定(譬如由某个string对象持有),并且你只想访问(而不修改)他们时,使用 std::string_view 可以避免字符串数据的复制,从而提高程序效率,这(指程序效率)也是这篇文章的主要内容.

这次要介绍的 string_view 是 C++17 的一个主要特性.

我假设你已经了解了一些 std::string_view 的知识,如果没有,可以看看我之前的这篇文章.C++ 中的 string 类型在堆上存放自己的字符串数据,所以当你处理 string 类型的时候,很容易就会产生(堆)内存分配.

Small string optimisation

我们先看下以下的示例代码:

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

void* operator new(std::size_t count)
{
	std::cout << "   " << count << " bytes" << std::endl;
	return malloc(count);
}

void getString(const std::string& str) {}



int main()
{
	std::cout << std::endl;

	std::cout << "std::string" << std::endl;

// 这里会调用上面的new函数,输出16字节(根据编译器的不同,值可能不同,下同)
	std::string small = "0123456789"; 
	small = "12345678"; // 这里不会再调用上面的new函数,因为长度小于16字节

    // 因为字符串长度大于16了,这里会再调用上面的new函数,输出32字节
	small = "1234567890123456789000066666";

 /* 这里会调用上面的new函数,第一次输出16字节(默认为substr分配16字节内存)
    第二次是发现为small.substr(5)长度大于16,所以重新为substr分配内存以便能容纳下它,输出32*/
	std::string substr = small.substr(5);
	std::cout << "   " << substr << std::endl;

	std::cout << std::endl;

	std::cout << "getString" << std::endl;

	getString(small);// 这里不会调用上面的new函数,因为small长度没有变得更长
	getString("0123456789"); // 这里会调用上面的new函数,因为长度小于16,所以输出16
	const char message[] = "0123456789";
	getString(message); // 这里会调用上面的new函数,因为长度小于16,所以输出16

	std::cout << std::endl;

	system("pause()");
	return 0;
}

代码第6到第10行,我重载了全局的 new 操作符,这样我就能跟踪(堆)内存的分配了,而后,代码分别在第23、31、39、41行创建了string对象,所以这几处代码都会产生(堆)内存分配.相关的程序输出如下:

咦, 红色方框中的32字节是怎么产生的?这是怎么回事?其实 string 类型只有在字符串超过指定大小(具体实现相关)时才会申请(堆)内存,对于 MSVC 来说,指定大小为 15, 对于 GCC 和 Clang,这个值则为 23.关于string的内存配置机制,当超过指定编译器下string内存配置的长度会重新分配内存,所以这里重新分配内存了,从而输出红色方框的32字。string内存分配机制具体请参见《STL库中string类内存布局的探究》。

这也就意味着,较短的字符串数据是直接存储于 string 的对象内存中的,不需要分配(堆)内存.

从现在开始,示例代码中的字符串将拥有至少30个字符,这样我们就不需要关注短字符串优化了.好了,带着这个前提(字符串长度>=30个字符),让我们重新开始讲解.

No memory allocation required

现在, std::string_view 无需复制字符串数据的优点就更加明显了(std::string不进行短字符串优化的情况下),下面的代码就是例证.

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

void* operator new(std::size_t count) 
{
	std::cout << "   " << count << " bytes" << std::endl;
	return malloc(count);
}

void getString(const std::string& str) {}

void getStringView(std::string_view strView) {}

int main()
{
	std::cout << std::endl;

	std::cout << "std::string" << std::endl;

	std::string large = "0123456789-123456789-123456789-123456789";
	std::string substr = large.substr(10);

	std::cout << std::endl;

	std::cout << "std::string_view" << std::endl;

	std::string_view largeStringView{ large.c_str(), large.size() };
	largeStringView.remove_prefix(10);

	assert(substr == largeStringView);

	std::cout << std::endl;

	std::cout << "getString" << std::endl;

	getString(large);
	getString("0123456789-123456789-123456789-123456789");
	const char message[] = "0123456789-123456789-123456789-123456789";
	getString(message);

	std::cout << std::endl;

	std::cout << "getStringView" << std::endl;

	getStringView(large);
	getStringView("0123456789-123456789-123456789-123456789");
	getStringView(message);

	std::cout << std::endl;
	
	return 0;
}

代码22行,23行,39行,41行因为创建了 string 对象 所以会分配(堆)内存,但是代码29行,30行,47行,48行,49行也相应的创建了 string_view 对象,但是并没有发生(堆)内存分配!输出结果如下:

上图输出结果中红色框都表示指定的字符串变量因为长度不够(大于编译器默认为该字符串分配的内存长度),重新发生了内存分配,上图中的16是定义字符串时,编译器默认为该字符串分配的内存空间。

这个结果令人印象深刻,(堆)内存分配是一个非常耗时的操作,尽量的避免(堆)内存分配会给程序带来很大的性能提升,使用 string_view 能提升程序效率的原因也正是在此,当你需要创建很多 string 的子字符串时, string_view 带来的效率提升将更加明显.

O(n) versus O(1)

std::string 和 std::string_view 都有 substr 方法, std::string 的 substr 方法返回的是字符串的子串,而 std::string_view 的 substr 返回的则是字符串子串的"视图".听上去似乎两个方法功能上比较相似,但他们之间有一个非常大的差别: std::string::substr 是线性复杂度, std::string_view::substr 则是常数复杂度.这意味着 std::string::substr 方法的性能取决于字符串的长度,而std::string_view::substr 的性能并不受字符串长度的影响.

让我们来做一个简单的性能对比:

#include <chrono>
#include <fstream>
#include <iostream>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#include <string_view>

static const int count = 30;
static const int access = 10000000;

int main()
{
	std::cout << std::endl;

	std::ifstream inFile("grimm.txt");

	std::stringstream strStream;
	strStream << inFile.rdbuf();
	std::string grimmsTales = strStream.str();

	size_t size = grimmsTales.size();

	std::cout << "Grimms' Fairy Tales size: " << size << std::endl;
	std::cout << std::endl;

	// random values
	std::random_device seed;
	std::mt19937 engine(seed());
	std::uniform_int_distribution<> uniformDist(0, size - count - 2);
	std::vector<int> randValues;
	for (auto i = 0; i < access; ++i) randValues.push_back(uniformDist(engine));

	auto start = std::chrono::steady_clock::now();
	for (auto i = 0; i < access; ++i) 
	{
		grimmsTales.substr(randValues[i], count);
	}
	std::chrono::duration<double> durString = std::chrono::steady_clock::now() - start;
	std::cout << "std::string::substr:      " << durString.count() << " seconds" << std::endl;

	std::string_view grimmsTalesView{ grimmsTales.c_str(), size };
	start = std::chrono::steady_clock::now();
	for (auto i = 0; i < access; ++i) 
	{
		grimmsTalesView.substr(randValues[i], count);
	}
	std::chrono::duration<double> durStringView = std::chrono::steady_clock::now() - start;
	std::cout << "std::string_view::substr: " << durStringView.count() << " seconds" << std::endl;

	std::cout << std::endl;

	std::cout << "durString.count()/durStringView.count(): " << durString.count() / durStringView.count() << std::endl;

	std::cout << std::endl;
	
	return 0;
}

展示程序结果之前,让我先来简单描述一下:测试代码的主要思路就是读取一个大文件的内容并保存为一个 string ,然后分别使用 std::string 和 std::string_view 的 substr 方法创建很多子字符串.我很好奇这些子字符串的创建过程需要花费多少时间.

我使用了<格林童话>作为程序的读取文件.代码中的 grimmTales(第22行) 存储了文件的内容.代码34行中我向 std::vector 填充了 10000000 个范围为[0, size - count - 2]的随机数字.接着就开始了正式的性能测试.代码37行到40行我使用 std::string::substr 创建了很多长度为30的子字符串,之所以设置长度为30,是为了规避 std::string 的短字符串优化.代码46行到49行使用 std::string_view::substr 做了相同的工作(创建子字符串).

程序的输出如下,结果中包含了文件的长度, std::string::substr 所花费的时间, std::string_view::substr 所花费的时间以及他们之间的比例.我使用的编译器是 GCC 6.3.0.

Size 30

没有开启编译器优化的结果:

开启编译器优化的结果:

编译器的优化对于 std::string::substr 的性能提升并没有多大作用,但是对于 std::string_view::substr 的性能提升则效果明显.而 std::string_view::substr 的效率几乎是 std::string::substr 的 45 倍!

Different sizes

那么如果我们改变子字符串的长度,上面的测试代码又会有怎样的表现呢?当然,相关测试我都开启了编译器优化,并且相关的数字我都做了3位小数的四舍五入.

对于上面的结果我并不感到惊讶,这些数字正好反应了 std::string::substr 和 std::string_view::substr 的算法复杂度. std::string::substr 是线性复杂度(依赖于字符串长度), std::string_view::substr 则是常数复杂度(不依赖于字符串长度).最后的结论就是: std::string_view::substr 的性能要大幅优于 std::string::substr.

告别‘命名空间std没有成员’:盘点VS中那些需要C++17/20才能用的宝藏特性(含string_view、optional、filesystem) 本文详细解析了Visual Studio中C++17/20标准下的核心特性,包括std::string_view、std::optional和std::filesystem等。通过解决'命名空间std没有成员string_view'等常见报错,指导开发者如何配置VS启用现代C++功能,提升代码效率与跨平台能力。 阅读详情

相关推荐

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

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

Lion_Long的博客 6907

C++ string_view

string_view 提供字符串的一个视图。使用 string_view 能够减少字符串的拷贝。 类 basic_string_view : 模板类 std::string_view : std::basic_string_view<char> std::wstring_view : std::basic_string_view<wchar_t> std::u8string_view : std::basic_string_view<char8_t> std::u16

木偶不哭不笑的博客 2681

C++(17):string堆的使用,以及string_view的高效

这个是根据字符串的长度来决定的,当字符串的长度(含结束符)小于等于16时,字符串实际上被存储在string对象内部。当通过char*的方式传递参数时,会首先根据char*构造出一个string对象来,然后再将其传递个string&。这种方式还是会造成堆内存的分配。虽然我们可以采用传递引用string的方式,减少不必要的内存分配,但这种方式仅对string对象有效,对于char*是失效的。string_view可以认为是string/char*的一个代理类,他可以观察对应的字符串,但不允许对其进行修改。

风静如云的博客 2931

【C++】C++ 17简单上手(2)——string_view

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

Pangda code twice 1万+

C++(20):view

可以看到f1的定义之后才将16插入到了data的尾,但是遍历f1时依然输出了256。view是一个 range,具有常数时间复杂度的的拷贝、移动和赋值操作。

风静如云的博客 593

Goose开源程序本地机上 AI 代理,能够从头到尾自动执行复杂的开发任务。Goose 不仅可以提供代码建议,还可以自主构建整个项目、编写和执行代码、调试故障、编排工作流程以及与外部 API 交互

Goose开源程序 是您的本机上 AI 代理,能够从头到尾自动执行复杂的开发任务。Goose 不仅可以提供代码建议,还可以自主构建整个项目、编写和执行代码、调试故障、编排工作流程以及与外部 API 交互。无论您是在构建想法原型、优化现有代码,还是管理复杂的工程管道,goose 都能适应您的工作流程并精确执行任务。

struggle2025的博客 2045

C++17 std::string_view 详解:告别字符串拷贝,提升性能与安全

在C++编程中,字符串处理是基础且频繁的操作,其性能直接影响程序效率。传统上,开发者常使用`std::string`,它通过所有权模型管理内存,提供了安全的修改操作,但深拷贝带来的开销在只读场景下成为性能瓶颈。为了优化这一过程,C++17引入了`std::string_view`,它是一种非拥有型的字符串视图,仅持有指向现有数据的指针和长度,构造与拷贝成本极低,完美解决了函数参数传递和临时字符串观察时的性能问题。其核心价值在于实现零拷贝的字符串访问,特别适用于解析、查找、比较等只读操作,能显著提升库函数和算

weixin_30905133的博客 394

Gephi新手必看:如何用Excel整理CSV数据快速生成网络图(附模板)

本文为Gephi新手提供了一份从Excel数据整理到生成专业网络图的完整指南。详细讲解了如何构建规范的节点表和边表CSV文件,并导入Gephi进行可视化分析。通过实战案例,帮助读者快速掌握利用Excel整理数据、规避常见错误,并运用布局算法与视觉编码,将复杂关系数据转化为直观的网络洞察。

weixin_28487433的博客 391

C++17 std::string_view 详解:零拷贝字符串处理的性能利器

在C++高性能程序开发中,字符串处理常常成为性能瓶颈。传统的std::string虽方便,但隐式拷贝、临时对象构造与堆内存分配在频繁子串提取、协议解析、日志分析等场景下会显著拖慢程序。C++17正式引入的std::string_view是一种非拥有型字符串视图,内部仅保存指针和长度,构建与substr操作全程零拷贝,能大幅降低字符串处理的额外开销。同时它与std::string、const char*无缝互操作,是只读字符串参数的最佳选择。理解其原理、生命周期约束以及与C接口交互的注意事项,是安全使用的前提

weixin_34116482的博客 311

数字信号处理实验:信号谱分析与FFT算法的应用

​ (1)给定信号为 ,其中a为42,b为1。理论分析信号 的频率成分。 (2)分别采用采样频率为 , , , 对 进行采样,其中 , , , ,根据是否满足采样定理要求分析四种采样频率下频谱的分布,在满足采样定理情况下做频谱分析。 (3)任意生成两组有限长信号,对信号分别进行直接卷积和FFT算法求卷积,并分析圆周卷积与线性卷积的关系。

m0_56324525的博客 3203

人工智能之语言领域 自然语言处理 第六章 情感分析

情感分析(Sentiment Analysis),又称意见挖掘(Opinion Mining),旨在自动识别和提取文本中表达的主观情感、态度或情绪。从用户评论到社交媒体,从产品反馈到政治舆情,情感分析已成为企业决策、品牌监控和人机交互的关键技术。本章将深入介绍情感分析的层级结构、核心技术、主流模型,并通过社交媒体舆情实战案例完整演示分析与可视化流程。层级目标示例词级(Word-level)判断单个词的情感倾向“好” → 正面;“差” → 负面句级(Sentence-level)判断整个句子的情感极性。

咚咚王者的博客 857

【C++现代#13】C++17 三大核心库特性详解:std::string_view、文件系统库(<filesystem>)与并行算法

📃个人主页:island1314。

island1314的博客 1106

yanshee 机器人全流程指南

2.点击yanshee app右上角根据提示操作 3.当手机显示成功连接 ,按下机器人胸前按钮说出“ip地址”4.根据机器人播报的ip地址加上:8888使用浏览器访问,你就会看到 jupyterd的界面,里面可以新建文件也可以改他存在的文件。我们使了yanshee机器人,这个是面向K12、中高职、高校学生推出的开源人形机器人教育平台,应用于课程教学、竞赛、科研活动等场景。我们接下来可以根据这些apl做许多好玩的,比如控制他运动,跳舞唱歌,it`s easy。注意电脑,手机,机器人必须连同一个网。

qq_44827929的博客 7613

VS2022里用不了C++17的string_view?手把手教你改项目语言标准(附C++14/17/20对比)

本文详细指导如何在VS2022中启用C++17特性,解决std::string_view报错问题,并提供C++14/17/20的核心特性对比与选择建议。通过项目属性修改语言标准,开发者可以充分利用现代C++特性,提升代码效率与性能。

weixin_33730836的博客 703

别再只会用串口助手了!手把手教你用手机App调试HC-05蓝牙模块(STM32F103实战)

本文详细介绍了如何利用手机App调试HC-05蓝牙模块与STM32F103的无线连接,突破传统串口助手的限制。通过实战指南,开发者可以掌握移动性测试、实时监控和快速验证等高效调试技巧,优化蓝牙透传的稳定性和性能。

weixin_33670786的博客 428

CKS-CN考试之路----02

其实我们根据题目也可以知道秘钥是叫clever-cactus。稍作等待后,确认pod的状态,如果是1/1基本就没问题了。这步不一定做.目的是确定秘钥的名字。

Q先生 656

vs2019在使用std::string_view报错时的解决方法

std::string_view是C++17标准添加进来的,我在使用的时候报错“std没有成员string_view",晚上查了一番发现是因为我vs2019的编译器选择的还是默认(C++14)版本。 所以修改编译器版本的方法如下: (项目—>属性—>C/C++—>语言—>C++语言标准,选C++17或者最新标准即可。) ...

weixin_51581591的博客 5546

基于四旋翼模型的火箭型无人机视线角保持控制仿真设计

本文针对火箭型无人机的视线角保持控制问题展开研究。基于四旋翼动力学模型,通过改造设计了一种摄像头朝上安装的特殊无人机系统,用于追踪空中目标。文章详细推导了坐标系定义、运动学和动力学方程,重点分析了摄像头安装与视线几何关系,建立了目标运动模型和视线向量计算方法。通过解算期望姿态角,实现了使机体Z轴始终对准目标的控制目标。仿真结果表明,该系统能有效完成视线角保持任务,为空对空监视等应用场景提供了可行方案。~~~~~

huanghm88的专栏 143

VS2022里std::string_view报错?别慌,一分钟搞定C++17标准切换

本文详细解析了在VS2022中使用std::string_view报错的根本原因及解决方案,指导开发者如何切换C++17标准以支持现代C++特性。通过对比测试展示了string_view的性能优势,并提供了跨版本兼容方案和最佳实践,帮助开发者高效利用这一轻量级字符串视图类。

weixin_33739646的博客 678

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

C++17 std::string_view:零拷贝字符串视图的设计原理与实战避坑指南

在C++高性能编程中,字符串处理是常见的性能瓶颈。传统上使用`std::string`进行参数传递时,常因隐式构造临时对象而引发不必要的堆内存分配与拷贝,影响程序效率。为解决此问题,C++17引入了`std::string_view`,它是一种非拥有(non-owning)的字符串视图,核心原理是仅封装一个指向外部连续字符序列的指针及其长度,自身不管理内存,从而实现极低的构造与传递开销。这项技术的核心价值在于提供了一种零拷贝(zero-copy)的字符串访问抽象,特别适用于只读场景,能显著提升函数参数传递、

weixin_30846347的博客 234

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【C++】string_view:高效字符串处理指南

C++17引入的string_view是一种高效处理只读字符串的工具,通过保存字符串起始地址和长度实现零拷贝访问,避免了const std::string&的隐式构造和const char*的长度计算问题。主要适用于函数参数传递、字符串切片操作和容器存储只读引用等场景。使用时需特别注意:原字符串生命周期必须长于string_view、不保证以'\0'结尾、不可修改字符串内容,且拷贝是浅拷贝。核心原则是仅用于只读访问,不管理内存生命周期。

Ape's IT Blog 865

C++的std--string_view字符串视图与临时字符串生命周期管理

为此,C++17引入了`std::string_view`,它提供了一种轻量级的字符串视图机制,通过引用现有字符串数据避免拷贝开销。`string_view`与`std::string`、`const char*`等类型可无缝转换,但需注意隐式构造可能掩盖生命周期问题。例如,`std::string`的`operator+`生成临时对象时,直接转换为视图会导致未定义行为。例如,从`std::string`或C风格字符串构造`string_view`时,无需分配新内存。临时字符串的生命周期陷阱。

pxkuwb_263的博客 45

C++ string::substr 函数 3 种用法详解:从基础语法到异常处理实战

本文详细解析C++中string::substr函数的3种核心用法,从基础语法到异常处理实战。涵盖默认复制整个字符串、单参数截取至末尾及双参数精确控制子串范围等场景,重点讲解参数边界条件与防御性编程技巧,帮助开发者编写健壮的字符串处理代码。

adiking520110的博客 389

C++17 std::string_view:高性能字符串视图的原理、陷阱与实战

在C++编程中,字符串处理是常见的性能瓶颈之一,尤其是频繁的拷贝操作会带来不必要的开销。为了解决这一问题,C++标准库引入了非拥有(non-owning)的字符串视图概念,它通过轻量级的指针和长度组合,实现了对现有字符串数据的零开销访问。其核心原理是避免内存分配和深拷贝,仅提供对底层字符序列的只读视图,从而在解析、查找、比较等场景中显著提升性能。然而,这种设计也带来了生命周期管理的挑战,开发者必须确保视图所引用的原始数据在其使用期间保持有效,否则会导致悬空引用等未定义行为。在工程实践中,std::strin

weixin_30536861的博客 269

C++高效只读字符串:std::string_view用法、陷阱与最佳实践

本文介绍 C++17 std::string_view 的常用写法、性能优势、生命周期陷阱及 data() 零结尾问题,适合作为日常开发速查。

m0_64395065的博客 421
上一篇: Qt的qrc资源文件在Visual studio编译出的Qt程序中不能显示问题解决
下一篇: STL库中string类内存布局的探究
荆楚闲人
博客等级 码龄20年 3万+粉丝 473原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值