• STL对比解说——顺序容器


    STL对比解说——顺序容器

     

    1. 顺序容器种类

      (1)  array(since TR1, C++11)

      (2)  vector

      (3)  deque

      (4)  list(singly(since C++ 11) and double linked)

      注:本文主要讲解:array, vector, deque, list,forword_list五个。

                vector<bool>(在开头叙述,后面讲解不包括它)

               string另作叙述

       //array: 静态数组,提供了STL容器接口的普通C-style array的外裹类。

      //具有静态大小,不能添加也不能删除元素。只能替换元素。

      #include <array>

      namespace std {

        template <typename T, size_t N>                   //N: 非类型模版形参:编译时常量表达式

        class array;

      }

      //可以swap,但是是线性复杂度。不交换内部指针,而是交换元素。

      //arr1 = std::move(arr2);        //c = rc;方式

      //array<elem, 0> arr; 也可以,arr常是empty。使用begin(), end()等产生唯一的值,但是不可以解引用,如:arr.front(), arr.back()

      //特有的:array提供tuple interface: tuple_size<>::value, tuple_element<>::type, get<>() 等。

      //vector: 动态数组,使用动态C-style数组管理元素的抽象(但是,标准没说一定要用动态数组实现)

      #include <vector>

      namespace std {

      template <typename T,

        typename Allocacator = allocator<T> >           //默认配置器:std::allocator, 实际中不一定使用它。vs使用,gcc不用。下同

      class vector;
      }

      //特有的:vector具有容量

      //在插入元素时,元素大小超过容量时,需要重新分配内存(reallocator)

      //reallocator问题:

      reallocator 使所有引用,指针,迭代器失效

      reallocator浪费时间

          //解决方法:

      可以预先分配内存reserve(size)

      可以直接给出大小值:

      //size() == 3, 使用3次T的默认构造函数。内置类型初始化为0

      //但是若T比较复杂,初始化可能比较费时间,所以最好用reserve()

      Vector<T> ivec(3);     

      Vector<T> ivec.resize(3);

      //string也有容量,与vector类似。但是,

      vector不可以使用reserve(size)压缩容量,当sie小于vector现有容量时no-op。

      vector的内存和速度方面的优化由实现定义。现在的许多实现为了避免内存碎片,第一次插入元素时会分配一块内存(such as 2K),比较浪费内存。

      //C++11中可以压缩内存

      vec.shrink_to_fit();            //不保证之后capacity() == size()一定成立,大多数都可以(vs, gcc都可以)。

      C++11之前:

      vector<T>(v).swap(v);  

      vector<T>().swap(v);       //清空vector使用的所有内存

         

      //deque: 使用动态数组管理内存,与vector类似,只不过有两个端口。

      deque典型的是用一组独立的块实现,第一个块在一个方向增长,最后一个块在另一个方向

      #include <deque>

      namespace std {

        template <typename T,

        typanema Allocator = alloacator<T> >

      class deque;

      }

      //特点

      在两端插入删除更快

      迭代器是smart point不能是普通指针,因为它需要在不同块间跳跃,因此访问元素是一种间接的方式,元素访问和移动有一点点慢

      系统的内存块有大小限制时,deque可以使用不止一个内存块,因此max_size()可能更大

      deque不能控制容量,任何在非首尾的插入和删除操作都会使所有的指针,引用,迭代器失效。重新分配可能比vector更快,因为它不需要复制所有元素。

      内存块在不使用时可能被释放,deque的内存大小可能减小(但是,具体由实现指定)。

      //list: 使用双向链表管理元素

      #include <list>

      namespace std {

      template <typename T,

        typename Allocactor = allocator<T> >

      class list;
      }

      //改变元素顺序方面有优势,在这方面有一些特殊成员函数,速度要比algorithm中相同的要快

      c.unique()              //删除相邻元素的重复

      c.unique(op)

      //这三项在forward_list中为splice_after

      c.splice(pos, c2)         //移动c2中所有元素到c中pos之前,之后c2变为空

      c.splice(pos, c2, c2pos)

      c.splice(pos, c2, c2beg, c2end)

      c.sort()     

      c.sort(op)

      c.merge(c2)           //移动c2中元素到c中,若c和c2都是已序的,合并后仍为已序。之后,c2为空

      c.merge(c2, op)

      c.reverse()             //元素顺序反向

      //forward_list:  since C++11单向链表管理元素

      #include <forward_list>

      namespace std {

      template <typename T,

        typename Allocator = allocator<T> >

      class fordward_list;
      }

      //特点:比list消耗更少内存,更好的运行时行为。sizeof(forward_list) = 4(list=8), 内部只有一个指针,指向首元素标准描述:相对于手写的list,forward_list没有更多空间和时间消耗。

      //相对于list的限制:

      单项链表仅有前向迭代器(forward iterator)。因此,它没有反向迭代器的支持,缺少reverse_iterator, rbegin(), rend(), crbegin(), crend()。

      没有size(),由于sizeof()具有线性复杂度,考虑到性能,舍弃。

      没有最后一个元素的指针,因此,缺少back(), push_back(), pop_back()。

      由于迭代器不能向前走,用后缀_after作为某些成员函数名字。insert_after代替insert, 还另外提供了before_begin(), cbefore_begin()产生第一个元素之前的位置

      //改变元素顺序方面和list一样有优势,在这方面有一些特殊成员函数,速度要比algorithm中相同的要快,参见list,注意splice_after()

      //vector<bool>, 后面讲述不包括它

      每个元素使用1 bit存储,由于最小的地址位要有1 byte大小,因此需要对引用和迭代器进行特殊处理。

      不满足其它vector的要求,性能可能比通常的是实现更差,因为元素操作需要转换为位操作。

      vector<bool>具有动态大小,可以增加和删除bits, 若需要静态大小则用bitset。

      c.flip();            //所有元素取反

      c[idx].flip();     //某个元素取反

      c[idx] = val;

      c[idx] = c[idx2];

      //可以使用[], at(), front(), back()

     

    2. 初始化

      //vector, deque, list, forward_list提供的

      Container c;      //容器都为空, 值采用默认构造函数。注释为红色说明较常用,下同。

      Container c(c2);    //Copy constructor; 创建另一个容器的副本

      Container c = c2;      //Copy constructor

      Container c(n);     //创建n个使用默认构造函数初始化元素的容器,容器size()=n

      Container c(n, elem); //创建n个elem元素的容器

      Container c(beg, end); //使用一对迭代器中元素进行初始化

      //since C++ 11

      Container c(rv);        //Move constructor; 创建一个rvalue rv 内容的新容器

      Container c =rv;     //Move constructor

      Container c(initlist);  //使用初始化列表中的元素进行初始化

      Container c = initlist;  //初始化列表

      

      //array(POD类型),上面红色array不提供的

      Container<Elem, N> c;      //默认构造函数初始化元素(int:0, string:""等等)

      Container<Elem, N> c(c2);    //Copy constructor; 创建另一个容器的副本

      Containe<Elem, N>r c = c2;      //Copy constructor

      Container<Elem, N> c(rv);        //Move constructor; 创建一个rvalue rv 内容的新容器

      Container<Elem, N> c =rv;     //Move constructor

      Container<Elem, N> c = initlist; //初始化列表,array<Elem, N>类似于普通数组的初始化,不属于C++11

      注:array<Elem, N>有一些特殊的性质,某些初始化方式不提供。

        array是一个静态数组,是提供了STL容器接口的普通C-style array的外包(wrap)类。

        是一个常量大小的顺序容器不能删除元算,改变容器大小,但是可以置换元素

        因此除了初始化使容器添加元素外,还可以c=c2, c=rv, fill(),swap()进行赋值,不存在替他方式。

     

    3. 赋值

      //vector, deque, list, forward_list提供的

      c = c2;

      c = rv;        //Move 赋值rvalue rv到c,since C++11

      c = initlist;      //since C++11

      c.assign(n, elem);  //使用相应的constructor

      c.assign(beg, end);  //使用相应的constructor

      c.assign(initlist);   //使用相应的constructor

      c1.swap(c2);

      swap(c1, c2);

      

      //array特殊性

      不提供以下:

      c = initlist;      //需要constructor(initlist),隐式转换initlist为array类型,但是array不提供

      c.assign(n, elem);  //使用相应的constructor,但是array缺少

      c.assign(beg, end);  //使用相应的constructor,但是array缺少

      c.assign(initlist);   //使用相应的constructor,但是array缺少

      提供一个:

      array.fill(val);    //将array中所有元素赋值为val

      

      注:c = c2, 当赋值后不再使用c2时,应该使用c = rv右值语义

        当要交换容器元素时应使用swap

        当要赋值时,尽量使用assign, c=initlist这种范围赋值的方式,避免一个一个的赋值。

     

    4. 插入

       //vector,deque,list都提供的

      c.push_back(elem);    //在结尾添加元素elem的副本

      c.insert(pos, elem);

      c.insert(pos, n, elem);

      c.insert(pos, beg, end);

      //since C++11

      c.insert(pos, initlist);

      c.emplace(pos, args...);

      c.emplace_back(args...);

     

       //对于dequelist这种双头增加

      c.push_front(elem);  

      c.emplace_front(args...);  

      //forward_list提供特殊的_after, insert只能_after, 只能在_front放置元素

          不提供c.push_back(), 提供

      c.push_front(elem);                            //只能在前端插入,不能在后端

          c.insert_after(pos, elem);

      c.insert_after(pos, n, elem);

      c.insert_after(pos, beg, end);

      //since C++11

      c.insert_after(pos, initlist);

      c.emplace_after(pos, args...);

      c.emplace_front(args...);                  //只能在前端插入,不能在后端

          注:对于dequelist这种双头和forward_list都提供

      c.push_front(elem);  

      c.emplace_front(args...);  

      //对于array不提供插入元素,可以初始化,赋值改变容器元素。不提供任何其它变动性操作。

    5. 访问

       //随机访问迭代器的容器arrayvector deqe:

      c[idx];    //返回索引的元素值,不检测参数有效性

      c.at(idx);   //唯一的检测参数有效性的函数

      c.front();

      c.back();

      //双向迭代器的容器list, 不能随机访问 

      c.front();

      c.back();

          //前向迭代器的forward_list, 不能随机访问

          fwlist.front();

      //通用的访问方式:迭代器

      c.begin();

      c.end();

      c.rbegin();

      c.rend();

      //since c++11, forward_list不提供反向

      c.cbegin();

      c.cend();

      c.crbegin();

      c.crend();

      //forward_list提供before_begin(),只有一个指针只向开始,不知结尾在哪

      fwlist.before_begin();              //返回第一个元素位置之前的前向迭代器

      fwlist.cbefore_begin();

      //array(since C++11), vector(since C++11), string提供特殊的data(), 返回首元素地址

    6. 删除

       //vector, deque, list都支持的操作

      c.pop_back();    //删除最后一个元素,但是不返回它,返回值void

      c.erase(pos);    //删除迭代器所指元素,返回下一个元素位置

      c.erase(beg, end);  //删除[beg, end)所指元素,返回下一个元素位置

      c.clear();      //清空容器

      

      //双头的deque,list,增加

      c.pop_front();    //删除第一个元素,不返回它, 一般front(), pop_front()

      

        //forward_list特殊的_after,但是没有_back

          fwlist.pop_front();

          fwlist.erase_after(pos);

          fwlist.erase_after(beg, end);

          fwlist.clear();

      //以上是根据位置删除元素要想根据值删除一个元素:

      //对于listforward_list(链表删除元素有自己特殊的方式):

      list.remove(val);    //删除值等于val的所有元素

      list.remove_if(op);  

       //对于vector,deque

      //删除所有值为val(erase-remove)

      //remove()移动val后面的元素覆盖val的值,

      //返回移除后的最后一个元素的位置,

      //最后容器大小不改变。

      //原因:算法不了解他所操作的容器,不能直接删除值

      //好处:可以使算法适应各种容器

      //         可以使用容器的一段区间作为操作区间

      c.erase(remove(c.begin(), c.end(), val), c.end());

      

      //删除第一个值val的操作

      c::iterator pos = find(c.begin(), c.end(), val);

      if (pos != c.end()) {

        c.erase(pos);
      }

      

    7. 修改

      可以通过:查找 + 访问,或者algorithm中算法加以修改。

     

    8. 查找

      //由于所有顺序容器本身无特殊结构,使用algorithm中的find,不做多余叙述

    9. 排序

      //array,vector,deque

      使用algorithm中sort,开优化条件下性能优于qsort。

      //list, forward_list结构特殊,使用自己的sort

      list.sort();

      list.sort(op);

    10. 大小

       //vector,deque, list都提供的,array当然不提供啦

      c.empty();      //可能比size() == 0更快

      c.size();                     

      c.max_size();     //在没有reallocation情况下,最大可能的元素数目,由于deque采用分块的结构,它的这个值可能更大

          注:forward_list没有size(), 有另外两个。因为,这需要线性复杂度,不符合设计初衷,可以使用distance()获得大小。

      //改变元素数目,使用默认构造函数初始化

      //当现有元素数目<num时,增加元素数目到num使用默认构造函数初始化多的元素

      //现有元素数目>num,元素数目变为num多的元素删除

      c.resize(num);  

      c.resize(num, elem);  //多的元素初始化为elem

       //特有的: vector具有容量概念,提供capacity();reserver(num);

      //deque在非开头和结尾插入元素时,所有的指针,引用,迭代器失效,

      //但是由于它特殊的内部结构,重新分配时比vector快,因为它不是必须复制所有的元素。所以它也不需要提供容量概念。

      //deque在不在被使用时,块的内存可能被释放,它的内存大小可能减少(是否一定减少根据根据具体实现)。

      //list为链表插入时不会引起重新分配内存,不需要容量概念。

      c.capacity();

      c.reserver(num);  //为容器分配num*元素大小容量的空间,但是没有初始化

       //vector,deque提供,listforward_list不需要

      c.shrink_to_fit();  //since C++11,减小容量到可以存放容器中所有元素。

      

    11. 算法

      对于顺序容器本身不提供的操作,多数都可以通过algorithm实现

    12. 常见操作

      //在大多数情况下,选用array或者vector

      //不需要动态改变大小时:array

      #include <array>

      using std::array;

      //在知道初值的情况下,使用初值进行初始化,例如:

      //array<int, 5>作为POD类型,可以静态初始化,与C中struct具有相同的内存布局。

      //此时采用的静态初始化方式,{1, 2, 3, 4, 5}就是arr在内存中布局方式。

      //从&arr -- &arr + sizeof(arr)的内存空间就是arr中数据的内存布局,

      //arr内存都在栈上,这与string,vector等有很大不同,

      //它们的对象本身内存可能在栈上,但是代表的数据是在堆上分配(使用指针代表分配的空间)。

      array<int, 5> arr = {1, 2, 3, 4, 5};              //若是array<string, 5>就不是POD类型, 也不是triviall类型,is_standard_layout。因为string不是POD。

      //ERROR

      array<int, 5> arr({1, 2, 3, 4, 5});  //没有这种初始化方式,其它容器可以。

      //不知道初值

      array<int, 5> arr = { };    //所有元素初始化为0(int()),使用默认构造函数

      array<int, 5> arr;                   //arr是未定义的值,int为POD类型,具有未定义的初值。POD生存期: 对象被定义 —— 被释放或者被重新利用。

      array<string, 5> arr;               //arr有5个空的string。string非POD类型,生存期: 构造函数 —— 析构函数

      arr.fill(val);          //所有元素赋值为val

      //对于array<int, 5>这种POD

      memset(&arr,  val, sizeof(arr));

      memcpy(&arr, src_des, sizeof(arr));

      memmove(&arr, src_des, sizeof(arr));

      //需要动态改变大小vector

      vector<int> ivec;      //ivec为空,就是没有元素,此时对ivec的所有元素访问都是错误的。

      //一般在知道vector需要使用的大小时,需要

      ivec.reserve(大小值);    //避免可能的内存重新分配

      //给容器添加元素,若允许,就使用assign(调用构造函数),一般vector为类成员时

      ivec.assign(n, elem);

      ivec.assign(beg, end);

      ivec.assign(initlist);

      ivec = initlist;

      //一般情况下:

      ivec.push_back(elem);   //添加elem

      

      //arrayvector当作普通数组使用

      //arrayvector中元素在连续的内存单元中

      &arr[i] = &arr[0] + i;    //只是当作地址,不能当作指针。数组名可以当作首地址,array,vector对象不可以,它们是类。

          //对于array,可以

          &arr[i] = &arr + i;        // vector不可以

          //但是这样不可以,arr是一种数据类型,不是数组不会退化为首元素地址

          &arr[i]  != arr + i;    //数组可以

      //作为C-style array

      array<char, 50> arr = {0};  or vector<char> arr;  arr.reserve(50);

      strcpy(&arr[0], "hancm, hi");  

      printf("%s ", &arr[0]);

      <=> printf("%s ", arr.data());  //since c++11使用arr.data()获取首地址,更好的。

      注意:

      printf("%s ", arr.begin());    //不保证可以工作,array和vector的迭代器类型大多数为普通的指针,但是不是所有的都是(某些为一个类)。

      //当需要:在头和尾插入和删除元素时(最常用情况)

             不需要引用容器的元素

           在不再使用时,看重容器是否释放内存(但是标准没有保证这点)

      //时,使用deque

      //deque的接口与vector大部分相同,但是不提供容量概念,即capacity(), reserve(),可以选择使用resize();

      //当需要:不只是在容器的头和尾中插入和删除元素(插入和删除元素在常量时间内完成)

             想要绝大多数操作要么成功,要么no—op(这是list支持的异常处理方式,另外的不具有)

      //时,使用list

      //list是双向迭代器,不提供[]at(),不提供容量capacity(),reserve,因为不需要的。它的每个元素都有自己的内存空间。

      //移动和删除元素方面,list提供了一些特殊成员函数,它们都比算法中相同的函数更快,因为list只需重定向指针,不需要复制和移动元素

      //使用方式与vector类似。

  • 相关阅读:
    mongodb时间戳转换成格式化时间戳
    Python批量删除指定目录下的指定类型的文件
    Java将list数据导出到Excel——(八)
    Java读取Excel文件转换成JSON并转成List——(七)
    Java获取资源路径——(八)
    Java的IO流——(七)
    Java用System读取系统相关信息、环境变量——(六)
    POI导出带格式的Excel模板——(六)
    POI读取Excel(xls、xlsx均可以)——(四)
    POI导出Excel(xls、xlsx均可以,也支持图片)——(三)
  • 原文地址:https://www.cnblogs.com/hancm/p/3642730.html
Copyright © 2020-2023  润新知