<Effective STL>笔记--容器

来源:互联网 发布:js控制视频播放 编辑:程序博客网 时间:2024/06/07 21:46

1 容器
~~~~~~~

1.1 仔细选择容器
=================
   1. C++中的容器回顾
      * 标准STL序列容器:
        vector、string、deque和list。
      * 标准STL关联容器:
        set、multiset、map和multimap。
      * 非标准序列容器:
        slist(slist是一个单向链表)和rope(rope本质上是一个重型字符串)
      * 非标准关联容器:
        hash_set、hash_multiset、hash_map和hash_multimap。
      * 几种标准非STL容器:
        数组、bitset、valarray、stack、queue和priority_queue。
   2. 连续内容容器和基于节点的容器的区别
      * 连续内容容器(也叫基于数组的容器)在一个或多个(动态分配)的内存块中保持它们的元素.
        连续内存容器包括vector,string和depue
      * 基于节点的容器在每个内存块(动态分配)中只保存一个元素。
        基于节点的容器包括list,slist,所有的标准关联容器(一般实现是用平衡树实现的)和非标准的散列容器(不同的基于节点的实现)
   3. 选用何种容器的标准
      * 你关心元素在容器中的顺序吗?
        如果不,散列容器就是可行的选择。否则,你要避免使用散列容器。
      * 你需要哪一类迭代器?
        如果必须是随机访问迭代器,在技术上你就只能限于vector、deque和string,但你也可能会考虑rope。
        如果需要双向迭代器,你就用不了slist和散列容器的一般实现
      * 容器中的数据的内存布局需要兼容C吗?
        如果是,你就只能用vector
      * 你介意如果容器的底层使用了引用计数吗?
        如果是,你就得避开string,因为很多string的实现是用引用计数。你也不能用rope,因为权威的rope实现是基于引用计数的。
        于是你得重新审核你的string,你可以考虑使用vector<char>。
      * 你需要插入和删除的事务性语义吗?也就是说,你需要有可靠地回退插入和删除的能力吗?
        如果是,你就需要使用基于节点的容器。如果你需要多元素插入(比如,以范围的方式)的事务性语义,你就应该选择list,因为list是唯一提供多元素插入事务性语义的标准容器。
      * 你要把迭代器、指针和引用的失效次数减到最少吗?
        如果是,你就应该使用基于节点的容器,因为在这些容器上进行插入和删除不会使迭代器、指针和引用失效(除非它们指向你删除的元素)。
        一般来说,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针和引用失效。
      * 你需要具有有以下特性的序列容器吗:1)可以使用随机访问迭代器;2)只要没有删除而且插入只发生在容器结尾,指针和引用的数据就不会失效?
        这个一个非常特殊的情况,但如果你遇到这种情况,deque就是你梦想的容器。
        有趣的是,当插入只在容器结尾时,deque的迭代器也可能会失效,deque是唯一一个“在迭代器失效时不会使它的指针和引用失效”的标准STL容器。

1.2 小心写"与容器类型无关"的代码
=================================
   1. 写既要和序列容器又要和关联容器一起工作的代码并没有什么意义.
      因为很多成员函数只存在于其中一类容器中.不同的容器是不同的,而且它们的优点和缺点有重大不同。它们并不被设计成可互换的,而且你做不了什么包装的工作。
   2. nultimap不存在operator[]
   3. 若存在改变容器类型的可能.
      可以使用typedef封装容器类型.
      也可以新建一个类,在类中定义一个隐藏的容器对象.通过类的接口限制容器特殊信息可见性的数量

1.3 使容器里对象的拷贝操作轻量且正确
=====================================
   1. 当你从容器中获取一个对象时,你所得到的对象不是容器里的那个对象.而是拷贝.
      当你向容器中添加一个对象,进入容器的是你指定的对象的拷贝.
      拷进去,拷出来。这就是STL的方式.
   2. 在容器中插入删除操作或使用排序算法等会移动容器元素的操作,也会引发元素的拷贝.
   3. 把一个派生类对象插入基类对象的容器几乎总是错的,因为对象在拷贝入容器时会产生分隔

1.4 用empty来代替检查size()是否为0
===================================
   1. 你应该首选empty的构造,而且理由很简单:对于所有的标准容器,empty是一个常数时间的操作,但对于一些list实现,size花费线性时间。

