序列式容器(Vector)
前言
在STL编程中,我们最常用到的就是容器,容器可分为序列容器和关联容器;本文记录的是我们经常使用的序列容器之vector,vector的数据安排和操作方式类似于C++内置数组类型array,唯一的区别就是在于空间的灵活运用。内置数组array是静态空间,一旦分配了内存空间就不能改变,而vector容器可以根据用户数据的变化而不断调整内存空间的大小。
vector容器有已使用空间和可用空间,已使用空间是指vector容器的大小,可用空间是指vector容器可容纳的最大数据空间capacity。vector容器是占用一段连续线性空间,所以vector容器的迭代器就等价于原生态的指针;
vector的实现依赖于内存的配置和内存的初始化,以及迭代器。其中内存的配置是最重要的,因为每当配置内存空间时,可能会发生数据移动,回收旧的内存空间,如果不断地重复这些操作会降低操作效率,所有vector容器在分配内存时,并不是用户数据占多少就分配多少,它会分配一些内存空间留着备用,即是用户可用空间。关于vector类定义可参考《vector库文件》或者《MSDN库的vector类》。
vector容器的数据结构
vector容器采用的是线性连续空间的数据结构,使用两个迭代器来管理这片连续内存空间,这两个迭代器分别是指向目前使用空间的头start和指向目前使用空间的尾finish,两个迭代器的范围[start,finish)表示容器的大小size()。由于为了提高容器的访问效率,为用户分配内存空间时,会分配多余的备用空间,即容器的容量,以迭代器end_of_storage作为可用空间的尾,则容器的容量capacity()为[start,end_of_storage)范围的线性连续空间。
//Alloc是SGI STL的空间配置器,默认是第二级配置器
15 namespace EasySTL
16 {
17 /******** vector ***********/
18 template<class T, class Alloc = alloc>
19 class vector
{
30 protected:
31 //simple_alloc 是空间配置器
32 typedef simple_alloc<value_type, Alloc> data_allocator;
33
34 iterator start_; //目前使用空间头
35 iterator finish_;//目前使用空间尾
36 iterator end_of_storage_;//目前可用空间的尾
...
}
}
下面给出vector的数据结构示意图:
vector迭代器
vector容器维护的空间的线性连续的,所以普通指针也可以作为迭代器,满足vector的访问操作;如:operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=等操作;同时vector容器支持随机访问,所以,vector提供的是随机访问迭代器。
15 namespace EasySTL
16 {
17 /******** vector ***********/
18 template<class T, class Alloc = alloc>
19 class vector
{
20 public:
21 //vector的嵌套型别定义,是iterator_traits<T>服务的类型
22 typedef T value_type;
23 typedef T* pointer;
24 typedef T* iterator;
25 typedef const T* const_pointer;
26 typedef T& reference;
27 typedef size_t size_type;
28 typedef ptrdiff_t difference_type;
56 public: //以下定义vector迭代器
57 iterator begin() const { return start_; }
58 iterator end() const { return finish_; }
59 size_type size() const { return size_type(end() - begin()); }
60 size_type capacity() const { return size_type(end_of_storage_ - begin()); }
61 bool empty() const { return begin() == end(); }
...
}
}
vector的构造函数和析构函数
这里把vector容器的构造函数列出来讲解,主要是我们平常使用vector容器时,首先要要定义相应的容器对象,所以,如果我们对vector容器的构造函数了解比较透彻时,在应用当中就会比较得心应手。
75 vector() : start_(0), finish_(0), end_of_storage_(0) {}
76 vector(size_type n, const T& value) { fill_initialize(n, value); }
77 vector(int n, const T& value) { fill_initialize(n, value); }
78 vector(long n, const T& value) { fill_initialize(n, value); }
79 vector(const std::initializer_list<T> v)
80 {
81 auto start_v = v.begin();
82 auto end_v = v.end();
83 size_type n = v.size();
84 fill_initialize(n, T());
85 finish_ = EasySTL::copy(start_v, end_v, start_);
86 }
87 explicit vector(size_type n) { fill_initialize(n, T()); }
88
89 ~vector()
90 {
91 destroy(start_, finish_); //construct.h中的函数
92 deallocate(); //vector的成员函数
93 }
vector容器的成员函数
vector容器的成员函数使我们访问容器时经常会用到。
95 reference front() { return *begin(); }
96 reference back() { return *end(); }
97 void push_back(const T& x)
98 {
99 if (finish_ != end_of_storage_)
100 {
101 construct(finish_, x);
102 finish_++;
103 }
104 else
105 {
106 insert_aux(end(), x);
107 }
108 }
109
110 void pop_back()
111 {
112 --finish_;
113 destroy(finish_);
114 }
115
116 void insert(iterator position, size_type n, const T& x);
117
118 iterator erase(iterator position)
119 {
120 if (position + 1 != end())
121 EasySTL::copy(position + 1, finish_, position);
122 finish_--;
123 destroy(finish_);
124 return position;
125 }
126
127 iterator erase(iterator start_earse, iterator end_earse)
128 {
129 size_type erase_size = end_earse - start_earse; //总共去掉多少元素
130
131 if (end_earse + 1 != end())
132 {
133 size_type left = finish_ - end_earse; //去掉中间一段后,尾巴上还有多少元素
134 EasySTL::copy(end_earse, finish_, start_earse);
135 destroy(start_earse + left, finish_);
136 }
137 else
138 {
139 destroy(start_earse, finish_);
140 }
141 finish_ = finish_ - erase_size;
142 return start_earse;
143 }
144
145 //调整容器大小,如果newsize小于原大小,则清空多余部分
146 //如果大于原大小,则从尾部插入
147 void resize(size_type new_size, const T& x)
148 {
149 if (new_size < size())
150 erase(begin() + new_size, end());
151 else
152 insert(end(), new_size - size(), x);
153 }
154
155 void resize(size_type new_size) { resize(new_size, T()); }
156 void clear() { erase(begin(), end()); }
157
158 protected:
159 //分配n个元素大小空间,并以x初始化
160 iterator allocate_and_fill(size_type n, const T& x)
161 {
162 //获取内存空间
163 iterator result = data_allocator::allocate(n);
164 //在获取到的内存上创建对象
165 unitialized_fill_n(result, n, x);
166 return result;
167 }
其中擦除函数是擦除输入迭代器之间的元素,但是没有回收内存空间,只是把内存空间作为备用空间,首先看下该函数的源代码:
127 iterator erase(iterator start_earse, iterator end_earse)
128 {
129 size_type erase_size = end_earse - start_earse; //总共去掉多少元素
130
131 if (end_earse + 1 != end())
132 {
133 size_type left = finish_ - end_earse; //去掉中间一段后,尾巴上还有多少元素
134 EasySTL::copy(end_earse, finish_, start_earse);
135 destroy(start_earse + left, finish_);
136 }
137 else
138 {
139 destroy(start_earse, finish_);
140 }
141 finish_ = finish_ - erase_size;
142 return start_earse;
143 }
根据上面函数的定义,我们可以知道,迭代器start和end_of_storage并没有改变,只是调整迭代器finish,并析构待擦除元素对象;下面通过图解进行分析:
插入元素函数是在指定位置position上连续插入n个初始值为x的元素,根据插入元素个数和可用空间大小的比较,分别进行不同的初始化,详细见源码:
170 template<class T, class Alloc>
171 void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
172 {
173 //从position的位置插入n个元素,元素初始值为x
174 std::cout << "i'm in" << std::endl;
175 T x_copy = x;
176 if (n != 0)
177 {
178 //n必须不为空
179 if (size_type(end_of_storage_ - finish_) >= n)
180 {
181 //case1:内存的空间可以装下新增加的元素
182 const size_type elems_after = finish_ - position;
183 unitialized_fill_n(finish_, n, x_copy);
184
185 EasySTL::copy(position, finish_, position + n);
186
187 EasySTL::fill(position, position + n, x_copy);
188
189 finish_ += n;
190 }
191 else
192 {
193 //case2:内存空间不足以装下新增加的元素
194 const size_type old_size = size();
195 const size_type new_size = old_size + std::max(old_size, n);
196
197 iterator new_start = data_allocator::allocate(new_size);
198 if (!new_start)
199 {
200 std::cout << "out_of_memory" << std::endl;
201 return;
202 }
203 std::cout << new_size << std::endl;
204 iterator new_finish = new_start;
205 try
206 {
207 new_finish = uninitialized_copy(start_, finish_, new_start);
208 size_type m = position - start_;
209 auto new_position = new_start + m;
210 unitialized_fill_n(new_finish, n, x_copy);
211 EasySTL::copy(new_position, new_finish, new_position + n);
212 EasySTL::fill(new_position, new_position + n, x_copy);
213 }
214 catch (...)
215 {
216 destroy(new_start, new_finish);
217 data_allocator::deallocate(new_start, new_size);
218 throw;
219 }
220 clear();
221 deallocate();
222 start_ = new_start;
223 finish_ = new_finish + n;
224 end_of_storage_ = new_start + new_size;
225 }
226 }
227 }
228
229 template<class T, class Alloc>
230 void vector<T, Alloc>::insert_aux(iterator position, const T& x)
231 {
232 if (finish_ != end_of_storage_)
233 {
234 //还有备用空间
235 //在备用空间开始处创建一个对象,并以vector的最后一个对象为初始值
236 construct(finish_, *(finish_ - 1));
237 ++finish_;
238 T x_copy = x;
239 copy_backward(position, finish_ - 2, finish_ - 1);
240 *position = x_copy; //position上是已经有构造好的对象,直接赋值就可以
241 }
242 else
243 {
244 //没有可用空间
245 const size_type old_size = size();
246 //内存不足则申请原来两倍的新内存
247 const size_type new_size = old_size != 0 ? 2 * old_size : 1;
248
249 iterator new_start = data_allocator::allocate(new_size);
250 iterator new_finish = new_start;
251 try
252 {
253 new_finish = uninitialized_copy(start_, position, new_start);
254 construct(new_finish, x); //未构造的内存,需要调用construct
255 new_finish++;
256 new_finish = uninitialized_copy(position, finish_, new_finish);
257 }
258 catch (...)
259 {
260 //出现异常,回滚
261 destroy(new_start, new_finish);
262 data_allocator::deallocate(new_start, new_size);
263 throw;
264 }
265
266 destroy(begin(), end());
267 deallocate(); //销毁原来的vector内存空间
268
269 start_ = new_start;
270 finish_ = new_finish;
271 end_of_storage_ = new_start + new_size;
272 }
下面对不同情况利用图解方式对插入函数进行分析:
case1-a:对应的源代码解析中的case1-a情况:
case1-b:对应源码剖析中的case1-b情况:
case2:针对源码剖析的case2情况:
vector的操作符重载
关于操作符重载的这里只给出部分代码:
62 reference operator[](size_type n) { return *(begin() + n); }
bool operator ==(const vector& other) const
{
64 auto first1 = begin(), last1 = end();
65 auto first2 = other.begin(), last2 = other.end();
66 for (; first1 != last1 && first2 != last2; ++first1, ++first2)
67 {
68 if (*first1 != *first2)
69 return false;
70 }
71 return (first1 == last1 && first2 == last2);
72
73 }
总结
vector(向量容器)容器,增加和获取元素效率很高,插入和删除的效率很低。主要是对该数据结构的了解,并且掌握其中的成员函数,做到这两点,对vector容器的使用就比较方便了。
End