SGI-STL简记(六)-序列容器(list)

女生的网名这么多〃 提交于 2019-12-01 02:03:50
stl_list.h :
    list:一个可从任意位置快速插入和删除元素的双向链表,可在常数时间内完成,但是取数据、查找等则需要线性时间;
    
    _List_node_base:链表节点基类struct,仅包含_M_next、_M_prev成员,其分别为指向当前节点基类类型的下一个、上一个节点的指针;
    
    _List_node:节点模板类,继承于_List_node_base,只是增加了一个数据成员_M_data,用以保存实际的node节点数据;
    
    _List_iterator_base:链表迭代器基类,所属迭代器类型为bidirectional_iterator_tag;
        内部成员_M_node为当前迭代器指向的节点;
        _List_node_base构造函数参数类型_List_node_base,该值以初始化_M_node;
        _M_incr:调整当前迭代器所指的节点为该节点的下一个节点;
        _M_decr:调整当前迭代器所指的节点为该节点的上一个节点;
        此外重载的operator==,operator!=比较操作符,其实际上是直接比较迭代器的_M_node成员(地址);
        
    _List_iterator:链表迭代器模板类,继承于_List_iterator_base;
        该模板类重声明了多个常规类型;多个重载版本的构造函数以初始化迭代器;
        此外重载了解引用、取指针操作符(返回为当前迭代器_M_node(需先强制转化为子类类型_List_node)中的数据成员_M_data,因_List_node兼容_List_node_base类型
        内存布局,故可强制C类型转化);
        重载的operator++、operator++(int)、operator--、operator--(int):分别调用相应的基类成员_M_incr、_M_decr实现迭代器前向和后向迭代操作;
        
    _List_alloc_base:链表分配模板基类,模板参数分别为数据类型T,分配器类型_Allocator,以及一个bool标识_IsStatic(用于区分是否为标准分配器或SGI分配器);
    数据成员:
        _M_node:当前链表的首链表节点指针,其还用来作为迭代器的首、尾判断依据(哨兵);
    成员函数:
        构造函数:分配器引用allocator_type类型以初始化_Node_allocator;
        get_allocator:获取分配器对象_Node_allocator;
        _M_get_node:通过分配器对象_Node_allocator分配大小为1个的数据元素类型的内存大小空间
        _M_put_node:释放指定数据元素类型指针地址大小为1个数据元素类型大小的内存空间;
    此外还提供特化版本_List_alloc_base<_Tp, _Allocator, true>,该分配模板基类内部不再使用分配器对象,而是直接使用simple_alloc的静态成员函数进行分配管理;
    
    _List_base:链表模板基类,继承于_List_alloc_base,其基类的模板参数_IsStatic则通过_Alloc_traits萃取获得的_S_instanceless来初始化,该链表为带头节点的双向链表,可以
    更方便实现而且空间浪费也不多就一个数据类型节点的大小;
        构造函数:通过分配器类型对象初始化基类分配器,此外调用_M_get_node分配一个数据类型大小的空间以此初始化_M_node,调整_M_node内的上一个和下一个节点指针均指向自己;
        析构函数:调用clear清空销毁整个链表缓存区并调用_M_put_node释放链表首指针缓存区;
        clear;遍历_M_node指向的后面所有链表节点,调用_Destroy析构各个节点以及调用_M_put_node销毁该节点缓冲区,此后调整_M_node内的上一个和下一个节点指针均指向自己;
    
    list:序列容器list链表模板类,保护继承于_List_base,模板参数中的分配器使用宏__STL_DEFAULT_ALLOCATOR作为默认分配器;可通过调整宏设置,故而可使用allocator< T >或alloc
        (malloc_alloc(即__malloc_alloc_template<0>)或__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>)作为默认的内存分配器;
        此外重声明常规类型以及迭代器类型和常量迭代器、翻转迭代器等类型;
        get_allocator:重写基类函数,获取基类的分配器对象;
        构造函数:提供多个重载版本,包括提供分配的allocator_type参数、提供初始化元素个数n并以给定值value初始化或使用默认初始化值、提供输入迭代器范围(调用insert在容器尾部
        插入输入迭代器范围数据或者插入n个元素数据);
        _M_create_node:创建节点缓冲区并初始化该节点值为指定值,内部先调用_M_get_node申请元素类型大小的缓冲区,此后调用_Construct初始化构造当前节点值;
        begin:获取容器的首元素节点包装的迭代器(返回_M_node->_M_next包装的迭代器对象);
        end:获取容器的尾元素后节点包装的迭代器(返回_M_node包装的迭代器对象);
        rbegin、rend:借助begin和end以初始化构造并返回反转迭代器对象;
        empty:判断容器是否为空(返回_M_node->_M_next == _M_node即判断当前链表首指针所指向下一个节点是否为自身);
        size:获取容器元素容量(内部调用distance遍历计算容器元素个数,因需要遍历,故效率比较低,常数时间完成,故对于一般下优先采用empty而不是size来判断容器是否为空,
        不过也得看不同编译器的实现,部分编译器保存了一个size大小的变量以记录当前容器数据元素个数,则此时size或empty均一样且获取元素个数时非常高效);
        max_size:获取链表最大容量,同deque返回size_t(-1),不同编译器实现方式不一样,有的返回为size_t(-1)/sizeof(T);
        front、back:获取容器首元素和尾元素(*begin()、*(end() - 1),若容器为空会出现运行时异常);
        swap:交换两个容器内容,实际上只是简单的交换_M_node值(也即其中对应的上一个和下一个节点指针值);
        insert:提供多种重载版本,包括指定位置前插入某个值,指定位置前插入n个某个值以及指定位置前插入输入迭代器范围的值;
            指定位置前插入某个值版本:
                调用_M_create_node创建并初始化该新节点值,调整指定插入节点以及其前一节点和新的节点的相互指向关系以构成双向链表,具体操作为:
                    1. 先将新节点的下一个节点指向插入点节点;
                    2. 将新节点的上一个节点指向插入点节点的上一个节点;
                    3. 将插入节点的上一个节点所指向的下一个节点指向新节点;
                    4. 将插入节点的上一个节点指向新节点;
                实际上以上步骤只是其中实现方式之一,还有其他可能的实现顺序;
            指定位置前插入n个值为val的版本:
                内部调用_M_fill_insert填充插入(事实上_M_fill_insert内部也是循环调用insert在指定迭代器位置前插入实现);
            指定位置前插入输入迭代器范围的值版本:
                内部调用_M_insert_dispatch模板函数分派插入;当其数据元素类型为_Is_integer时则调用_M_fill_insert填充插入,否则遍历输入迭代器并在指定位置处调用insert插入;
        push_front、push_back:分别在容器的首部和尾部插入数据元素(内部调用insert分别在首部和尾部迭代器前处插入);
        erase:重载版本,移除指定迭代器位置的数据元素和输入迭代器范围的数据元素;其前者只需要掉在待移除迭代器位置前节点和后节点相互关系即可,此后调用_Destroy销毁迭代器对应的节点元素,
        并调用_M_put_node销毁该节点缓冲区;后者版本则只需要在循环迭代器范围内调用前者版本erase即可,移除时调整关系为:
            1. 取得移除迭代器节点的上一个节点和下一个节点(有两个临时指针变量保存此两个节点位置);
            2. 将该取得的上一个节点的指向下一个节点为取得的下一个节点;
            3. 将该取得的下一个节点的指向上一个节点为取得的上一个节点;
            事实上可以不需要1中的临时指针,即直接按照以下步骤调整即可;
            1. 直接调整移除迭代器节点的上一个节点的指向下一个节点为移除迭代器节点的下一个节点;
            2. 调整移除迭代器节点的下一个节点的指向上一个节点为移除迭代器节点的上一个节点;
        clear:清空容器元素(调用基类clear);    
        resize:提供两个重载版本,重置容器空间容量大小,内部先通过for循环得到重置的大小和容器原大小间最小值,若该值与重置大小相同则调用erase移除其后剩余容器元素;否则调用insert
        迭代器尾部插入不足重置大小时剩余的值(之所以使用for循环去取该值因为得到后直接可以用当前迭代器位置在需要时调用earse,若采用size获取到大小后还得再次移动迭代器位置使得整体上太过耗时);
        pop_front、pop_back:移除容器首部元素、尾部元素,其内部均调用earse移除首尾部迭代器位置的值即可;
        assign:分配函数,重载版本,分别提供分配n个值为val的初始化容器以及分配输入迭代器范围的初始化容器;前者调用_M_fill_assign填充分配;后者则调用_M_assign_dispatch模板函数分配;
        _M_fill_assign:其策略为若原容器有数据则通过for循环赋值拷贝数据,若不足指定大小的容器时则调用insert在后面插入填充剩余个数的数据,否则调用earse移除原容器多余的数据元素;
        _M_assign_dispatch:分配分派模板函数,根据数据元素类型调用相应版本,当为_Is_integer类型时则调用_M_fill_assign填充即可,否则处理方式为若原容器有数据则通过for循环赋值拷贝数据,
        若不足指定大小的容器时则调用insert在后面插入填充剩余迭代器范围的数据元素,否则调用earse移除原容器多余的迭代器范围的数据元素,类似于_M_fill_assign的处理策略;
        splice:拼接函数,移除源容器元素并插入到目标容器指定迭代器位置前;提供三个重载版本;
            提供目标容器的插入迭代器位置和源容器版本:内部调用transfer将源容器元素拼接到模板容器指定插入位置前;而另外两个版本分别提供插入某个源迭代器或迭代器范围,均转化为调用transfer
            实现,后两个版本第二个参数为容器引用,不过没有用到的无效参数;
        transfer:转移容器元素,操作过程为:先断开源容器中迭代器范围数据部分指针指向,再将断开的数据插入到指定插入迭代器位置前,最后再完善数据指向,具体如下:
            1. 将源容器last迭代器的上一节点的下一个节点指向为指定插入迭代器节点;
            2. 将源容器first迭代器的上一节点的下一个节点指向为last迭代器节点;
            3. 将插入迭代器节点的上一个节点的下一个节点指向为first迭代器节点;
            4. 获取插入节点的上一节点tmp;
            5. 插入节点的上一个节点指向为last节点的上一个节点;
            6. last节点的上一个节点指向为first节点的上一个节点;
            7. first节点的上一个节点指向为tmp;
        remove:移除容器中第一次出现与指定的值相同的数据元素(内部while循环找到迭代器下与指定值相同的元素并调用erase移除);        
        unique:移除相邻相同元素只保留邻近一份数据元素,提供两种重载版本;一种为operator=比较,另一种由外部提供比较函数实现,无论哪个版本,内部均while循环遍历对于相邻相同元素则调用erase
        移除找到的后续元素;
        merge:归并合并,移除源容器元素并归并于当前容器中且有序排序(前提是两个容器元素已有序)(默认为升序),(借助于transfer),此外提供外部提供比较函数的重载版本;
        reverse:翻转链表数据元素顺序,内部调用__List_base_reverse实现实际翻转操作(遍历节点,交换各节点的上一个节点和下一个节点的指向,交换后,当前节点的下一个节点应该为现在的上一个节点,
        依次遍历直到回到原始节点处);
        sort:排序容器元素,默认为升序,也提供了外部自定义比较函数实现排序;实现策略类似于二路归并排序的思想,具体如下:
            __carry:作为一个临时的缓冲区链表,主要作用:1.调用splice从当前容器中取出一个数据;2.与__counter归并趟数合并、交换;
            __counter:保存每次排序合并后的缓冲区链表数组,其数组个数为64,每个数组下标索引值表示其可容纳最多排序后的元素数量(如__counter[3]表示可容纳2的3次方数据元素个数,事实上当为63时
            表明是一个很大的链表了,一般不会处理如此大量的排序工作,另外目前PC机64位的内存也无法保存如此多的数据,或许有也会导致__counter越界异常);
            此外相邻的__counter索引才会归并排序合并且组成更大的有序数据,此后保存于下一个索引__counter下;按照此原则,对于不构成合并的__counter各个索引数据则遍历各个索引调用merge依次合并
            直到合并到最后一个索引值为fill-1的__counter数组链表中,此后调用swap交换该链表和本容器数据实现最终的排序;
            整个排序时间复杂度同归并排序O(nlogn),而不同之处在于其不需要占用额外的空间;
            list专门提供sort操作而不用算法中的sort,是因为算法中的迭代器类型为RandomAccessIter,而list的为bidirectional_iterator;
        此外重载了各种比较操作符,而其中最重要的是operator==为遍历依次比较两个容器的元素,operator<调用lexicographical_compare依次遍历比较容器元素;
    
    list若采用默认带内存池的版本分配器时,若超过128字节将直接采用malloc、free管理内存,一般情况下需求内存空间比较大的情况下时分配器意义不大基本上是直接采用malloc、free管理,容器内部缓冲区
    结构不同于vector、deque,其为各个节点带指向上一个和下一个节点的指针,故相对vector,各个元素会多占用两个指针大小的空间,若节点保存值为char、int或一个指针等则反而指针占用的空间超过实际存储
    的值内存占用,故对于list容器一般保存较大占用的数据类型元素且需要快速删除或插入、有序合并、拼接的操作情况下,此外其不支持随机存取;
    

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!