zoukankan      html  css  js  c++  java
  • C++ Standard Stl SGI STL源码学习笔记(06) stl_vector 与 一些问题的细化 2 push_back函数剖析

      上一篇文章中,关于stl_vector的故事只是个开始.  这篇文章中,接着去分析vector中的细节问题. 

     

      再次声明,我没有看过关于stl源码分析方面的书籍,强调这一点是为了不会让别人误会我是从别的地方抄袭的. 另外,欢迎大家拍砖。

      后面陆续的几篇文章都会详细分析vector中的函数实现. 我尽量做到篇幅不大,而且能够尽量用自己的语言和自己分析的结果给大

      家展现实现的过程. 这样不会浪费大家的时间.写的不好就请原谅了.

     

        push_back函数的作用就是从_M_finish指向的位置开始插入数据。如果预先分配的存储单元不够,则会做扩展处理.会在后面分析的时候

        说明这个问题.

      void push_back(const _Tp& __x) {
        if (_M_finish != _M_end_of_storage) {
          construct(_M_finish, __x);
          ++_M_finish;
        }
        else
          _M_insert_aux(end(), __x);
      }
    // **
    // *  实现和上面相同,只是在需要初始化的地方都是用默认初始化值
    // **
      void push_back() {
        if (_M_finish != _M_end_of_storage) {
          construct(_M_finish);
          ++_M_finish;
        }
        else
          _M_insert_aux(end());
      }
    

           1. _M_finish和_M_end_of_storage有什么区别?_M_finish和_M_end_of_storage同样都是vector从vector_base中继承来的,

        但是二者的作用是不同的.

          [ _M_finish ] :  数据类型的指针,执行当前操作的数据. 在默认的初始化时候,_M_finish==_M_start.也就是指向底层保存元素

                     数组的头元素.例如不断的往里面push_back数据,那么_M_finish就会不断的像数组的尾端移动,

                   直到_M_end_of_storage.

          

     [_M_end_of_storage ] :  _M_end_of_storage在使用vector第一次初始化分配数组内存的时候,是指向数组的最后一个元素的后一个位置

                   因为初始化的时候 _M_finish = _M_start, _M_end_of_storage = _M_start + _n. (_n是元素个数).

                     随着不断的对vector进行操作,后面可能会导致_M_end_of_storage发生变化.

     

        说的直白一点就是,_M_finish是_M_start(头指针)和_M_end_of_storage(尾指针的后一个位置)之间游动的游标.

     

      2. 执行push_back(n)的操作的时候,首先会检查数组的容量有没有达到上限,如果_M_finish == _M_end_of_storage.那么就需要扩大

        存储单元,就会在_M_insert_aux(end(),_x)中进行容量的扩增.

     

      3. 如果没有达到上限的话,也就是说有空余的空间,那么就给当前游标_M_finish指向的位置赋值,然后将游标向下一个位置移动.

       

      4. 如果没有达到上限,就使用construct对_M_finish指向的内存区域进行赋值. 

    template <class _T1, class _T2>
    inline void _Construct(_T1* __p, const _T2& __value) {
      new ((void*) __p) _T1(__value);
    }
    

      对于new的这种用法,不是很常见,但是在<<c++ primer>>中后面"特殊工具与技术"章节有讲解.

    new (place_address) type
    new (place_address) type(parm-list)
    

      5. 如果达到上限,就需要扩张存储区域,按照原区域的2倍进行扩张.    

     const size_type __old_size = size();                         // 获取原存储区域长度
        const size_type __len = __old_size != 0 ? 2 * __old_size : 1;         // 扩张为原来的2倍
        iterator __new_start = _M_allocate(__len);                    // 按照新存储长度重新申请内存
        iterator __new_finish = __new_start;
        __STL_TRY {
          __new_finish = uninitialized_copy(_M_start, __position, __new_start);     // 将原来数组中的_M_start -> _M_finish之间的数据复制到新的存储区域
          construct(__new_finish, __x); // insert new value.                // 插入新数据,和前面的 操作一样 
          ++__new_finish; // go next.                            // 移动_M_finish
          __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);     
        }
        __STL_UNWIND((destroy(__new_start,__new_finish), 
                      _M_deallocate(__new_start,__len)));
        destroy(begin(), end());
        _M_deallocate(_M_start, _M_end_of_storage - _M_start);             // 释放原存储内存
        _M_start = __new_start;                               // 处理其他改动
        _M_finish = __new_finish;
        _M_end_of_storage = __new_start + __len;
    

      这里可能有些人不明白为什么要调用两次uninitialized_copy函数,其实有点想多了,这要看上面的调用:

        _M_insert_aux(end(), _x). 而end()的作用就是获取_M_finish.而在uninitialized_copy函数中,首先会进行判断.   

    template <class _InputIter, class _ForwardIter>
    _ForwardIter 
    __uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                             _ForwardIter __result,
                             __false_type)
    {
      _ForwardIter __cur = __result;
      __STL_TRY {
        for ( ; __first != __last; ++__first, ++__cur)
          _Construct(&*__cur, *__first);
        return __cur;
      }
      __STL_UNWIND(_Destroy(__result, __cur));
    }
    

       虽然通过很多层的转换,但是还是可以看到判断_first != _last. 所以在上面的调用中这句是不会有执行的.而这看起来有点多此一举是吧?

      不是. 因为_M_insert_aux提供的是通用的使用方法,需要使用一种通用的实现.而上面的调用最终还是会得到 _new_finish = _new_finish.

      所以不需要纠结.

      

      最后,关于push_back的另一个一个参数的版本就不需要多解释了,因为二者之间在实现上方法和原理都是一样的,多说无益.

        

        

     

     

     

     
  • 相关阅读:
    LFI、RFI、PHP封装协议安全问题学习
    Django:(博客系统)添加文章(中文)出现UnicodeEncodeError乱码
    Django:(博客系统)使用使用mysql数据->后台管理tag/post/category的配置
    Spark:性能调优
    Django:(博客系统)使用使用mysql数据&创建post/category/tag实体,并同步到数据中
    C#:多进程开发,控制进程数量
    Django通过pycharm创建后,如何登录admin后台?
    扑克牌游戏研究先告一段落
    Zookeeper通过java创建、查看、修改、删除znode
    ZooKeeper:win7上安装单机及伪分布式安装
  • 原文地址:https://www.cnblogs.com/respawn/p/2615446.html
Copyright © 2011-2022 走看看