• 《深入实践C++模板编程》之六——标准库中的容器


    1、容器的基本要求
    a、并非所有的数据都可以放进容器当中。各种容器模板对所存数据类型都有一个基本要求——可复制构造。将数据放进容器的过程就是通过数据的复制构造函数在容器内创建数据的一个副本的过程。
    b、容器中必须有若干与所存数据类型有关的嵌套定义类型。
    C::value_type 容器所存数据类型
    C::reference 容器数据的引用类型
    C::const_reference 容器数据的只读引用类型
    C::size_type 容器容量类型,通常是一个无符号整数类型
    c、除嵌套类型定义外,容器还必须拥有默认构造函数以及复制构造函数。
    d、容器还须提供一系列与迭代器有关的嵌套定义类型以及成员函数。
    e、其它:
    新增四个成员函数cbegin()、cend()、crbegin()、crend()分别明确返回对应的只读迭代器。
    容器必须有成员函数size()、max_size()、empty(),分别返回当前容器内元素个数、最大可容元素数以及容器是否为空。
    容器必须有一个成员函数swap(C &a)用于与另一同类容器互换内容。
    两个同类容器之间可以比较是否相等。
    还有其它附加要求。
     
    2、序列型容器
    2.1、vector
    vector是一个边长数组容器类模板。数据在vector中连续存储。谓词vector会预先申请一段内存空间以保存数据。随着数据不断增加,当预留的内存空间不够用时,vector会再申请一段更大的空间并将现有数据搬到新空间中后再继续接受新数据。由于数据在内存空间内连续存放,所以vector可提供快速随机访问。
    所以,vector适用于数据增删不频繁但需要高效随机存取的场合,而不适用于需要频繁在序列中增删数据的场合。
    如果数据可以预估,可以先调用reserve()函数申请足够空间,这样可以避免无谓的数据迁移。
     
    void main()
    {
        int array[] = { 1, 2, 3, 4, 5 };
        std::vector<int> v;
    
        v.reserve(5);
        v.assign(array, array + 5);
    
        typedef std::vector<int>::iterator iterator;
        iterator p = v.begin();
        iterator q = v.end() - 1;
    
        v.erase(p + 1, p + 3);
    
        v.insert(v.end(), array, array + 5);
    
        getchar();
    }
    由于erase以及insert操作,原本的p和q指针已经不指向原本预期的位置了。
    vector除了要求所存数据类型是可复制构造的,还要求是可赋值的。
    了解容器,就要了解支撑这个容器的底层的数据结构,才能以最高效的方式使用这个容器。
    2.2、双向链表
    2.3、双端序列
     
    3、容器转换器
    容器转换器是某种模板,这种模板利用某种容器而构建成某种数据结构。
    容器转换器,是利用容器实现特定数据结构的模板。标准中定义三种容器转换器模板:stack、queue、priority_queue。
    stack与queue的模板有两个参数,前一参数定义stack及queue要保存的数据类型,后一参数则定义所用容器类型。
    std::stack<int, std::vector<int>> vector_stack;    //用vector实现stack
        std::stack<int> deque_stack;    //用deque实现stack
        std::queue<char, std::list<char>> queue;        //用list实现queue
        std::queue<char> deque_queue;    //用deque实现queue
    以priority_queue为例:
    #include <iostream>
    #include <queue>
    #include <vector>
    
    struct my_pair
    {
        int first;
        int second;
    };
    
    struct comp_my_pair//这是一个函数对象
    {
        bool operator()(my_pair const &left,
            my_pair const &right)
        {
            return left.first == right.first ? 
                left.second > right.second :
            left.first  > right.first;
        }
    };
    
    void main()
    {
        my_pair array[] = {{3, 0}, {2, 1}, {1, 2}, {0, 3}, {0, 4}};
        using std::priority_queue;
        using std::vector;
    
        priority_queue<my_pair, vector<my_pair>, comp_my_pair> pq(array, array + 5);
        while(!pq.empty())
        {
            std::cout << pq.top().first << ", " << pq.top().second << std::endl;
            pq.pop();
        }
    
        return;
    }
    4、关联型容器
    关联型容器的特征就是以“值”寻“址”,即容器可以快速找出某个给定值在容器中的位置,或者查无此值返回。
    关联型容器包括集合set、多值集合multiset、映射map以及多值映射multimap。四种容器都是以红黑树保存数据。所以这些容器的优势就等同于红黑树的优势。
    a、要了解什么是红黑树,参考:https://blog.csdn.net/cout_sev/article/details/24628903
    b、要知道什么是“多值”。
     
    关联型容器都会支持一个函数型对象,用来比较插入的数据大小。
     
    插入:执行插入操作时,如果你心中有一个map的底层数据结构模型,那么你就可以知道你每一步插入的时间复杂度是多少。并使用“智能插入”来实现高效插入。
    举例:
    typedef std::set<int> int_set;
    typedef void func_type(int*, int*);
    
    float measure(func_type func, int *start, int *end)
    {
        //int start_clock = clock();
        func(start, end);
        //int end_clock = clock();
        return 0;
        //return float(end_clock - start_clock) / CLOCKS_PER_SEC;
    }
    
    void test_plain_insert(int *start, int *end)
    {
        int_set s;
        s.insert(start, end);
        for(int_set::iterator it = s.begin(); it != s.end(); it++)
        {
            printf("%d ", *it);
        }
        printf("
    ");
    }
    
    void test_smart_insert(int *start, int *end)
    {
        int_set s;
        int_set::iterator prev = s.begin();
        for(; start != end; ++start)
        {
            prev = s.insert(prev, *start);
            for(int_set::iterator it = s.begin(); it != s.end(); it++)
            {
                printf("prev:%d ;", *prev);
                printf("%d ", *it);
            }
            printf("
    ");
    
        }
        
        printf("
    ");
    }
    
    void main()
    {
        
        const int num = 8;
        const int half = num / 2;
    
        int array1[num];
        int array2[num];
        for(int i = 0; i < num; ++i)
        {
            array1[i] = i;
            array2[i] = (i & 1) ? (i - num) : (num - i);
        }
    
        measure(test_plain_insert, array2, array2 + num -1);
        measure(test_smart_insert, array2, array2 + num -1);
        //measure(test_plain_insert, array1, array1 + num -1);
        //measure(test_smart_insert, array1, array1 + num -1);
        return;
    }

    正常的插入insert(i, j)底层执行的是insert(end, v),而我们看这里的逻辑,end始终指向的是8;
    而如果使用insert(prev, v),那么prev始终指向的是刚刚插入的位置;
    就是二者的迭代器指向不同,导致了效率的差异。
    所以,想用好stl库,一定要对其底层数据结构有深入了解。
     
     
  • 相关阅读:
    useState 的介绍和多状态声明(二)
    PHP:相对于C#,PHP中的个性化语法
    PHP:IIS下的PHP开发环境搭建
    PHP:同一件事,有太多的方式
    Javascript:再论Javascript的单线程机制 之 DOM渲染时机
    Javascript:拦截所有AJAX调用,重点处理服务器异常
    DDD:谈谈数据模型、领域模型、视图模型和命令模型
    .NET:再论异常处理,一个真实的故事
    Javascript:由 “鸭子类型” 得出来的推论
    Workflow:采用坐标变换(移动和旋转)画箭头
  • 原文地址:https://www.cnblogs.com/predator-wang/p/11518372.html
Copyright © 2020-2023  润新知