首页 > 编程语言 >《STL 源码剖析》学习笔记之容器(一)vector

《STL 源码剖析》学习笔记之容器(一)vector

时间:2023-01-04 10:09:17浏览次数:48  
标签:finish end STL start vector new 源码 size


《STL 源码剖析》学习笔记之容器(一)vector_初值

[图]The Container 2019-08-01



前言


侯捷大师的《STL源码剖析》,实乃一本神书,可以说也是一本很硬核的书了,不管是实验室的师兄师姐,还是牛客网上一些大佬们,都无不推荐此书,想要深入 C++ STL 这一块的,这本书必须掌握!


回想起来,大概在我大二的时候,就买了这本书,现在非常后悔的是,到大学毕业我都没看完这本书==。


人,总是到了某个特定的时期,才会突然醒悟,发现当初的自己为什么没有好好珍惜时间,没有静下来心来研读这些非常棒的书籍==

《STL 源码剖析》学习笔记之容器(一)vector_ci_02

《STL 源码剖析》学习笔记之容器(一)vector_ci_02


废话不多说,所谓


码之前,了无秘密


今天先从最基本的容器开始学习吧。



1、容器的概观与分类


容器,置物之所也。

众所周知,常用的数据结构不外乎 array(数组)、list(串行)、tree(树)、stack(堆栈)、queue(队列)、hash table(杂凑表)、set(集合)、map(映像表)…等等。


根据「资料在容器中的排列」特性,这些数据结构分为序列式(sequence)和关系型(associative)两种。


《STL 源码剖析》学习笔记之容器(一)vector_迭代器_04


所谓序列式容器,其中的元素都可序(ordered),但未排序(sorted)。


2、vector 容器


先来看看 vector 的源码。

以下是 vector 定义式的源码摘录。虽然 STL规定,欲使用vector者必须先含入,但 SGI STL 将 vector 实作于更底层的<stl_vector.h>。

不要被这一部分源码吓跑,如果阅读源码有困难,可以先跳过这部分,直接看后面的注解。

// alloc是 SGI STL的空间配置器
template <class T, classAlloc = alloc>
class vector
{
public:
// vector 的巢状型别定义
typedef T value_type;
typedef value_type*pointer;
typedef value_type*iterator;
typedef value_type&reference;
typedef size_t size_type;
typedef ptrdiff_tdifference_type;
protected:
// 以下,simple_alloc 是 SGI STL的空间配置器。
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()
{
if (start)
data_allocator::deallocate(start, end_of_storage - start);
}
void fill_initialize(size_type n, const T& value)
{
start =allocate_and_fill(n, value);
finish = start + n;
end_of_storage = finish;
}
public:
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() const
{
return begin() == end();
}
reference operator[](size_type n)
{
return *(begin() + n);
}
vector() : start(0), finish(0), end_of_storage(0) {}
vector(size_type n, const T& value)
{
fill_initialize(n, value);
}
vector(int n, const T& value)
{
fill_initialize(n, value);
}
vector(long n, const T& value)
{
fill_initialize(n, value);
}
explicit vector(size_type n)
{
fill_initialize(n, T());
}
~vector()
destroy(start, finish); //全域函式
deallocate(); // 这是 vector 的一个 member function
}


reference front()
{
return *begin(); //第一个元素
}
reference back()
{
return *(end() - 1); //最后一个元素
}
void push_back(const T& x)
{
if (finish != end_of_storage)//将元素安插至最尾端
{
construct(finish, x); //全域函式
++finish;
}
else
insert_aux(end(), x); // 这是 vector 的一个 member function
}


void pop_back() //将最尾端元素取出
{
--finish;
destroy(finish);
}
iterator erase(iterator position)
{
if (position + 1 != end()) //清除某位置上的元素
copy(position + 1, finish, position);//后续元素往前搬移
--finish;
destroy(finish);
return position;
}
//全域函式
void resize(size_type new_size, const T& x)
{
if (new_size < size())
erase(begin() + new_size, end());
else
insert(end(), new_size - size(), x);
}
void resize(size_type new_size)
{
resize(new_size, T());
}
void clear()
{
erase(begin(), end());
}
protected:
// 配置空间并填满内容
iterator allocate_and_fill(size_type n, const T& x)
{
iterator result =data_allocator::allocate(n);
uninitialized_fill_n(result, n, x); // 全域函式
return result;
}


几点注解


1、vector 是一种动态增长的一种容器,当原来分配的内存用完了之后,会动态扩容,事实上,没有一个容器能在原地内存扩充,因为你要了一块内存之后,这块内存之后的内容或者接下来的内容可能会被使用。


