STL源码剖析(侯捷)笔记——容器与迭代器

1、容器的结构与分类

在这里插入图片描述
上图中,缩进代表复合关系,即拥有。如rb_tree与set,set拥有rb_tree,heap拥有一个vector,即heap内部有vector作为其数据的一部分。又如:deque与stack,stack是在deque基础上编写的,其源码实现中包含一个deque。

左右两侧的sizeof含义为:创建一个容器对象所需要的大小(不是创建100个元素的容器总大小)。如假设vector内部有3根指针,begin,end,end_of_storage三个迭代器指针,那么创建这个vector的空间即为3*4=12B。

2、容器list

2.1 容器的结构

在这里插入图片描述

对上图的解析:
包含了数据部分node、迭代器iterator(typedef,操作符重载)
1、数据部分:
protected:
link_type node;这是list的数据部分,即在创建list时,会申请该数据的空间大小
由于有很多typedef,所以要追踪该数据的类型,如下:
link_type->list_node*->_list_Node<T>*,即node是一个指向_list_Node的类模板的指针。
_list_Node结构如下:
template<class T>
struct _list_Node{
	typedef void* void_pointer;
	void_pointer *prev;
	void_Pointer *next;
	T data;
}

即list中的节点为_list_Node,该结构包含2根指针和一个data。也就是循环链表。
typedef void* void_pointer;该设计并不好,设计成void*在使用的时候还需要进行转化,新版本有改善。
此外list的末尾有一个空节点以表示前闭后开,即迭代器的list.end();

2.2、list迭代器(typedef,操作符重载)

在这里插入图片描述
list中的iterator类如上图。上图中注意点:容器中的迭代器至少5个typedef以及大量的操作符重载。
2、迭代器部分(所有的容器为了对外提供迭代器iterator,都要有一个typedef,如上图中的typedef _list_iterator<T,T&,T*> iterator;)// 实际上只需要一个T即可,没必要传入三个(T,T&,T*)
list中的迭代器需要实现++,跳转到下一个节点的功能。
注意:除了vector和array之外,所有的容器的迭代器都必须是一个class。
在这里插入图片描述

上图中,是迭代器重载++的源码:
前置++比较简单,返回的是对象本身,self&
后置++比较复杂,代码如下:
self operator++(int){ //返回的不是对象本身,而是它的拷贝,因此在使用循环时不建议使用后置++
	self tmp = *this; // 创建对象的拷贝,使用了重载后的=,即调用了迭代器的拷贝构造
	++*this;// 将对象本身++,使用重载后的前置++
	return tmp;//返回临时对象
}
注意点:c++不允许后加加2次,如i++++,错误。

2.3 list迭代器中操作符重载

在这里插入图片描述

reference(T&) operator*() const{ return (*node).data;};
_list_iterator(const iterator& x):node(x.node){};
self& operator++(){node=(link_type)((*node).next); return *this};
pointer operator->() const {return &(operator*())};

3、萃取机traits(iterator_traits)

种类:(iterator_traits、type_traits、char_traits、allocator traits、pointer traits、array_traits)

3.1 iterator设计原则

在这里插入图片描述
对于一个算法来说,如上图rotate(),它的参数只有first,middle,last三根迭代器,那么对于迭代器指向的数据的类型算法是不知道的。由于_list_iterator已经提供了如下的五种associated types,即

template<class T, class Ref, class Ptr>
struct _list_iterator
{
	typedef bidirectional_iterator_tag iterator_category;
	typedef T value_type;
	typedef Ptr pointer;
	typedef Ref reference;
	typedef ptrdiff_t diffenence_type;
}

list的迭代器内部已经提供了这五种类型(所有迭代器都必须要),因此rotate函数在进行工作的时候,便能够通过迭代器获取数据的类型(迭代器通过泛型实现),从而完成相应工作。

问题:如果函数的参数不是一个迭代器class(只有class才能typedef),而是普通指针呢?(指针并没有提供该五种类型,函数也就不知道数据具体是什么类型)

解决:添加一个中间层,即traits,iterator traits用以分离class iterators 和 non-class iterators

3.2 iterator_traits源代码(部分)

// 泛化
template<class I>
struct iterator_traits{
	typedef typename I::value_type value_type;
}

//偏特化1,范围偏特化
template<class T>
struct iterator_traits<T*>{
	typedef T value_type;
};
// 偏特化2,范围偏特化
template<class T>
struct iterator_traits<const T*>{
	typedef T value_type;// 这里是T,而不是const T。因为value_type是用来声明变量的,对于const常量使用没有意义
}

于是,如下使用iterator_traits:
template <class T, ...>
void algorithm(...){
	typename iterator_traits<T>::value_type v1;
}

上文的泛化用于对迭代器生效,对于普通的指针,将会进入偏特化1,从而达到获得value_type的效果。

3.3 iterator_traits源代码全

