《Effective STL》学习笔记

该篇笔记转自以下两个连接:(另外附件里有STL源码分析和编写高质量代码:改善C++程序的150个建议)

http://my.csdn.net/swordll80

  http://blog.csdn.net/pizi0475/article/details/5382117

 

 

条款1:仔细选择你的容器
了解各种容器的实现方法,知道各种容器的内存管理方式、各种操作所对应的底层操作,然后根据需要选择恰当的容器。
条款2:小心对“容器无关代码”的幻想
       各种容器支持的操作集合不同、各容器相同操作的特性不同。
条款3:使容器里对象的拷贝操作轻量而正确
       一个使拷贝更高效、正确而且对分割问题免疫的简单的方式是建立指针的容器而不是对象的容器。
条款4:用empty来代替检查size()是否为0
       不管发生了什么,如果你用empty来代替检查是否size() == 0,你都不会出错。所以在想知道容器是否包含0个元素的时候都应该调用empty。另外,有的容器调用size()耗时比较长。
条款5:尽量使用区间成员函数代替它们的单元素兄弟
       看懂这行代码:v1.assign(v2.begin() + v2.size() / 2, v2.end());
几乎所有目标区间被插入迭代器指定的copy的使用都可以用调用的区间成员函数的来代替。 区间构造、区间插入、区间删除、区间赋值。
条款6:警惕C++最令人恼怒的解析
       语法混乱。
条款7:当使用new得指针的容器时,记得在销毁容器前delete那些指针
       记得new完了要delete,但管理方法可以选择更多。
struct DeleteObject { 
        template<typename T>
        void operator()(const T* ptr) const {         delete ptr; }
}
for_each(dssp.begin(), dssp.end(),DeleteObject());
typedef boost::shared_ ptr<Widget> SPW;
vector<SPW> vwp;
for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)
               vwp.push_back(SPW(new Widget));
条款8:永不建立auto_ptr的容器
       auto_ptr的容器(COAPs)是禁止的。试图使用它们的代码都不能编译。智能指针的容器是很好的, 条款50描述了你在哪里可以找到和STL容器咬合良好的智能指针。只不过auto_ptr不是那样的智能指针。
条款9:在删除选项中仔细选择
c.erase(remove_if(c.begin(), c.end(), badValue), c.end());// 当c是vector、string或deque时这是去掉badValue返回真的对象的最佳方法
c.remove_if(badValue); // 当c是list时这是去掉badValue返回真的对象的最佳方法
去除一个容器中有特定值的所有对象:
如果容器是vector、string或deque,使用erase-remove惯用法。
如果容器是list,使用list::remove。
如果容器是标准关联容器,使用它的erase成员函数。
去除一个容器中满足一个特定判定式的所有对象:
如果容器是vector、string或deque,使用erase-remove_if惯用法。
如果容器是list,使用list::remove_if。
如果容器是标准关联容器,使用remove_copy_if和swap,或写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它。
在循环内做某些事情(除了删除对象之外):
如果容器是标准序列容器,写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你的迭代器。
如果容器是标准关联容器,写一个循环来遍历容器元素,当你把迭代器传给erase时记得后置递增它。
条款10:注意分配器的协定和约束
把你的分配器做成一个模板,带有模板参数T,代表你要分配内存的对象类型。
提供pointer和reference的typedef,但是总是让pointer是T*,reference是T&。
决不要给你的分配器每对象状态。通常,分配器不能有非静态的数据成员。
记得应该传给分配器的allocate成员函数需要分配的对象个数而不是字节数。也应该记得这些函数返回T*指针(通过pointer typedef),即使还没有T对象被构造。
一定要提供标准容器依赖的内嵌rebind模板。
条款11:理解自定义分配器的正确用法
       自定义分配器,暂时不打算这样做,俺水平不够。
条款12:对STL容器线程安全性的期待现实一些
       多个读取者是安全的。对不同容器的多个写入者是安全的。Stl的线程安全性由用户自己实现。

 条款13:尽量使用vectorstring来代替动态分配的数组

 

       原因是简单省事。

条款14:使用reserve来避免不必要的重新分配

       避免不必要的内存重新分配。

条款15:小心string实现的多样性

       字符串值可能是或可能不是引用计数的。

string对象的大小可能从1到至少7char*指针的大小。

新字符串值的建立可能需要012次动态分配。

string对象可能是或可能不共享字符串的大小和容量信息。

string可能是或可能不支持每对象配置器。

不同实现对于最小化字符缓冲区的配置器有不同策略。

