最近在知乎上看到一个回答:
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
来源:https://www.cnblogs.com/yao2yaoblog/p/6708105.html