文章目录
第1章 简介
STL六大组件:
- 容器(containers):各种数据结构,是一种template class
- 算法(algorithms):各种常用算法,是一种function template
- 迭代器(iterators):容器与算法之间的桥梁,所谓的”泛型指针“
- 仿函数(functors):行为类似于函数,但他是一个类(对象),实际是重载了operator ()的class或者template class
- 适配器(adapters):分为三种function adapter、container adapter、iterator adapter
- 配置器(allocators):负责空间配置与管理。配置器实际是一个实现了动态空间配置、空间管理、空间释放的class template
Container通过Allocator获取数据结构存储空间,Algorithm通过Ierator来获取Container的内容,Functor可以协助Algorithm完成不同的策略变化,Adapter可以修饰或套接Functor.
2 空间配置器
3 迭代器与traits编程技法
迭代器是一种行为类似智能指针的对象,最重要的工作是内容提领(dereference)和成员访问(member access),所以迭代器的主要编程工作是对operator*和operator->进行重载。
3.1、模板偏特化(Partial Sepcialzation)
- 模板偏特化(Partial Sepcialzation):针对(任何)template参数更进一步的条件限制所设计出来的一个特化版本,例如:
template<typename T> // 这个泛化版本允许T为任何类型
class C{...};
template<typename T> // 这个特化版本仅适用于”T为原生指针“的情况
class C<T*>{...};
3.2、traits(特性萃取)编程技法
-
traits(特性萃取)编程技法:就是通过模板偏特化来将不同类型的迭代器(甚至原生指针)的相应类型提取出来,最常用的迭代器相应型别有五种
-
value type:是指迭代器所指向对象的型别。例如vector的迭代器的value type就是int。
-
difference type:是指两个迭代器之间的距离。
-
reference type:STL未使用
-
pointer type:STL未使用
-
iterator category:迭代器类别,有五大类:
- Input Iterator:只读迭代器,这种迭代所指向的对下个不允许外界改变
- Output Iterator:只写迭代器。
- Forward Iterator:允许”写入型“算法(例如replace())在此类迭代器上进行读写操作。
- Bidirectional Iterator:可双向移动的迭代器。
- Random Access Iterator:前四种迭代器只支持了原始指针的部分算术能力(前三种只支持operator++,第四种还支持operator–),而第五种则支持p±n,p[n],p1-p2,p1<p2
这五种迭代器的从属关系:Random Access Iterator属于Bidirectional Iterator大类中的其中一种,Bidirectional Iterator属于Forward Iterator大类中的的其中一种,Forward Iterator属于Input Iterator和Output Iterator大类中的其中一种。代码体现:
struct input_iterator_tag {}; struct output_iterator_tag {}; struct forward_iterator_tag : public input_iterator_tag {}; struct bidirectional_iterator_tag : public forward_iterator_tag {}; struct random_access_iterator_tag : public bidirectional_iterator_tag {};
这样,算法就可以根据不同的迭代器类型来进行不同程度的优化,比如advance(),需要将当前迭代器”向前“移动”n步“:RandomAccessIterator可以一步到位;BidirectionalIterator可以双向移动;InputIterator只能单步单单向移动。
template<class InputIterator, class Distance> void advance(InputerIterator& i, Distance n) { // iterator_traits就是一个class template,专门用于提取迭代器的特性 __advance(i, n, iterator_traits<InputIterator>::iterator_category()); } template<class InputIterator, class Distance> void __advance(InputerIterator& i, Distance n, input_iterator_tag) { while(n--)++i; } template<class BidirectionalIterator, class Distance> void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag) { if(n >= 0) while(n--)++i; else while(n++)--i; } template<class RandomAccessIterator, class Distance> void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag) { i += n; }
-
3.3、iterator_traits的实现原理
对于3.2的iterator_traits为什么能精确提取迭代器的类型,这里有答案:
template<class I>
struct iterator_traits {
typedef typename I::iterator_category iterator_category;
}
//针对原始指针的偏特化
template<class T>
struct iterator_traits<T*> {
typedef random_access_iterator_tag iterator_category;
}
//...............其他偏特化版本
3.3 std::iterator
template<class Category,
class T,
class Distance = ptrdiff_t,
class Pointer = T*,
class Reference = T&>
struct iterator {
typedef Category iterator_category;
typedef T value_type;
typedef Distance difference_type;
typedef Pointer pointer;
typedef Reference reference;
}
3.4 __type_traits
除了萃取iterator属性外,还有对类型的一些特性进行萃取,例如是否有trivial default constructor(无用默认构造函数)
这里和iterator_traits一样,都是class template和偏特化的运用。下面给出的是一个__type_traits
template<class type>
struct __type_traits {
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial__copy_constructor;
typedef __false_type has_trivial__assign_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
}
4 序列式容器
4.1 vector
vector实际上是一个动态数组,其实现技术的关键在于其对大小的控制以及重新配置时的数据移动效率。
4.1.1 迭代器
- vector的迭代器定义如下。是普通的指针
template<class T, class Alloc = alloc>
class vector {
public:
typedef T value_type;
typedef value_type* iterator;
.........
};
4.1.2 数据结构
- vector的数据结构。其中[start, finish)是已经使用了的空间,而[finish, end_of_storage)是已经分配给vector,但还没使用的空间。
template<class T, class Alloc = alloc>
class vector {
protected:
iterator start;
iterator finish;
iterator end_of_storage;
.........
};
4.1.3 元素操作
-
vector的元素操作:下面的construct和destroy都是全局函数,见第2章
- push_back:
void push_back(const T& x) { // 内存满足条件则直接追加元素, 否则需要重新分配内存空间 if (finish != end_of_storage) { construct(finish, x); ++finish; } else insert_aux(end(), x); }
- pop_back:
void pop_back() { --finish; destroy(finish); }
- erase:是通过移动(copy)覆盖要删除的内容,然后删除最后的内容
iterator erase(iterator position) // 将postion后的内容都向前移动一位,然后将最后的析构 { if (position + 1 != end()) copy(position + 1, finish, position); --finish; destroy(finish); return position; } iterator erase(iterator first, iterator last) { iterator i = copy(last, finish, first); // 析构掉需要析构的元素 destroy(i, finish); finish = finish - (last - first); return first; }
- clear:
- insert:
template <class T, class Alloc> void insert_aux(iterator position, const T& x) { if (finish != end_of_storage) // 还有备用空间 { // 在备用空间起始处构造一个元素,并以vector最后一个元素值为其初值 construct(finish, *(finish - 1)); ++finish; T x_copy = x; copy_backward(position, finish - 2, finish - 1); *position = x_copy; } else // 已无备用空间 { const size_type old_size = size(); const size_type len = old_size != 0 ? 2 * old_size : 1; // 以上配置元素:如果大小为0,则配置1(个元素大小) // 如果大小不为0,则配置原来大小的两倍 // 前半段用来放置原数据,后半段准备用来放置新数据 iterator new_start = data_allocator::allocate(len); // 实际配置 iterator new_finish = new_start; // 将内存重新配置 try { // uninitialized_copy()的第一个参数指向输入端的起始位置 // 第二个参数指向输入端的结束位置(前闭后开的区间) // 第三个参数指向输出端(欲初始化空间)的起始处 // 将原vector的安插点以前的内容拷贝到新vector new_finish = uninitialized_copy(start, position, new_start); // 为新元素设定初值 x construct(new_finish, x); // 调整已使用迭代器的位置 ++new_finish; // 将安插点以后的原内容也拷贝过来 new_finish = uninitialized_copy(position, finish, new_finish); } catch(...) { // 回滚操作 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; } } iterator insert(iterator position, const T& x) { size_type n = position - begin(); if (finish != end_of_storage && position == end()) { construct(finish, x); ++finish; } else insert_aux(position, x); return begin() + n; }
4.1.4 动态扩容
参考连接:https://blog.csdn.net/qq_44918090/article/details/120583540
-
高效使用vector,避免扩容
- 扩容机制回顾:当向vector中插入元素时,如果元素有效个数size与空间容量capacity相等时,vector内部会触发扩容机制:1、开辟新空间;2、拷贝元素;3、释放旧空间。
注意:每次扩容新空间不能太大,也不能太小,太大容易造成空间浪费,太小则会导致频繁扩容而影响程序效率。 - 如何避免扩容导致效率低:提前预估元素个数并reserve。如果要避免扩容而导致程序效率过低问题,其实非常简单:**如果在插入之前,可以预估vector存储元素的个数,提前将底层容量开辟好即可。**如果插入之前进行reserve,只要空间给足,则插入时不会扩容,如果没有reserve,则会边插入边扩容,效率极其低下。
- 扩容机制回顾:当向vector中插入元素时,如果元素有效个数size与空间容量capacity相等时,vector内部会触发扩容机制:1、开辟新空间;2、拷贝元素;3、释放旧空间。
-
为什么选择以倍数方式扩容:
-
如果以等长个数进行扩容:等长个数方式扩容,新空间大小就是将原空间大小扩增到capacity+K个空间(capacity为旧空间大小)。假设需要向vector中插入100个元素,K为10,那么就需要扩容10次;每次扩容都需要将旧空间元素搬移到新空间,第i次扩容拷贝的元素数量为:ki(第1次扩容,新空间大小为20,旧空间中有10个元素,需要搬移到新空间中;第2次扩容,新空间大小为30,旧空间中有20个元素,需要全部搬移到新空间中),假设元素插入与元素搬移为1个单位操作,则n个元素push_back()的总操作次数为:
-
如果以倍数方式进行扩容,假设有n个元素需要像vector插入,倍增因子为m,则完成n个元素像vector的push_back操作需要扩容log以m为低n的次方。比如:以二倍方式扩容,当向vector插入1000个元素,需要扩容log以2为底1000次方,就是扩容10次,第i次增容会把m的i次方个元素搬移到新空间,n次push_back的总操作次数为:
-
-
为什么选择1.5倍或者2倍方式扩容,而不是3倍、4倍?扩容原理为:申请新空间,拷贝元素,释放旧空间,**理想的分配方案是在第N次扩容时如果能复用之前N-1次释放的空间就太好了,**如果按照2倍方式扩容,第i次扩容空间大小如下:1 2 4 8 … 2^i。可以看到,每次扩容时,前面释放的空间都不能使用。比如:第4次扩容时,前2次空间已经释放,第3次空间还没有释放(开辟新空间、拷贝元素、释放旧空间),即前面释放的空间只有1 + 2 = 3,假设第3次空间已经释放才只有1+2+4=7,而第四次需要8个空间,因此无法使用之前已释放的空间,但是按照小于2倍方式扩容,多次扩容之后就可以复用之前释放的空间了。如果倍数超过2倍(包含2倍)方式扩容会存在:
- 空间浪费可能会比较高,比如:扩容后申请了64个空间,但只存了33个元素,有接近一半的空间没有使用。
- 无法使用到前面已释放的内存
使用2倍(k=2)扩容机制扩容时,每次扩容后的新内存大小必定大于前面的总和。
而使用1.5倍(k=1.5)扩容时,在几次扩展以后,可以重用之前的内存空间了。 -
Windows和Linux的扩容底层原理
- Windows中堆管理系统会对释放的堆块进行合并,因此:vs下的vector扩容机制选择使用1.5倍的方式扩容,这样多次扩容之后,就可以使用之前已经释放的空间。
- linux下主要使用glibc的ptmalloc来进行用户空间申请的.如果malloc的空间小于128KB,其内部通过brk()来扩张,如果大于128KB且arena中没有足够的空间时,通过mmap将内存映射到进程地址空间.
- linux中引入伙伴系统为内核提供了一种用于分配一下连续的页而建立的一种高效的分配策略,对固定分区和动态分区方式的折中,固定分区存在内部碎片,动态分区存在外部碎片,而且动态分区回收时的合并以及分配时的切片是比较耗时的.
- 伙伴系统是将整个内存区域构建成基本大小basicSize的1倍、2倍、4倍、8倍、16倍等,即要求内存空间分区链均对应2的整次幂倍大小的空间,整齐划一,有规律的而不是乱糟糟的。
- 在分配和释放空间时,可以通过log2(request/basicSize)向上取整的哈希算法快速找到对应内存块。
通过伙伴系统管理空闲分区的了解,可以看到在伙伴系统中的每条空闲分区链中挂的都是2i的页面大小,通过哈希思想进行空间分配与合并,非常高效。估计可能是这个原因SGI-STL选择以2倍方式进行扩容
4.1.3 emplace_back和push_back???
4.2 list
4.2.1 节点
template <class T>
struct __list_node {
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
};
4.2.2 迭代器
list的迭代器是Bidirectional Iterator
template<class T,class Ref,class Ptr>
struct __list_iterator{
typedef __list_iterator<T,T &,T*> iterator;
typedef __list_iterator<T,Ref,Ptr> self;
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef __list_node<T>* link_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
link_type node; //一个普通的指针,指向list的节点
//构造函数
__list_iterator(link_type x):node(x){}
__list_iterator(){}
__list_iterator()(const iterator& x):node(x.node){}
//一些迭代器操作
//重载等号和不等号
bool operator==(const self&x) const
{
return node==x.node;
}
bool operator!=(const self&x) const
{
return node!=x.node;
}
//对迭代器取值返回节点存储的数据值
reference operator*()const{return (*node).data;}
//重载->,返回指针
pointer operator->()const {return &(operator*());}
//重载递增递减
self& operator++()
{
node=(link_type)((*node).next);
return *this;
}
self operator++(int) //后+,返回原值的拷贝,然后原值++
{
self tmp=*this;
++(*this);
return tmp;
}
self& operator--()
{
node=(link_type)((*node).prev);
return *this;
}
self operator--(int) //后-,返回原值的拷贝,然后原值--
{
self tmp=*this;
--(*this);
return tmp;
}
}
4.2.3 数据结构
template<class T,class Alloc=alloc>
class list{
protected:
typedef __list_node<T> list_node;
public:
typedef list_node* link_type
protected:
link_type node;
};
list是一个环状双向链表,只需一个指向尾端的空白节点则可表示整个链表。
4.3.3 元素操作
- 顾名思义的操作:push_front, push_back, erase, pop_front, pop_back, clear, merge, reverse
- remove(const T& value):移除所有值为value的节点
- unique():将所有”连续而相同的元素“移除(只留下一个)
- splice:将所指范围接合到当前list
- sort(难点):由于STL的sort()只接受RandomAccessIterator,所以list需要单独实现一个member function sort
https://blog.csdn.net/A315776/article/details/120607364
4.3 deque(没写)
4.4 stack
stack并不是新的容器,而是一个adapter,stack没有迭代器,其主要提供三个对外接口:pop、top、push
可以在构造时指定底层容器,默认的是deque,也可以是list和vector
4.6 queue
queue和stack一样是一个adapter,而且也没有迭代器
对外接口:push、pop、front、back
可以在构造时指定底层容器,默认的是deque,也可以是list,但不能是vector(因为vector没有pop_front)
4.7 heap(隐式的,很少直接用,通常用于priority_queue)
底层为vector、array或者deque,将其作为二叉树。然后提供建堆(make_heap)、排序(sort_heap)、入堆(push_heap)、出堆(pop_heap)
4.8 priority_queue
priority_queue也是adapter,主要是通过4.7的heap完成功能。底层容器只能是vector或deque,提供:
- top
- push(借助push_heap完成)
- pop(pop_heap)
4.9 slist(cpp11为forward_list)
4.9.1 数据结构
template <class T, class Alloc = alloc>
class slist
{
public:
typedef __slist_iterator<T, T&, T*> iterator;
...
private:
typedef __slist_node<T> list_node;
typedef __slist_node_base list_node_base;
...
private:
list_node_base head;
...
};
slist中只有一个成员head,它是单向链表的链表头,其定义如下
struct __slist_node_base
{
__slist_node_base* next;
};
仅仅包含一个指针
下面看看slist中的链表节点的定义
template <class T>
struct __slist_node : public __slist_node_base
{
T data;
};
__slist_node继承于__slist_node_base,因此每个__slist_node中都有两个变量,一个维护链表的指针,一个数据
__slist_node_base* next;
T data;
所以__slist_node可以设计成下面的模样
template <class T>
struct __slist_node
{
__slist_node_base* next;
T data;
};
但为什么需要使用继承呢?
那是因为可以使用基类指针指向派生类,编译器不会产生警告
slist内部数据结构如下
4.9.2 迭代器
slist的迭代器也是采用继承的方式实现的,如下__slist_iterator
继承于__slist_iterator_base
struct __slist_iterator : public __slist_iterator_base
我们先看__slist_iterator_base的定义
struct __slist_iterator_base
{
/* STL迭代器的设计规范 */
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef forward_iterator_tag iterator_category;
__slist_node_base* node;
void incr() { node = node->next; }
...
};
__slist_iterator_base
的定义较为简单,它包含成员__slist_node_base* node
,用于指向链表中的某个节点
然后还有自增操作incr
,就是将当前的node指向下一个node
下面再来看看__slist_iterator
的定义
template <class T, class Ref, class Ptr>
struct __slist_iterator : public __slist_iterator_base
{
/* STL迭代器的设计规范 */
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
reference operator*() const { return ((list_node*) node)->data; }
self& operator++()
{
incr();
return *this;
}
};
__slist_iterator继承于
__slist_iterator_base
它重载自增运算符,调用基类的incr函数,然后返回
它还重载的取值操作operator*
,它先将node
强制转换为派生类,然后再取出其中的data
5 关联式容器
这一章的容器大多是以红黑树为底层的,除了hash开头的容器
5.0 红黑树
5.1 set和multiset
set的特点:
- 在set中key和value是相同的
- 不能通过set的迭代器去修改内容,所以set的iterator是一种const iterator
- set不允许两个元素有相同的键值。
multiset的特点:只有一点与set不同,即multiset允许插入键值相同的元素
最重要的一点是set insert时调用的是红黑树的insert_unique,而multiset insert时调用的是红黑树的insert_equal
set的定义。可见其只有一个数据成员,就是_M_t(红黑树),且注意到其所有迭代器都加上了const。
且在构造时可以传入比较方法,默认是std::less<_Key>。
template<typename _Key, typename _Compare = std::less<_Key>,
typename _Alloc = std::allocator<_Key> >
class set
{
public:
// typedefs:
//@{
/// Public typedefs.
typedef _Key key_type;
typedef _Key value_type;
typedef _Compare key_compare;
typedef _Compare value_compare;
typedef _Alloc allocator_type;
//@}
private:
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
rebind<_Key>::other _Key_alloc_type;
typedef _Rb_tree<key_type, value_type, _Identity<value_type>,
key_compare, _Key_alloc_type> _Rep_type;
_Rep_type _M_t; // Red-black tree representing set.
typedef __gnu_cxx::__alloc_traits<_Key_alloc_type> _Alloc_traits;
public:
//@{
/// Iterator-related typedefs.
typedef typename _Alloc_traits::pointer pointer;
typedef typename _Alloc_traits::const_pointer const_pointer;
typedef typename _Alloc_traits::reference reference;
typedef typename _Alloc_traits::const_reference const_reference;
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 103. set::iterator is required to be modifiable,
// but this allows modification of keys.
typedef typename _Rep_type::const_iterator iterator;
typedef typename _Rep_type::const_iterator const_iterator;
typedef typename _Rep_type::const_reverse_iterator reverse_iterator;
typedef typename _Rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename _Rep_type::size_type size_type;
typedef typename _Rep_type::difference_type difference_type;
//@}
// allocation/deallocation
/**
* @brief Default constructor creates no elements.
*/
set()
: _M_t() { }
。。。
}
5.2 map 和 multimap
pair的定义
template<class T1, class T2>
struct pair{
typedef T1 fisrt_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {}
pair(const T1& a, const T2& b) : first(a), second(b) {}
};
map的定义
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc> //默认比较函数是less<Key>,内存分配器是alloc
class map {
public:
//类型申明
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
//注意此处const key,所以说map的key键不能更改
typedef pair<const Key, T> value_type;
typedef Compare key_compare;
class value_compare //比较函数的类
: public binary_function<value_type, value_type, bool> {
friend class map<Key, T, Compare, Alloc>;
protected :
Compare comp; // 就是传入的模板参数
value_compare(Compare c) : comp(c) {}
public:
bool operator()(const value_type& x, const value_type& y) const {
return comp(x.first, y.first); //注意此处,作比较时只比较key
}
};
private:
typedef rb_tree<key_type, value_type,
select1st<value_type>, key_compare, Alloc> rep_type; //树的参数与map参数一致
rep_type t; // red-black tree representing map t就是一颗红黑树
public:
//下面全是调用t的接口
typedef typename rep_type::pointer pointer;
typedef typename rep_type::const_pointer const_pointer;
typedef typename rep_type::reference reference;
typedef typename rep_type::const_reference const_reference;
typedef typename rep_type::iterator iterator;
typedef typename rep_type::const_iterator const_iterator;
typedef typename rep_type::reverse_iterator reverse_iterator;
typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
typedef typename rep_type::size_type size_type;
typedef typename rep_type::difference_type difference_type;
map中的大多数操作和set一样都是要调用红黑树,需要特别关注的是,map比set多了一个operator[]的重载
T& operator[](const key_type& k) {
return (*((insert(value_type(k, T()))).first)).second;
//在红黑树insert中会先判断key值节点是否存在
//如果存在,则返回key值对应的data值
//不存在,那么会新创一个节点,初始key键为k,返回对应的data值
//注意返回T引用,所以可以修改节点上的data值
}
最重要的一点是map insert时调用的是红黑树的insert_unique,而multimap insert时调用的是红黑树的insert_equal
注意,接下来的hashXXX对应cpp11 中的 unordered_XXX
5.3 hashtable
哈希表的重要问题:
- hash function的实现。
- 负载系数的选择。
- 碰撞解决或冲突解决。
- 线性探测。缺点:容易在某个位置形成一块(形成主集团),导致后面的插入需要很多探测操作。
- 二次探测。可以消除主集团,但是会形成次集团。可以采用多次hash的方法来解决。
- 开链法(STL采用的方法)。
5.3.1 迭代器
template <class Value, class Key, class HashFun, class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
// typedefs:
typedef hashtable<Value, Key, HashFun, ExtractKey,EqualKey, Alloc> hashtable;
typedef __hashtable_iterator<Value, Key, HashFun, ExtractKey,EqualKey, Alloc> iterator;
typedef __hashtable_const_or<Value, Key, HashFun, ExtractKey,EqualKey, Alloc> const_iterator;
typedef __hash_node<Value> node;
typedef forward_iterator_tag iterator_category; // 迭代器类别
typedef Value value_type;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
typedef T& reference_type;
typedef T* pointer;
node* cur;
hashtable* ht; // 保持对容器的连接,因为有可能从一个链跳到另一个链(一个bucket跳到另一个bucket)
//下面是operator重载,主要看++
iterator& operator++() {
const node* old = cur;
cur = cur->next;
if(!cur){//如果当前节点已经是链的最后一个节点,则需要跳到另一个bucket
size_type bucket = ht->bucket_num(old->val);
while(!cur && ++bucket < ht->bucket.size()) {
cur = ht->buckets[bucket];
}
}
return *this;
}
}
5.3.2 数据结构
hash table节点的定义
template<class Value>
struct __hash_node {
__hash_node* next;
Value val;
};
hashtable的定义
template <class Value,class Key,class HashFcn,class ExtractKey,class EqualKey,class Alloc=alloc>
class hashtable{
public:
typedef HashFcn hasher;
typedef EqualKey key_equal;
typedef size_t size_type;
private:
hasher hash;
key_equal equals;
ExtratKey get_key;
typedef _hashtable_node<Value>node;
vector<node*,Alloc>buckets;
size_type num_elements;
...
}
hashtable主要的几个操作:
- resize。若当前元素个数大于bucket个数,就resize,大小为下一个质数。然后将原来的节点一个个rehash到新的空间,最后将旧的的空间释放。
- insert_unique:会先调用resize再调用insert_unique_noresize
- insert_equal:会先调用resize再调用insert_equal_noresize
- clear:只对每个hash_node释放,其中存放bucket的vector并没有释放
- copy_from:先清除己方的buckets,再调整大小,最后一个个复制节点
- bkt_num:用于判断元素的落脚处。先调用hash function来对元素生成一个数,然后将这个数堆hashtable的大小进行模运算(因为有些元素不能直接对表大小进行取模运算)
5.3.3 hash function
hash function的重要任务是找到元素的落脚点,也就是计算后应该放到哪里。
我们都知道计算过程与是一个取模过程,但是也会有一些不同。比如遇到字符和字符串这种类型就不能进行单纯的计算了。在STL包装了一个函数bkt_num(),在此函数中调用hash function。
size_type bkt_num_key(const key_type& key,size_t n)const
{
return hash(key)%n;
}
这里的hash运用了偏特化的设计方法(在traits见过的设计方法)
根据传入的值设定不同的做法。(大部分不需要改动,直接返回原值再取模即可)
//泛化版本
template <class Key> struct hash{};
//特化版本
_STL_TEMPLATE_NULL struct hash<char>{
size_t operator()(char x)const {return x;}
}
_STL_TEMPLATE_NULL struct hash<short>{
size_t operator()(short x)const {return x;}
}
_STL_TEMPLATE_NULL struct hash<unsigned short>{
size_t operator()(unsigned short x)const {return x;}
}
_STL_TEMPLATE_NULL struct hash<int>{
size_t operator()(int x)const {return x;}
}
...
上面这些都是不需要改动的,直接传回原值。
下面来看需要改动的做法(对于字符字符串):
_STL_TEMPLATE_NULL struct hash<char*>{
size_t operator()(const char* s)const {return _stl_hash_string(s);}
}
_STL_TEMPLATE_NULL struct hash<const char*>{
size_t operator()(const char* s)const {return _stl_hash_string(s);}
}
再跳转到_stl_hash_string(s)
inline size_t _stl_hash_string(const char* s){
unsigned long h=0;
for(;*s;++s){
h=5*h+*s;
return size_t(h);
}
}
看不懂这个做法很正常,因为这本身就是很随机的一段代码。
举个例子,我们现在有字符串abc,那么这个结果就会算出5*(5*a+b)+c,这里的abc是acsii码。
因为hash function就是要求出一个元素的编码以找到合适的位置,所以这个做法就是计算出一个足够乱且不容易重复的值。
如果要将上面类型之外的元素作为键值,需要自己为这个类别写一个hash function
比如想要一个Customer作为Key,则在构造hash table时为其配置hash function有两种形式:函数对象和函数指针。
左上角这种实现方法太naive,这里标准库提供了一个hash_val
当需要对自定义类型作hash时,只需要传入所有的成员变量(这些成员变量必须要有对应类型的std::hash)
假设Customer有三个成员变量:fname、Iname、no
- hash_val(c.fname, c.Iname, c.no)调用第1个版本的hash_val.(其中typename… types)是可变模板参数,其中args…就是传入的三个参数(c.fname, c.Iname, c.no)
- 第1个版本的hash_val初始化seed,然后调用第2个版本的hash_val
- 第2个版本的hash_val的val和args…参数起到对args…进行拆分的作用。(第2个版本的形参)val用于接收第1个版本的hash_val中传入的arg…的第一个参数,(第2个版本的形参)args…用于接收第1个版本的hash_val中传入的arg…的除第一个之外的剩下参数。第2个版本的hash_val主要作两件事:1、用val来改变seed(hash_combine),2、递归调用自己。注意到第2个版本的hash_val的seed是引用传递,即seed的变化会影响到第一个版本的seed。
- 第3个版本的hash_val作用未知。
- hash_combine就是生成hash值的操作了。
5.4 hash_set(unordered_set)和hash_multiset(unordered_multiset)
- hash_set(unordered_set)与set操作基本一样
- hash_multiset(unordered_multiset)与multiset操作基本一样
- hash_set(unordered_set)的insert使用的是insert_unique,而hash_multiset(unordered_multiset)的insert使用的是insert_equal
5.5 hash_map(unordered_map)和hash_multimap(unordered_multimap)
- hash_map(unordered_map)与map操作基本一样
- hash_multimap(unordered_multimap)与multimap操作基本一样
- hash_map(unordered_map)的insert使用的是insert_unique,而hash_multimap(unordered_multimap)的insert使用的是insert_equal
6 算法(algorithms)
6.1 概述
-
STL的算法类别:
-
质变算法mutating algorithms–会改变操作对象的值。例如:拷贝(copy)、互换(swap)、替换(replace)、填写(fill)、删除(remove)、排列组合(permutation)、分割(partition)、随机重排(random shuffle)、排序(sort)等等。
-
非质变算法nonmutating algorithms–不会改变操作对象的值。例如:查找(find)、匹配(search)、计数(count)、巡访(for_each)、比较(equal、mismatch)、寻找极值(min、max)等等。
-
-
STL算法的一般形式:
- 所有泛型算法的前两个参数一般都是一对迭代器,通常称为first和last,前闭后开,用以标示算法的操作区间。
- 许多STL算法不止支持一个版本。这一类算法的某个版本采用缺省运算行为,另一个版本提供额外参数,接收外界传入一个仿函数,以便采用其他策略。后面的这个版本通常以_if结尾。
6.2 算法的泛化过程
关键在于,把操作对象的型别抽象化,把操作对象的标示法和区间目标的移动行为抽象化,整个算法也就在一个抽象的层面上工作了。整个过程叫做算法的泛化。
6.3 数值算法
这一类算法都必须包含头文件
6.3.1 accumulate累加
template<class InputIterator, class T>
T accumulate(InputIterator first, InputIterator second, T init) {
for (; first != last; ++first) {
init += *first;
}
return init;
}
template<class InputIterator, class T, class BinaryOperation>
T accumulate(InputIterator first, InputIterator second, T init, BinaryOperation binary_op) {
for (; first != last; ++first) {
init = binary_op(init, *first);
}
return init;
}
例子:
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
template<class T>
struct Minus {
int operator()(int a, int b) {
return a - b;
}
};
int main() {
vector<int> vec{1,2,3,4,5};
// 15, i.e 0 + 1 + 2 + 3 + 4 + 5
cout << accumulate(vec.begin(), vec.end(), 0) << endl;
// -15, i.e 0 - 1 - 2 - 3 - 4 - 5
cout << accumulate(vec.begin(), vec.end(), 0, Minus<int>()) << endl;
// -15, i.e 0 - 1 - 2 - 3 - 4 - 5
cout << accumulate(vec.begin(), vec.end(), 0, [](int a, int b) {return a -b;}) << endl;
}
6.3.2 adjacent_difference
相邻差(后一个减前一个)
// 版本1
template<class InputIterator, class OutputIterator>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result) {
if (first == last) return result;
*result = * first;
return __adjacent_difference(first, last, result, value_type(first));
}
template<class InputIterator, class OutputIterator, class T>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result, T*) {
T value = *first;
while (++first != last) {
T tmp = *first;
*++result = tmp - value;
value = tmp;
}
return ++result;
}
// 版本2
template<class InputIterator, class OutputIterator, class BinaryOperation>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result, BinaryOperation binary_op) {
if (first == last) return result;
*result = * first;
return __adjacent_difference(first, last, result, value_type(first), binary_op);
}
template<class InputIterator, class OutputIterator, class T, class BinaryOperation>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result, T*, BinaryOperation binary_op) {
T value = *first;
while (++first != last) {
T tmp = *first;
*++result = binary_op(tmp - value);
value = tmp;
}
return ++result;
}
例子:
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
template<class T>
void print(const vector<T>& vec) {
for (const T& t : vec) {
cout << t << " ";
}
cout << endl;
}
int main() {
vector<int> vec{1,2,3,4,5};
vector<int> diff(4, 0);
adjacent_difference(vec.begin(), vec.end(), diff.begin());
print(diff); // 1 1 1 1
adjacent_difference(vec.begin(), vec.end(), diff.begin(), [](int a, int b){return a + b;});
print(diff); // 1 3 5 7
}
6.3.4 inner_product(内积)
// 版本1
template<class InputIterator1, class InputIterator2, class T>
T inner_product(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, T init) {
for ( ; first1 != last1; ++first1, ++first2) {
init += (*first1 * *first2);
}
return init;
}
// 版本2
template<class InputIterator1, class InputIterator2, class T, class BinaryOperation1, class BinaryOperation2>
T inner_product(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, T init, BinaryOperation1 binary_op1, BinaryOperation2 binary_op2) {
for ( ; first1 != last1; ++first1, ++first2) {
init = binary_op1(init, binary_op2(*first1, *first2));
}
return init;
}
例子:
#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
int main() {
vector<int> vec1{1,2,3,4,5};
vector<int> vec2{5,4,3,2,1,0,-1};
cout << inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0) << endl;
// 35, i.e. 1*5 + 2*4 + 3*3 +4*2 + 5*1
}
6.3.5 partial_sum(部分和)
前n个元素的总和
// 版本1
template<class InputIterator, class OutputIterator>
OutputIterator partial_sum(InputIterator first, InputIterator last, OutputIterator result) {
if (first == last) return result;
*result = *first;
return __partial_sum(first, last, result, value_type(first));
}
template<class InputIterator, class OutputIterator, class T>
OutputIterator __partial_sum(InputIterator first, InputIterator last, OutputIterator result, T*) {
T value = *first;
while (++first != value) {
value = value + *first;
*++result = value;
}
return ++result;
}
// 版本2
template<class InputIterator, class OutputIterator, class BinaryOperation>
OutputIterator partial_sum(InputIterator first, InputIterator last, OutputIterator result, BinaryOperation binary_op) {
if (first == last) return result;
*result = *first;
return __partial_sum(first, last, result, value_type(first), binary_op);
}
template<class InputIterator, class OutputIterator, class T, class BinaryOperation>
OutputIterator __partial_sum(InputIterator first, InputIterator last, OutputIterator result, T*, BinaryOperation binary_op) {
T value = *first;
while (++first != value) {
value = binary_op(value, *first);
*++result = value;
}
return ++result;
}
例子:
#include <iostream>
#include <vector>
#include <numeric>
#include <iterator>
using namespace std;
template<class T>
void print(const vector<T>& vec) {
for (const T& t : vec) {
cout << t << " ";
}
cout << endl;
}
int main() {
vector<int> vec1{1,2,3,4,5};
vector<int> vec2;
partial_sum(vec1.begin(), vec1.end(), back_inserter(vec2));
print(vec2);
// 1 3 6 10 15
}
6.4 基本算法
6.4.2 equal,fill,fill_n,iter_swap,max,min,mismatch,swap
6.4.2.1 equal
如果两个序列在[first, last)区间内相等,equal()返回true。如果第二序列的元素比较多,多出的元素不予考虑。
// 版本1
template<class InputIterator1, class InputIterator2, class InputIterator2 first2>
inline bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2) {
for ( ; first1 != last1; ++first1, ++first2) {
if (*first1 != *first2)
return false;
}
return true;
}
// 版本2
template<class InputIterator1, class InputIterator2, class InputIterator2 first2, class BinaryPredicate>
inline bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, BinaryPredicate binary_pred) {
for ( ; first1 != last1; ++first1, ++first2) {
if (!binary_pred(*first1, *first2))
return false;
}
return true;
}
6.4.2.2 fill
template<class ForwardIterator, class T>
void fill(ForwardIterator first, ForwardIterator last, const T& value) {
for (; first != last; ++first) {
*first = value;
}
}
6.4.2.3 fill_n
template<class OutputIterator, class T>
OutputIterator fill(ForwardIterator first, Size n, const T& value) {
for (; n > 0; --n, ++first) {
*first = value;
}
return first;
}
6.4.2.4 iter_swap
template<class ForwardIterator1, class ForwardIterator2>
inline void iter_swap(ForwardIterator1 a, ForwardIterator2 b) {
typename iterator_traits<ForwardIterator1>::value_type tmp = *a;
*a = *b;
*b = tmp;
}
6.4.2.5 max
template<class T>
inline const T& max(const T& a, const T& b) {
return a < b ? b : a;
}
template<class T, class Compare>
inline const T& max(const T& a, const T& b, Compare comp) {
return comp(a, b) ? b : a;
}
6.4.2.6 min
template<class T>
inline const T& min(const T& a, const T& b) {
return a < b ? b : a;
}
template<class T, class Compare>
inline const T& min(const T& a, const T& b, Compare comp) {
return comp(a, b) ? b : a;
}
6.4.2.7 mismatch
用来平行比较两个序列,指出两者之间的第一个不匹配点,返回一对迭代器,分别指出两个序列中的不匹配点。
第一个序列的元素必须多于或等于第二个序列的。
// 版本1
template<class InputIterator1, class InputIterator2>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2) {
while (first1 != last1 && *first1 == *first2) {
++first1;
++first2;
}
return pair<InputIterator1, InputIterator2>(first1, first2);
}
// 版本2
template<class InputIterator1, class InputIterator2, class BinaryPredicate>
pair<InputIterator1, InputIterator2> mismatch(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, BinaryPredicate binary_pred) {
while (first1 != last1 && binary_pred(*first1, *first2)) {
++first1;
++first2;
}
return pair<InputIterator1, InputIterator2>(first1, first2);
}
6.4.3 copy——强化效率无所不用其极
copy()是一个常常被调用的函数,由于copy进行的是复制操作,而复制操作做不外乎用assignment operator或copy constructor(copy算法用的是前者),但是某些元素型别拥有的是trivial assignment operator,因此,如果能够使用内存直接复制行为(例如memmove或memcpy),便能节省大量时间。
作用:
copy函数是将输入区间[first,last)内的元素复制到输出区间[result,result+(last-first))。
定义:
copy函数定义在stl_algobase.h里面,一个泛化和两个特化。
//泛化
template <class InputIterator, class OutputIterator>
inline OutputIterator copy(InputIterator first, InputIterator last,
OutputIterator result)
{
return __copy_dispatch<InputIterator,OutputIterator>()(first, last, result);
}
//特化
inline char* copy(const char* first, const char* last, char* result) {
memmove(result, first, last - first);
return result + (last - first);
}
//特化
inline wchar_t* copy(const wchar_t* first, const wchar_t* last,
wchar_t* result) {
memmove(result, first, sizeof(wchar_t) * (last - first));
return result + (last - first);
}
特化就直接调用C的标准函数memmove,如果是泛化,运用了运算符重载,__copy_dispatch是一个结构体,并且它重载了operator()运算符。
内部实现:
__copy_dispatch的定义:
template <class InputIterator, class OutputIterator>
struct __copy_dispatch
{
OutputIterator operator()(InputIterator first, InputIterator last,
OutputIterator result) {
return __copy(first, last, result, iterator_category(first));
}
};
template <class T>
struct __copy_dispatch<T*, T*>
{
T* operator()(T* first, T* last, T* result) {
typedef typename __type_traits<T>::has_trivial_assignment_operator t;
return __copy_t(first, last, result, t());
}
};
template <class T>
struct __copy_dispatch<const T*, T*>
{
T* operator()(const T* first, const T* last, T* result) {
typedef typename __type_traits<T>::has_trivial_assignment_operator t;
return __copy_t(first, last, result, t());
}
};
__copy_dispatch也有两个特化和一个泛化定义,特化都是调用__copy_t函数,且最后一个参数都用了__type_traits来判断型别,__type_traits::has_trivial_assignment_operator就是__true_type或__false_type的别名,而_true_type和__false_type都是结构体,所以等于传一个结构体过去t(),而__copy_t根据函数重载决定调用哪一个,__copy_t看下面。
__copy的定义:
template <class InputIterator, class OutputIterator>
inline OutputIterator __copy(InputIterator first, InputIterator last,
OutputIterator result, input_iterator_tag)
{
for ( ; first != last; ++result, ++first)
*result = *first;
return result;
}
template <class RandomAccessIterator, class OutputIterator>
inline OutputIterator
__copy(RandomAccessIterator first, RandomAccessIterator last,
OutputIterator result, random_access_iterator_tag)
{
return __copy_d(first, last, result, distance_type(first));
}
__copy_t的定义:
template <class T>
inline T* __copy_t(const T* first, const T* last, T* result, __true_type) {
memmove(result, first, sizeof(T) * (last - first));
return result + (last - first);
}
template <class T>
inline T* __copy_t(const T* first, const T* last, T* result, __false_type) {
return __copy_d(first, last, result, (ptrdiff_t*) 0);
}
__copy_d的定义:
template <class RandomAccessIterator, class OutputIterator, class Distance>
inline OutputIterator
__copy_d(RandomAccessIterator first, RandomAccessIterator last,
OutputIterator result, Distance*)
{
for (Distance n = last - first; n > 0; --n, ++result, ++first)
*result = *first;
return result;
}
6.5 set相关算法
7 仿函数(functor或function object)
概念:仿函数其实就是函数对象,一个行为类似函数的对象,需要重载operator()
7.1 可配接的关键
为了拥有配接能力,每一个仿函数必须定义自己的相应型别,就像迭代器如果要融入整个STL大家庭,也必须依照规定定义自己的5个相应型别一样,这些型别是为了让配接器能够取出,获得仿函数的某些信息,相应型别都只是一些typedef,所有必要操作在编译期就全部完成,对程序的执行效率没有任何影响,不带来任何额外负担。
仿函数的相应型别主要用来表现函数参数型别和传回值型别。
STL定义了两个class分别代表一元仿函数和二元仿函数。
template<class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
template<class Arg1, class Arg2, class Result>
struct unary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
继承了以上两个class就能获取对应型别
7.2 算术类仿函数
- 加法:plus
- 减法:minus
- 乘法:multiplies
- 除法:divides
- 取模:moduls
- 取反:negate
//算术类仿函数 + - * / %
//plus仿函数,生成一个对象,里面仅仅有一个函数重载的方法。
template <class T>
struct plus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x + y; }
};
//minus仿函数
template <class T>
struct minus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x - y; }
};
template <class T>
struct multiplies : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x * y; }
};
template <class T>
struct divides : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x / y; }
};
template <class T>
struct modulus : public binary_function<T, T, T> {
T operator()(const T& x, const T& y) const { return x % y; }
};
//取负值
template <class T>
struct negate : public unary_function<T, T> {
T operator()(const T& x) const { return -x; }
};
7.3 关系运算符仿函数
- 等于:equal_to
- 不等于:not_equal_to
- 大于:greater
- 大于或等于:greater_equal
- 小于:less
- 小于或等于:less_equal
//关系运算符仿函数
// x==y 仿函数
template <class T>
struct equal_to : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x == y; }
};
// x!=y 仿函数
template <class T>
struct not_equal_to : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x != y; }
};
// x>y 仿函数
template <class T>
struct greater : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x > y; }
};
// x<y 仿函数
template <class T>
struct less : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x < y; }
};
// x>=y 仿函数
template <class T>
struct greater_equal : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x >= y; }
};
// x<=y 仿函数
template <class T>
struct less_equal : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x <= y; }
};
7.4 逻辑运算符仿函数
- And: logical_and
- Or: logical_or
- Not: logical_not
template <class T>
struct logical_and : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x && y; }
};
template <class T>
struct logical_or : public binary_function<T, T, bool> {
bool operator()(const T& x, const T& y) const { return x || y; }
};
template <class T>
struct logical_not : public unary_function<T, bool> {
bool operator()(const T& x) const { return !x; }
};
8 配接器(adapters)
8.1 container adapters
queue和stack
8.2 iterator adapters
8.2.1 Insert Iterators
所谓insert iterators,可以将一般迭代器的赋值操作转换为插入操作,这样的迭代器有3种:
辅助函数 | 实际产生的对象 |
---|---|
back_inserter(Container& x); | back_insert_iterator(x); |
front_inserter(Container& x); | front_insert_iterator(x); |
insert_inserter(Container& x, Iterator i); | insert_iterator(x, Container::iterator(i)); |
template<class Container>
class back_insert_iterator {
protected:
Container* container; // 底层容器
public:
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
//下面这个ctor使back_insert_iterator与容器绑定起来
explicit back_insert_iterator(Container& x) : container(&x) {}
back_insert_iterator<Container>& operator=(const typename Container::value_type& value) {
container->push_back(value);
return *this;
}
//下面这三个操作符对back_insert_iterator不起作用
back_insert_iterator<Container>& operator*() { return *this; }
back_insert_iterator<Container>& operator++() { return *this; }
back_insert_iterator<Container>& operator++(int) { return *this; }
};
template<class Container>
inline back_insert_iterator<Container> back_inserter(Container& x) {
return back_insert_iterator<Container>(x);
}
可见back_inserter函数返回的是一个back_insert_iterator,而back_insert_iterator通过绑定容器,每次调用赋值运算符就调用容器的push_back函数,所以back_inserter函数只能对有push_back成员函数的容器使用。
同理front_inserter和insert_inserter也一样,不再赘述。
8.2.2 Reverse Iterators
就是将迭代器的++和–操作调换,具体略
8.2.3 IOStream Iterators
略
8.4 function adapters
- 容器是以class template完成
- 算法是以function template完成
- 仿函数是一种将operator()重载的class template
- 迭代器则是一种将operator++和operator*等指针习惯行为重载的class template
而function adapters就是一个拥有若干个functor作为成员变量的functor,举一个例子,binary_pred
template<class Predicate>
class binary_negate : public binary_function<typename Predicate::first_argument_type, typename Predicate::second_argument_type, bool> {
protected:
Predicate pred;
public:
explicit binary_negate(const Predicate& x) : pred(x) {}
bool operator()(const typename Predicate::first_argument_type& x, const typename Predicate::second_argument_type& y) {
return !pred(x, y);
}
}