最近在知乎上看到一个回答:
https://www.zhihu.com/question/49995099
怎样读《二十四史》。重点在于苏轼有个读书的方法,“每书数过,一意求之”。读书要带着目的读,有所取舍,反复多次,才得到的多。
我一想,写代码看这些技术书也是啊。一次只看一个侧重点,反复多次。而不是每次都从头看,这样效率低下,学不到东西。
工作中经常使用vector,具体细节并未了解。通常情况下,如果长度固定的数据结构会使用数组,长度不定则使用vector。
最近又翻看了《STL源码剖析》,发现以前看不懂的地方可以看懂了。这次看源码剖析,就看vector。
首先需要有一个整体的概念。所谓vector,是一个动态的数组。使用的时候不需要像数组一样关心长度(但是同样有越界)。
使用[]操作符,同样需要考虑越界。不需要关心长度指的是往里面增加元素时,push_back时vector会动态增加。
数组和vector都是连续的内存空间。区别在于数组配置了就不能改变,需要变大,需要另外申请空间,把数据从原来的地方搬到新的地方。
这些工作需要用户(程序员)自己做。而vector不需要,它封装了这些操作。vector有很多实现版本。核心难点在于空间满载时,扩充空间时的三个操作,
申请新空间--数据迁移--释放旧空间
简单看一下vector有哪些内容:
// simple vector template <class T, class Alloc = alloc> class YVector { public: typedef T value_type; typedef value_type* pointer; typedef value_type* iterator; typedef value_type& reference; typedef size_t size_type; typedef ptrdiff_t difference_type; iterator begin() { return start; } iterator end() { return finish; } size_type size() const { return size_type(end() - begin()); } size_type capacity() const { return size_type(end_of_storage - begin()); } bool empty() { return begin() == end(); } reference operator[](size_type n) { return *(begin() + n); } reference front() { return *begin(); } reference back() { return *(end() - 1); } void push_back(const T& x); void pop_back(); iterator erase(iterator first, iterator last); iterator erase(iterator position); void resize(size_type n, const T& x); void resize(size_type n); void clear() { erase(begin(), end()); } YVector() :start(0), finish(0), end_of_storage(0) {} YVector(size_type n, const T& value) { fill_initialize(n, value); } YVector(int n, const T& value) { fill_initialize(n, value); } YVector(long n, const T& value) { fill_initialize(n, value); } explicit YVector(size_type n) { fill_initialize(n, T()); } ~YVector() { destroy(start, finish); deallocate(); } protected: typedef simple_alloc<value_type, Alloc> data_allocator; iterator start; iterator finish; iterator end_of_storage; void insert_aux(iterator position, const T& x); void deallocate(); void fill_initialize(size_type n, const T& value); void allocate_and_fill(size_type n, const T& value); };
看到有很多关于alloc的,这其实是内存分配的问题,在源码剖析里第二章专门讲了这部分。由于目前阶段只需要会用vector就行了,不需要理解到太深。
所有我想关于内存的地方,不深究实现代码,只大概明白做了什么就行。接下来就来具体看几个常用的,而且能看懂的部分。
1.iterator
typedef value_type* iterator;
vector的迭代器是一个简单的指针。6哦朋友。迭代器是个复杂的东西。在源码剖析第三章详细介绍。
为什么vector的迭代器是个简单的指针,因为vector迭代器需要的操作*,->,++,--,+=,-=,+,-这些普通的指针都具备。
2.size和capacity
vector的大小和容量是怎么界定的。如图:
关注3个指针:
iterator start;
iterator finish;
iterator end_of_storage;
start是起始位置,finish是结束位置,end_of_storage是申请空间的结束位置。有了这3个指针之后,很好理解一些操作:
iterator begin() { return start; } //起始位置 iterator end() { return finish; } //结束位置 size_type size() const { return size_type(end() - begin()); } //可用大小 size_type capacity() const { return size_type(end_of_storage - begin()); } //申请的内存大小 bool empty() { return begin() == end(); } //是否为空 reference operator[](size_type n) { return *(begin() + n); } //[]操作 reference front() { return *begin(); } //首元素 reference back() { return *(end() - 1); } //尾元素
size()其实是finish和start之间的大小,capacity是end_of_storage到start之间的大小。
3.push_back
写一段代码测试:
#include <stdlib.h> #include <iostream> #include <vector> using namespace std; void print_vec(vector<int> &vec) { cout << "elem : "; for (int i = 0; i < (int)vec.size(); ++i) { cout << vec[i] << " "; } cout << endl; cout << "size = " << vec.size() << endl; cout << "capacity = " << vec.capacity() << endl; cout << endl; } int main() { vector<int> vec(2, 1); print_vec(vec); vec.push_back(2); print_vec(vec); vec.push_back(2); print_vec(vec); vec.push_back(3); print_vec(vec); vec.push_back(4); print_vec(vec); vec.push_back(5); print_vec(vec); system("pause"); return 0; }
按书上说的需要扩充vector大小时,是capacity变成2倍大小。但是VS里面并不是
而在ubuntu里面g++编译下:
试过之后发现g++才是扩充2倍。VS是特殊版本的STL吧。
template <class T, class Alloc = alloc> void YVector::push_back(const T& x) { if (finish != end_of_storage) { construct(finish, x); ++finish; } else { insert_aux(finish, x); } }
有备用空间时,把最后一个元素构造成X,finsih指针调整。没有备用了,调用内部insert_aux函数。insert_aux暂时不深入了吧。有点复杂。
Tips1:全局constuct函数。
void construct( pointer p, const _Ty& value ) { new(static_cast<void*>(p)) _Ty(value); }
这是Vs里的版本。注意到是使用的placement new。c++里有3种new
1.new operator。申请空间,调用构造函数。不能被重载。
2.operator new。只申请空间,不调用构造函数。可重载。
3.placement new。一个特殊的operator new,在一块已经分配好的内存上构造对象,返回这块内存的首地址。
因为finish到end_of_storage之间是早已经申请好的空间,所以使用placement new。
4.pop_back,erase,clear
template <class T, class Alloc = alloc> void YVector::pop_back() { --finish; destroy(finish); } template <class T, class Alloc = alloc> iterator erase(iterator first, iterator last) { iterator i = copy(last, finish, first); destroy(i, finish); finish = finish - (last - first); return first; } template <class T, class Alloc = alloc> iterator erase(iterator position) { if (position + 1 != end()) { copy(position + 1, finish, position); } --finish; destroy(finish); return position; } void clear() { erase(begin(), end()); }
destroy()和copy()都是内存操作相关。destroy()相对于construct()。
iterator copy(first, last, result),copy负责拷贝,即将迭代器区间[first,last)的元素复制到由复制目标result给定的区间[result,result+(last-first))中,返回一个已复制区间的最后一个位置。
erase前和erase后的对比,如图:
5.resize和reserve
源码剖析里,好像没有特别讲。但是常用,容易弄错。写一段代码来理解怎么用
void print_vec(vector<int> &vec) { cout << "size = " << vec.size() << endl; cout << "capacity = " << vec.capacity() << endl; cout << endl; } int main() { vector<int> vec; print_vec(vec); vec.resize(100); print_vec(vec); vec.reserve(200); print_vec(vec); system("pause"); return 0; }
可以看到,resize是改变size和capacity的大小,而reserve是改变capacity大小。
引用:
1.《STL源码剖析》
2. http://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html
3. http://blog.csdn.net/jerryjbiao/article/details/7376088