C++17 string_view 详细案例

(鄙人总结,希望和大家交流,切莫转载,谢谢!)

C++17 使用std::string_view来观测一段字符数组,减少内存的拷贝,或者说不至于像string对象移动后,前一个对象放弃那块本属于它的内存。

// 原码:
// TYPEDEFS FOR basic_string_view
using string_view = basic_string_view<char>;

// CLASS TEMPLATE basic_string_view
template <class _Elem, class _Traits>
class basic_string_view {
public:
	using const_pointer          = const _Elem*;
    using size_type              = size_t;
	...
private:
	const_pointer _Mydata;
    size_type _Mysize;
}

可以看到string_view的成员变量只有2个,const_pointer _Mydata:指向字符串首地址的指针。 size_type _Mysize:字符串的长度(不含"\0")

比如下例:使用string s给string_view sv初始化

	// 例1:
	string s{ "Hello" };
	string_view sv{ s };

完成sv的初始化后,我们分别监视s和sv:
在这里插入图片描述
在这里插入图片描述
可以看到sv是对s的buf的观测,即s.data() == sv.data() == sv._Mydata,且sv._Mysize = s.size(),至此sv初始化完成,它刻画一个字符串,这个字符串的长度为5(不能说长度为s.size()),起始地址为s.data().

	// 例子2 给s拼接一个" World"
	string s{ "Hello" };
	string_view sv{ s };
	s += " World";

	cout << s << endl;
	cout << sv << endl;

结果显示:

Hello World
Hello

再次监测s和sv
在这里插入图片描述

在这里插入图片描述
发现s在capacity范围内(当前环境得出的capacity如上图s的监视图的[capacity]所示为5),增加了" World",而sv只是刻画访问那个内存的5个字节的字符串,所以sv的输出依旧是"Hello"。

那么,如果s增加的字符数大于原本的capacity,势必会迎来成长,发生内存的搬移,sv依旧访问原本的内存,此时就会得到意料之外的值。比如:

	// 例3 突破此环境(MSVC)下capacity为15的上限
	s += " World World World World World";
	
	cout << s << endl;
	cout << sv << endl;

结果显示:

Hello World World World World World
萯?o

stringstring_view都有返回子串的方法:substr(pos, cnt),看看它干了什么事:

    _NODISCARD constexpr basic_string_view substr(const size_type _Off = 0, size_type _Count = npos) const {
        // return a new basic_string_view moved forward by _Off and trimmed to _Count elements
        _Check_offset(_Off);
        _Count = _Clamp_suffix_size(_Off, _Count);
        return basic_string_view(_Mydata + _Off, _Count);
    }

它返回一个临时对象的拷贝,这个临时对象的_Mydata = 原对象的_Mydata + _Off_Mysize 为第二入参_Count,因此可以知道返回值依旧是指向同一块内存的不同位置的指针,其长度为_Count,因此子串仍旧会出现上述因为string成长所带来的问题。

stringstring_view都提供.data()的方法,返回一个指向字符串的指针。当使用::printf("%s", ptr)时,或者cout << ptr << endl来打印字符串时,稍微注意string_view的输出

	// 例4 sv_sub的内部指针指向sv的第3个字符的地址
	string s{ "Hello" };
	string_view sv{ s };
	string_view sv_sub = sv.substr(2, 2);

	cout << sv_sub << endl;
	::printf("%s", sv_sub.data());

结果显示:

ll
llo

这里printf的输出并不能达到只打印子串所观测的2个字符,它会一直打到字符串结束为止。稍微注意一下即可。

又由于string_view避免了字符串对象在非移动赋值时的内存申请,和N个字符的拷贝,因此从效率上来说是相当高的,这里做一个对比。

// 例5 添加ctime头文件用于获取tick
#include <ctime>

	...
	
	const long _LOOP_MILLION = 1000000; // 循环一百万次
	/* 在ctime中有这样一个定义,tick的差值除以1000就等于时间差(毫秒级)
	// The number of clock ticks per second
	#define CLOCKS_PER_SEC  ((clock_t)1000)
	*/
	clock_t startTime, endTime;
	startTime = clock();
	for (long i{}; i < _LOOP_MILLION; ++i)
	{
		string sub(std::move(s.substr()));  // string子串涉及内存拷贝,然后移动给string sub
	}
	endTime = clock();
	cout << "The run time is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;

	startTime = clock();
	for (long i{}; i < _LOOP_MILLION; ++i)
	{
		string_view sub(move(sv.substr()));  // string_view子串不涉及内存拷贝,构造string_view sub时传入地址和长度
	}
	endTime = clock();
	cout << "The run time is: " << (double)(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;

结果显示:

The run time is: 3.975s
The run time is: 0.174s

所以在设计优秀的算法之外,优秀的编程方式也很重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歪锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值