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

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。

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 喵。
欢迎大家一起讨论喵