条款16: 如何将vectorstring的数据传给遗留的API

可以这样if (!v.empty()) {  doSomething(&v[0], v.size());}

v.begin()不能代替&v[0]

可以这样doSomething(s.c_str());

条款17:使用交换技巧来修整过剩容量

vector<Contestant> v;

string s;

...                                   // 使用vs

vector<Contestant>().swap(v);          // 清除v而且最小化它的容量

string().swap(s);      // 清除s而且最小化它的容量

看看源代码或许明白,现在先记着。

条款18:避免使用vector<bool>

       vector<bool>不满足STL容器的必要条件,你最好不要使用它;而deque<bool>bitset是基本能满足你对vector<bool>提供的性能的需要的替代数据结构。

 

条款19:了解相等和等价的区别
       操作上来说,相等的概念是基于operator==的。如果表达式“x == y”返回true,x和y有相等的值,否则它们没有。等价是基于在一个有序区间中对象值的相对位置。两个对象x和y如果在关联容器c的排序顺序中没有哪个排在另一个之前,那么它们关于c使用的排序顺序有等价的值。!(w1 < w2) && !(w2<w1)      要完全领会相等和等价的含义,考虑一个忽略大小写的set<string>,也就是set的比较函数忽略字符串中字符大小写的set<string>。这样的比较函数会认为“STL”和“stL”是等价的。
条款20:为指针的关联容器指定比较类型
struct DereferenceLess {
template <typename PtrType>
bool operator()(PtrType pT1, PtrType pT2) const                                           {return *pT1 < *pT2; }
};
typedef set<string*, DereferenceLess > StringPtrSet;
StringPtrSet ssp;
set不要一个函数,它要的是能在内部用实例化 建立函数的一种类型(仿函数模板)。
条款21: 永远让比较函数对相等的值返回false
       除非你的比较函数总是为相等的值返回false,你将会打破所有的标准关联型容器,不管它们是否允许存储复本。从技术上说,用于排序关联容器的比较函数必须在它们所比较的对象上定义一个“严格的弱序化(strict weak ordering)”。任何一个定义了严格的弱序化的函数都必须在传入相同的值的两个拷贝时返回false。
条款22:避免原地修改set和multiset的键
       对于set和multiset,如果你进行任何容器元素的原地修改,你有责任确保容器保持有序。
条款23:考虑用有序vector代替关联容器
       在有序vector中存储数据很有可能比在标准关联容器中保存相同的数据消耗更少的内存;当页面错误值得重视的时候,在有序vector中通过二分法查找可能比在一个标准关联容器中查找更快。当然,有序vector的大缺点是它必须保持有序!当一个新元素插入时,大于这个新元素的所有东西都必须向上移一位。
条款24:当关乎效率时应该在map::operator[]和map-insert之间仔细选择
       m[k] = v; 检查键k是否已经在map里。如果不,就添加上,以v作为它的对应值。如果k已经在map里,它的关联值被更新成v。通过使用map-insert代替map::operator[]就能节省一些。
条款25:熟悉非标准散列容器
       hash_set、hash_multiset、hash_map和hash_multimap
条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator
    如果你尽量使用iterator代替const或reverse类型的迭代器,可以使得容器的使用更简单,更高效而且可以避免潜在的bug。
条款27:用distance和advance把const_iterator转化成iterator
       advance(i, distance<ConstIter>(i, ci)); distance返回两个指向同一个容器的iterator之间的距离;advance则用于将一个iterator移动指定的距离。
条款28:了解如何通过reverse_iterator的base得到iterator
        reverse_iterator的base成员函数返回一个“对应的”iterator的说法并不准确。对于插入操作而言,的确如此;但是对于删除操作,并非如此。
v.erase((++ri).base());// 删除ri指向的元素,这是删除一个由reverse_iterator指出的元素时首选的技巧
条款29:需要一个一个字符输入时考虑使用istreambuf_iterator
ifstream inputFile("interestingData.txt");
string fileData((istreambuf_iterator<char>(inputFile)),              istreambuf_iterator<char>());
istreambuf_iterator不忽略任何字符。相对于istream_iterator,它们抓取得更快。

条款30:确保目标区间足够大

results.reserve(results.size() + values.size());

transform(values.begin(), values.end(),back_inserter(results),

transmogrify);

在内部,back_inserter返回的迭代器会调用push_back,所以你可以在任何提供push_back的容器上使用back_inserter。如果你想让一个算法在容器的前端插入东西,你可以使用front_inserter。在内部,front_inserter利用了push_front,所以front_insert只和提供那个成员函数的容器配合。inserter允许你强制算法把它们的结果插入容器中的任意位置。

