您的位置:首页 > 财经 > 金融 > 免费成品网站模板_钉钉小程序开发教程_广州30万人感染_百度收录申请

免费成品网站模板_钉钉小程序开发教程_广州30万人感染_百度收录申请

2025/4/29 22:32:15 来源:https://blog.csdn.net/qq_20892953/article/details/142941321  浏览:    关键词:免费成品网站模板_钉钉小程序开发教程_广州30万人感染_百度收录申请
免费成品网站模板_钉钉小程序开发教程_广州30万人感染_百度收录申请

提升 C++ std::string 操作效率:善用 pop_back

  • 先说结论
  • 看下源码
    • _M_mutate() 函数
      • _M_check() 函数
      • _M_limit() 函数
      • _M_mutate() 函数
    • _M_move() 函数
    • 关于 char_traits 模板类

先说结论

咱直接先说结论吧,pop_back() 函数是用于删除 std::string 对象的最后一个字符,
实现很高效,本质上就是把要删除的最后一个字符置为null结束符,也就是置为: \0。

如果我们有这样的测试代码,那么 pop_back() 背后究竟做了哪些事情呢?

#include <string>
int main() {string strPhone = "150****1234";strPhone.pop_back();return 0;
}

看下源码

我的linux环境是gcc 4.8.5,c++的头文件位于/usr/include/c++
最终我们需要看的是这两个文件:
/usr/include/c++/4.8.5/bits/basic_string.h
/usr/include/c++/4.8.5/bits/basic_string.tcc

void pop_back() {erase(size()-1, 1);
}

GNU的c++实现,pop_back() 是调用了 erase() 函数来实现相关功能,正好可以顺道学习 erase() 函数了。
erase() 函数调用 _M_mutate() 函数来执行实际的删除操作,主要是更新字符串的内容和大小。
mutate是转变,转换的意思。

basic_string& erase(size_type __pos = 0, size_type __n = npos) {_M_mutate(_M_check(__pos, "basic_string::erase"),_M_limit(__pos, __n), size_type(0));return *this;
}

根据我们前述的测试代码,erase() 的第一个入参 __pos 就是 10,第二个入参 __n 就是 1 了。
代表 erase() 开始删除的第一个字符的索引是 10 (也就是最后一个字符 4 ),删除的字符数量是 1 个。

_M_mutate() 函数

_M_mutate() 函数略微复杂一点,我们等下再看。这里先放出函数的声明,其需要的是3个 size_type 类型的入参。
第一个参数 __pos 表示要开始修改的字符位置。
第二个参数 __len1 表示要删除的字符数量,由 _M_limit(__pos, __n) 计算得来。
第二个参数 __len2 表示要增加的字符数量。在我们讨论 pop_back() 这个场景下,是没有要增加的字符数量的,所以 erase() 调用 _M_mutate() 时,__len2 置为 0 。

void _M_mutate(size_type __pos, size_type __len1, size_type __len2)
{}

_M_check() 函数

然后我们来看传入 _M_mutate 的第一个参数: _M_check(__pos, “basic_string::erase”)
_M_check 是 std::string 的一个私有成员函数,就是检查一下给定的位置 __pos 是否在字符串的有效范围内。
如果 __pos (在本例中,是10)超出字符串的长度,则抛出 std::out_of_range 异常。
返回值仍然是 __pos。其实从函数本身来看是不需要返回值的,这里主要是方便操作。
就比如,其返回值可以作为 _M_mutate 的第一个入参。

size_type _M_check(size_type __pos, const char* __s) const {if (__pos > this->size())__throw_out_of_range(__N(__s));return __pos;
}

_M_limit() 函数

_M_limit 是 std::basic_string 类的一个私有成员函数,主要是用来限制偏移量以确保它不会超过字符串的有效范围。
用人话讲就是,现在要从 __pos 这个位置,再偏移 __off,看看是否会超出字符串的长度。

