C++STL库探究

C++STL库探究

STL即标准模板库,封装了一些常用的数据结构和算法

它的价值是从小来说,给用户直接提供了一套高效的算法接口,并且讲算法和数据容器分离,让算法变成不依赖数据类型,不依赖存储结构的逻辑代码。大的来说它将抽象概念通过泛型技术描述出来,对于庞大的系统利用组件分层设计。

六大组件

容器通过空间配置器获得存储空间,算法利用迭代器获取容器内容,仿函数协助算法完成不同策略变化,适配器可以修饰仿函数和容器。

容器:利用数据结构,线性树形哈希,来存储数据

算法:利用迭代器实现和数据结构无关的代码,或者依赖容器的算法

迭代器:容器和算法之间的胶水,算法看到统一的++, –遍历方法,容器有自己的遍历方式

空间配置器:提高零碎内存分配效率,解决内存碎片,防止空间浪费。

仿函数:比如在算法中需要更加普通化,sort函数需要传入定制的比较规则,把比较规则封装成类,再重载(),之后可以通过无名对象方式调用仿函数得到结果。

适配器:修饰仿函数时,根据语义需求创造出灵活的表达式供算法使用。修饰容器,直接封装其接口,像stack封装deque。

扫描二维码关注公众号,回复: 2668845 查看本文章

空间配置器

在容器背后默默奉献的角色,因为C++本身的动态内存分配机制不够高效,所以诞生了空间配置器。它不仅可以分配内存空间,甚至可以索取硬盘空间。

它把原先new和delete的两个阶段分开设计,分配空间是alloc,构造对象是construct,还有填充函数用于内存填充/拷贝/移动。

alloc分配内存采用两级设计,避免了内存碎片的问题。大于128字节直接用一级配置器向系统堆空间申请内存,小于128字节使用二级配置器,利用内存池管理内存。

内存池配合16个自由链表,8,16…128字节的内存块挂在链表上,类似哈希筒的方式管理回收内存。内存池主要注重的是水位线,水位线过低就要采取措施补充水源。

而不管是几级配置,给用户使用的都是同一的一个alloc接口,透明!

仿函数

仿函数可以实现智能指针的删除器,还可以实现算法的泛化。

bubble_sort

template<typename T>
class Great {
public:
    bool operator()(const T& left, const T& right) {
        return left > right;
    }
};

template<typename T, typename Com>
void bubble_sort(T* arr, int size, Com cmp) {
    bool flag = true;
    for (int i = 0; i < size - 1; i++) {
        flag = true;
        for (int j = 0; j < size - 1 - i; j++) {
            if (cmp(arr[j], arr[j + 1])) {
                swap(arr[j], arr[j + 1]);
                flag = false
            }
        }
        if(flag){
            return;
        }
    }
}

void test_bubble() {
    int arr[] = { 5,6,8,7,9 };
    //传入无名对象,在算法里可以直接当函数调用
    bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), Great<int>());
}

容器

C++有三个重要的顺序容器,vector,list和deque。顺序是指容器内的元素可以被按序排列。

关联容器主要为了查询元素是否存在,并且获取元素。有map和set。

选择vector ,list ,deque ?

在c语言中根据元素个数是否确定就能决定使用array或者list。但是对于顺序容器,都是动态增长的,主要准则是关注插入和访问特性

vector 封装的是数组,是一段连续的内存空间,因此任意插入和删除效率很低,插入的话需要先把后续元素向后搬移,是On的复杂度。但是随机访问性很好。

list 封装了带头节点的双向循环链表,非连续地址空间,插入删除只需要调整前后结点指向,是O1的复杂度。但是不支持随机访问,每个结点还有额外的指针空间开销。

deque 是逻辑上的连续空间,但底层是许多连续空间拼接起来的,虽然支持随机访问,但是开销很大,只有在对容器头尾操作才有优势。

vector实现细节

关键技术是增容时机,SGI版本在插入元素之前先判断容量是不是满了,若满则开辟两倍大的空间,搬移元素到新空间,释放旧空间。

开辟新空间不一定在原来的地址上,因为不能保证原空间后有足够大的新空间,意味着原来的迭代器失效。

数据结构用三个迭代器,start ,finish,end分别指向已用空间,未用空间,空间末尾。迭代器是直接封装了数据类型的指针,因为指针完全胜任对连续空间的操作。

list实现细节

list封装的就是带头节点双向循环链表,所以它的实现都是基于链表的操作。

它的迭代器比较特殊,由于是非连续地址空间,迭代器必须支持++,–的操作。这需要对指针再次封装,定义它的++–操作。

deque和vector的差异

  1. deque在头端操作时间复杂度可以达到O1
  2. deque没有vector的容量概念,随时拼接上连续的空间,没有原空间不足搬移元素三步骤,扩容性比vector好,代价是迭代器复杂了
  3. 随机访问的性能要比vector差

排序deque元素可以先将元素复制到vector中,在vector中排序后复制回deque。

关联容器

关联容器是为了提高元素查询效率诞生的,存放结构变成了key-value。一种基于红黑树,一种基于哈希。

map,set,multi系列,由于底层是红黑树,所以是有序的,但是不能修改结点元素会破坏树的性质。

unorder系列基于哈希,为了进一步提高查询效率,哈希结构决定了它的无序性。

关联容器和序列容器的差异

  1. 查询的效率,逐个比较——key和地址绑定
  2. 无序存放,空间浪费
  3. 哈希筒方式的哈希冲突处理

猜你喜欢

转载自blog.csdn.net/hanzheng6602/article/details/81540342