transform(values.begin(), values.end(),results.begin(),transmogrify);//覆盖

条款31:了解你的排序选择

如果你需要在vectorstringdeque或数组上进行完全排序,你可以使用sortstable_sort

如果你有一个vectorstringdeque或数组,你只需要排序前n个元素,应该用partial_sort

如果你有一个vectorstringdeque或数组,你需要鉴别出第n个元素或你需要鉴别出最前的n个元素,而不用知道它们的顺序,nth_element是你应该注意和调用的。

如果你需要把标准序列容器的元素或数组分隔为满足和不满足某个标准,你大概就要找partitionstable_partition

如果你的数据是在list中,你可以直接使用partitionstable_partition,你可以使用listsort来代替sortstable_sort。如果你需要partial_sortnth_element提供的效果,你就必须间接完成这个任务,但正如我在上面勾画的,会有很多选择。

你可以通过把数据放在标准关联容器中的方法以保持在任何时候东西都有序。你也可能会考虑标准非STL容器priority_queue,它也可以总是保持它的元素有序。

需要更少资源(时间和空间)的算法列在需要更多的前面:1. partition  2. stable_partition  3. nth_element  4. partial_sort  5. sort  6. stable_sort

条款32:如果你真的想删除东西的话就在类似remove的算法后接上erase

remove并不真的删除东西,因为它只是把不删除的移到前面,后面信息内容不定。另外有两种类似remove”的算法:remove_ifunique

条款33:提防在指针的容器上使用类似remove的算法

       提防在指针的容器上使用类似remove的算法。没有注意这个建议的人只能造成资源泄漏。

条款34:注意哪个算法需要有序区间

       11个需要有序区间的算法。

条款35:通过mismatchlexicographical比较实现简单的忽略大小写字符串比较

       或许用stricmp(s1.c_str(), s2.c_str());更好。

条款36:了解copy_if的正确实现

               template<typename InputIterator, typename OutputIterator, typename Predicate>

OutputIterator copy_if(InputIterator begin, InputIterator end, OutputIterator destBegin, Predicate p) {

        while (begin != end) {

               if (p(*begin))*destBegin++ = *begin;

               ++begin;

        }

        return destBegin;

}

条款37:用accumulatefor_each来统计区间

list<Point> lp;
...
Point avg =accumulate(lp.begin(), lp.end(),Point(0, 0), PointAverage());
class PointAverage:
               public binary_function<Point, Point, Point> { 
public:
        PointAverage(): numPoints(0), xSum(0), ySum(0) {}
        const Point operator()(const Point& avgSoFar, const Point& p) {
               ++numPoints;
               xSum += p.x;
               ySum += p.y;
               return Point(xSum/numPoints, ySum/numPoints);
        }
private:
        size_t numPoints;
        double xSum;
        double ySum;
};


    
条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator
    如果你尽量使用iterator代替const或reverse类型的迭代器,可以使得容器的使用更简单,更高效而且可以避免潜在的bug。
条款27:用distance和advance把const_iterator转化成iterator
       advance(i, distance<ConstIter>(i, ci)); distance返回两个指向同一个容器的iterator之间的距离;advance则用于将一个iterator移动指定的距离。
条款28:了解如何通过reverse_iterator的base得到iterator
        reverse_iterator的base成员函数返回一个“对应的”iterator的说法并不准确。对于插入操作而言,的确如此;但是对于删除操作,并非如此。
v.erase((++ri).base());// 删除ri指向的元素,这是删除一个由reverse_iterator指出的元素时首选的技巧
条款29:需要一个一个字符输入时考虑使用istreambuf_iterator
ifstream inputFile("interestingData.txt");
string fileData((istreambuf_iterator<char>(inputFile)),              istreambuf_iterator<char>());
istreambuf_iterator不忽略任何字符。相对于istream_iterator,它们抓取得更快。

 

 

条款38:把仿函数类设计为用于值传递

函数指针是值传递。STL中的习惯是当传给函数和从函数返回时函数对象也是值传递的。这暗示了两个东西。第一,你的函数对象应该很小。否则它们的拷贝会很昂贵。第二,你的函数对象必须单态(也就是,非多态)——它们不能用虚函数。

条款39:用纯函数做判断式

