STL源码学习系列七: 序列式容器( Vector )

序列式容器(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

猜你喜欢

转载自blog.csdn.net/qq_34777600/article/details/80573364