C++17中std::string_view的使用
1.引言
在C/C++日常編程中,我們常進行數(shù)據(jù)的傳遞操作,比如,將數(shù)據(jù)傳給函數(shù)。當(dāng)數(shù)據(jù)占用的內(nèi)存較大時,減少數(shù)據(jù)的拷貝可以有效提高程序的性能。在C++17之前,為了接收只讀字符串的函數(shù)選擇形參一直是一件進退兩難的事情,它應(yīng)該是const char*嗎?那樣的話,如果客戶用std::string,這必須使用c_str()或data()來獲取const char*。更糟糕的是,函數(shù)講失去std::string良好的面向?qū)ο蟮姆椒捌淞己玫妮o助方法?;蛟S,形參應(yīng)改用const std::string&?這種情況下,始終需要std::string。例如,如果傳遞一個字符串字面量,編譯器將默認(rèn)創(chuàng)建一個臨時字符串對象(其中包括字符串字面量的副本),并將該對象傳遞給函數(shù),因此會增加一點開銷。有時,人們會編寫同一函數(shù)的多個重載版本,一個接收const char*,另一個接收const std::string&,單顯然,這并不是一個優(yōu)雅的解決方案,而且std::string的substr函數(shù),每次都要返回一個新生成的子串,很容易引起性能問題。實際上我們本意不是要改變原字符串,為什么不在原字符串基礎(chǔ)上返回呢?
在C++17中引入了std::string_view,就很好的解決了上面這些問題。
2.原理分析
std::string_view是字符串的視圖版本,它能讓我們像處理字符串一樣處理字符序列,而不需要為它們分配內(nèi)存空間。也就是說,std::string_view類型的對象只是引用一個外部的字符序列,而不需要持有它們。因此,一個字符串視圖對象可以被看作字符串序列的引用 。
使用字符串視圖的開銷很小,速度卻很快(以值傳遞一個std::string_view的開銷總是很?。?。然而,它也有一些潛在的危險,就和原生指針一樣,在使用string_view時也必須由程序員自己來保證引用的字符串序列是有效的。那么,std::string_view為什么能做到開銷小,速度很快呢?我們接著往下看,本文以VS2019平臺展開講解std::string_view的原理和深層次用法。
2.1.結(jié)構(gòu)
std::string_view的類UML圖如下:
std::string_view是std::basic_string_view<char>的特化版本,std::basic_string_view的所有接口都適用于std::std::string_view。對于使用寬字符集,例如Unicode
或者某些亞洲字符集的字符串,還定義了其它幾個版本:
#ifdef __cpp_lib_char8_t using u8string_view = basic_string_view<char8_t>; #endif // __cpp_lib_char8_t using u16string_view = basic_string_view<char16_t>; using u32string_view = basic_string_view<char32_t>; using wstring_view = basic_string_view<wchar_t>;
std::basic_string_view的內(nèi)部結(jié)構(gòu):
template <class _Elem, class _Traits> class basic_string_view { // wrapper for any kind of contiguous character buffer public: 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; //... private: const_pointer _Mydata; size_type _Mysize; };
從中可以看出std::basic_string_view只存儲了{_Mydata,_Mysize}兩個元素,不會具體存儲原數(shù)據(jù),僅僅存儲指向的數(shù)據(jù)的起始指針和長度,所以這個開銷是非常小的。
2.2.構(gòu)造函數(shù)
std::basic_string_view的構(gòu)造函數(shù)如下:
//構(gòu)造函數(shù) constexpr basic_string_view() noexcept : _Mydata(), _Mysize(0) {} //1 constexpr basic_string_view(const basic_string_view&) noexcept = default; //2 constexpr basic_string_view& operator=(const basic_string_view&) noexcept = default; //3 /* implicit */ constexpr basic_string_view(_In_z_ const const_pointer _Ntcts) noexcept // strengthened : _Mydata(_Ntcts), _Mysize(_Traits::length(_Ntcts)) {} //4 constexpr basic_string_view( _In_reads_(_Count) const const_pointer _Cts, const size_type _Count) noexcept // strengthened : _Mydata(_Cts), _Mysize(_Count) { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Count == 0 || _Cts, "non-zero size null string_view"); #endif // _CONTAINER_DEBUG_LEVEL > 0 } //5 #ifdef __cpp_lib_concepts // clang-format off template <contiguous_iterator _It, sized_sentinel_for<_It> _Se> requires (is_same_v<iter_value_t<_It>, _Elem> && !is_convertible_v<_Se, size_type>) constexpr basic_string_view(_It _First, _Se _Last) noexcept(noexcept(_Last - _First)) // strengthened : _Mydata(_STD to_address(_First)), _Mysize(static_cast<size_type>(_Last - _First)) {} //6 // clang-format on #endif // __cpp_lib_concepts // 從迭代器中獲取地址函數(shù)to_address
從上面的源碼可以看出,td::basic_string_view的構(gòu)造函數(shù)有:
1)std::string_view a; 調(diào)用第1個構(gòu)造函數(shù)
2)std::string_view a("123"); 調(diào)用第4個構(gòu)造函數(shù),改函數(shù)里面調(diào)用了_Traits::length函數(shù)
_NODISCARD static _CONSTEXPR17 size_t length(_In_z_ const _Elem* const _First) noexcept /* strengthened */ { // find length of null-terminated string #if _HAS_CXX17 #ifdef __cpp_char8_t if constexpr (is_same_v<_Elem, char8_t>) { #if _HAS_U8_INTRINSICS return __builtin_u8strlen(_First); #else // ^^^ use u8 intrinsics / no u8 intrinsics vvv return _Primary_char_traits::length(_First); #endif // _HAS_U8_INTRINSICS } else #endif // __cpp_char8_t { return __builtin_strlen(_First); } #else // _HAS_CXX17 return _CSTD strlen(reinterpret_cast<const char*>(_First)); #endif // _HAS_CXX17 }
_Traits::length函數(shù)又調(diào)用了strlen計算了字符串的長度;構(gòu)造了一個3長度的std::string_view。
3)std::string_view a("122323423", 5); 調(diào)用第5個構(gòu)造函數(shù),把內(nèi)容和長度構(gòu)造函數(shù),構(gòu)建了一個內(nèi)容為"12232",長度為5的std::string_view。
4)構(gòu)造函數(shù)還可以接收迭代器,如下:
char b[] = "121212e124124"; std::string_view e(std::begin(b), std::end(b));
調(diào)用第6個構(gòu)造函數(shù),里面調(diào)用to_address通過迭代器獲取指針:
template <class _Ty, class = void> inline constexpr bool _Has_to_address_v = false; // determines whether _Ptr has pointer_traits<_Ptr>::to_address(p) template <class _Ty> inline constexpr bool _Has_to_address_v<_Ty, void_t<decltype(pointer_traits<_Ty>::to_address(_STD declval<const _Ty&>()))>> = true; template <class _Ty> _NODISCARD constexpr _Ty* to_address(_Ty* const _Val) noexcept { static_assert(!is_function_v<_Ty>, "N4810 20.10.4 [pointer.conversion]/2: The program is ill-formed if T is a function type."); return _Val; } template <class _Ptr> _NODISCARD constexpr auto to_address(const _Ptr& _Val) noexcept { if constexpr (_Has_to_address_v<_Ptr>) { return pointer_traits<_Ptr>::to_address(_Val); } else { return _STD to_address(_Val.operator->()); // plain pointer overload must come first } }
5)拷貝構(gòu)造函數(shù)和賦值構(gòu)造函數(shù)都是使用的系統(tǒng)默認(rèn)函數(shù),類似memcpy,直接把_Mydata,_Mysize的值拷貝到另外對象,比較簡單,就不贅述了。
上面的都好理解,唯一需要說明的是:為什么我們代碼string_view test(string("123"))可以編譯通過,但為什么沒有對應(yīng)的構(gòu)造函數(shù)?
實際上這是因為std::string類重載了std::string到std::string_view的轉(zhuǎn)換操作符:
operator std::basic_string_view<CharT, Traits>() const noexcept;
所以,std::string_view test(std::string("123"))實際執(zhí)行了兩步操作:
a.std::string("abc")轉(zhuǎn)換為std::string_view對象
b.test調(diào)用了第2個構(gòu)造函數(shù)生成std::string_view對象
2.3.成員函數(shù)
1)獲取容量的函數(shù)
_NODISCARD constexpr size_type size() const noexcept { return _Mysize; } _NODISCARD constexpr size_type length() const noexcept { return _Mysize; } _NODISCARD constexpr bool empty() const noexcept { return _Mysize == 0; } _NODISCARD constexpr size_type max_size() const noexcept { // bound to PTRDIFF_MAX to make end() - begin() well defined (also makes room for npos) // bound to static_cast<size_t>(-1) / sizeof(_Elem) by address space limits return (_STD min)(static_cast<size_t>(PTRDIFF_MAX), static_cast<size_t>(-1) / sizeof(_Elem)); }
2) 迭代器相關(guān)的函數(shù)
_NODISCARD constexpr const_iterator begin() const noexcept { #if _ITERATOR_DEBUG_LEVEL >= 1 return const_iterator(_Mydata, _Mysize, 0); #else // ^^^ _ITERATOR_DEBUG_LEVEL >= 1 ^^^ // vvv _ITERATOR_DEBUG_LEVEL == 0 vvv return const_iterator(_Mydata); #endif // _ITERATOR_DEBUG_LEVEL } _NODISCARD constexpr const_iterator end() const noexcept { #if _ITERATOR_DEBUG_LEVEL >= 1 return const_iterator(_Mydata, _Mysize, _Mysize); #else // ^^^ _ITERATOR_DEBUG_LEVEL >= 1 ^^^ // vvv _ITERATOR_DEBUG_LEVEL == 0 vvv return const_iterator(_Mydata + _Mysize); #endif // _ITERATOR_DEBUG_LEVEL } _NODISCARD constexpr const_iterator cbegin() const noexcept { return begin(); } _NODISCARD constexpr const_iterator cend() const noexcept { return end(); } _NODISCARD constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } _NODISCARD constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } _NODISCARD constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } _NODISCARD constexpr const_reverse_iterator crend() const noexcept { return rend(); }
3)元素訪問函數(shù)
_NODISCARD constexpr const_pointer data() const noexcept { return _Mydata; } _NODISCARD constexpr const_reference operator[](const size_type _Off) const noexcept /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Off < _Mysize, "string_view subscript out of range"); #endif // _CONTAINER_DEBUG_LEVEL > 0 return _Mydata[_Off]; } _NODISCARD constexpr const_reference at(const size_type _Off) const { // get the character at _Off or throw if that is out of range _Check_offset_exclusive(_Off); return _Mydata[_Off]; } _NODISCARD constexpr const_reference front() const noexcept /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Mysize != 0, "cannot call front on empty string_view"); #endif // _CONTAINER_DEBUG_LEVEL > 0 return _Mydata[0]; } _NODISCARD constexpr const_reference back() const noexcept /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Mysize != 0, "cannot call back on empty string_view"); #endif // _CONTAINER_DEBUG_LEVEL > 0 return _Mydata[_Mysize - 1]; }
4)修改器函數(shù)
constexpr void remove_prefix(const size_type _Count) noexcept /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Mysize >= _Count, "cannot remove prefix longer than total size"); #endif // _CONTAINER_DEBUG_LEVEL > 0 _Mydata += _Count; _Mysize -= _Count; } constexpr void remove_suffix(const size_type _Count) noexcept /* strengthened */ { #if _CONTAINER_DEBUG_LEVEL > 0 _STL_VERIFY(_Mysize >= _Count, "cannot remove suffix longer than total size"); #endif // _CONTAINER_DEBUG_LEVEL > 0 _Mysize -= _Count; } constexpr void swap(basic_string_view& _Other) noexcept { const basic_string_view _Tmp{_Other}; // note: std::swap is not constexpr before C++20 _Other = *this; *this = _Tmp; }
remove_prefix(size_type)和remove_suffix(size_type)方法,前者是將起始指針前移給定的偏移量來收縮字符串,后者是將結(jié)尾指針倒退給定的偏移量來收縮字符串,三個函數(shù)僅會修改std::string_view的數(shù)據(jù)指向,不會修改指向的數(shù)據(jù)。
5)查找比較函數(shù)
_CONSTEXPR20 size_type copy( _Out_writes_(_Count) _Elem* const _Ptr, size_type _Count, const size_type _Off = 0) const { // copy [_Off, _Off + Count) to [_Ptr, _Ptr + _Count) _Check_offset(_Off); _Count = _Clamp_suffix_size(_Off, _Count); _Traits::copy(_Ptr, _Mydata + _Off, _Count); return _Count; } _Pre_satisfies_(_Dest_size >= _Count) _CONSTEXPR20 size_type _Copy_s(_Out_writes_all_(_Dest_size) _Elem* const _Dest, const size_type _Dest_size, size_type _Count, const size_type _Off = 0) const { // copy [_Off, _Off + _Count) to [_Dest, _Dest + _Count) _Check_offset(_Off); _Count = _Clamp_suffix_size(_Off, _Count); _Traits::_Copy_s(_Dest, _Dest_size, _Mydata + _Off, _Count); return _Count; } _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); } constexpr bool _Equal(const basic_string_view _Right) const noexcept { return _Traits_equal<_Traits>(_Mydata, _Mysize, _Right._Mydata, _Right._Mysize); } _NODISCARD constexpr int compare(const basic_string_view _Right) const noexcept { return _Traits_compare<_Traits>(_Mydata, _Mysize, _Right._Mydata, _Right._Mysize); } _NODISCARD constexpr int compare(const size_type _Off, const size_type _Nx, const basic_string_view _Right) const { // compare [_Off, _Off + _Nx) with _Right return substr(_Off, _Nx).compare(_Right); } _NODISCARD constexpr int compare(const size_type _Off, const size_type _Nx, const basic_string_view _Right, const size_type _Roff, const size_type _Count) const { // compare [_Off, _Off + _Nx) with _Right [_Roff, _Roff + _Count) return substr(_Off, _Nx).compare(_Right.substr(_Roff, _Count)); } _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 _Nx, _In_z_ const _Elem* const _Ptr) const { // compare [_Off, _Off + _Nx) with [_Ptr, <null>) return substr(_Off, _Nx).compare(basic_string_view(_Ptr)); } _NODISCARD constexpr int compare(const size_type _Off, const size_type _Nx, _In_reads_(_Count) const _Elem* const _Ptr, const size_type _Count) const { // compare [_Off, _Off + _Nx) with [_Ptr, _Ptr + _Count) return substr(_Off, _Nx).compare(basic_string_view(_Ptr, _Count)); } #if _HAS_CXX20 _NODISCARD constexpr bool starts_with(const basic_string_view _Right) const noexcept { const auto _Rightsize = _Right._Mysize; if (_Mysize < _Rightsize) { return false; } return _Traits::compare(_Mydata, _Right._Mydata, _Rightsize) == 0; } _NODISCARD constexpr bool starts_with(const _Elem _Right) const noexcept { return !empty() && _Traits::eq(front(), _Right); } _NODISCARD constexpr bool starts_with(const _Elem* const _Right) const noexcept /* strengthened */ { return starts_with(basic_string_view(_Right)); } _NODISCARD constexpr bool ends_with(const basic_string_view _Right) const noexcept { const auto _Rightsize = _Right._Mysize; if (_Mysize < _Rightsize) { return false; } return _Traits::compare(_Mydata + (_Mysize - _Rightsize), _Right._Mydata, _Rightsize) == 0; } _NODISCARD constexpr bool ends_with(const _Elem _Right) const noexcept { return !empty() && _Traits::eq(back(), _Right); } _NODISCARD constexpr bool ends_with(const _Elem* const _Right) const noexcept /* strengthened */ { return ends_with(basic_string_view(_Right)); } #endif // _HAS_CXX20 _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); } _NODISCARD constexpr size_type find(_In_z_ const _Elem* const _Ptr, const size_type _Off = 0) const noexcept /* strengthened */ { // look for [_Ptr, <null>) beginning at or after _Off return _Traits_find<_Traits>(_Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr)); } _NODISCARD constexpr size_type rfind(const basic_string_view _Right, const size_type _Off = npos) const noexcept { // look for _Right beginning before _Off return _Traits_rfind<_Traits>(_Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize); } _NODISCARD constexpr size_type rfind(const _Elem _Ch, const size_type _Off = npos) const noexcept { // look for _Ch before _Off return _Traits_rfind_ch<_Traits>(_Mydata, _Mysize, _Off, _Ch); } _NODISCARD constexpr size_type rfind(_In_reads_(_Count) const _Elem* const _Ptr, const size_type _Off, const size_type _Count) const noexcept /* strengthened */ { // look for [_Ptr, _Ptr + _Count) beginning before _Off return _Traits_rfind<_Traits>(_Mydata, _Mysize, _Off, _Ptr, _Count); } _NODISCARD constexpr size_type rfind(_In_z_ const _Elem* const _Ptr, const size_type _Off = npos) const noexcept /* strengthened */ { // look for [_Ptr, <null>) beginning before _Off return _Traits_rfind<_Traits>(_Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr)); } _NODISCARD constexpr size_type find_first_of(const basic_string_view _Right, const size_type _Off = 0) const noexcept { // look for one of _Right at or after _Off return _Traits_find_first_of<_Traits>( _Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_first_of(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_first_of(_In_reads_(_Count) const _Elem* const _Ptr, const size_type _Off, const size_type _Count) const noexcept /* strengthened */ { // look for one of [_Ptr, _Ptr + _Count) at or after _Off return _Traits_find_first_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Count, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_first_of( _In_z_ const _Elem* const _Ptr, const size_type _Off = 0) const noexcept /* strengthened */ { // look for one of [_Ptr, <null>) at or after _Off return _Traits_find_first_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr), _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_of(const basic_string_view _Right, const size_type _Off = npos) const noexcept { // look for one of _Right before _Off return _Traits_find_last_of<_Traits>( _Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_of(const _Elem _Ch, const size_type _Off = npos) const noexcept { // look for _Ch before _Off return _Traits_rfind_ch<_Traits>(_Mydata, _Mysize, _Off, _Ch); } _NODISCARD constexpr size_type find_last_of(_In_reads_(_Count) const _Elem* const _Ptr, const size_type _Off, const size_type _Count) const noexcept /* strengthened */ { // look for one of [_Ptr, _Ptr + _Count) before _Off return _Traits_find_last_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Count, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_of( _In_z_ const _Elem* const _Ptr, const size_type _Off = npos) const noexcept /* strengthened */ { // look for one of [_Ptr, <null>) before _Off return _Traits_find_last_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr), _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_first_not_of(const basic_string_view _Right, const size_type _Off = 0) const noexcept { // look for none of _Right at or after _Off return _Traits_find_first_not_of<_Traits>( _Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_first_not_of(const _Elem _Ch, const size_type _Off = 0) const noexcept { // look for any value other than _Ch at or after _Off return _Traits_find_not_ch<_Traits>(_Mydata, _Mysize, _Off, _Ch); } _NODISCARD constexpr size_type find_first_not_of(_In_reads_(_Count) const _Elem* const _Ptr, const size_type _Off, const size_type _Count) const noexcept /* strengthened */ { // look for none of [_Ptr, _Ptr + _Count) at or after _Off return _Traits_find_first_not_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Count, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_first_not_of( _In_z_ const _Elem* const _Ptr, const size_type _Off = 0) const noexcept /* strengthened */ { // look for none of [_Ptr, <null>) at or after _Off return _Traits_find_first_not_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr), _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_not_of(const basic_string_view _Right, const size_type _Off = npos) const noexcept { // look for none of _Right before _Off return _Traits_find_last_not_of<_Traits>( _Mydata, _Mysize, _Off, _Right._Mydata, _Right._Mysize, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_not_of(const _Elem _Ch, const size_type _Off = npos) const noexcept { // look for any value other than _Ch before _Off return _Traits_rfind_not_ch<_Traits>(_Mydata, _Mysize, _Off, _Ch); } _NODISCARD constexpr size_type find_last_not_of(_In_reads_(_Count) const _Elem* const _Ptr, const size_type _Off, const size_type _Count) const noexcept /* strengthened */ { // look for none of [_Ptr, _Ptr + _Count) before _Off return _Traits_find_last_not_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Count, _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr size_type find_last_not_of( _In_z_ const _Elem* const _Ptr, const size_type _Off = npos) const noexcept /* strengthened */ { // look for none of [_Ptr, <null>) before _Off return _Traits_find_last_not_of<_Traits>( _Mydata, _Mysize, _Off, _Ptr, _Traits::length(_Ptr), _Is_specialization<_Traits, char_traits>{}); } _NODISCARD constexpr bool _Starts_with(const basic_string_view _View) const noexcept { return _Mysize >= _View._Mysize && _Traits::compare(_Mydata, _View._Mydata, _View._Mysize) == 0; }
這些函數(shù)大致std::string的功能一致,在這里我就不贅述了。
2.4.std::string_view字面量
標(biāo)準(zhǔn)的用戶自定義字面量sv,將字符串字面量解釋為std::string_view,類似Qt的QString中的QStringLiteral。看源碼是怎么實現(xiàn)的:
// basic_string_view LITERALS inline namespace literals { inline namespace string_view_literals { _NODISCARD constexpr string_view operator"" sv(const char* _Str, size_t _Len) noexcept { return string_view(_Str, _Len); } _NODISCARD constexpr wstring_view operator"" sv(const wchar_t* _Str, size_t _Len) noexcept { return wstring_view(_Str, _Len); } #ifdef __cpp_char8_t _NODISCARD constexpr basic_string_view<char8_t> operator"" sv(const char8_t* _Str, size_t _Len) noexcept { return basic_string_view<char8_t>(_Str, _Len); } #endif // __cpp_char8_t _NODISCARD constexpr u16string_view operator"" sv(const char16_t* _Str, size_t _Len) noexcept { return u16string_view(_Str, _Len); } _NODISCARD constexpr u32string_view operator"" sv(const char32_t* _Str, size_t _Len) noexcept { return u32string_view(_Str, _Len); } } // namespace string_view_literals } // namespace literals
用戶就可以這樣定義:
using namespace std::literals::string_view_literals; using namespace std::string_view_literals; using namespace std::literals; using namespace std; std::string_view sv { "My Hello world"sv };
3.實例
3.1.std::string_view和std::string的運算符操作
當(dāng)我們將std::string_view類型的常量弱引用類型的字符串和std::string類型的字符串進行相加(運算符+)操作時會出錯,必須要先將string_view轉(zhuǎn)化為const char*,也就是調(diào)用data()接口,測試代碼如下:
#include<iostream> #include<string> #include<string_view> int main() { std::string str1 = "hello"; std::string_view sv1 = " world"; //使用+號運算符時,必須將string_view轉(zhuǎn)化為const char* auto it = str1 + sv1.data(); //使用append追加字符串不會出錯 auto it2 = str1.append(sv1); std::cout << it2 << std::endl; return 0; }
警告:返回字符串的函數(shù)應(yīng)該返回const std::string&或std::string,但不應(yīng)該返回std::string_view。返回std::string_view會帶來使返回的std::string_view無效的風(fēng)險,例如當(dāng)它指向的字符串需要重新分配時。
警告:將const std:string&或std::string_view存儲為類的數(shù)據(jù)成員需要確保它們指向的字符串在對象的生命周期內(nèi)保持有效狀態(tài),存儲std::string更安全。
3.2.查找函數(shù)使用
先看一個例子:
string replace_post(string_view src, string_view new_post) { // 找到點的位置 auto pos = src.find(".") + 1; // 取出點及點之前的全部字符,string_view的substr會返回一個 // string_view對象,所以要取data()賦值給string對象 string s1 = src.substr(0, pos).data(); // 加上新的后綴 return s1 + new_post.data(); } int main() { string_view sv = "abcdefg.xxx"; string s = replace_post(sv, "yyy"); cout << sv << " replaced post by yyy result is:" << s << endl; return 0; }
輸出:
abcdefg.xxxyyy
為什么輸出 "abcdefg.xxxyyy" 了呢?那是因為在這一步string s1 = src.substr(0, pos).data();返回后s1還是 "abcdefg.xxx",std::string_view內(nèi)部只是簡單地封裝原始字符串的起始位置和結(jié)束位置, 相當(dāng)于給字符串設(shè)置了一個觀察窗口,用戶只能看到通過窗口能看到的那部分?jǐn)?shù)據(jù). data()成員返回的是char*的指針, 是std::string_view內(nèi)部字符串的起始位置. 所以其表現(xiàn)再來的行為跟C字符串一樣了, 直到遇到空字符串才結(jié)束。
3.3.std::string_view和臨時字符串
std::string_view并不擁有其指向內(nèi)容的所有權(quán),用Rust的術(shù)語來說,它僅僅是暫時borrow(借用)了它。如果擁有者提前釋放了,你還在使用這些內(nèi)容,那會出現(xiàn)內(nèi)存問題,這跟懸掛指針(dangling pointer)或懸掛引用(dangling references)很像。Rust專門有套機制在編譯時分析變量的生命期,保證borrow的資源在使用期間不會被釋放,但C++沒有這樣的檢查,需要人工保證。下面列出一些典型的問題情況:
std::string_view sv = std::string{"hello world"};
string_view foo() { std::string s{"hello world"}; return string_view{s}; }
auto id(std::string_view sv) { return sv; } int main() { std::string s = "hello"; auto sv = id(s + " world"); }
警告:永遠(yuǎn)不要使用std::string_view保存臨時字符串的視圖。
4.總結(jié)
std::string_view的優(yōu)點:
1)高效性:std:string_view主要用于提供字符串的視圖(view),使std::string_view拷貝字符串的過程非常高效,永遠(yuǎn)不會創(chuàng)建字符串的任何副本,不像std::string會效率低下且導(dǎo)致內(nèi)存開銷。std::string_view不擁有字符串?dāng)?shù)據(jù),它僅提供對現(xiàn)有字符串的視圖或引用(view or reference)。這使得它適合需要訪問或處理字符串而無需內(nèi)存分配或重新分配開銷的場景,特別是在處理大量字符串時非常有用。
2)安全性:由于stdstring_view是只讀的,因此它不能被用來修改字符串。這使得它成為一個安全的工具,可以防止由于修改字符串而導(dǎo)致的錯誤。
3) 靈活性:stdstring_view可以輕松地與各種字符串類型一起使用包括std::string、字符數(shù)組和字符指針。這使得它成為處理字符串的靈活工具。
當(dāng)然任何事物都有它的兩面性,它也有些不足,在一些復(fù)雜的場景的,人工是很難保證指向的內(nèi)容的生命周期足夠長。所以,推薦的使用方式:僅僅作為函數(shù)參數(shù),因為如果該參數(shù)僅僅在函數(shù)體內(nèi)使用而不傳遞出去,這樣使用是安全的。
參考:std::basic_string_view - cppreference.com
到此這篇關(guān)于C++17中std::string_view的使用的文章就介紹到這了,更多相關(guān)C++17 std::string_view內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言MultiByteToWideChar和WideCharToMultiByte案例詳解
這篇文章主要介紹了C語言MultiByteToWideChar和WideCharToMultiByte案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C語言入門篇--學(xué)習(xí)選擇,if,switch語句以及代碼塊
本篇文章是基礎(chǔ)篇,適合c語言剛?cè)腴T的朋友,本文主要帶大家學(xué)習(xí)一下C語言的選擇,if,switch語句及代碼塊,幫助大家快速入門c語言的世界,更好的理解c語言2021-08-08