STL源码学习——Vector(向量)


STL源码学习——Vector(向量)

  今天继续看STL源码喵。虽然基本上说vector是最简单的容器了,但其实相对来说我觉得同list比起来,还是list实现方便一些喵~让电脑以人脑的方式工作总比让人脑以电脑的方式工作简单吧喵~
  Vectors 包含着一系列连续存储的元素,其行为和数组类似。访问Vector中的任意元素或从末尾添加元素都可以在常量级时间复杂度内完成,而查找特定值的元素所处的位置或是在Vector中插入元素则是线性时间复杂度。(C/C++ 语言参考,见第8点)
  1 :vector中[]没有检查数组越界,相对应的at()函数检查了。所以at()相对来说较为安全喵~
  2 :注意capacity()与max_size()的区别喵~总觉得max_size()这个函数怪怪的。

max_size
1     size_type max_size() const
2     {
3         return size_type(-1) / sizeof(_Tp);
4     }
5     size_type capacity() const
6     {
7         return size_type(_M_end_of_storage._M_data - _M_start);
8     }

  3 :vector的swap()成员函数效率为O(1),只是交换了各自的指针。

 

insert
 1     iterator insert(iterator __position, const _Tp &__x)
 2     {
 3         size_type __n = __position - begin();
 4         if (_M_finish != _M_end_of_storage._M_data)
 5         {
 6             if (__position == end())
 7             {
 8                 _Construct(_M_finish, __x);
 9                 ++_M_finish;
10             }
11             else
12             {
13                 _Construct(_M_finish, *(_M_finish - 1));
14                 ++_M_finish;
15                 _Tp __x_copy = __x;
16                 copy_backward(__position, _M_finish - 2, _M_finish - 1);
17                 *__position = __x_copy;
18             }
19         }
20         else
21             _M_insert_overflow(__position, __x);
22         return begin() + __n;
23     }

  4 :这个insert()函数开始我看那里求出了相对偏移量 __n 然后我再看,这个函数只有最后那行return语句用到了这个 __n。

    然后我再想,这里为什么要这样写呢,原来这里有迭代器失效的问题喵~

  5 :第15行出现了一个__x_copy,我开始一直在想,这里复制一次有什喵用呢?这里并没对__x或__x_copy做任何改变啊。那复制一份有什喵用呢?
    原来关键的问题就在__x_copy使用的那两行之间的区间复制函数。虽然__x是一个const引用,不会在这个函数里被改变,但在其它地方是可以被改变的哦,而且当它正好是[__position, _M_finish - 2)之间的数时,就可能会被改变。所以这里提前做了一次备份喵。
  6 :接着看下一个insert()函数,STL中效率考虑得很细嘛,开始看vector中的

        iterator insert(iterator __position)

    我以为会像list中一样后一个函数直接调用前一个函数,可这里并没有,而用重写了一次。
    然后我仔细看了一下,重写的那个函数没有第15行的__x_copy这个变量,
    重写可以避免元素的构造而产生的浪费。(不过个人还是觉得仅仅为了这点而重写这个函数有点得不偿失喵~)
  7 :vector.size()居然能返回-1。

测试size()
1     int main()
2     {
3         vector<int> v(1, 1);
4         v.pop_back();
5         v.pop_back();
6         printf("%d v.size\n", v.size());
7         printf("%d v.capacity\n", v.capacity());
8         return 0;
9     }

    原因是pop_back()函数,这个函数居然没有判断当前容器中的元素个数!这样真的没问题喵~

  7 :大家请看如下函数,注释为期望输出。
    如果你觉得注释期望输出与你想的一致,那么你可以试着运行一下这段代码,将会有一个惊喜等着你哦~

 1     int main()
 2     {
 3         vector<int> v;
 4         int n = 3;
 5         for (int i=0; i<n; i++)
 6             v.push_back(i);
 7         for (vector<int>::iterator it=v.begin(); it!=v.end(); ++it)
 8             printf("%d ", *it);
 9         puts("");   ///0 1 2
10         v.insert(v.begin(), v.end()-1, v.end());
11         for (vector<int>::iterator it=v.begin(); it!=v.end(); ++it)
12             printf("%d ", *it);
13         puts("");   ///2 0 1 2
14         return 0;
15     }

    第二行的真实输出为“1 0 1 2”。为什喵会这样呢?

    因为在这次insert过程中,STL并没有判断被移动区间[0,3)与被复制区间[2,3)有重叠的情况。
    这里的实际操作顺序为先移动[0,3)再复制[2,3)(而此时[2,3)的值而被改变),这与我们的思维是不一致的喵~
    喵喵喵~
    不过,如果你的好奇心比较强的话,你可能会多做一些实验,这样的话你就会发现有的时候复制是正确的,比如将上式中的n=3改为n=2然后你就会发现结果是与我们预期的结果是一致的。(有一定随机性)
    这又是为什喵呢?为什喵呢?为什喵呢?
    原来是,当n=2的时候,在执行insert的过程中,已经将作为参数的那片内存区域丢弃了,也就是说在insert执行到一半的时候作为参数传入的迭代器就已经失效了哦。所以在后面复制[end-1,end),实际是去读的那块已经丢弃,但还没有被改写的内存区域里的数据。(当然也可能被改写的)
    所以在insert传入区间参数时,最好不要是自身vector的迭代器。如果实在是要在某点插入自身的部分区间,那最好还是先手动复制一份,再使用。避免这种意想不到的bug喵~
  8 :这一点也是之前就有耳闻的喵~就是vector的push_back的平均效率为线性的。
    因为当vector需要自己增加存储空间的时候一般是增加为原来两倍。
    所以 1 + 2 + 4 + 8 + ... + n = 2n 喵。
  

  欢迎大家一起讨论喵


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM