为什么C++17要引入std::string_view?

目录

1.引言

2.原理分析

2.1.结构

2.2.构造函数

2.3.成员函数

2.4.std::string_view字面量

3.实例

3.1.std::string_view和std::string的运算符操作

3.2.查找函数使用

3.3.std::string_view和临时字符串

4.总结


1.引言

        在C/C++日常编程中,我们常进行数据的传递操作,比如,将数据传给函数。当数据占用的内存较大时,减少数据的拷贝可以有效提高程序的性能。在C++17之前,为了接收只读字符串的函数选择形参一直是一件进退两难的事情,它应该是const char*吗?那样的话,如果客户用std::string,这必须使用c_str()或data()来获取const char*。更糟糕的是,函数讲失去std::string良好的面向对象的方法及其良好的辅助方法。或许,形参应改用const std::string&?这种情况下,始终需要std::string。例如,如果传递一个字符串字面量,编译器将默认创建一个临时字符串对象(其中包括字符串字面量的副本),并将该对象传递给函数,因此会增加一点开销。有时,人们会编写同一函数的多个重载版本,一个接收const char*,另一个接收const std::string&,单显然,这并不是一个优雅的解决方案,而且std::string的substr函数,每次都要返回一个新生成的子串,很容易引起性能问题。实际上我们本意不是要改变原字符串,为什么不在原字符串基础上返回呢?

        在C++17中引入了std::string_view,就很好的解决了上面这些问题。

2.原理分析

        std::string_view是字符串的视图版本,它能让我们像处理字符串一样处理字符序列,而不需要为它们分配内存空间。也就是说,std::string_view类型的对象只是引用一个外部的字符序列,而不需要持有它们。因此,一个字符串视图对象可以被看作字符串序列的引用 。
        使用字符串视图的开销很小,速度却很快(以值传递一个std::string_view的开销总是很小)。然而,它也有一些潜在的危险,就和原生指针一样,在使用string_view时也必须由程序员自己来保证引用的字符串序列是有效的。那么,std::string_view为什么能做到开销小,速度很快呢?我们接着往下看,本文以VS2019平台展开讲解std::string_view的原理和深层次用法。

2.1.结构

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的内部结构:

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}两个元素,不会具体存储原数据,仅仅存储指向的数据的起始指针和长度,所以这个开销是非常小的。

2.2.构造函数

std::basic_string_view的构造函数如下:

    //构造函数
    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

    // 从迭代器中获取地址函数to_address

从上面的远源码可以看出,td::basic_string_view的构造函数有:

1)std::string_view a;  调用第1个构造函数

2)std::string_view  a("123"); 调用第4个构造函数,改函数里面调用了_Traits::length函数

_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函数又调用了strlen计算了字符串的长度;构造了一个3长度的std::string_view。

3)std::string_view  a("122323423",  5); 调用第5个构造函数,把内容和长度构造函数,构建了一个内容为"12232",长度为5的std::string_view。

4)构造函数还可以接收迭代器,如下:

char b[] = "121212e124124";
std::string_view  e(std::begin(b), std::end(b));

调用第6个构造函数,里面调用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)拷贝构造函数和赋值构造函数都是使用的系统默认函数,类似memcpy,直接把_Mydata,_Mysize的值拷贝到另外对象,比较简单,就不赘述了。

上面的都好理解,唯一需要说明的是:为什么我们代码string_view test(string("123"))可以编译通过,但为什么没有对应的构造函数?

实际上这是因为std::string类重载了std::string到std::string_view的转换操作符:

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

所以,std::string_view test(std::string("123"))实际执行了两步操作:

a.std::string("abc")转换为std::string_view对象
b.test调用了第2个构造函数生成std::string_view对象

2.3.成员函数

1)获取容量的函数

    _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) 迭代器相关的函数

_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)元素访问函数

    _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)修改器函数

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)方法,前者是将起始指针前移给定的偏移量来收缩字符串,后者是将结尾指针倒退给定的偏移量来收缩字符串,三个函数仅会修改std::string_view的数据指向,不会修改指向的数据。

5)查找比较函数

    _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;
    }

这些函数大致std::string的功能一致,在这里我就不赘述了。

2.4.std::string_view字面量

标准的用户自定义字面量sv,将字符串字面量解释为std::string_view,类似Qt的QString中的QStringLiteral。看源码是怎么实现的:

// 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的运算符操作

当我们将std::string_view类型的常量弱引用类型的字符串和std::string类型的字符串进行相加(运算符+)操作时会出错,必须要先将string_view转化为const char*,也就是调用data()接口,测试代码如下:

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

int main()
{
	std::string str1 = "hello";
	std::string_view sv1 = " world";
	//使用+号运算符时,必须将string_view转化为const char*
	auto it = str1 + sv1.data();
	//使用append追加字符串不会出错
	auto it2 = str1.append(sv1);
	std::cout << it2 << std::endl;

	return 0;
}