返回值代表限制之后的偏移量,确保它不会超过从 __pos 开始的字符串长度。
用人话讲就是,字符串的大小是11,现在要从位置 __pos (第 10 个字符)开始偏移,假如偏移量 __off 是 2,那就超过了字符串大小 11,我就限制一下,你只能偏移 1,不能偏移2。
这个返回值就是,经过计算之后的,偏移后不能超过字符串大小的,允许偏移的量。

在本例中,this->size() 是 11 ,第一个参数 __pos 就是 10,第二个入参 __off 就是 1。
所以 __testoff 计算得到是 false,代表没有超出范围,返回的就是原始的入参偏移量 __off。
如果 __testoff 计算得到是 tue,代表从位置 __pos 再偏移 __off 超出了范围,那就不能按入参 __off,而是可以偏移的量 this->size() - __pos。

size_type _M_limit(size_type __pos, size_type __off) const {const bool __testoff =  __off < this->size() - __pos;return __testoff ? __off : this->size() - __pos;
}

_M_mutate() 函数

最后我们来看一下 _M_mutate() 函数。
第二个参数 __len1 表示要删除的字符数量,我们这里就是 1。
第二个参数 __len2 表示要增加的字符数量,本例中是 0。

template<typename _CharT, typename _Traits, typename _Alloc>
void basic_string<_CharT, _Traits, _Alloc>::
_M_mutate(size_type __pos, size_type __len1, size_type __len2)
{const size_type __old_size = this->size(); // 计算之后 __old_size = 11const size_type __new_size = __old_size + __len2 - __len1; // 计算之后 __new_size = 10const size_type __how_much = __old_size - __pos - __len1; // 计算之后 __how_much = 0// 新大小超过当前容量,或者当前字符串是共享的(共享意味着多个字符串对象可能指向同一块内存)。// 就需要重新分配内存,然后执行拷贝动作if (__new_size > this->capacity() || _M_rep()->_M_is_shared()){// Must reallocate. 必须重新分配const allocator_type __a = get_allocator();_Rep* __r = _Rep::_S_create(__new_size, this->capacity(), __a);// 拷贝 __pos 位置之前的数据if (__pos)_M_copy(__r->_M_refdata(), _M_data(), __pos);// __how_much 是指,计算从 __pos 开始,删除 __len1 个字符后剩余的字符数量// 这部分数据也需要拷贝到新字符串中if (__how_much)_M_copy(__r->_M_refdata() + __pos + __len2, _M_data() + __pos + __len1, __how_much);// 释放旧的内存块_M_rep()->_M_dispose(__a);_M_data(__r->_M_refdata());}else if (__how_much && __len1 != __len2){// Work in-place. 原地操作// 如果不需要重新分配内存,并且删除和插入的字符数量不同,则可以直接在原地移动字符。_M_move(_M_data() + __pos + __len2, _M_data() + __pos + __len1, __how_much);}// 最后,更新字符串的长度,并设置其为可共享的状态。_M_rep()->_M_set_length_and_sharable(__new_size);
}void _M_set_length_and_sharable(size_type __n)
{this->_M_set_sharable();  // One reference.this->_M_length = __n;// 将字符数组中索引为 __n 的位置赋值为 终止符 _S_terminaltraits_type::assign(this->_M_refdata()[__n], _S_terminal);
}

_M_move() 函数

_M_move() 函数是 std::string 的一部分,并且是静态函数。主要用于在字符串中移动数据。
第一个入参 _CharT* __d :指向目标字符数组的指针,往哪儿去。
第二个入参 const _CharT* __s :指向源字符数组的指针,从哪儿来。
第三个入参 size_type __n :表示要移动的字符数量。
如果 __n 是 1,处理比较简单。如果移动多个字符,还需要处理内存重叠的问题。

static void _M_move(_CharT* __d, const _CharT* __s, size_type __n) {if (__n == 1) traits_type::assign(*__d, *__s);elsetraits_type::move(__d, __s, __n);
}