判断式是返回bool。纯函数是返回值只依赖于参数的函数。如果f是一个纯函数,xy是对象,f(x, y)的返回值仅当xy的值改变的时候才会改变。一个判断式类是一个仿函数类,它的operator()函数是一个判断式,也就是,它的operator()返回truefalse

条款40:使仿函数类可适配

       四个标准函数适配器(not1not2bind1stbind2nd)都需要存在某些typedef,一些其他人写的非标准STL兼容的适配器(比如来自SGIBoost——参见条款50)也需要。提供这些必要的typedef的函数对象称为可适配的,而缺乏那些typedef的函数对象不可适配。What?改天再了解。

条款41:了解使用ptr_funmem_funmem_fun_ref的原因

       每当你传递一个函数给STL组件时都使用它ptr_funmem_funmem_fun_ref的情况则完全不同,只要你传一个成员函数给STL组件,你就必须使用它们。与for_each等函数有关。

条款42:确定less<T>表示operator<

       保持一致吧。

 

条款43:尽量用算法调用代替手写循环

有三个理由:

效率:算法通常比程序员产生的循环更高效。

正确性:写循环时比调用算法更容易产生错误。

可维护性:算法通常使代码比相应的显式循环更干净、更直观。

条款44:尽量用成员函数代替同名的算法

存在既是道理。

条款45:注意countfindbinary_searchlower_boundupper_boundequal_range的区别

了解这些函数的依赖和特性。

条款46:考虑使用函数对象代替函数作算法的参数

       STL函数对象——化装成函数的对象——传递给算法所产生的代码一般比传递真的函数高效。原因是内联。另一个用函数对象代替函数的原因是它们可以帮助你避免细微的语言陷阱。

条款47:避免产生只写代码

       不能读和理解的软件不能被维护,不能维护的软件几乎没有不值得拥有。最后,这样的代码完全不高效。

条款48:总是#include适当的头文件

       几乎所有的容器都在同名的头文件里,比如,vector<vector>中声明,list<list>中声明等。例外的是<set><map><set>声明了setmultiset<map>声明了mapmultimap

除了四个算法外,所有的算法都在<algorithm>中声明。例外的是accumulate(参见条款37)、inner_productadjacent_differencepartial_sum。这些算法在<numeric>中声明。

特殊的迭代器,包括istream_iteratorsistreambuf_iterators(参见条款29),在<iterator>中声明。

标准仿函数(比如less<T>)和仿函数适配器(比如not1bind2nd)在<functional>中声明。

条款49:学习破解有关STL的编译器诊断信息

       对于vectorstring,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。例如,如果你的源代码涉及vector<double>::iterator,编译器消息有时会提及double*指针。(一个值得注意的例外是当你使用来自STLportSTL实现,而且你运行在调试模式。那样的话,vectorstring的迭代器干脆不是指针。对STLport和它调试模式的更多信息,转向条款50。)

提到back_insert_iteratorfront_insert_iteratorinsert_iterator的消息经常意味着你错误调用了back_inserterfront_inserterinserter,一一对应,(back_inserter返回back_insert_iterator类型的对象,front_inserter返回front_insert_iterator类型的对象,而inserter返回insert_iterator类型的对象。关于使用这些inserter的信息,参考条款30。)如果你没有调用这些函数,你(直接或间接)调用的一些函数做了。

类似地,如果你得到的一条消息提及binder1stbinder2nd,你或许错误地使用了bind1stbind2nd。(bind1st返回binder1st类型的对象,而bind2nd返回binder2nd类型的对象。)

输出迭代器(例如ostream_iteratorostreambuf_iterators(参见条款29),和从back_inserterfront_inserterinserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。为了明白我的意思,试着编译这段代码:

vector<string*> v;                                   // 试图打印一个

copy(v.begin(), v.end(),                            // string*指针的容器,

ostream_iterator<string>(cout, "/n"));       // 被当作string对象       

你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在<algorithm>中),也许是你试图给那算法用的类型出错了。例如,你可能传了错误种类的迭代器。要看看这样的用法错误是怎样报告的,通过把这段代码喂给你的编译器来启发(并愉快!)自己:

list<int>::iterator i1, i2;        // 把双向迭代器

sort(i1, i2);                  // 传给一个需要随机访问迭代器的算法        

你使用常见的STL组件比如vectorstringfor_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件。正如条款48的解释,这问题会降临在长期以来都可以顺利编译而刚移植到新平台的代码。

条款50:让你自己熟悉有关STL的网站

SGI STL网站,http://www.sgi.com/tech/stl/

STLport网站,http://www.stlport.org/

Boost网站,http://www.boost.org/