// 泛化
template <class I>
struct iterator_traits{
	typedef typename I::iterator_category iterator_category;
	typedef typename I::value_type value_type;
	typedef typename I::difference_type difference_type;//两个迭代器之间的距离用该变量表示
	typedef typename I::pointer pointer;
	typedef typename I::reference reference;
}

// 偏特化
template <class T>
struct iterattor_traits<T*>{
	typedef random_access_iterator_tag iterator_category;//随机访问迭代器
	typedef T value_type;
	typedef ptrdiff_t difference_type;//两个迭代器之间的距离用该变量表示
	typedef T* pointer;
	typedef T& reference;
}

template <class T>
struct iterattor_traits<cosnt T*>{
	typedef random_access_iterator_tag iterator_category;//随机访问迭代器
	typedef T value_type;
	typedef ptrdiff_t difference_type;//两个迭代器之间的距离用该变量表示
	typedef T* pointer;
	typedef T& reference;
}

4、容器vector

在这里插入图片描述
1、vector使用默认分配器

2、使用三根指针控制整个容器:start,finish,end_of_storage。因此sizeof(vector<int>)=12。

3、vector空间不足时,会进行2倍扩充。

vector扩充的代码:当插入元素时,若空间不足会引发扩充,push_back,insert(p,x)
void push_back(const T& x) {
	if (finish != end_of_storage) { // 当还有备用空间时
		construct(finish, x); // 使用全局函数在finish位置分配x
		++finish; // 后移末尾迭代器
	}
	else // 若无备用空间
		insert_aux(end(), x); // 辅助插入函数
}

template<class T, class Alloc>
void vector<T,Alloc>::insert_autx(iterator position, const T& x) { // 辅助插入函数,可能被多个函数调用,因此也要做空间判断if
	if (finish!=end_of_storage){
		construct(finish, *(finish-1)); // 还有备用空间
		++finish;
		T x_copy = x;
		copy_backward(postision, finish-2, finish-1);
		*position = x_copy;
	}
	else { // 无备用空间
		cosnt size_type old_size = size();
		const size_type len = old_size != 0? 2*old_size:1;
		//分配原则:如果原大小为0,则分配1个,否则调整为2倍。
		
		iterator new_start = data_allocator::allocate(len);
		iterator new_finish = new_start;
		try{...} catch(...){...};
		destroy(begin(), end());
		deallocate();
		start = new_start;
		finish = new_finish;
		end_of_storage = new_start+len;
	}
}

//try,catch部分的代码如下:
try{
	new_finish = uninitialized_copy(start,position,new_start); // 拷贝原vector插入点之前的内容
	construct(new_finish,x);// 新元素设初始值
	++new_finish;
	new_finish = uninitialize_copy(position,finish,new_finish); // 拷贝按差点之后的内容,因为他有可能被insesrt(p,x)调用。
} catch(...){
	destroy(new_start, new_finish);// 分配出现异常,将临时指针删掉;
	data_allocator::deallocate(new_start,len);
	throw;
}

vector中的iterator如下图所示,较为简单,T*
在这里插入图片描述

5、容器array

在这里插入图片描述

1、array的创建和其他容器有不同,需要指定大小:
array<int,10> arr;
//
array没有ctor和dtor
2、对于连续的容器,迭代器可以用普通的指针,即
typedef value_type* iterator;

6、容器deque

6.1 deque结构

在这里插入图片描述
deque为双向链表,使用一个vector控制每个缓冲区(vector中的内容是也从中间向两边扩充的),push_back的时候,如果最后一个缓冲区不够用,那么会再申请一块缓冲区,并在vector中添加一个该缓冲区的指针,push_front(),同理。

deque有两个迭代器,first和finish。

deque的迭代器是一个类,它会提供一个deque是连续的假象,即++的时候可以在多个buffer之间跳转。

iterator类包括cur、first、last、node4个指针,大小为16B.

cur:指向第一个元素。

first:指向第一个buffer。

last:指向最后一个buffer。

node:指向当前的控制中心map

deque源码:
在这里插入图片描述

6.2 deque插入删除的底层实现

deque的模板参数有一个缓冲区大小,即图中每个buffer能容纳的元素个数。

deque_iterator源码:
在这里插入图片描述
deque如何做到插入删除操作:

例子:deque::insert();

// 在position位置插入一个x
iterator insert(iterator position, const value_type& x) {
	if (position.cur == start.cur) {// 如果插入的是头部
		push_front(x); //调用该函数完成头部插入操作
		return start; // 返回指向头部的迭代器,和插入之前的start已经不同了
	}else if (position.cur == finish.cur) {
		push_back(x);
		iterator tmp = finish;
		--tmp;
		return tmp; //不太明白为什么和插入头部不同
	} else
		return insert_aux(position, x);	
}