关于 char_traits 模板类

上述的 _M_move() 函数中的 traits_type 只是 basic_string 模板类里面的一个类型定义。

template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
{
public:typedef _Traits	      traits_type;
};

我们可以来看一下这一组声明(我调整了顺序,方便理解)。
string 的 _Traits 类型 就是默认的 char_traits<_CharT> (下述代码第4行)

typedef basic_string<char>    string;  template<typename _CharT, typename _Traits = char_traits<_CharT>,typename _Alloc = allocator<_CharT> >
class basic_string;

char_traits 又是什么呢?它也是一个模板类。是用于定义和处理字符特性的基类。

template<typename _CharT>
struct char_traits
{
};template<> struct char_traits<char>;
template<> struct char_traits<wchar_t>;
template<> struct char_traits<char16_t>;
template<> struct char_traits<char32_t>;

我们知道,basic_string 是一个模板类,字符类型不同,会有不同的 string。
只不过我们比较常用的是 string。

// /usr/include/c++/4.8.5/bits/stringfwd.h
typedef basic_string<char>    string;  
typedef basic_string<wchar_t> wstring;   
typedef basic_string<char16_t> u16string; 
typedef basic_string<char32_t> u32string; 

我们来看一下 string。
string 就是用来处理 char 这种字符的。
根据下面 basic_string 的声明,可以知道:
_CharT 类型就是 char 类型
_Traits 类型就是 char_traits 类型
_Alloc 类型就是 allocator 类型

template<typename _CharT, typename _Traits = char_traits<_CharT>,typename _Alloc = allocator<_CharT> >
class basic_string;template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
{
public:typedef _Traits	      traits_type;
};

所以,回到 _M_move() 函数中的那个 traits_type::assign() 和 traits_type::move()
其实就是 _Traits::assign() 和 _Traits::move()
也就是 char_traits::assign() 和 char_traits::move()
就是对 char 这种内置类型的 assign 和 move()。

在 /usr/include/c++/4.8.5/bits/char_traits.h 文件中,可以看到 struct char_traits 的声明。
当然 char_traits<wchar_t>、char_traits<char16_t>、char_traits<char32_t>这些类型,也都在这个文件里。

template<typename _CharT>
struct char_traits
{static void assign(char_type& __c1, const char_type& __c2){ __c1 = __c2; }static char_type* move(char_type* __s1, const char_type* __s2, std::size_t __n);
};

char_traits 模板类只是提供了通用的操作,针对每一种特定的类型,又做了模板特化。

// 模板特化
template<>
struct char_traits<char>
{typedef char              char_type;static void assign(char_type& __c1, const char_type& __c2){ __c1 = __c2; }static char_type* move(char_type* __s1, const char_type* __s2, size_t __n){ return static_cast<char_type*>(__builtin_memmove(__s1, __s2, __n)); }
};// 模板特化
template<>
struct char_traits<wchar_t>
{typedef wchar_t           char_type;static void assign(char_type& __c1, const char_type& __c2){ __c1 = __c2; }static char_type* move(char_type* __s1, const char_type* __s2, size_t __n){ return static_cast<char_type*>(__builtin_memmove(__s1, __s2, __n)); }
};// 模板特化
template<>
struct char_traits<char16_t>
{typedef char16_t          char_type;static void assign(char_type& __c1, const char_type& __c2) noexcept{ __c1 = __c2; }static char_type* move(char_type* __s1, const char_type* __s2, size_t __n){return (static_cast<char_type*>(__builtin_memmove(__s1, __s2, __n * sizeof(char_type))));}
};

总结一下,basic_string 模板类如何通用地操作这些不一样的字符类型(char/wchar_t/char16_t/char32_t)呢?
标准库做了另外一个模板类:char_traits,用于提取出这些字符类型的通用操作特性。
然后再针对每一种字符类型,做模板特化。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com