STL的本质
通俗说:STL是Standard Template Library(标准模板库),是高效的C++程序库,其采用泛型编程思想对常见数据结构(顺序表,链表,栈和队列,堆,二叉树,哈希)和算法(查找、排序、集合、数值运算…)等进行封装,里面处处体现着泛型编程程序设计思想以及设计模式,已被集成到C++标准程序库中。具体说:STL中包含了容器、适配器、算法、迭代器、仿函数以及空间配置器。STL设计理念:追求代码高复用性以及运行速度的高效率,在实现时使用了许多技术,因此熟悉STL不仅对我们正常使用有很大帮助,而且对自己的知识也有一定的提高。
STL的六大组件
容器
STL中的容器,可以划分为两大类:序列式容器和关联式容器。
算法
算法:问题的求解步骤,以有限的步骤,解决数学或逻辑中的问题。STL中的算法主要分为两大类:与数据结构相关算法(容器中的成员函数)和通用算法(与数据结构不相干)。STL中通用算法总共有70多个,主要包含:排序,查找,排列组合,数据移动,拷贝,删除,比较组合,运算等。 以下只列出了部分常用的算法:
- accumulate
template <class InputIterator, class T> T accumulate ( InputIterator first, InputIterator last, T init );
template <class InputIterator, class T, class BinaryOperation> T accumulate ( InputIterator first, InputIterator last, T init,
BinaryOperation binary_op );
#include<iostream>
#include <numeric>
#include <vector>
using namespace std;
struct Mul2
{
int operator()(int x, int y) { return x + 2 * y; }
};
int main()
{
vector<int> v{ 10, 20, 30 };
cout << accumulate(v.begin(), v.end(), 0) << endl;
cout << accumulate(v.begin(), v.end(), 0, Mul2()) << endl;
return 0;
}
- count与count_if
该算法的作用是统计区间中某个元素出现的次数。
template <class InputIterator, class T>
ptrdiff_t count ( InputIterator first, InputIterator last, const T& value )
{
ptrdiff_t ret=0;
while (first != last) if (*first++ == value) ++ret;
return ret;
}
template <class InputIterator, class Predicate>
ptrdiff_t count_if ( InputIterator first, InputIterator last, Predicate pred )
{
ptrdiff_t ret=0;
while (first != last) if (pred(*first++)) ++ret;
return ret;
}
#include<iostream>
#include <algorithm>
#include <vector>
using namespace std;
bool IsOdd(int i)
{
return ((i % 2) == 1);
}
int main()
{
vector<int> v1{ 10, 20, 30, 30, 20, 10, 10, 20 };
cout << count(v1.begin(), v1.end(), 10) << endl;
vector<int> v2{ 0,1,2,3,4,5,6,7,8,9 };
cout << count_if(v2.begin(), v2.end(), IsOdd) << endl;
return 0;
}
- find、find_if
该算法的作用是找元素在区间中第一次出现的位置。
template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
for ( ;first!=last; first++) if ( *first==value ) break;
return first;
}
template<class InputIterator, class Predicate>
InputIterator find_if ( InputIterator first, InputIterator last, Predicate pred )
{
for ( ; first!=last ; first++ ) if ( pred(*first) ) break;
return first;
}
#include<iostream>
#include <algorithm>
#include <vector>
using namespace std;
bool IsOdd(int i)
{
return ((i % 2) == 1);
}
int main()
{
vector<int> v1{ 10, 20, 30, 30, 20, 10, 10, 20 };
auto it1 = find(v1.begin(), v1.end(), 10);
vector<int> v2{ 0,1,2,3,4,5,6,7,8,9 };
auto it2 = find_if(v2.begin(), v2.end(), IsOdd);
return 0;
}
- max和min
max返回两个元素中较大值,min返回两个元素中较小值。
template <class T>
const T& max(const T& a, const T& b) {
return (a<b)?b:a;
}
template <class T>
const T& min(const T& a, const T& b)
{
return !(b<a)?a:b;
}
- merge
该算法作用将两个有序序列合并成一个有序序列, 使用时必须包含头文件。
template <class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator merge ( InputIterator1 first1, InputIterator1 last1,
InputIterator2 first2, InputIterator2 last2,
OutputIterator result )
{
while (true)
{
*result++ = (*first2<*first1)? *first2++ : *first1++;
if (first1==last1) return copy(first2,last2,result);
if (first2==last2) return copy(first1,last1,result);
}
}
template<class InputIterator, class OutputIterator>
OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result)
{
while (first!=last) {
*result = *first;
++result; ++first;
}
return result;
}
#include <algorithm>
#include <vector>
#include <list>
#include<iostream>
using namespace std;
int main()
{
vector<int> v{ 2, 6, 5, 8 };
list<int> L{ 9, 3, 0, 5, 7 };
sort(v.begin(), v.end());
L.sort();
vector<int> vRet(v.size() + L.size());
merge(v.begin(), v.end(), L.begin(), L.end(), vRet.begin());
for (auto e : vRet)
cout << e << " ";
cout << endl;
return 0;
}
注意:
- partial_sort
该算法的作用是:找TOPK。
template <class RandomAccessIterator>
void partial_sort(RandomAccessIterator first, RandomAccessIterator middle,
RandomAccessIterator last);
template <class RandomAccessIterator, class Compare>
void partial_sort(RandomAccessIterator first, RandomAccessIterator middle,
RandomAccessIterator last, Compare comp);
partial_sort的实现原理是:对原始容器内区间为[first, middle)的元素执行make_heap()操作构造一个最大堆,然后拿[middle, last)中的每个元素和first进行比较,first内的元素为堆内的最大值。如果小于该最大值,则互换元素位置,并对[first, middle)内的元素进行调整,使其保持最大堆序。比较完之后在对[first, middle)内的元素做一次对排序sort_heap()操作,使其按增序排列。注意,堆序和增序是不同的。因此该算法的功能实际就是:TOP-K。
#include <algorithm>
#include <vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> v1{ 4, 1, 8, 0, 5, 9, 3, 7, 2, 6 };
partial_sort(v1.begin(), v1.begin() + 4, v1.end());
vector<int> v2{ 4, 1, 8, 0, 5, 9, 3, 7, 2, 6 };
partial_sort(v2.begin(), v2.begin() + 4, v2.end(), greater<int>());
return 0;
}
- partition
该算法的作用是按照条件对区间中的元素进行划分,使用时必须包含头文件。
template <class BidirectionalIterator, class Predicate>
BidirectionalIterator partition(BidirectionalIterator first,
BidirectionalIterator last, Predicate pred) {
while (true)
{
while (first!=last && pred(*first)) ++first;
if (first==last--) break;
while (first!=last && !pred(*last)) --last;
if (first==last) break;
swap (*first++,*last);
}
return first;
}
#include <algorithm>
#include <vector>
#include<iostream>
using namespace std;
bool IsOdd(int i)
{
return (i % 2) == 1;
}
int main()
{
vector<int> v{ 0,1,2,3,4,5,6,7,8,9 };
auto div = partition(v.begin(), v.end(), IsOdd);
for (auto it = v.begin(); it != div; ++it)
cout << *it << " ";
cout << endl;
for (auto it = div; it != v.end(); ++it)
cout << " " << *it;
cout << endl;
return 0;
}
- reverse
该算法的作用是对区间中的元素进行逆置,使用时必须包含头文件。
template <class BidirectionalIterator>
void reverse ( BidirectionalIterator first, BidirectionalIterator last) {
while ((first!=last)&&(first!=--last))
swap (*first++,*last);
}
- sort
排序在实际应用中需要经常用到,而在目前的排序中,快排平均情况下是性能最好的一种排序,但是快排也有其自身的短板,比如说:元素接近有序、元素量比较大的情况下,直接使用快排时,堪称一场灾难。因此STL中sort算法并没有直接使用快排,而是针对各种情况进行了综合考虑。下面关于sort函数分点进行说明:
1). sort函数提供了两个版本
- sort(first, last):默认按照小于方式排序,排序结果为升序,一般用排内置类型数据。
- sort(first, last, comp):可以通过comp更改元素比较方式,即可以指定排序的结果为升序或者降序,一般以仿函数对象和函数指针的方式提供。
2).sort并不是一种排序算法,而是将多个排序算法混合而成 3).当元素个数少于__stl_threshold阈值时(16),使用直接插入排序处理 4).当元素个数超过__stl_threshold时,考虑是否能用快排的方式排序,因为当元素数量达到一定程度,递归式的快排可能会导致栈溢出而崩溃,因此:
template <class Size>
inline Size __lg(Size n)
{
Size k;
for (k = 0; n > 1; n >>= 1) ++k;
return k;
}
- unique
该函数的作用是删除区间中相邻的重复性元素,确保元素唯一性,注意在使用前要保证区间中的元素是有序的,才能达到真正的去重。
template <class ForwardIterator>
ForwardIterator unique ( ForwardIterator first, ForwardIterator last );
template <class ForwardIterator, class BinaryPredicate>
ForwardIterator unique ( ForwardIterator first, ForwardIterator last,
BinaryPredicate pred );
template <class ForwardIterator>
ForwardIterator unique ( ForwardIterator first, ForwardIterator last )
{
ForwardIterator result=first;
while (++first != last)
{
if (!(*result == *first))
*(++result)=*first;
}
return ++result;
}
注意: 1). 该函数并不是将重复性的元素删除掉,而是用后面不重复的元素将前面重复的元素覆盖掉了。 2). 返回值是一个迭代器,指向的是去重后容器中不重复最后一个元素的下一个位置。 3). 该函数需要配合erase才能真正的将元素删除掉。
#include <algorithm>
#include <vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
auto it = unique(v.begin(), v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
v.erase(it, v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
sort(v.begin(), v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
it = unique(v.begin(), v.end());
v.erase(it, v.end());
for (auto e : v)
cout << e << " ";
cout << endl;
return 0;
}
- next_permutation和pre_permutation
next_permutation是获取一个排序的下一个排列,可以遍历全排列,prev_permutation刚好相反,获取一个排列的前一个排列, 使用时必须包含头文件。 对序列 {a, b, c},每一个元素都比后面的小,按照字典序列,固定a之后,a比bc都小,c比b大,它的下一个序列即为{a, c, b},而{a, c, b}的上一个序列即为{a, b, c},同理可以推出所有的六个序列为:{a, b,c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a},其中{a, b, c}没有上一个元素,{c, b, a}没有下一个元素。 注意:使用时,必须保证序列是有序的。他们排完之后,仍然是升序或者降序。
#include <algorithm>
#include <vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> v = { 4, 1, 2, 3 };
sort(v.begin(), v.end());
do
{
cout << v[0] << " " << v[1] << " " << v[2] << " " << v[3] << endl;
} while (next_permutation(v.begin(), v.end()));
cout << endl;
sort(v.begin(), v.end(), greater<int>());
do
{
cout << v[0] << " " << v[1] << " " << v[2] << " " << v[3] << endl;
} while (prev_permutation(v.begin(), v.end()));
return 0;
}
迭代器
迭代器是一种设计模式,让用户通过特定的接口访问容器的数据,不需要了解容器内部的底层数据结构。C++中迭代器本质:是一个指针,让该指针按照具体的结构去操作容器中的数据。 STL中算法分为容器相关联与通用算法。所谓通用算法,即与具体的数据结构无关,比如:
template<class InputIterator, class T>
InputIterator find ( InputIterator first, InputIterator last, const T& value )
{
for ( ;first!=last; first++)
if ( *first==value )
break;
return first;
}
find算法在查找时,与具体的数据结构无关,只要给出待查找数据集合的范围,find就可在该范围中查找,找到返回该元素在区间中的位置,否则返回end。
迭代器实现原理
容器底层结构不同,导致其实现原理不同,容器迭代器的设计,必须结合具体容器的底层数据结构。比如:
- vector
因为vector底层结构为一段连续空间,迭代器前后移动时比较容易实现,因此vector的迭代器实际是对原生态指针的封装,即:typedef T* iterator。 - list
list底层结构为带头结点的双向循环链表,迭代器在移动时,只能按照链表的结构前后依次移动,因此链表的迭代器需要对原生态的指针进行封装,因为当对迭代器++时,应该通过节点中的next指针域找到下一个节点。
如果迭代器不能直接使用原生态指针操作底层数据时,必须要对指针进行封装,在封装时需要提供以下方法:
- 迭代器能够像指针一样方式进行使用:重载
pointer operator*() / reference operator->() - 能够让迭代器移动
向后移动:self& operator++() / self operator++(int) 向前移动:self& operator--() / self operator--(int) (注意:有些容器不能向前移动,比如 forward_list) - 支持比较-因为在遍历时需要知道是否移动到区间的末尾
bool operator!=(const self& it)const / bool operator==(const self& it)const
迭代器与类的融合
- 定义迭代器类
- 在容器类中统一迭代器名字
比如list:
template <class T, class Alloc = alloc>
class list
{
typedef __list_iterator<T, T&, T*> iterator;
}
- 在容器类中添加获取迭代器范围的接口:
template <class T, class Alloc = alloc>
class list
{
iterator begin(){ return (link_type)((*node).next);}
iterator end(){ return node;}
};
反向迭代器
反向迭代器:正向迭代器的适配器,即正向迭代器++往end方向移动,–往begin方向移动,而反向迭代器++则往begin方向移动,–则向end方向移动。
适配器
适配器:又接着配接器,是一种设计模式,简单的说:需要的东西就在眼前,但是由于接口不对而无法使用,需要对其接口进行转化以方便使用。即:将一个类的接口转换成用户希望的另一个类的接口,使原本接口不兼容的类可以一起工作。 STL中适配器总共有三种类型:
- 容器适配器-stack和queue
stack的特性是后进先出,queue的特性为先进先出,该种特性deque的接口完全满足,因此stack和queue在底层将deque容器中的接口进行了封装。 - 迭代器适配器:(反向迭代器、插入迭代器、IO流迭代器)
- 函数适配器:函数适配器能够将仿函数和另一个仿函数(或某个值、或某个一般函数)结合起来。
仿函数
仿函数:一种具有函数特征的对象,调用者可以像函数一样使用该对象 ,为了能够“行为类似函数”,该对象所在类必须自定义函数调用运算符operator(),重载该运算符后,就可在仿函数对象的后面加上一对小括号,以此调用仿函数所定义的operator()操作,就其行为而言,“仿函数”一次更切贴。 仿函数一般配合算法,作用就是:提高算法的灵活性。
#include <algorithm>
#include <vector>
#include<iostream>
using namespace std;
class Mul2
{
public:
void operator()(int& data)
{
data <<= 1;
}
};
class Mod3
{
public:
bool operator()(int data)
{
return 0 == data % 3;
}
};
int main()
{
vector<int> v{ 1,2,3,4,5,6,7,8,9,0 };
for_each(v.begin(), v.end(), Mul2());
for (auto e : v)
cout << e << " ";
cout << endl;
auto pos = remove_if(v.begin(), v.end(), Mod3());
v.erase(pos, v.end());
for_each(v.begin(), v.end(), [](int data) {cout << data << " "; });
cout << endl;
for_each(v.begin(), v.end(), [](int& data) {data *= 10; });
cout << endl;
for_each(v.begin(), v.end(), [](int data) {cout << data << " "; });
cout << endl;
return 0;
}
空间配置器
空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。 前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:
- 空间申请与释放需要用户自己管理,容易造成内存泄漏
- 频繁向系统申请小块内存块,容易造成内存碎片
- 频繁向系统申请小块内存,影响程序运行效率
- 直接使用malloc与new进行申请,每块空间前有额外空间浪费
- 申请空间失败怎么应对
- 代码结构比较混乱,代码复用率不高
- 未考虑线程安全问题
因此需要设计一块高效的内存管理机制。 内存池介绍
|