警告:返回字符串的函数应该返回const std::string&或std::string,但不应该返回std::string_view。返回std::string_view会带来使返回的std::string_view无效的风险,例如当它指向的字符串需要重新分配时。

警告:将const std:string&或std::string_view存储为类的数据成员需要确保它们指向的字符串在对象的生命周期内保持有效状态,存储std::string更安全。

3.2.查找函数使用

先看一个例子:

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内部只是简单地封装原始字符串的起始位置和结束位置, 相当于给字符串设置了一个观察窗口,用户只能看到通过窗口能看到的那部分数据. data()成员返回的是char*的指针, 是std::string_view内部字符串的起始位置. 所以其表现再来的行为跟C字符串一样了, 直到遇到空字符串才结束。

3.3.std::string_view和临时字符串

std::string_view并不拥有其指向内容的所有权,用Rust的术语来说,它仅仅是暂时borrow(借用)了它。如果拥有者提前释放了,你还在使用这些内容,那会出现内存问题,这跟悬挂指针(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"); 
}

警告:永远不要使用std::string_view保存临时字符串的视图。

4.总结

std::string_view的优点:

1)高效性:std:string_view主要用于提供字符串的视图(view),使std::string_view拷贝字符串的过程非常高效,永远不会创建字符串的任何副本,不像std::string会效率低下且导致内存开销。std::string_view不拥有字符串数据,它仅提供对现有字符串的视图或引用(view or reference)。这使得它适合需要访问或处理字符串而无需内存分配或重新分配开销的场景,特别是在处理大量字符串时非常有用。
2)安全性:由于stdstring_view是只读的,因此它不能被用来修改字符串。这使得它成为一个安全的工具,可以防止由于修改字符串而导致的错误。
3)  灵活性:stdstring_view可以轻松地与各种字符串类型一起使用包括std::string、字符数组和字符指针。这使得它成为处理字符串的灵活工具。

当然任何事物都有它的两面性,它也有些不足,在一些复杂的场景的,人工是很难保证指向的内容的生命周期足够长。所以,推荐的使用方式:仅仅作为函数参数,因为如果该参数仅仅在函数体内使用而不传递出去,这样使用是安全的。

参考:std::basic_string_view - cppreference.com

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Offer必备算法_双指针_八道力扣OJ题详解(由浅到深)

目录 双指针算法原理 ①力扣283. 移动零 解析代码 ②力扣1089. 复写零 解析代码 ③力扣202. 快乐数 解析代码 ④力扣11. 盛最多水的容器 解析代码 ⑤力扣611. 有效三角形的个数 解析代码 ⑥剑指 Offer 57. 和为s的两个数字 解析代码&#xff1a; ⑦力扣15. 三数之…

最通俗易懂的JVM内存管理与对象创建原理

前言 对于Java程序员来说&#xff0c;在虚拟机自动内存管理机制的帮助下&#xff0c;不再需要像 C/C程序为每一个new操作去写配对 的delete/free代码&#xff0c;不容易出现内存泄漏和内存溢出问题。也正是因为Java程序员把控制内存的权力交给了Java虚拟机&#xff0c;一旦出现…

SpringMVC基础知识学习笔记

Universe Infinity Inc. 目录 一、学习SpringMVC主要是学什么1、SpringMVC的基本原理2、SpringMVC学习串联 二、快速体验SpringMVC的开发1、新建项目&#xff0c;转成web项目2、引入依赖3、编写Spring的配置类4、配置web启动类&#xff0c;替代web.xml5、编写Handler&#xff…

都在卷鸿蒙开发,那就推荐 几个鸿蒙开源项目

如果要问2024年最火的技术是什么,那鸿蒙开发必须占据一些位置,HarmonyOS是华为自主研发的物联网操作系统,自2019年8月正式发布以来便受到了广大开发者的追崇。为了方便大家学习鸿蒙开发,本文分享 12 个开源的鸿蒙实战项目,希望能从这些项目中获得启发和实用经验。 小狐浏…

IDEA的database使用

一、数据据库 在使用database之前&#xff0c;首先你的电脑要安装好了数据库并且启动。 MySQL卸载手册 链接&#xff1a;https://pan.baidu.com/doc/share/AVXW5SG6T76puBOWnPegmw-602323264797863 提取码&#xff1a;hlgf MySQL安装图解 链接&#xff1a;https://pan.baidu.…

Win10/11中VMware Workstation设置网络桥接模式

文章目录 一、添加VMware Bridge Protocol服务二、配置桥接参数1.启用系统Device Install Service服务2.配置VMware 需要确认物理网卡是否有添加VMware Bridge Protocol服务 添加VMware Bridge Protocol服务 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参…