http://boost.sourceforge.net/

 

 

 

第1条:慎重选择容器类型
             STL容器的分类远比我想像中的多。别人意外的是stack,queue等不是STL容器,但这不是这章的重点。在多数应用中,只有可能有一种容器供符合你的要求,尽管我觉得这些容器的操作上雷同。
             ■ 你是否需要在容器的任意位置插入新元素?如果是,就选择序列容器。
             ■ 你是否关心容器中的元素是如何排序的?如是不关心,哈希容器是一个可能的选择;否则,你要避免哈希容器。
             ■ 你需要哪种类型的迭代器?如果必须是随机访问迭代器,就只能限定的vector、deque和string。如果要求使用双向迭代器,就不能使用slist。
             ■ 当发生元素的插入或删除操作时,避免移动容器中的原来的元素是否很重要?如果是,就要避免连续内存的容器。
             ■ 容器中的数据布局是否需要和C兼容?如果是,只能选择vector。
             ■ 元素的查找速度是否是关键考虑因素?如果是,就要考虑哈希容器、排序的vector(二分查找)和标准关联容器。
              ■ 你是否介意容器内使用引用计数?如果是,就要避免使用string和rope,可以考虑vector<char>替代。
             ■ 对插入和删除操作,你需要事务语义(可以回滚)吗?如果需要,list将是你的最好选择。
             ■ 你需要使迭代器、指针和引用变为无效的次数最少吗?如果是,请使用基于节点的容器。
             ■ 如果序列容器的迭代器是随机访问类型,而且只要没有删除操作发生,且插入操作只发生在容器的末尾,则指向数据的指针和引用就不会变为无效,这样的容器是否对你有帮助?这是很特殊的情况,如果真的是这样的,deque是绝佳选择。

第2条:不要试图编写独立于容器类型的代码
             这是现在很流行的泛化(generalization)编程,但作者强调不要在这里使用。原因在于,每个容器所支持的操作不一样,就连erase、insert这样的操作在STL中也不统一。除非你的代码是给自己用的,否则不要使用,天知道那个程序员用的是什么容器!

第3条:确保容器中的对象拷贝正确而高效
             这一条是告诉我们,STL操作中,拷贝发生的频率是相当大的。指针是一个好的想法,但内存泄漏很麻烦。

第4条:调用empty而不是检查size()是否为0
             empty的存在不仅仅是为了少敲几个字符。

第5条:区间成员函数优先于与之对应的单元素成员函数
             如第3条所说,STL操作中,拷贝发生的频率是相当大的。如果用单元素一个一个地操作,这种性能上的损耗将会更多。下面是书中给出的一般区间操作。
            ■ 区间创建
                container::container(Inputiterator begin, Inputiterator end);
             ■ 区间插入
               void container::insert(iterator position, Inputiterator begin, Inputiterator end);
               关联容器不需要position
            ■ 区间删除
              iterator container::eraser(iterator begin, iterator end);
              关联容器有所不同
              void container::eraser(iterator begin, iterator end);
           ■ 区间赋值
              作者非常强调这个,因为很多程序员不使用它
              void container::assign(Inputiterator begin, inputiterator end);

第6条:当心C++编译器最烦人的分析机制
             int g(double ());编译器认为是,g是一个函数,返回值是int,参数是一个函数指针,返回值为double,该参数为空。int g(double d) = int g(double (d)) = int g(double),之所以STL这里要提到,是因为在声明时常常被误解。比如:
             ifstream dataFile("ints.dat");
              list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
             在VS2005中,测试中得到警告,说data被声明库函数,可能这个函数不能被调用。一个解决方法是加上括号以区分   list<int> data((istream_iterator<int>(dataFile)), istream_iterator<int>());更好的方式是不使用匿名对象。C++的一条普遍规律是,尽可能地解释为函数声明。这真是一个让人恼火的问题。

第7条:如果容器中包含了通过new操作创建的指针,切记在容器对象析构前将指针delete掉
             这个是常识。不过每次都要写for循环,实在很麻烦。书中提出用for_each代替,这正是我讨厌的。

第8条:切勿创建包含auto_ptr的容器对象
             这是很危险的,以至于C++标准都禁止它。

