STL string分析

看得懂的知识,要仔细看; 看不懂的知识,要硬着头皮看. 真正用到这些知识的时候. 才发现自己掌握的很不扎实. 了解, 理解, 记住, 熟练, 精通, 这个过程可能需要对一个知识或者一个知识体系进行反复的运用和思考. 我觉得对知识理解的越深刻、完整, 记忆才会更持久, 使用起来才更熟练. 所以个人认为多角度的理解问题是关键. 下面记录下我最近遇到的一些问题, 方便日后查看, 后面可能会更新:D.这里没有好用的代码编辑工具, 插入代码编辑起来很费力, 只好手动编辑, 哪位大大有好的方法请留言给我:P

 

STL string做字符串分割的几种方法:

  • 1. string::substr 按照位置分割字符串

string substr(size_t pos = 0, size_t n = npos) const;

  • 2. boost::alogrithm::split  按照token分割字符串

template<typename SequenceSequenceT, typename RangeT, typename PredicateT>

SequenceSequenceT & split( SequenceSequenceT & Result, RangeT & Input, PredicateT Pred, token_compress_mode_type eCompress = token_compress_off );

Boost文档中的一个例子:

   1: string str1("hello abc-*-ABC-*-aBc goodbye");
   2: typedef vector< string > split_vector_type;
   3:     
   4: split_vector_type SplitVec; // Search for tokens
   5: split( SplitVec, str1, is_any_of("-*") ); // SplitVec == { "hello abc","ABC","aBc goodbye" }

 


 

STL string in VC80 的实现机制

string的定义是这样的

typedef basic_string<char, char_traits<char>, allocator<char> > string;

 

其中char_traits是一个模板类, 声明如下:

template<class _Elem>
struct char_traits: public _Char_traits_base;

函数的定义可以在iosfwd看到, char_traits的成员函数有如下几类:

字符的整数值转换: to_char_type() to_int_type() eq_int_type()

char比较: eq() lt()

数组操作 move() copy() assign() compare() length() find()

IO操作 eof() not_eof() get_state()

* move(_First1, _Count, _First2, _Count)和copy(_First1, _Count, _First2, _Count)都是由[_First2, _First2+_Count)复制到[_First1, _First1+_Count), 区别是当_First2<_First1<_First2+_Count时, 从前到后的赋值会产生错误.move()对这种情况进行了处理, 此时从后向前赋值, 而copy()按照覆盖的方式进行处理. 他们的区别和memmove()和memcpy()的区别相似, 可参见kenny的blog.

* 这里还有个问题, 为什么要提供字符与整型之间的转换?

 

由string的定义我们看到string是一个basic_string模板类的一个特化, basic_string定义如下

template<class _Elem,
    class _Traits = char_traits<_Elem>,
    class _Ax = allocator<_Elem> >
    class basic_strin;

因此string也可写作 typedef basic_string<char> string; basic_string定义了串的基本操作, 实现在<xstring>文件中可以看到, string的主要功能有:

构造

迭代器

随机访问 operator[]() at

赋值 operator=()

比较 operator==()

插入 insert()

拼接 append() operator+=()

查找 find() rfind() find_first_of() find_first_not_of() ,无异常处理 没找到返回npos

替换 replace() earse() clear()

容量 size() resize() capacity()

IO操作 operator<<() operator>>() getline()  ,basic_string的IO操作在basic_string中提供而不是在<iostream>中

这里有些地方值得注意:

* basic_string没有虚析构函数, 因此不能作为其他类的基类使用, string也一样.

* 比较函数(如operator<())连接函数(operator+)都作为非成员函数, 来处理第一个操作数为_Elem的情况.

* 写时拷贝(延迟拷贝)的优化是怎样的? vc8的string没有采用Copy-On-Write技术.

* at()和operator[]()的区别是 at()进行数组下标越界检查即throw一个out_of_range异常, 而operator[]()只是用assert做一个中断(VC80).

* basic_string和vector的区别? 数据内存的布局是连续的还是非连续的, 插入操作复杂度?

通过跟踪basic_string中的 _Grow()函数可以看到string的实现细节

size_type _Mysize;    // current length of string
size_type _Myres;    // current storage reserved for string

用_Mysize存储串的实际长度, _Myres存储串所占空间. 说明basic_string的存储是存在冗余的, 具体的内存分配办法在_Copy()函数中, 除去了异常处理并更换了几个函数

  1: void  _Copy(size_type _Newsize, size_type _Oldlen) 
  2:     {    // copy _Oldlen elements to newly allocated buffer 
  3:     size_type _Newres = _Newsize | _ALLOC_MASK; 
  4:     if (max_size() < _Newres) 
  5:         _Newres = _Newsize;    // undo roundup if too big 
  6:     else if (_Newres / 3 < _Myres / 2 && _Myres <= max_size() - _Myres / 2) 
  7:         _Newres = _Myres + _Myres / 2;    // grow exponentially if possible 
  8:     _Elem *_Ptr = 0;
  9: 
 10:     _Ptr = _Mybase::_Alval.allocate(_Newres + 1); 
 11:    
 12: 
 13:     if (0 < _Oldlen) 
 14:         _Traits_helper::copy_s<_Traits>(_Ptr, _Newres + 1, _Myptr(), _Oldlen);    // copy existing elements 
 15:     _Tidy(true);  // deallocating old storage
 16:     _Bx._Ptr = _Ptr; 
 17:     _Myres = _Newres; 
 18:     _Traits::assign(_Myptr()[_Mysize = _Oldlen], _Elem()); // set new length and null terminator
 19:     } 

3~7行是关键(3~5行没看懂:P), _Newres扩展到的空间, _Newsize是需要空间, _Oldlen是以前空间, 如果_Oldlen+_Oldlen*1/2能够满足需求的且不超过最大可以分配的空间, 则增长空间为_Oldlen*1/2, 否则按_Newsize来增长. 因此新的内存空间为max{_Oldlen*3/2, _Newsize}, 13~18行是清理以及复制旧值设定新值的工作.

basic_string还对对small string进行存储优化(trimming), 优化的界限_BUF_SIZE = 16 / sizeof (_Elem) < 1 ? 1 : 16 / sizeof(_Elem). basic_string占用的内存由一个union定义, 

  1: union _Bxty 
  2:     {    
  3:     _Elem _Buf[_BUF_SIZE]; // 当_Elem数量小于_BUF_SIZE时直接存储 
  4:     _Elem *_Ptr;                // 对于大的串使用pointer存储 
  5:     } _Bx;

可以看出, 当占用空间小于16B时使用数组在栈上存储, 当大于16B时在堆中存储. 通过_Myptr()函数看的很清楚.

  1: _Elem * _Myptr() 
  2:     {    // determine current pointer to buffer for mutable string 
  3:     return (_BUF_SIZE <= _Myres ? _Bx._Ptr : _Bx._Buf); 
  4:     } 

经过以上分析, basic_string中内存连续存储, 以指数级增长, 并对小串进行存储优化.

basic_string和vector的区别是basic_string中的_Elem必须是POD(Plain Old Data in C++98), 特别的它不能有用户自定义的复制构造函数, 析构函数和复制函数. 因为basic_string中存储的拷贝都是由memcpy_s实现的.因此basic_string不算是一个STL container.
对于insert操作, insert到basic_string的末尾而不超过_Myres时可以直接赋值, 否则需要一次赋值操作.

posted @ 2011-10-22 22:31  nickolas  阅读(2645)  评论(0编辑  收藏  举报