艾比森的“增长炼金术”:从四力驱动到三层面增长

在技术驱动的LED显示行业&#xff0c;产品迭代快、周期波动大&#xff0c;企业经营时时承压。 但是&#xff0c;市场上总不乏打破“传统”的个例。根据2023年业绩预告&#xff0c;艾比森预计2023年实现归母净利润3.10亿-3.50亿元&#xff0c;同比增长52.70%-72.40%&#xff1b…

HttpServletRequest getServerPort()、getLocalPort() 、getRemotePort() 区别

getRemotePort() 、getServerPort()、getLocalPort() request.getServerPort()、request.getLocalPort() 和 request.getRemotePort() 这三个方法都是获取与HTTP请求相关的端口信息的 客户端(如浏览器)通过某个随机分配的网络连接端口(7070) 向服务器发送HTTP请求( http://exam…

【Docker】未来已来 | Docker技术在云计算、边缘计算领域的应用前景

欢迎来到英杰社区&#xff1a; https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区&#xff1a; https://bbs.csdn.net/topics/617897397 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff…

[小程序]向服务器上传图片和从服务器下载图片

本例的服务器基于flask&#xff0c;配置flask可以参见[Flask]上传多个文件到服务器https://blog.csdn.net/weixin_37878740/article/details/128435136?ops_request_misc%257B%2522request%255Fid%2522%253A%2522170581653516800185854860%2522%252C%2522scm%2522%253A%252220…

【iOS】UICollectionView的基本使用

使用UITableView作为表格来展示数据完全没有问题&#xff0c;但仍有许多局限性&#xff0c;对于一些更加复杂的布局样式&#xff0c;就有些力不从心了 比如&#xff0c;UITableView只允许表格每一行只能显示一个cell&#xff0c;而不能在一行中显示多个cell&#xff0c;对于这…

项目管理该考哪个证书❓NPDP还是软考❓

有小伙伴在纠结是要考NPDP认证呢还是考软考呢❓ 今天小编要给大家好好说说NPDP认证❗️ &#x1f4a1;NPDP全称New Product Development Professional&#xff0c;也就是产品经理国际资格认证。 &#x1f525;NPDP是国际公认的为一的新产品开发专业认证&#xff0c;是集理论、方…

【神经网络】火箭点火发射-诠释一场数据与学习的奇妙之旅

火箭点火发射来理解神经网络的故事细节 在一个充满科技气息的研究室里&#xff0c;一群科学家们正在忙碌地准备着一次重要的火箭点火发射。这次发射不仅是一次航天探索的壮丽征程&#xff0c;更是一场利用神经网络处理数据的智慧之旅。 在火箭发射的背后&#xff0c;神经网络…

使用freessl为网站获取https证书及配置详细步骤

文章目录 一、进入freessl网站二、修改域名解析记录三、创建证书四、配置证书五、服务启动 一、进入freessl网站 首先进入freessl网站&#xff0c;需要注册一个账号 freessl网站 进入网站后填写自己的域名 接下来要求进行DCV配置 二、修改域名解析记录 到域名管理处编辑域名…

MySQL基础笔记(8)多表查询

一.多表关系介绍 项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也会存在着各种联系&#xff0c;分为如下3类&#xff1a; 一对…

【算法详解】力扣162.寻找峰值

​ 目录 一、题目描述二、思路分析 一、题目描述 力扣链接&#xff1a;力扣162.寻找峰值 峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums&#xff0c;找到峰值元素并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回 任何一个…

Vulnhub靶机:FunBox 2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;FunBox 2&#xff08;10.0.2.27&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnhub.com/funbo…

【LeetCode每日一题】2788. 按分隔符拆分字符串

2024-1-20 文章目录 [2788. 按分隔符拆分字符串](https://leetcode.cn/problems/split-strings-by-separator/)思路&#xff1a; 2788. 按分隔符拆分字符串 思路&#xff1a; 对于每个单词&#xff0c;使用一个可变字符串 StringBuilder 来构建拆分后的单词。初始时&#xff0…

蓝桥杯单片机零基础到国二经验分享

我参加的是第十三届蓝桥杯大赛&#xff0c;从最开始的零基础&#xff0c;毫无头绪&#xff0c;到拿下国二&#xff0c;颇有体会&#xff0c;在这里将我的备赛经验分享给大家,希望可以帮到一些正在备赛的蓝桥杯er 目录 一. 蓝桥杯-单片机组介绍 二 . 零基础到国二历程 客观题&…

web架构师编辑器内容-图层拖动排序功能的开发

新的学习方法 用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用 从两个DEMO开始 Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC 列表排序的三个阶段 拖动开始&#xff08;dragstart&#x…