第9条:慎重选择删除元素的方法
             没有通用的删除方法。例如,从container<int> c;中删除所有1963的元素。如果你有一个连续的容器(vector,deque,string),最好的方法是erase-remove习惯用法
            c.erase(remove(c.begin(), c.end(), 1963), c.end()); //remove返回删除后的新c.end(),在后面还有一些数据是没有清除掉的(事实上remove没有删除元素,它把找到的元素用后面的填补上)
            对于list,remove更加有效
            c.remove(1963);
            对于关联容器,正确方法调用erase
            c.erase(1963);
            如果找到删除的值没这么简单,remove_if配合erase会派上用场。
            bool badValue(int );
             c.erase(remove_if(c.begin(), c.end(), badValue), c.end()); //连续内存型容器
             c.remove_if(badValue);    // list
            对于关联容器,直接写for循环会比较好,但很容易写出错误的代码。比如
           container<int>::iterator it = ...
           c.erase(it);       // 执行后it变库无效,很多程序员把这个it用到for循环中的++,立马出错!如果c是序列容器,它会返回下一个元素的迭代器,关联容器就没这么好运了。
          for(AssocCaotainer<int>::iterator it = c.begin(); it != c.end(); ) {
               if(badValue(*it)) {
                     c.erase(i++);
               }
                else ++i;
          }
          注意下面的
          for(SeqCaotainer<int>::iterator it = c.begin(); it != c.end(); ) {
               if(badValue(*it)) {
                     i = c.erase(i);   //这里绝对不要用c.erase(i++),因为像vector这类连续内存容器在插入或删除后,原来的指针或引用会无效!!!
               }
                else ++i;
          }

第10条:了解分配子(allocaltor)的约定和限定

第11条:理解自定义分配子的合理用法

第12条:切勿对STL容器的线程安全性有不切实际的依赖
               一般来说,STL不是线程安全的。

第13条:vector和string优先于动态分配的数组
               很明显,在各个方面,vector和string都表现得很优秀。但是可能是受到C的影响,至今还末用到这条,现在决定,以后凡是简单的动态数组,全用vector;如果是字符串,全用string。

第14条:使用reserve来避免不必要的重新分配
               相信这一点是很容易理解的。下面几个函数是vector和string特有的,会常常用到
               ■ capacity() 返回已经分配的内存可以容纳的元素
               ■ resize(Container::size_type n) 强迫容器改变到包含n个元素状态,如果n<capacity(),capacity()的值是不会减少。它的特点就是,执行后,调用size()就是返回n。
               ■ reserve(Container::size_type n) 强迫容器把它的容量变为至少是n。如果n小于capcity(),可能不会做什么事。

第15条:注意string实现的多样性
               string的实现中有引用计数,这一点恐怕你已经清楚了。但它的实现不同版本都不一样,直接的表现是sizeof(string)的值不同,即使相同它们的内存方案也可能不一样。通常来说,对程序员这些没什么影响。

第16条:了解如何把vector和string的数据传给旧的API
               因为它们是内存连续的容器,所以这点很好实现。
               对于vector,简单采用 &v[0] 就可以了;对于string,s.c_str()是唯一方法,不要用&s[0]的方法,因为string的实现多样性,而且很有可能string内部不保证空字符结尾。如果你介意c_str()带来的性能损失,用vector<char>代替string。

第17条:使用“swap技巧”除去多余的容量
               14条中,只有reserve可能有这个能力除去多余容量,但仅仅是可能。比如
                        vector<int>(v).swap(v);
                 vector<int>(v)声明一个隐式对象,有v初始化,然后再与v交换。请放心,原来的指针和引用都能工作。string也类似。string(s).swap(s);

第18条:避免使用vector<bool>
              你也许在怀疑,bool是内置类型,为什么不行?STL的实现者原先对bool做了压缩处理,每个元素只占2个bit,标准的是点8个bit,这样指针指向就出错了。总之C++标准委员会声明vector<bool>是一个失败 的产物,你就不要使用了。取而代之deque<bool>或bitset。

第19条:理解相等(equality)和等价(equivalence)的区别
               相等的概念是基于operator==;等价关系是以“的已排序的区间中对象值的相对顺序”为基础。在关联容器中,需要指定一个排序类(注意,不是函数),默认的比较函数是less<T>。对于w1和w2,等价的关系表示库 !(w1<w2)&&!(w2<w1)。如果人为故意,等价和相等不一样。
                STL中关联函数的比较函数只有一个,当然,一个足够了。

第20条:为包含指针的关联容器指定比较类型
               上面一条提到比较函数,通常有一个默认的比较函数。考虑set<string*> s,如果你向里面插入数据后,你会发现没有按照顺序输出。原因在于,s是这样声明的set<string*, less<string*> s,比较的是string*,结果输出是按指针地址的值来排序。所以,对于指针关联容器,我们要指定自己的排序类型
               struct StringPtrLess : pblic binary_function<const string*, const string*, bool>
                {
                    bool operator()(const string* ps1, const string* ps2) const
                    {
                          return *ps1<*ps2;
                    }
                 };
                set<string*, StringPtrLess> s;
                对于map,用法类似。

