《深入实践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("\n");
}

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("\n");

    }

    printf("\n");
}

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库,一定要对其底层数据结构有深入了解。

原文地址:https://www.cnblogs.com/predator-wang/p/11518372.html

时间: 2024-10-29 00:31:10

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

STL笔记(6)标准库:标准库中的排序算法

STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew Austern http://www.cuj.com/experts/1908/austern.htm?topic=experts 用泛型算法进行排序    C++标准24章有一个小节叫“Sorting and related operations”.它包含了很多对已序区间进行的操作,和三个排序用泛型

参考C++STL标准库中对了的使用方法

http://www.cppblog.com/zhenglinbo/archive/2012/09/18/191170.html 参考:http://www.cppblog.com/zhenglinbo/archive/2012/09/18/191170.html 当然是使用c++中的STL 的queue啦.下面简要介绍一下使用方法. 1 准备工作 头文件 #include<queue> 2 声明和定义的方法.STL的队列是泛型模板,支持任何内置和构造类型. 比如对于刚才那个牛奶问题.我把状态

Swift标准库中的协议_012_swift协议

//: Playground - noun: a place where people can play import UIKit //--Swift标准库中的协议---// //1.实例的比较:判断两个实例值是否相同 let a = 4, b = 4 a == b //(Int类型的比较) //自定义结构体类型,进行是否相等的比较 struct Games { var winCount : Int var loseCount : Int } let g1 = Games(winCount: 2

c/c++标准库中的文件操作总结

1 stdio.h是c标准库中的标准输入输出库 2 在c++中调用的方法 直接调用即可,但是最好在函数名前面加上::,以示区分类的内部函数和c标准库函数. 3 c标准输入输出库的使用 3.1 核心结构体 FILE结构体 打开一个文件的时候获取它,然后就可以不用管它了. 3.2 核心方法 3.2.1 fopen 第一个字符串是文件的路径. 第二个参数是一个字符串,表示操作该文件的模式,"rb"表示read binary,即以二进制的形式来读该文件. 3.2.2 fseek 第一个参数是F

C++标准库中next_permutation和pre_permutation实现原理

标准库中next_permutation函数:找当前序列中元素排列的下一个排列,按照字典顺序进行排列.比如说数组排列"123",那么它的下一个排列为"132",并且返回true.如果当前序列没有下一个排列,我们返回false,且把当前排列置为最小的排列,比如说:排列"321",因为该排列已经是最大的排列,所以它没有下一个排列.我们把该排列置为"123",并且返回false. 标准库实现两个重载版本的next_permutati

swift标准库中常见的55个协议。

swift标准库中常见的55个协议. 从协议名结尾字面上我们可以将Protocol分为able.Type.Convertible 三类 从功能角度上来讲的话可以总结为: 功能添加型(able结尾) "可以做什么?" 举例: Hashable: 给你的实例添加一个生成哈希值的功能. Equatable: 给你的实例添加一个判断相等的功能. 2.类型对比型(Type结尾) "这个实例是什么?" 举例: CollectionType: swift中所有的集合类型都要遵守的

c++ 标准库的各种容器(vector,deque,map,set,unordered_map,unordered_set,list)的性能考虑

转自:http://blog.csdn.net/truexf/article/details/17303263 一.vector vector采用一段连续的内存来存储其元素,向vector添加元素的时候,如果容量不足,vector便会重新malloc一段更大的内存,然后把原内存中的数据memcpy到新的内存中,并free原内存块,然后将新元素加入.vector的元素插入性能跟以下几个要素关系重大: 1. 插入的位置 头部插入:将所有元素后移,然后将新元素插入 中间插入:将插入点后面的元素后移,然

《深入实践C++模板编程》之五——容器与迭代器

1.容器的定义 容器:专门用于某种形式组织及存储数据的类称为“容器”. 2.容器与迭代器 迭代器:封装了对容器虚拟数据序列的操作并按约定提供统一界面以遍历容器内容的代理类即为迭代器. 举例理解: template<typename T> class list; template<typename T> struct list_node { typedef T value_type; typedef T& reference_type; typedef const T con

C++11标准库中cstdio头文件新增的5个格式化I/O函数学习

刚开始学网络编程,稍微扩展书上的简单C/S程序时,发现以前太忽略标准I/O这一块,查官网发现C++11新增了几个格式化I/O函数. snprintf    将格式化输出写入到有大小限制的缓存中 vfscanf     从流中读取数据到可变参数列表中 vscanf      读取格式化数据到可变参数列表中 vsnprintf  从可变参数列表中写入数据到有大小限制的缓存中 vsscanf     从字符串中读取格式化数据到可变参数列表中 主要谈谈snprintf,后面4个都是辅助可变参数列表的.