2、vector 的数据安排以及操作方式,与 array 非常像似。两者的唯一差别在于空间的运用弹性。array 是静态空间,一旦配置了就不能改变。vector 是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。


3、事实上,扩容的时候,是这样一种机制:会去别的地寻找一块空闲的内存,然后把原来的东西搬过去,这样子所谓的扩充,而不能在原地扩充。


4、vector 是一种前闭后开的容器,在 vector 里头,有一些变量记录着一些信息,分别是三个迭代器


start:表示目前使用空间的头。

finish:目前使用空间的尾。

end_of_storage:目前可用空间的尾。


注意,目前使用空间和目前可用空间是有区别的,看下面的这张图


《STL 源码剖析》学习笔记之容器(一)vector_初值_05



5、一个 vector 容器基本包含了下面这几个函数,下面分别讲解


1、begin() 返回的是 start

2、end() 返回的是 finish

3、size() 返回的是 size_type(end() - begin()) 为什么这里要这样写法,而不是直接调用 finish - start呢?侯捷大师说道:在使用比较复杂的容器的时候,直接用迭代器相减,可能会出问题,这里用 size 函数可以表示唯一确定函数的使用。

4、capacity() 返回的是 end_of_storage - begin() 的大小 也就是目前可用空间的大小。

5、empty() 只要判断是 begin() == end()即可。



6、二倍增长的原理?


当我们以 使用 push_back() 将新元素安插于 vector 尾端,该函数首先检查是否还有备用空间,如果有,就直接在备用空间上建构元素,并调整迭代器 finish,使 vector变大。如果没有备用空间了,就扩充空间(重新配置、搬移数据、释放原空间):


《STL 源码剖析》学习笔记之容器(一)vector_ci_06


细节:上面第二个函数进来,我们可以看到,又做了一次空间检查,这是为何呢?这是因为 insert_aux 函数 除了被 push_back 函数调用之外还会被其它函数调用!所以在那种情况下,需要做一次检查。