第21条:总是让比较函数在等值的情况下返回false
               如果把它代入到等价的关系式,这就明白了。

第22条:切勿直接修改set或multiset中的键
               键 = key。key是排序的依据,随意修改会打乱容器结构。

第23条:考虑用排序vector替代关联容器
               连续内存窗口有它的优势,调用一些排序算法和查找算法可以达到set和map的效果,不过大量元素插入和删除的代价太高了,只能用来查找。

第24条:当效率至关重要时,请在map::operator[]与map::insert之间谨慎选择
               产生这个误会的原因是,operator[]也可以达到insert的效果,但它的代价确实偏高,却常常不被查觉。本条的要点概括为:如果要更新一个已有的映射表元素,优先使用operator[];如果是添加一个新元素,最好还是insert。
              (也许你会觉得insert有些麻烦
                  typedef map<int, double> MyMap;
                 Map m;
                 m[1] = 1.0;            //       m.insert(Map::value_type(1, 1,0);
                 m[2] = 1.1;            //       m.insert(Map::value_type(1, 1,1);
                 ... ...)

第25条:熟透非标准的哈希容器
               哈希容器没包括在C++标准库中,据说是因为标准委员会为了赶时间。

第26条:iterator优于const_iterator、reverse_iterator以及const_reverse_iterator
               iterator可以直接转换到其它的iterator,反过来则不行。

第27条:使用distance和advance将容器的const_iterator转换成iterator
               代码上看去像
              const_itrator ci = ...
              iterator i(c.begin());
               advance(i, distance(i ,ci));
              如果能避免const_iterator,就没有这些事了。

第28条:正确理解由reverse_iterator的base()成员函数所生成的iterator用法
               base()返回的iterator指向的位置不是原来的位置,而是后面一个元素,这一点很迷惑人。

第29条:对于逐个字符的输入请考虑使用istreambuf_iterator
              如果纯粹读文件中的内容,不用格式化(如读出一个整数,路过空格等),首选istreambuf_iterator代替istream_iterator。当然,与之对应的还有ostreambuf_iterator替代ostream_iterator.

第30条:确保目标区间足够大
               如果你通过STL算法来向容器中增加元素,那么这是一个值得重视的问题。虽然STL能自动管理内存,但我们常常高估了它的能力。常见的例子是:
               int trans(int x);
              vector<int> v1, v2;
              ... ...
              transform(v1.begin(), v1.end(), v2.end(), trans); //将v1的值经过trans变换加到v2.end()上
              在STL算法中,迭代的样子是 v2.end()++。这时你就会明白,v2.end()后面没有可能内存,会访问非法地址。这时你在前面用 v2.reserve(v2.size()+v1.size()),很抱歉,又错了,原因在于后面的空间没有初始化,结果将未知。正确的方法是
              transform(v1.begin, v1.end(), back_insert(v2), trans);
             这样返回迭代器前v2.push_back()将被调用,这样插入就正确了。类似的还有front_insert, inserter等

第31条:了解各种与排序有关的选择
               STL提供的排序算法要比你自己写的好得多,何必自寻烦恼呢?共有6种可用的排序算法(按消耗资源由少至多排列),partition, stable_partition, nth_element, partial_sort, sort, stable_sort。并不是说,消耗少的就一定好,是因为它们实现的功能有限。在不同的场合请选择合适的算法。

第32条:如果确实要删除元素,则需要在remove这一类算法之后调用erase
              相信你已经知道vector经典的remove-erase组合,所有的内存连续容器都适用。remove算法没有删除元素的能力,因为它不可册通过一个迭代器判断出能否调用erase,即使标准的STL容器erase使用方式也不尽相同,因此,这个工作就交给用户完成。

第33条:对包含指针的容器使用remove这一类算法时要特别小心
              对于这类容器就不应该使用remove算法,手工写for循环将是更好的选择。如果你参照remove的实现,或许你可以把for循环加以改进。

第34条:了解哪些算法要求使用排序的区间作为参数
                需要的有 binary_search, lower_bound, upper_bound, equal_range, set_union, set_intersection, set_difference, set_symmetric_difference, merge, inplace_merge, includes. unique和unique_copy虽然没有强制使用,但它们确实需要工作的排序的容器中。另一个要注意的是,上面这些算法通常都需要一个比较函数,而且要与提供给排序的比较函数相一致。

