• STL源码剖析


      花了两天时间略读了一下《stl源码分析》,看了个大体,对于细节并没有深究。之所以想翻翻这本书,主要是想看看stl中的特性、适配器的具体实现。看完之后收获还是蛮大的,模板的各种组合让我眼前一亮,下面大概总结一些内容。

      1.内存分配:sgi内存分配采用两级实现,对于大内存块的申请(大于128k)由第一级实现,第一级实现较简单,直接调用malloc和free。对于小于128k的内存请求,由第二级实现。第二级使用了内存池,维护了一些链表,分别指向不同大小的空闲内存块,这些内存块都是8的倍数的大小,分别是8k,16k,24k,...,128k。分配内存时,根据请求大小找到相应链表的表头,然后从表头摘取一个结点块,回收的时候也是从表头插入。第二级内存分配的实现方式跟linux操作系统的空闲内存管理很相似,不过内存回收的时候有区别,linux内存管理还涉及到空闲块的合并。
     
      2.迭代器:要设计一种迭代器,必须对容器的具体实现有丰富的了解,所以把迭代器的实现交给容器的设计者,这样就可以是容器的实现细节得以封装,所以每一种STL容器都提供了专属的迭代器。迭代器中的end()具体指向了哪个位置?对于vector这种由连续内存块来实现的容器,end()指向数组之后的一个位置。而对于list,map,set这种由”结点“实现的容器,会有专门的一个结点,该结点含有一些重要的信息,比如指向开头结点的指针。为什么有些迭代器只能是向前的?因为其容器的内部实现决定了它的迭代器只能是前向的,比如单向链表的迭代器当然只能是向前的了。
     
      3.容器:deque的结构挺复杂的,使用了分段数组,并使用了一个map数组来映射不同分段数组的地址,通过封装,使其使用起来就像是个连续的数组一样。stack和queue的内部结构使用的是deque。vector容器增长的平均时间复杂度是O(1)。具体计算,大家可以自己想想。
     
      4.traits:c++的特性让我见识了c++抽象的强大,通过增加一层抽象,使问题的解决变得很微妙。这部分主要利用了模板的类型推断能力。通过偏特化,能够粹取出类类型和原始指针的特性。印象很深的一个例子是advance函数。
     
    template<class InputIterator, class Distance>
    inline void __advance(InputIterator& i, Distance n, input_iterator_tag){
        whie(n--) ++i;
    }
    
    template<class ForwardIterator, class Distance>
    inline void __advance(ForwardIterato& i, Distance n, fowrd_iterator_tag){
        __advance(i,n,input_iterator_tag());
    }
    
    template<class BidirectionalIterator, class Distance>
    inline void __advance(BidirectionalIterator& i, Distance n, bidirectional_iterator_tag){
        if(n >= 0){
            while(n--) ++i;    
        }else{
            while(n++) --i;
        }
    }
    
    template<class RandomAccessIterator, class Distance >
    linue void __advance(RandomAccessIterator& i, Distance n, random_access_iterator_tag){
        i += n;
    }
    View Code

      实现了四个不同的函数重载,再增加一个上层函数advance(),粹取出迭代器的类型,并调用相应的__advance版本。

    template<class InputIterator, class Distance>
    inline void advance(InputIterator& i, Distance n){
            __advance(i,n,iterator_traits<InputIterator>::iterator_category());
    }
    View Code

      5.适配器:适配器的实现主要是在内部包含了一个对象,在外面展示不同的接口,内部实际上还是间接使用了其所包含的对象。为什么说为了让一个函数对象在stl是可适配的?因为在外层类中要使用到内部包含类型的某些”特性“,比如bind1st,就要使用到内部二元函数仿函数的first_argument_type。所以为了能够被适配,一般都要继承unary_function和binary_function。

     
      6.采用辅助函数:我们知道使用模板类声明一个对象,需要给出具体的类型,有时手动地给出具体类型会很麻烦,那么有没有办法让编译器自动推导出具体的类型呢?这里使用了”增加一层抽象“,使用模板函数,模板函数具有自动推导的功能,利用模板函数推导出来的类型来具现模板类。比如下面的例子。
     
    template<class Operation>
    class binder1st : public unary_function<typename Operation::second_argument_type, typename Operation::result_type>{
    protected:
        Operation op;
        typename Operation::first_argument_type value;
    public:
        binder1st(const Operation& x, const typename Operation::first_argument_type& y):op(x),value(y){}
        
        typename Operation::result_type
        operator()(const typename Operation::second_argument_type& x)const{
            return op(value,x);
        }
    };
    
    template<class Operation, class T>
    inline binder1st<Operation> bind1st(const Operation& op, const T& x){
        typedef typename Operation::first_argument_type arg1_type;
        return binder1st<Operation>(op,arg1_type(x));
    }
    View Code

      嗯,就先总结到这些吧。其他的一些东西要么之前了解得比较熟了,那么印象不是很深刻,就不写出来了。

  • 相关阅读:
    Android测试入门篇
    SQL的基本知识
    正则表达式
    ES5语法
    vscode
    继承小结
    工作遇到的问题
    后台程序员的HTTP缓存
    xhr下载图片/服务器向客户端推送消息
    HTTP2.0
  • 原文地址:https://www.cnblogs.com/yplhh/p/4631192.html
Copyright © 2020-2023  润新知