STL源码分析:浅析string和vector的区别

目录

前言

转换为C-style字符串

重载输入输出流操作符

重载+、+=操作符


前言

      为什么会关注string和vector的区别?

      先来说下string这个“类”。

      string和vector、list、deque等容器不一样,在STL中并不存在class string{...},它实际上是一个全局类型,通过typedef定义:

STL源码分析:浅析string和vector的区别_第1张图片

       也就是说,string实际上是通过basic_string类来实现的,而string则是模板类basic_string的char特例化版本,同样还有针对宽字节的wstring。

       然后看看basic_string类:

template  
class basic_string : private _String_base<_CharT,_Alloc> 
{
    ...
    size_type size() const { return _M_finish - _M_start; }
    size_type capacity() const { return (_M_end_of_storage - _M_start) - 1; }
    ...
}


/* string_base 类*/

template  class _String_base {
...
protected:
  typedef simple_alloc<_Tp, _Alloc> _Alloc_type;

/*三个主要的成员*/
  _Tp* _M_start;
  _Tp* _M_finish;
  _Tp* _M_end_of_storage;
                                // Precondition: 0 < __n <= max_size().

  ...

  void _M_allocate_block(size_t __n) { 
    if (__n <= max_size()) {
      _M_start  = _M_allocate(__n);
      _M_finish = _M_start;
      _M_end_of_storage = _M_start + __n;
    }
    else
      _M_throw_length_error();
  }
  ...
};

       可以看到,basic_string类从其父类_String_base继承得到了_M_start、_M_finish和_M_end_of_storage。在vector中也有这样的三个成员,变量名都完全相同。观察其中的_M_allocate_block、size和capacity函数,也可以知道,这三个成员的含义分别是:_M_start和_M_end_of_storage分别保存分配空间的首尾地址,_M_finish保存的是最后一个元素的地址。

 

       从这一点来看,string和vector在数据结构、内存管理等方面都是相同的。但是,vector只是单纯的一个“char元素的容器”,而string不仅是一个“char元素的容器”,它还扩展了一些针对字符串的操作

转换为C-style字符串

       string类中将string转换为char *的函数为c_str()和data(),其定义如下:

const _CharT* c_str() const { return _M_start; }
const _CharT* data()  const { return _M_start; }

       这两个函数的实现都是相同的,直接返回string所占空间的首地址即可。 这就相当于为char * s给出了s的地址一样。而这一点在vector中却不一定能实现。vector的begin()也是返回_M_start,在SGI STL中,begin()就是直接返回的_M_start,因此可以通过(char *)v.begin();来达到一样的效果,但是在VS2013 STL中,将iterator封装到了一个类中,而一个类是无法转换为char *类型的,因此是不能通过直接返回begin()来把vector转换为char *。

重载输入输出流操作符

       vector中并未对输入输出流操作符进行重载,因此无法直接对vector进行cin或者cout这样的操作,但是string可以。

template 
ostream& operator<<(ostream& __os, 
                    const basic_string<_CharT,_Traits,_Alloc>& __s)
{
  streambuf* __buf = __os.rdbuf();
  if (__buf) {
    size_t __n = __s.size();
    ...
    const size_t __nwritten = __buf->sputn(__s.data(), __n);   //向流中写入数据

    ...
  }

template 
istream& operator>>(istream& __is, basic_string<_CharT,_Traits,_Alloc>& __s)
{
  if (!__is)
    return __is;

  streambuf* __buf = __is.rdbuf();
  if (__buf) {
    ...
    if (__is) {
      ...
      while (__n-- > 0) {
        int __c1 = __buf->sbumpc();    //获取流中的字符
        if (__c1 == EOF) {
          __is.clear(__is.rdstate() | ios::eofbit);
          break;
        }
        else {
          _CharT __c = _Traits::to_char_type(__c1);

          if (isspace((unsigned char) __c)) {
           ...
          }
          else
            __s.push_back(__c);     //放入string中
        }
      }
      ...
    }
    __is.width(0);
  }
  else    
    ...                     
  return __is;
}

       需要注意的是,对于重载输出流操作符,string是输出size()个元素,也就是说,不管string中存放的字符元素是什么,它都会将其输出,如果在string中存放的元素是{‘x’、'y'、'\0'、'z'},由于string的size是4,因此cout出来的结果就是"xy z",它并不会因为其中有一个终止符'\0'而不输出。这一点和C风格的char *字符串是不同的。

重载+、+=操作符

      vector并不能直接实现字符串的拼接,但是string可以,string中重载了+、+=运算符,以拼接两个字符串为例:

template 
inline basic_string<_CharT,_Traits,_Alloc>
operator+(const basic_string<_CharT,_Traits,_Alloc>& __x,
          const basic_string<_CharT,_Traits,_Alloc>& __y)  //重载+
{
  typedef basic_string<_CharT,_Traits,_Alloc> _Str;
  typedef typename _Str::_Reserve_t _Reserve_t;
  _Reserve_t __reserve;
  _Str __result(__reserve, __x.size() + __y.size(), __x.get_allocator());   //开辟一个新的string
  __result.append(__x);  //向新的string中添加传入的两个参数
  __result.append(__y);
  return __result;
}


basic_string& operator+=(const basic_string& __s) { return append(__s); }  //重载+=

      对比+和+=,可以看到,+=只是在原来基础上append新的字符串,而+则是需要将参数的两个字符串全部拷贝到新的字符串中,因此,和p+=q相比,p = p + q会更加的耗时。

你可能感兴趣的:(STL,C/C++)