第35条:通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较
               这种程序,说难也难,说简单也简单。

第36条:理解copy_if算法的正确正确实现
               首先,标准STL中没有copy_if,但各大厂商的“好事者”几乎者实现了它。本书作者也加入到这个行列中了。

第37条:使用accumulate或者for_each进行区间统计
               accumulate绝不仅仅是将几个数加在一起求合那么简单,可以自定义运算子。相比之下,for_each稍显繁琐但更灵活。原因在于 accumulate返回的是size_type,而for_each返回Function。

第38条:遵循按值传递的原则来设计函数子类
               这一条我不太理解,我看到很多operator()按引用传递值。

第39条:确保判别式是“纯函数”
               有几个名词可以来解释一下
               ■ 判别式(predicate)返回bool类型的函数
               ■ 纯函数(pure fuction)指返回值仅与输入有关
               ■ 判别式类 一个函数子类,它的operator()函数是一个判别式
               问题根源在于:在一个算法中,你无法确定函数子被调用多少次。像for_each这类简单的算法调用次数就是迭代器的个数,但其它的较复杂的就不一定了,比如find_if就不一样。设计函数功能单一是一个重要的原则。

第40条:若一个类是函数子,则应使它可配接
               总共有4个配接器not1, not2, bind1st, bind2nd。如果你要使用它们,你必须确保函数子类从unary_function, binary_function等继承。或者使用ptr_fun, mem_fun等(这个用起来不太方便)。对于前一种方法,有一个使用特点
              1、对于无状态函数子类(指没有私有成员变量的),通常定义为struct;这是STL的风格,当然用class也完全一样。
              2、一般情况下,传递给unary_function或binary_function的非指针类型需要去掉const和引用(&)部分。(作者不愿意解释其中的原因,但我的编译器加上它们后也没错)
             struct Cmp: public std::binary_function<int, int, bool> {
                       bool operator()(const int& i, const int& j);
              }
             如果是指针,就要写成一致
             struct Cmp: public std::binary_function<const int*, const int*, bool> {
                       bool operator()(const int* i, const int* j);
              }
              其实写函数子也不是很麻烦

第41条:理解ptr_fun、mem_fun和mem_fun_ref的由来
               用一句话来说就是“函数调用方式的多样性,使STL必须规定一种方式来约束”(哦,估计下次我自己看到也无法理解)。在第40条中,我们看到函数子的声明是从其它类中继承过来的,如果不继承或者是内部成员函数,可能需要这几个适配器的支持,因此,在编译无法通过的时候,查查它们的用法也许有意想不到的收获。

第42条:确保less<T>与operator<具有相同的语义
               在排序容器中,有一个默认的比较函数子less<T>,见第20条。确保比较函数子可行性是很重要的,同时,为了避免出现混乱,less<T> (如果需要自己实现,就不能用less这个名字)与operator<不同有歧义。

第43条:算法调用优先于手写循环
               诚然,不论是简洁,速度,正确性,算法都比手写循环好,只是我原来一直都想避免写函数子,因为我不了解(我的记忆中只在一个项目中写了一个,仅此一个)。不过现在情况好多了,函数子,算法,适配器都不是难事了。

第44条:窗口的成员函数优先于同名算法
              比如说查找,对于set,可以使用成员函数find,也可以用算法find。但是很明显,成员函数对list知道得更多技术细节,就如同手写循环和算法一样。

第45条:正确区分count, find, binary_search, lower_bound, upper_bound和equal_range
               确切地说,它们是不同的算法,作用也不同,关键在于,有时候我们可以在它们中间任选一个来解决问题,我是说在某些方面,它们的共同点。现在我还记得它们的特点,也许以后还有印象。

第46条:考虑使用函数对象而不是函数作为STL算法的参数
                STL中的sort算法比qsort快,这一点确实让人吃惊。这样你就会喜欢函数对象了。

第47条:避免产生“直写型”(write-only)的代码
               直写型 —— 直接写所有函数。这是编程风格问题,意思是请不要所STL的算法写得太复杂了,我还达不到这个境界。

第48条:总是包含(#include)正确的头文件
               不同的平台可能需要不同的头文件。

第49条:学会分析STL相关的编译器诊断信息
               stlsplit是可以选用的工具。

第50条:熟悉与STL相关的Web站点
               boost将是我下一个关注的对象

 

你可能感兴趣的:(effective)