跟我学c++中级篇——STL字符串之std::string_view

229 篇文章 94 订阅

一、标准库字符串处理

C和C++的一个很不一样的区别就是对字符串的处理,在c++的标准库里提供了一个std::string的字符串操作类。这使得c++对字符串的操作从某种程度上摆脱了原始指针的操作。从这个角度来说,对c++小白来说,肯定是利好的。但麻烦就在于c++强大的灵活性,导致在处理字符串时,效率会由于不同的应用编码导致差异性很大;同时,一不小心就有可能对原始字符串进行了修改,导致异常的发生。而这些,都不是一个普通菜鸟能够解决的。
当然,如果从一个完美的角度来看待这个问题,基本是无解的,毕竟c++的整体的设计目标在那儿。但是在一些细节上不断完善,或者在某一个方面上进一步的优化,c++是可以办到的,这就是今天要分析的c++17提供的std::string_view这个类。

二、std::string_view

这个类,有点类似于Golang语言中的切片Slice。这就意味着std::string_view本身并不拥有内存本身,它只是一个View,一个窗口,观看内存的窗口。这样理解可能就比较形象了。搞出这个类的目的非常简单,就是因为c++在处理字符串时,经常会对字符串进行显示的Copy或者隐式的拷贝,显示的还容易优化,隐式的就非常考验是不是老鸟了。但是如果有这么一个类,只是用来对内存字符串进行展示操作,也就是说不可能有内存本身的复制操作,直接不就优化到最底层了。这也是std::string_view类的思想。
但是有一得则必有一失,使用这个类需要有两点注意:
1、既然它是内存的视图、观察者,那么,就意味着字符串内存的生命周期(Runtime为动态)或者说作用域(Compile为静态)一定要大于std::string_view,否则可能有未知的后果。
2、std::string_view不像c/c++有一样有一个’\0’的终结符。一定要注意这点,这意味着,这个显示的长度内容,需要手动控制。
在下面的代码分析中,会对这两点进行一个说明。

三、源码分析

看一下在c++中的定义:

template<
    class CharT,
    class Traits = std::char_traits<CharT>
> class basic_string_view; 

Type	Definition
std::string_view (C++17)	std::basic_string_view<char>
std::wstring_view (C++17)	std::basic_string_view<wchar_t>
std::u8string_view (C++20)	std::basic_string_view<char8_t>
std::u16string_view (C++17)	std::basic_string_view<char16_t>
std::u32string_view (C++17)	std::basic_string_view<char32_t>

这里对基本的代码进行一下分析:

template<class _Elem,
	class _Traits>
	class basic_string_view
	{	// wrapper for any kind of contiguous character buffer
public:
	static_assert(is_same_v<_Elem, typename _Traits::char_type>, "Bad char_traits for basic_string_view; "
	"N4659 24.4.2 [string.view.template]/1 \"the type traits::char_type shall name the same type as charT.\"");

	using traits_type            = _Traits;
	using value_type             = _Elem;
	using pointer                = _Elem *;
	using const_pointer          = const _Elem *;
	using reference              = _Elem&;
	using const_reference        = const _Elem&;
	using const_iterator         = _String_view_iterator<_Traits>;
	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 auto npos{static_cast<size_type>(-1)};

	constexpr basic_string_view() noexcept
		: _Mydata(),
		_Mysize(0)
		{	// construct empty basic_string_view
		}

	constexpr basic_string_view(const basic_string_view&) noexcept = default;
	constexpr basic_string_view& operator=(const basic_string_view&) noexcept = default;	
	/* implicit */ constexpr basic_string_view(_In_z_ const const_pointer _Ntcts) noexcept // strengthened
		: _Mydata(_Ntcts),
		_Mysize(_Traits::length(_Ntcts))
		{	// construct basic_string_view around a null-terminated character-type sequence
		}

	constexpr basic_string_view(_In_reads_(_Count) const const_pointer _Cts, const size_type _Count)
			noexcept // strengthened
		: _Mydata(_Cts),
		_Mysize(_Count)
		{	// construct basic_string_view around a character-type sequence with explicit size
#if _ITERATOR_DEBUG_LEVEL >= 1
		_STL_VERIFY(_Count == 0 || _Cts, "non-zero size null string_view");
#endif /* _ITERATOR_DEBUG_LEVEL >= 1 */
		}
	_NODISCARD constexpr int compare(_In_z_ const _Elem * const _Ptr) const
		{	// compare [0, _Mysize) with [_Ptr, <null>)
		return (compare(basic_string_view(_Ptr)));
		}

	_NODISCARD constexpr int compare(const size_type _Off, const size_type _N0,
		_In_z_ const _Elem * const _Ptr) const
		{	// compare [_Off, _Off + _N0) with [_Ptr, <null>)
		return (substr(_Off, _N0).compare(basic_string_view(_Ptr)));
		}

	_NODISCARD constexpr int compare(const size_type _Off, const size_type _N0,
		_In_reads_(_Count) const _Elem * const _Ptr, const size_type _Count) const
		{	// compare [_Off, _Off + _N0) with [_Ptr, _Ptr + _Count)
		return (substr(_Off, _N0).compare(basic_string_view(_Ptr, _Count)));
		}

	_NODISCARD constexpr size_type find(const basic_string_view _Right, const size_type _Off = 0) const noexcept
		{	// look for _Right beginning at or after _Off
		return (_Traits_find<_Traits>(_Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize));
		}

	_NODISCARD constexpr size_type find(const _Elem _Ch, const size_type _Off = 0) const noexcept
		{	// look for _Ch at or after _Off
		return (_Traits_find_ch<_Traits>(_Mydata, _Mysize, _Off, _Ch));
		}

	_NODISCARD constexpr size_type find(_In_reads_(_Count) const _Elem * const _Ptr, const size_type _Off,
		const size_type _Count) const noexcept // strengthened
		{	// look for [_Ptr, _Ptr + _Count) beginning at or after _Off
		return (_Traits_find<_Traits>(_Mydata, _Mysize, _Off, _Ptr, _Count));
		}
......
} 