else   //已无备用空间
{
const size_type old_size = size();
const size_typelen = old_size != 0 ? 2 * old_size : 1;
// 以上配置原则:如果原大小为 0,则配置 1(个元素大小);
// 如果原大小不为 0,则配置原大小的两倍,
// 前半段用来放置原资料,后半段准备用来放置新资料。
iterator new_start =data_allocator::allocate(len); //实际配置
iterator new_finish = new_start;
try
{
// 将原 vector 的内容拷贝到新 vector。
new_finish = uninitialized_copy(start, position, new_start);
// 为新元素设定初值 x
construct(new_finish, x);// 调整水位。
++new_finish;
// 将原 vector 的备用空间中的内容也忠实拷贝过来(侯捷疑惑:啥用途?)
new_finish =uninitialized_copy(position, finish, new_finish);
}
catch(...)
{
// "commit or rollback" semantics.
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
// 解构并释放原 vector
destroy(begin(), end());
deallocate();
// 调整迭代器,指向新 vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}


几点注解:

1、那么进入 else 语句里面就开始了两倍增长的实现,调用分配器分配。
2、调用 uninitialized_copy函数 将原来 vector 的内容拷贝到新的 vector 内容中去。
3、为新元素设置初值。
4、拷贝安插点后的原内容(因为这个函数也有可能白 insert 函数调用)

注意,所谓动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大空间,然后将原内容拷贝过来,然后才开始在原内容之后建构新元素,并释放原空间。

因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心。


3、vector 的元素操作:pop_back, erase, clear, insert


// 将尾端元素拿掉,并调整大小。
void pop_back() {
--finish;//将尾端标记往前移一格,表示将放弃尾端元素。
destroy(finish);// destroy是全域函式
}
// 清除 [first,last) 中的所有元素
iterator erase(iterator first, iterator last) {
iterator i = copy(last, finish, first);// copy 是全域函式
destroy(i, finish);// destroy是全域函式
finish = finish - (last - first);
return first;
}
void clear() { erase(begin(), end()); }// erase()就定义在上面


下面展示 erase(first, last)的动作。


《STL 源码剖析》学习笔记之容器(一)vector_初值_07

《STL 源码剖析》学习笔记之容器(一)vector_ci_08



下面展示 vector::insert() 实作内容:从 position 开始,安插 n个元素,元素初值为 x。

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
if (n != 0) // 当 n != 0 才进行以下所有动作
{
if (size_type(end_of_storage - finish) >= n) // 备用空间大于等于「新增元素个数」
{
T x_copy = x;// 以下计算安插点之后的现有元素个数
const size_type elems_after = finish - position;
iterator old_finish = finish;
if (elems_after > n) // 「安插点之后的现有元素个数」大于「新增元素个数」
{
uninitialized_copy(finish - n, finish, finish);
finish += n;//将 vector 尾端标记后移
copy_backward(position, old_finish - n, old_finish);
fill(position, position + n, x_copy);//从安插点开始填入新值
}
else
{
// 「安插点之后的现有元素个数」小于等于「新增元素个数」
uninitialized_fill_n(finish, n - elems_after, x_copy);
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish);
finish += elems_after;
fill(position, old_finish, x_copy);
}
}
else
{
// 备用空间小于「新增元素个数」(那就必须配置额外的内存)
// 首先决定新长度:旧长度的两倍,或旧长度+新增元素个数。
const size_type old_size = size();
const size_type len = old_size + max(old_size, n);
// 以下配置新的 vector 空间
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
__STL_TRY
{
// 以下首先将旧 vector的安插点之前的元素复制到新空间。
new_finish = uninitialized_copy(start, position, new_start);
// 以下再将新增元素(初值皆为 n)填入新空间。
new_finish = uninitialized_fill_n(new_finish, n, x);
// 以下再将旧 vector 的安插点之后的元素复制到新空间。
new_finish = uninitialized_copy(position, finish, new_finish);
}
# ifdef __STL_USE_EXCEPTIONS
catch(...)
{
// 如有异常发生,实现 "commit or rollback" semantics.
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
// 以下清除并释放旧的 vector
destroy(start, finish);
deallocate();
// 以下调整水位标记
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}


注意,安插完成后,新节点将位于标兵迭代器(上例之 position,标示出安插点)所指之节点的前方—这是 STL 对于「安插动作」的标准规范。


图 4-3b 展示 insert(position,n,x) 的动作。

可以参照图片里的注解,细细体会。


《STL 源码剖析》学习笔记之容器(一)vector_迭代器_09


《STL 源码剖析》学习笔记之容器(一)vector_初值_10

《STL 源码剖析》学习笔记之容器(一)vector_初值_11



参考资料:《STL源码剖析》(侯捷著)。




标签:finish,end,STL,start,vector,new,源码,size
From: https://blog.51cto.com/u_15368396/5986960

相关文章

  • PostgreSQL源码结构
    PostgreSQL源码结构  5440 次浏览      6 2019-9-6 编辑推荐:本文来自于csdn,本文主要介绍了PostgreSQL......
  • 在线客服系统实现消息声音提醒效果 - 在线客服系统源码
    在在线客服系统中实现消息声音提醒效果可以带来许多好处,包括:改善用户体验:通知声音可以帮助提醒用户有新消息,鼓励他们及时回复,提高整体用户体验。提高生产率:通过提醒......
  • stl学习之rope
    简介rope是本人在极度不想敲平衡树的情况下从朋友偶然听说的,了解之后用寥寥数行边秒杀了那道平衡树的题。笔者遂深感其强大与低调,故写笔记以纪念之。头文件与定义#inclu......
  • 《STL 源码剖析》学习笔记之容器(二)list
    [图]Theorange 2019-08-061、list概述相较于vector的连续线性空间,list就显得复杂许多,它的好处是每次安插或删除一个元素,就配置或释放一个元素空间。因此,list对于空间......
  • java基于springboot外卖系统在线订餐系统app源码厨艺论坛APP
    简介本项目主要包括了外卖订餐系统(在线订餐和外卖配送)、厨艺论坛系统、管理员后台、用户中心等功能。用户注册后可以选择餐桌在线点餐支付,也可以选择外卖配送到家的方式。......
  • java开发的考研系统大学生考研推荐网站考研网站源码
    简介:考研信息推荐查询。主要是管理发布管理考研的知识文章,或者上传资料,发布考研的视频。学生可以注册后下载资料,查看考研文章视频等。文章分为vip文章和普通文章,学生查看v......
  • java家装网装修网站装修系统源码
    简介本平台主要是家装网站。管理员发布装修案例,看工地,装修设计师,装修攻略,装修知识文章等,嵌入3d全景图。普通用户注册,填写装修房型报价等。演示视频:https://www.bilibil......
  • STL补充
    1vector,变长数组,倍增的思想1size()返回元素个数2empty()返回是否为空3clear()清空4front()/back()5push_back()/pop_back()6begin()/end()7[]8......
  • Spring 事务源码(四):事务执行流程
    一、执行入口Spring事务是通过AOP实现,在AOP源码(五):具体执行流程-责任链模式中提到AOP流程执行入口为CglibAopProxy#DynamicAdvisedInterceptor#intercept,事务的代......
  • 读 Go项目源码,可以试试这个UML工具
    这个工具可以帮我们梳理出代码的整体结构,我觉得还是挺有用的。是一个开源项目:项目地址:​​ https://github.com/jfeliu007/goplantuml​​这个项目可以分析一个Go项目,然......