当前位置: 首页 > news >正文

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

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

  • 先说结论
  • 看下源码
    • _M_mutate() 函数
      • _M_check() 函数
      • _M_limit() 函数
      • _M_mutate() 函数
    • _M_move() 函数

先说结论

咱直接先说结论吧,pop_back() 函数是用于删除 std::string 对象的最后一个字符,
实现很高效,本质上就是把要删除的最后一个字符置为null结束符,也就是置为: \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() 函数来执行实际的删除操作:主要是更新字符串的内容和大小。

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。

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

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

这里的 traits_type 是一个类型定义。
我们来看看这一组定义。可以得知,

template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
{
public:typedef _Traits	      traits_type;
};template<class _CharT>struct char_traits;template<typename _CharT, typename _Traits = char_traits<_CharT>,typename _Alloc = allocator<_CharT> >class basic_string;template<> struct char_traits<char>;/// A string of @c char
typedef basic_string<char>    string;  

不难得知,traits_type::move() 其实 就是 char 这种内置类型的 move()。
在 /usr/include/c++/4.8.5/bits/char_traits.h 文件中,可以看到 struct char_traits 的声明。

template<typename _CharT>
struct char_traits
{
};

我们知道,basic_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; 

basic_string 模板类如何通用地操作这些不一样的字符类型呢?标准库做了另外一个模板类: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>;

http://www.mrgr.cn/news/50592.html

相关文章:

  • Qt-界面优化盒子模型(71)
  • 意外断电 导致docker 部署禅道 的mariadb 启动报错
  • FPGA中的双向信号inout与三态门
  • git--git reset
  • 【linux】记录 n 次 疑似显卡缺电导致的服务器故障
  • androidStudio编译导致的同名.so文件冲突问题解决
  • Linux进程控制小练习|手撕一个简易版shell(Version 1.0)
  • 致同举办企业重组案例及南沙“双15”税收优惠政策分享会
  • 全面解析CUPS零日远程代码执行漏洞曝光事件
  • Mac book不会应用双开?一篇文章教会你最全的应用双开方法
  • 高端官网制作公司怎么分辨是否靠谱?2024专业网站制作公司哪家好TOP5
  • 如何选择适合自己的电子元器件?
  • 性格色彩报告的解读
  • 光控资本:中航电测西部大开发概念股接力大涨,它们业绩如何?
  • SSD | (四)NAND闪存(中)
  • HiT-SR:基于层级Transformer的超分辨率,计算高效且能提取长距离关系 | ECCV‘24
  • Accessibility into Development for Web Developers
  • 标题:民峰金融:全球投资者的智能化财富管理平台
  • 自学网络安全Web安全,一般人我还是劝你算了吧
  • 一文深度学习java内存马