template<class _Traits>
	constexpr int _Traits_compare(_In_reads_(_Left_size) const _Traits_ptr_t<_Traits> _Left, const size_t _Left_size,
		_In_reads_(_Right_size) const _Traits_ptr_t<_Traits> _Right, const size_t _Right_size) noexcept
	{	// compare [_Left, _Left + _Left_size) to [_Right, _Right + _Right_size) using _Traits
	const int _Ans = _Traits::compare(_Left, _Right, _Min_value(_Left_size, _Right_size));

	if (_Ans != 0)
		{
		return (_Ans);
		}

	if (_Left_size < _Right_size)
		{
		return (-1);
		}

	if (_Left_size > _Right_size)
		{
		return (1);
		}

	return (0);
	}

template<class _Traits>
	constexpr size_t _Traits_find(
		_In_reads_(_Hay_size) const _Traits_ptr_t<_Traits> _Haystack, const size_t _Hay_size, const size_t _Start_at,
		_In_reads_(_Needle_size) const _Traits_ptr_t<_Traits> _Needle, const size_t _Needle_size) noexcept
	{	// search [_Haystack, _Haystack + _Hay_size) for [_Needle, _Needle + _Needle_size), at/after _Start_at
	if (_Needle_size > _Hay_size || _Start_at > _Hay_size - _Needle_size)
		{	// xpos cannot exist, report failure
			// N4659 24.3.2.7.2 [string.find]/1 says:
			// 1. _Start_at <= xpos
			// 2. xpos + _Needle_size <= _Hay_size;
			// therefore:
			// 3. _Needle_size <= _Hay_size (by 2) (checked above)
			// 4. _Start_at + _Needle_size <= _Hay_size (substitute 1 into 2)
			// 5. _Start_at <= _Hay_size - _Needle_size (4, move _Needle_size to other side) (also checked above)
		return (static_cast<size_t>(-1));
		}

	if (_Needle_size == 0)
		{	// empty string always matches if xpos is possible
		return (_Start_at);
		}

	const auto _Possible_matches_end = _Haystack + (_Hay_size - _Needle_size) + 1;
	for (auto _Match_try = _Haystack + _Start_at; ; ++_Match_try)
		{
		_Match_try = _Traits::find(_Match_try, static_cast<size_t>(_Possible_matches_end - _Match_try), *_Needle);
		if (!_Match_try)
			{	// didn't find first character; report failure
			return (static_cast<size_t>(-1));
			}

		if (_Traits::compare(_Match_try, _Needle, _Needle_size) == 0)
			{	// found match
			return (static_cast<size_t>(_Match_try - _Haystack));
			}
		}
	}

其实你看它的底层代码实现,其实也没有什么,有一点经验就可以看得比较清楚。又回复到了最初的算法和数据结构,看来还是要把数据结构算法搞的扎实一些。

四、实例

看一下几个相关的实例(cppreference.com):

 
#include <string_view>
 
int main()
{
    using namespace std::literals;
 
    constexpr auto str{" long long int;"sv};
 
    static_assert(
        1 == str.find("long"sv)            && "<- find(v , pos = 0)" &&
        6 == str.find("long"sv, 2)         && "<- find(v , pos = 2)" &&
        0 == str.find(' ')                 && "<- find(ch, pos = 0)" &&
        2 == str.find('o', 1)              && "<- find(ch, pos = 1)" &&
        2 == str.find("on")                && "<- find(s , pos = 0)" &&
        6 == str.find("long double", 5, 4) && "<- find(s , pos = 5, count = 4)"
    );
 
    static_assert(str.npos == str.find("float"));
}

再看一个生命周期和终结符的示例:

#include <iostream>
#include <vector>
#include <string>
#include <string_view>

std::string_view Test() 
{
 std::string s("sssss  dd");
 return std::string_view(s);
}
void TestView()
{
    std::string_view s = Test();
    std::cout<<s<<std::endl;
}

int main() {
  const char* cStr = "My Test";
  
  std::string_view sView(cStr, 3);
  
  std::cout << sView << std::endl;
  
  std::cout << sView.data() << std::endl;
  
  TestView();
  
  return 0;
}

运行结果:

My 
My Test
.......#@    //说明,此处是未知的代码,无法拷贝上来

在《c++17入门经典》这本书里提到过std::string_view在处理字符串常量时,const仍然无法阻止字符串的隐式复制的情形,其实这也是std::string_view的一个重要的应用之处。其它如传参和返回值中都有这种情形,可以认真的思考一下。

五、总结

在网上听人说过,任何进步都是站在巨人的肩膀的前进的。换句话说,进步甚至技术暴发都不是凭空出现的,都是在前面的坑的基础上不断的总结和完善,堆积到一定程度,量变到质变。c++也是如此,蜇伏了多年的c++在其它语言的快速发展影响下,也不断的在吸收自己和别人的先进经验,不断的推陈出新,这就是c++的生命力所在。
梧桐一叶落,而知天下秋。如是而已!
努力要从今日始!归来的少年!
在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值