诞生原因
为了建立数据结构合算法的一套标准并且降低期间耦合关系以提升各自的独立性、弹性和互操作性,C++里诞生了STL。STL是由惠普实验室开发的一系列的软件的统称,在C++之前就已经存在了,但是在C++添加STL之后才广为人知的。
六大组件
- 容器:是各种数据结构,从实现角度来看,STL容器是一种类模板。
- 算法:从实现角度来看,STL算法是一种函数模板。
- 迭代器:扮演算法和容器之间的胶合剂,是所谓的泛型指针,迭代器是一种将operator*,operator->,operator++,operator–等指针相关操作予以重载的类模板。所有的STL容器都附带有自己的迭代器,因为只有容器设计者才知道如何进行遍历自己的元素。原生指针也可以看做是一种迭代器。
- 仿函数:从实现角度来看,仿函数是一种重载了operator()的类或者类模板。一般的函数指针可以看做狭义的仿函数
- 配接器:一种用来修饰容器、仿函数或者迭代器接口的东西。
- 配置器:负责空间的配置和管理,从实现角度来看,配置器是一个实现了动态空间配置、空间管理以及空间释放的类模板。
相互关系
容器通过配置器取得数据存储空间,算法通过迭代器存取容器中的数据,仿函数协助算法实现不同的策略,配接器可以修饰容器、仿函数或者迭代器。
空间配置器
考虑到小型区块可能造成的内存碎片问题,SGI设计了双层级配置器,第一级配置器直接使用malloc和free,第二级配置器则视情况采用不同的策略:当配置区块大于128byte时,将会使用第一级配置器,当配置区块小于128byte时,为了降低额外负担,便采用复杂的内存池整理方式,而不再求助于第一级配置器。
第一级配置器直接使用malloc和free管理内存,并且模拟了C++的set_new_handler()以处理内存不足的情况
第二级配置器维护了16个自由链表 ,负责16种小型区块的次配置能力。内存池以malloc()分配内存 ,如果内存不足,就转调用第一级配置器(模拟了set_new_handler)。
在16个自由链表的设计过程中,我们可以采取以下的方式。
union obj {
union obj * free_list_link;
char client_data[1]; /* The client sees this. */
};
这样做的好处是,在没有分配出去的时候存放指针信息,当分配给客户端的时候,直接存放数据,减少了空间浪费。
看到这里我们可以知道,所谓的空间配置器也就是一个类或者一些类,他们负责从系统中申请内存空间供容器使用。
迭代器
迭代器是一种抽象的设计概念,现实程序语言中并没有直接对应于这个概念的实物,设计模式中对迭代器的定义:提供一种方法,使之能够依序巡访某个容器所含的各个元素,而又无需暴露该容器的内部表达方式。
不论是泛型思想还是STL的实际运用,迭代器都扮演着重要的角色。STL的中心思想在于:将数据容器和算法分开,彼此独立设计,最后再以胶着剂将他们撮合在一起,容器和算法的泛型化,从技术的角度来看并不困难,C++的类模板和函数模板可以实现,但是如何设计好两者之间的胶着剂是一个大问题。
所谓的迭代器,其实就是一种行为类似指针的对象,即实现了operator*和operator->的类对象。每一种容器都有其对应的迭代器,毕竟只有实际设计该容器的人才知道如何进行遍历该容器。
模板的偏特化
常用迭代器种类
种类 | 特点 |
---|---|
输入迭代器 | 提供对数据的只读访问 |
输出迭代器 | 提供对数据的只写访问 |
前向迭代器 | 提供读写操作,并只能向前推进迭代器 |
双向迭代器 | 提供读写操作,并能向前和向后操作 |
随机访问迭代器 | 并能以跳跃的方式访问容器的任意数据,是功能最强的迭代器 |
//针对不同类型的容器对应的迭代器,调用算法的时候可以借此结构体实现重载,
//提高了运行时效率(相较于在程序中判断)
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 {};
//为了避免在开发过程中出现遗漏,最后继承自该类
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;
};
template <class Iterator>
struct iterator_traits {
typedef typename Iterator::iterator_category iterator_category;
typedef typename Iterator::value_type value_type;
typedef typename Iterator::difference_type difference_type;
typedef typename Iterator::pointer pointer;
typedef typename Iterator::reference reference;
};
// 针对原生指针进行的特化
template <class T>
struct iterator_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 iterator_traits<const T*> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
// 借助次函数可以实现在程序运行时获得某一迭代器的类型对象
template <class Iterator>
inline typename iterator_traits<Iterator>::iterator_category
iterator_category(const Iterator&) {
typedef typename iterator_traits<Iterator>::iterator_category category;
return category();
}
当我们对上面的内容进行发散之后就会产生下面的内容
// 为的是实现函数重载
struct __true_type {
};
struct __false_type {
};
template <class type>
struct __type_traits {
typedef __true_type this_dummy_member_must_be_first;
typedef __false_type has_trivial_default_constructor;
typedef __false_type has_trivial_copy_constructor;
typedef __false_type has_trivial_assignment_operator;
typedef __false_type has_trivial_destructor;
typedef __false_type is_POD_type;
};
容器
容器的分类
序列式容器:序列式容器强调值的排序,序列式容器中的每个元素都有固定的位置,除非用删除或者插入等操作改变这个位置。例如vector、deque、list容器都属于序列式容器。
关联式容器:关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。关联式容器的另一个特点是:在值中选择一个值作为关键字key,这个关键字对值起到索引的作用。
算法
定义:以有限的步骤,解决逻辑上或者是数学上的问题
算法的分类
质变算法:指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等。
非质变算法:指运算过程中不会更改区间内的元素内容,如果查找,计数,遍历,寻找极值。
仿函数(函数对象)
为了搭配STL算法,我们常常需要修改该算法的默认表现,常见的做法有将操作设计为一个函数,然后将函数指针作为算法的一个参数,或者是将该操作设计为一个所谓的仿函数,再以该仿函数产生一个对象,并以此对象作为算法的一个参数
既然函数指针可以大道将整租操作当做算法的参数,那又何必有所谓的仿函数呢?原因在于函数指针毕竟不能满足对抽象性的要求,也不能满足软件积木的要求–函数指针无法和STL其他组件搭配,产生更灵活的变化。
究其实现而言,仿函数就是一个行为类似函数的对象,为了能够行为类似函数,其类别定义的时候就必须自定义function call操作符
在STL六大组件中,仿函数可以说是体积最小、观念最简单、实现最容易的一个,他可以让STL算法更灵活,而更加灵活的关键在于STL仿函数具有可配接性。STL仿函数可以被函数配接器修饰,彼此就像积木一样地串接,为了拥有配接能力,每一个仿函数必须定义自己的相应类别,包括函数参数类型以及返回值类型(通常继承自unary_function和binary_function),所有必要操作在编译期完成,对于程序的执行效率没有任何影响,不带来任何额外负担。
适配器
定义
适配器可以将一个类的接口转换为另一个类的接口,使得原本因为接口不兼容而不能合作的类,可以一起运作。
分类
- 仿函数适配器
- 容器适配器
- 迭代器适配器
容器适配器
栈源码
template <class T, class Sequence>
class stack {
friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);
friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);
public:
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_type;
typedef typename Sequence::reference reference;
typedef typename Sequence::const_reference const_reference;
protected:
Sequence c;
public:
bool empty() const { return c.empty(); }
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(); }
};
队列源码
template <class T, class Sequence>
class queue {
friend bool operator== __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
friend bool operator< __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
public:
typedef typename Sequence::value_type value_type;
typedef typename Sequence::size_type size_type;
typedef typename Sequence::reference reference;
typedef typename Sequence::const_reference const_reference;
protected:
Sequence c;
public:
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
const_reference front() const { return c.front(); }
reference back() { return c.back(); }
const_reference back() const { return c.back(); }
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
迭代器适配器
尾插迭代器适配器
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;
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<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);
}
同理,头插迭代器适配器
template <class Container>
class front_insert_iterator {
protected:
Container* container;
public:
explicit front_insert_iterator(Container& x) : container(&x) {}
front_insert_iterator<Container>&
operator=(const typename Container::value_type& value) {
container->push_front(value);
return *this;
}
};
template <class Container>
inline front_insert_iterator<Container> front_inserter(Container& x) {
return front_insert_iterator<Container>(x);
}
随机插入迭代器适配器
template <class Container>
class insert_iterator {
protected:
Container* container;
typename Container::iterator iter;
public:
typedef output_iterator_tag iterator_category;
typedef void value_type;
typedef void difference_type;
typedef void pointer;
typedef void reference;
insert_iterator(Container& x, typename Container::iterator i)
: container(&x), iter(i) {}
insert_iterator<Container>&
operator=(const typename Container::value_type& value) {
iter = container->insert(iter, value);
++iter;
return *this;
}
insert_iterator<Container>& operator*() { return *this; }
insert_iterator<Container>& operator++() { return *this; }
insert_iterator<Container>& operator++(int) { return *this; }
};
template <class Container, class Iterator>
inline insert_iterator<Container> inserter(Container& x, Iterator i) {
typedef typename Container::iterator iter;
return insert_iterator<Container>(x, iter(i));
}
每个插入迭代器内部都维护了一个容器,容器当然有自己的迭代器,于是,当客户端对插入迭代器进行赋值操作的时候,就在插入迭代器中被转为对该容器做插入操作。其他迭代器惯常行为都被关闭。
逆向迭代器
template <class Iterator>
class reverse_iterator
{
protected:
Iterator current;
public:
typedef typename iterator_traits<Iterator>::iterator_category
iterator_category;
typedef typename iterator_traits<Iterator>::value_type
value_type;
typedef typename iterator_traits<Iterator>::difference_type
difference_type;
typedef typename iterator_traits<Iterator>::pointer
pointer;
typedef typename iterator_traits<Iterator>::reference
reference;
typedef Iterator iterator_type;
typedef reverse_iterator<Iterator> self;
public:
reverse_iterator() {}
explicit reverse_iterator(iterator_type x) : current(x) {}
reverse_iterator(const self& x) : current(x.current) {}
template <class Iter>
reverse_iterator(const reverse_iterator<Iter>& x) : current(x.current) {}
iterator_type base() const { return current; }
reference operator*() const {
Iterator tmp = current;
return *--tmp;
}
pointer operator->() const { return &(operator*()); }
self& operator++() {
--current;
return *this;
}
self operator++(int) {
self tmp = *this;
--current;
return tmp;
}
self& operator--() {
++current;
return *this;
}
self operator--(int) {
self tmp = *this;
++current;
return tmp;
}
self operator+(difference_type n) const {
return self(current - n);
}
self& operator+=(difference_type n) {
current -= n;
return *this;
}
self operator-(difference_type n) const {
return self(current + n);
}
self& operator-=(difference_type n) {
current += n;
return *this;
}
reference operator[](difference_type n) const { return *(*this + n); }
};
可以看出,对于同一个地址,对于前向迭代器合反向迭代器而言,是指代不同的元素的。
流迭代器
仿函数适配器
ptr_fun适配器可以让一般函数当做仿函数使用。一般函数当做仿函数传给STL算法,就语言层次上本来就是可以的,就好像原生指针可以被当做迭代器传给STL算法一样,但是如果直接使用函数指针那么将会丧失配接能力
template <class Arg, class Result>
class pointer_to_unary_function : public unary_function<Arg, Result> {
protected:
Result (*ptr)(Arg);
public:
pointer_to_unary_function() {}
explicit pointer_to_unary_function(Result (*x)(Arg)) : ptr(x) {}
Result operator()(Arg x) const { return ptr(x); }
};
template <class Arg, class Result>
inline pointer_to_unary_function<Arg, Result> ptr_fun(Result (*x)(Arg)) {
return pointer_to_unary_function<Arg, Result>(x);
}
template <class Arg1, class Arg2, class Result>
class pointer_to_binary_function : public binary_function<Arg1, Arg2, Result> {
protected:
Result (*ptr)(Arg1, Arg2);
public:
pointer_to_binary_function() {}
explicit pointer_to_binary_function(Result (*x)(Arg1, Arg2)) : ptr(x) {}
Result operator()(Arg1 x, Arg2 y) const { return ptr(x, y); }
};
template <class Arg1, class Arg2, class Result>
inline pointer_to_binary_function<Arg1, Arg2, Result>
ptr_fun(Result (*x)(Arg1, Arg2)) {
return pointer_to_binary_function<Arg1, Arg2, Result>(x);
}
mem_fun可以让成员函数当做仿函数使用,于是成员函数可以搭配各种泛型算法。这里需要注意的是,虽然多态可以对指针或者引用起作用,但是STL容器只支持实值语义,而不支持引用语义