template<class T, class Alloc, size_t BufSize>
typename deque<T, Alloc, BufSize>::iterator
deque<T, Alloc, BufSize>::insert_aux(iterator pos, const value_type& x) {
	difference_type index = pos-start;//计算按差点之前的元素
	value_type x_copy = x;
	if (index < size()/2) { // 如果安插点之前的元素比之后的元素少
		push_front(front()); // 最前面插入一个与第一元素相等的元素
		...
		copy(front2, pos1, front1); // 元素搬移
	} else { // 安插点之后的元素较少
		push_back(back()); //在尾端插入与最末尾元素相同的元素
		...
		copy_backward(pos, back2, back1); // 元素搬移
	}
}

6.3 deque如何模拟连续空间(iterator)

// 更新当前的buffer所在地址 ,
void set_node(map_pointer new_node) {//map_pointer是存放指针的容器
	node = new_node; // node是指向指针的指针,即**,所以*new_node是一个指针
	first = *new_node; // first是一个*,所以是*new_node
	last = first + difference_type(buffer_size());
}

self& operator++() {
	++cur;
	if (cur == last) {
		set_node(node+1);
		cur = first;
	}
	return *this;
}

self operator++(int) {
	self tmp = *this;
	++*this;
	return tmp;
}

self& operator--(){
	if (cur == first){
		set_node(node-1);
		cur = last;
	}
	--cur;
	return *this;
}

self operator--(int) {
	self tmp = *this;
	--*this;
	return tmp;
}

操作符重载
self& operator+=(difference_type n) {
	difference_type offset = (cur-first)+n;
	// 目标位置在同一缓冲区
	if (offset >= 0 && offset < difference_type(buffer_size())){
		cur += n;
	} else { // 不在同一个缓冲区
		difference_type node_offset = offset>0?offset/difference_type(buffer_size()):-difference_type((-offset-1)/buffer_size())-1;
        set_node(node_offset+node);
		cur = first + (offset-node_offset*difference_type(buffer_size()));
	}
	return *this;

}

self& operator-=(difference_type n) {
	return *this += -n;
}

self operator+(difference_type n) const {
	self tmp = *this;
	tmp += n;
	return tmp;
}

self operator-(differnece_type n) const {
	self tmp = *this;
	return tmp -= n;
}

reference operator[](difference_type n) const{
	return *(*this+n); // 重载了+
}

7、适配器queue和stack

queue和stack叫做容器适配器,底层默认是用deque实现的
queue代码:

template<class T,class Sequence=deque<T>>
class queue {
...
public:
	typedef typename::Sequence value_type value_type;
	typedef typename::Sequence size_type size_type;
	typedef typename::reference reference;
	typedef typename::const_reference const_reference;
protected:
	Sequence c;// 底层容器,一个deque
public:
	bool empty() const {return c.emtpy();}
	size_type size() const {return c.size();}
    reference front() {return c.front();}
    const_reference front() const {return c.front();}
    reference back() const {return c.back();}
    const_reference back() {return c.back();}
    void push (const value_type& x) {c.push_back(x));}
    void pop (){c.pop_front();}
}

stack代码:

template<class T, class Sequence=deque<T>>
class stack {
public:
	typedef typename::Sequence value_type value_type;
	typedef typename::Sequence size_type size_type;
	typedef typename::reference reference;
	typedef typename::const_reference const_reference;
private:
	Sequence c;// 底层容器,也是一个deque
public:
	bool empty() const {return c.emtpy();}
	size_type size() const {return c.size();}
	reference top() { return c.back();}
	const_reference top() const { return c.back();}
	void push(const value_type& x) {c.push_back(x);}
	void pop() {c.pop_back();}
}

8、容器rb_tree,红黑树

在这里插入图片描述
红黑树代码如上图所示,模板参数有5个,我们实际使用的时候使用更上层的map等,不许特别指定该模板参数。

大小:

...
protected:
	size_type node_count; rb_tree中节点的数量
	link_type header;指向红黑树节点的指针
	Compare key_compare;函数或者类,因为是比较的作用,内部不包含数据,所以大小为0,对于空类,大小会分配为1,编译器会自动对齐为4的倍数,所以rb_tree的大小为12.

9、容器set

在这里插入图片描述
上图可以看出,set底层用的是红黑树,因此也叫叫做容器适配器,即containter adapter。

其使用的迭代器为rb_tree中的const_iterator,因为set不允许修改键。

rb_tree自身提供了find函数,因此set、map、multiset、multimap都用自身带的find函数即可。

10、容器map

在这里插入图片描述

11、容器hashtable

在这里插入图片描述
1、模板参数

template<class Value, class Key, class HashFcn, class ExtractKey,
			class EqualKey, class Alloc=alloc>
Value: 值
Key:键
HashFcn:哈希函数,将对象(键值对)通过该函数计算得到编号hashcode
Extractkey:哈希表放入的东西可能是pair等,使用该函数拿出元素值
EqualKey:告诉哈希表什么叫做元素相等,比如石头对象,两个石头如何相等,如何比大小。

2、rehashing

当元素个数大于篮子bucket个数时,就会进行扩充,因此篮子个数总是大于元素个数
[文档及视频资源github地址(非本人整理):https://github.com/ZachL1/Bilibili-plus]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值