1.5 尽量使用区间成员函数来代替它们的单元素兄弟
===============================================
   1. 尽量使用区间成员函数代替它们的单元素兄弟的理由。
      * 一般来说使用区间成员函数可以输入更少的代码。
      * 区间成员函数会导致代码更清晰更直接了当。
      * 当处理标准序列容器时,应用单元素成员函数比完成同样目的的区间成员函数需要更多地内存分配,更频繁地拷贝对象,而且/或者造成多余操作.
        1) 第一种税在于没有必要的函数调用。
           把numValues个元素插入v,每次一个,自然会花费你numValues次调用insert。
           而区间成员函数只需一次调用开销
        2) 无效率地把v中的现有元素移动到它们最终插入后的位置的开销。
           每次调用insert来增加一个新元素到v,插入点以上的每个元素都必须向上移动一次来为新元素腾出空间。这就移动了numValues次.
           而区间成员函数只需一次移动开销
        3) 插入数据时可会会有内存分配的开销.
           多次插入数据可能会有多次内存分配的开销.
           区间分配使得只需要分配一次内存
   2. string和vector在元素数目减少时,不会自动缩减内存分配

1.6 警惕C++最令人恼怒的解析
============================
   1. 几乎任何东西都可能被分析成函数声明.
  //假设你有一个int的文件,你想要把那些int拷贝到一个list中。这看起来像是一个合理的方式:
 

  1. ifstream dataFile("ints.dat"); 
  2.   list<int> data(istream_iterator<int>(dataFile),   // 警告!这完成的并不 
  3.   istream_iterator<int>());         // 是像你想象的那样 



      事实上,这声明了一个函数data,它的返回类型是list<int>。这个函数data带有两个参数:
      第一个参数叫做dataFile。它的类型是istream_iterator<int>。dataFile左右的括号是多余的而且被忽略。
      第二个参数没有名字。它的类型是指向一个没有参数而且返回istream_iterator<int>的函数的指针。
      正确的做法是
 

  1. ifstream dataFile("ints.dat"); 
  2. istream_iterator<int> dataBegin(dataFile); 
  3. istream_iterator<int> dataEnd; 
  4. list<int> data(dataBegin, dataEnd); 


1.7 当使用new的指针的容器时,记得在销毁容器后delete那些指针
===========================================================
   1. auto_ptr在C++标准中时禁止插入容器的,虽然有些编译器允许容器中插入auto_ptr,但这是不可移植的.
   2. 由于auto_ptr复制方面的特性,在调用容器的方法后,可能会使原元素设为NULL

1.8 从容器中删除元素时,仔细选择不同的策略
==========================================
   1. 去除一个容器中有特定值的所有对象:
      * 如果容器是vector、string或deque,使用erase-remove惯用法。
 

  1. c.erase(remove(c.begin(), c.end(), 1963),       // 当c是vector、string 
  2.         c.end());                               // 或deque时, 
  3.                                         // erase-remove惯用法 
  4.                                         // 是去除特定值的元素 
  5.                                         // 的最佳方法 



      * 如果容器是list,使用list::remove。
      * 如果容器是标准关联容器,使用它的erase成员函数。
   2. 去除一个容器中满足一个特定判定式的所有对象:
      * 如果容器是vector、string或deque,使用erase-remove_if惯用法。
      * 如果容器是list,使用list::remove_if。
      * 如果容器是标准关联容器,使用remove_copy_if和swap,或写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它。
        要注意,关联容器中删除一个元素后,指向该元素的iterator会失效,故采用后置递增.在iterator被删除前递增,并将递增前的值传给erase
   3. 在循环内做某些事情(除了删除对象之外):
      * 如果容器是标准序列容器,写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你的迭代器。
        要注意,序列容器中删除一个元素后,指向该元素的iterator及之后的所有迭代器都会失效,但erase会返回一个iterator,指向被删除元素的后一个元素.
      * 如果容器是标准关联容器,写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它。

1.9 注意allocator的协定和约束
==============================
   看不懂

1.10 理解自定义allocator的正确用法
===================================
   看不懂

1.11 对STL容器的线程安全性的期待现实一点
=========================================
   1. STL容器对多线程的支持为
      * 多个读取者是安全的:
        多线程可能同时读取一个容器的内容,这将正确地执行。当然,在读取时不能有任何写入者操作这个容器。
      * 对不同容器的多个写入者是安全的:
        多线程可以同时写不同的容器。
 

本文出自 “暗日” 博客,请务必保留此出处http://darksun.blog.51cto.com/3874064/1162469

0 0
原创粉丝点击