《Effective STL》读书笔记

  • 写在前面
  • 0《Effective STL》中可能过时的内容
  • 1 容器
    • 第1条:慎重选择容器类型。
    • 第2条:不要试图编写独立于容器类型的代码。
    • 第3条:确保容器中的对象拷贝正确而高效。
    • 第4条:调用empty而不是检查size()是否为0。
    • 第5条:区间成员函数优先于与之对应的单元素成员函数。
    • 第6条:当心C++编译器最烦人的分析机制。
    • 第7条:如果入容器中包含了通过new操作创建的指针,切记在容器对象析构前将delete掉。
    • 第8条:切勿创建包含auto_ptr的容器对象。
    • 第9条:慎重选择删除元素的方法。
    • 第10条:了解分配子(allocator)的约定和限制。
    • 第11条:理解自定义分配子的合理用法。
    • 第12条:切勿对STL容器的线程安全性有不切实际的依赖。
  • 2 vector和string
    • 第13条:vector和string优先于动态分配的数组。
    • 第14条:使用reserve来避免不必要的重新分配。
    • 第15条:注意string实现的多样性。
    • 第16条:了解如何把vector和string数据传给旧的API。
    • 第17条:使用“swap技巧”除去多余的容量。
    • 第18条:避免使用vector。
  • 3 关联容器
    • 第19条:理解相等(equality)和等价(equivalence)的区别。
    • 第20条:为包含指针的关联容器制定比较类型。
    • 第21条:总是让比较函数在等值情况下返回false。
    • 第22条:切勿直接修改set或multiset中的键。
    • 第23条:考虑用排序的vector替代关联容器。
    • 第24条:当效率直观重要时,请在map::operator[]与map::insert之间谨慎作出选择。
    • 第25条:熟悉非标准的散列容器
  • 4 迭代器
    • 第26条:iterator优先于const_iterator\reverse_iterator及const_reverse_iterator
    • 第27条:使用distance和advance将容器的const_iterator转换成iterator
    • 第28条:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法
    • 第29条:对于诸葛字符对输入请考虑使用istreambuf_iterator。
  • 5 算法
    • 第30条:确保目标区间足够大。
    • 第31条:了解各种与排序有关的选择。
    • 第32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase。
    • 第33条:对包含指针对容器使用remove这一类算法时要特别小心。
    • 第34条:了解哪些算法要求使用排序的区间作为参数。
    • 第35条:通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较
    • 第36条:理解copy_if算法的正确实现。
    • 第37条:使用accumulate或者for_each进行区间统计。
  • 6 函数子、函数子类、函数及其他
    • 第38条:遵循按值传递的原则来设计函数子类。
    • 第39条:确保判别式是“纯函数”。
    • 第40条:若一个类是函数子,则应使它可配接。
    • 第41条:理解ptr_fun、men_fun和mem_fun_ref的来由。
    • 第42条:确保less与operator<具有相同的语义。
  • 7 在程序中使用STL
    • 第43条:算法调用优先于手写的循环。
    • 第44条:容器的成员函数优先于同名的算法。
    • 第45条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range。
    • 第46条:考虑使用函数对象而不是函数作为STL算法的参数。
    • 第47条:避免产生“直写型”(write-only)的代码。
    • 第48条:总是包含(#include)正确的头文件。
    • 第49条:学会分析与STL相关的编译器诊断信息。
    • 第50条:熟悉与STL相关的Web站点。
  • 写在最后

写在前面

《Effective STL》是一本经典老书, 但它有一个问题就是太老了, 市面上已经没有出版社还在出新书, 对于这本书, 我也是在学校的图书馆无意间看到这本书, 并且是当年比较流行的版本(封面和《Effective C++》一样的版本).
由于它太老了,很多内容其实已经过时.但考虑到其经典性,以及底层原理是永不过时的,还是在反复挣扎中决定快速看一看(206页,也不是很厚).毕竟绝版书,再不看,以后就没有机会看到了.在一开始,就先从知乎上摘了一些可能过时的说明,贴到文章开头,方便查阅比较,少在老旧的细节中走弯路

0《Effective STL》中可能过时的内容

  • 条款2:迭代器可以直接用C++11的auto获取类型
  • 条款5:所有insert函数现在都有了对应的emplace函数
  • 条款6:现在可以用大括号初始化来避免C++'s most vexing parse
  • 条款7:C++11已经引入了std::shared_ptr,不需要用原始指针了
  • 条款8:std::auto_ptr已经移除了,现在的替代品是std::unique_ptr
  • 条款12:C++11自带std::mutex
  • 条款17:C++11有了std::shrink_to_fit
  • 条款18:C++11有了std::bitset
  • 条款21:C++17已经移除了std::binary_function
  • 条款22:C++17可以直接用extract修改关联容器的key
  • 条款24:用emplace代替insert,emplace可以直接传参而不用构造一个对象再传入
  • 条款25:C++11引入了std::unordered_set和std::unordered_map
  • 条款26-29:全部过时,会用auto、std::cbegin、std::cend就行 条款36:C++11有了std::copy_if
  • 条款38-42:全部过时,用C++11的lambda替代仿函数
  • 条款46:用lambda替代仿函数
  • 条款50:https://en.cppreference.com/w/

可能过时部分的来源https://www.zhihu.com/question/50997867

1 容器

STL包含迭代器、算法、函数对象等,但最值得注意的是容器。这一章讲的是内容适用于所有的STL容器的准则。

第1条:慎重选择容器类型。

不同的容器有不同有应用场景,要根据容器的特性和实际需要来选择容器。

第2条:不要试图编写独立于容器类型的代码。

不要把容器类型抽象出来,因为不同的容器类型有不同的特性,它们的函数操作等也都有很大的不同。
如果想要后期更方便地更改容器类型,可以用typedef,比如:

class Widget{...}
template vector<Widget> WidgetContainer;
WidgetContainer cw;

第3条:确保容器中的对象拷贝正确而高效。

容器内保存的是对象的拷贝,而不是其本身,因此要求对象有拷贝构造函数。
如果想要提高其效率,可以考虑把对象的指针放进容器(而不是对象本身),指针的拷贝是非常高效的。

第4条:调用empty而不是检查size()是否为0。

有些容器调用size()的时候会遍历整个容器(比如list),其时间复杂度是O(n),而empty()的调用通常都是O(1)的时间复杂度。

第5条:区间成员函数优先于与之对应的单元素成员函数。

善用区间成员函数(容器算法作用于范围的那些,比如assign()),可以使得更少的代码量(不用写循环)和更高的效率
常用的有

  • 区间创建(container(iterator begin, iterator end)))
  • 区间插入(.insert(begin, end))
  • 区间删除(.erase(begin, end))
  • 区间赋值(.assign(begin, end))

第6条:当心C++编译器最烦人的分析机制。

有些编译器,会把带()的构造,理解为声明了一个函数,比如Widget w()会被理解为声明了一个w()函数,如果在参数列表中,可能会被理解为一个函数名/指针被作为参数

第7条:如果入容器中包含了通过new操作创建的指针,切记在容器对象析构前将delete掉。

如果容器的元素是对象的指针,则用户需要在析构容器之前,把对象指针析构掉,或者使用share_ptr等引用计数智能指针。

第8条:切勿创建包含auto_ptr的容器对象。

由于auto_ptr的机制,使用一些容器中的算法时(比如sort()),会出错,比如把auto_ptr的置为NULL了。
ps: C++11以后已经不使用auto_ptr此条仅供思考

第9条:慎重选择删除元素的方法。

不同的容器适用于不同的删除方法,使用错误的删除方法可能会使得稍后需要用到的指针失效,从而造成错误。
因此正确的做法如下:

  • 删除容器中有特定值的所有对象:

    • 容器是vector、string或deque,则使用erase-remove习惯用法
    • 容器是list,则使用list::remove
    • 容器是一个标准关联容器,则使用它的erase成员函数
  • 删除容器中容易满足特定判定式(条件)的所有对象:
    • 容器是vector、string或deque,则使用erase-remove_if的习惯用法
    • 容器是list,则使用list::remove_if
    • 容器是一个标准关联容器,则使用remove_if和swap,或者写一个循环遍历容器中的元素,记住当把迭代器传给earse时,要对它进行后缀递增
  • 在循环内部做某些(除了删除对象之外的)操作:
    • 容器是一个标准序列容器,则写一个循环来遍历容器中的元素,记得每次调用erase时,要用它的返回值更新迭代器
    • 容器是一个标准关联容器,则写一个循环来遍历容器中的元素,记住当把迭代器传给erase时,要对迭代器做后缀递增

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

  • 你的分配子是一个模板,模板参数T代表你为它分配内存的对象的类型
  • 提供类型定义pointer和reference,但是始终让pointer为T*,reference为T&
  • 千万别让你的分配子拥有随对象而不同的状态(per-object state).通常,分配子不应该有非经费的数据成员
  • 记住,传给分配子的allocate成员函数的是那些要求内存的对象个数,而不是所需的字节数.同时要 记住,这些函数返回T*指针(通过pointer类型定义),即使尚未有T对象被构造出来
  • 一定要提供嵌套的rebind模板, 因为标准容器依赖该模板

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

“只要你遵守了同一类型的分配子必须是等价的这一限制要求,那么,当你使用自定义的分配子来控制通用的内存管理策略的时候,或者在聚集成员关系的时候,或者在使用共享内存和其他特殊堆的时候,就不会陷入麻烦”

第10、11条,我不甚了解,总结是引用的书本内容,暂时跳过,mark一下

第12条:切勿对STL容器的线程安全性有不切实际的依赖。

STL并不是线程安全的,我们只能“期望”它,但不能“依赖”它。其部分能够做到:

  • 多个线程是安全的
  • 多个线程对不同的容器做入操作是安全的

如果想要获得异常安全,我们应该在处理之前和处理之后进行加锁。同时,结合现代面向对象的特性,以及避免在释放锁之前的代码抛出异常造成死锁,我们应该把“加锁”和“释放锁”分别放进一个锁类的构造函数和析构函数

2 vector和string

vector和string的使用频率是最高的,这章将介绍什么时候应该使用vector和string,如何提高vector和string的途径,不同string实现的重要区别,研究如何把vector和string的数据传递给只能理解C的API,学会怎样避免不必要的内存分配。并且研究一个不完全的vector——vector

第13条:vector和string优先于动态分配的数组。

如果需要使用new来动态分配数组等,最好用vector或者string来替代,而避免因为使用new带来的一系列注意事项。
vector和string的内存是动态增长的,而且当vector和string被析构的时候,会自动调用元素的析构函数并释放包含这些元素的内存。
vector和string也能很好地兼容老旧的API

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

首先需要注意4个函数:

  • size(),容器中有多少个元素
  • capacity,容器(已分配的内存)可以容纳多少个元素
  • resize(size_type n),强迫容器改变到包含n个元素的状态(可能会引起裁切析构)
  • reserve(size_type n), 强迫容器把它的容量变为>=n,n不小于当前的大小。
    如果容器重新分配大小,原来的迭代器、指针和引用会失效。
    因此,最好在容器刚被构造出来的时候就使用reserve()来重新分配容器的大小,如:
vector<int> v;
v.reserve(1000);            //避免约10次的重新分配(2^10=1024),每次重新分配是前一次大小的2倍
for(int i = 1; i <= 1000; i++)v.push_back(i);

通常为了避免不必要的重新分配,方法1是预留适当大小的空间;方法2是先留足够大的空间,再去除多余的空间,其中一个去除多余空间的小窍门见第17条

第15条:注意string实现的多样性。

string的实现是多种多样的,不同的实现有不同的特点。大致的区别如下:

  • string的值可能会被引用技术,也可能不会
  • string对象大小的范围可以是一个char*指针的大小的1倍到7倍
  • 创建一个新的字符串值可能需要零次、一次或两次动态分配内存
  • string对象可能共享,也可能不共享其大小和容量信息
  • string可能支持,也可能不支持对单个对象的分配子
  • 不同的实现对字符内存的最小分配单位有不同的策略

虽然string有多种实现,但还是鼓励用户多使用string。只是如果对性能和内存比较敏感的话,可能需要更加详细的了解string的底层机制。

第16条:了解如何把vector和string数据传给旧的API。

string可以使用&v[0]或者.c_str()来获取字符串类型char*,特别说明不要使用v.begin(),而应该使用&*v.begin(),这跟&v[0]产生相同结果。
vector更加灵活,但被调用但历程不能试图改变vector中元素的个数。
无论是vector还是其他的容器,都推荐先把数据复制到vector中,再传给老旧的C API,使用方法如下:

//从C API到容器
//向数组中写入数据。返回被写入的double的个数。
size_t fillArray(double* aArray, size_t arraySize);
vector<double> vd(maxNumDouble);//创建大小为maxNumDoubles的vector
vd.resize(fillArray(&vd[0],vd.size()));//使用fillArray向vd中写入数据
//然后把vd的大小改为fillArray所写入的元素的个数deque<double> d(vd.begin(),vd.end());   //把数据复制到deque中
list<double> l(vd.begin(),vd.end());  //把数据复制到list中
set<double> s(vd.begin(),vd.end());   //把数据复制到set中//从容器到C API
void doSomething(const int* pInts,size_t numInts);//C API
set<int> intSet;
...
vector<int> v(intSet.begin(),intSet.end());   //把set的数据复制到vector
if(!v.empty()) doSomething(&v[0],v.size()); //把数据传给API

也可以把数据先复制到数组中,再传给C API,但根据第13条,更推荐使用vector

第17条:使用“swap技巧”除去多余的容量。

注意这里指但是容量capacity。
一些容器内的元素被大量删除后,容量会远远大于实际数量。使用以下方法可以很好地缩减多余的容量,节约空间:

vector<Contestant> contestants;
vector<Contestant>(contestants).swap(contestants);

vector创建一个临时变量,然后和原本contestants中的内容交换,并且容量也会根据contestans的元素数量来决定。原来臃肿的容量已经被交换到临时变量中去了。在这行代码结束后臃肿的临时变量会被析构,从而释放了先前为contestants所占用的多余的内存。
string也同样适用:

vector<Contestant> v;
string s;
...
vector<Contest>().swap(v);    //清楚v并把它的容量变为最小
string().swap(s);           //清楚s并把它的容量变为最小

另外,在swap的时候,不仅仅容器内容被交换,其迭代器、指针和引用也被交换(string除外)。因此,在发生交换后,原来的迭代器、指针和引用依然有效,并指向同样的元素——但这些元素已经在另外一个容器中

第18条:避免使用vector。

vector不是一个容器,或者说不是一个完全的,容器。
我们应该避免使用vector,可以使用deque或者bitset来替代它(bitset在老标准中不是容器,新标准中有来std::bitset)

3 关联容器

关联容器和序列容器都是容器,但是其本质有很多的不同。

第19条:理解相等(equality)和等价(equivalence)的区别。

相等和等价的意思是不一样的。
find对“相同”的定义是相等,是以operator==为基础的,
set::insert对“相同”的定义是等价,是以operator<为基础的。
因为两个定义不同,所以,有可能用一个定义断定两个对象有相同的值,而用另一个定义断定它们的值不同。

第20条:为包含指针的关联容器制定比较类型。

如果容器内保存的是指针,其“按顺序”打印的情况可能是按内存地址排列,而不是这个指针真正指向的内容。
如果想要按照我们自己想要的方式输出排列的内容,需要我们指定比较函数。但是set容器接受的是一个类,然后在内部用这个类去创建比较函数。
我们可以使用以下这个为比较函数子准备的模版:

struct DereferenceLess{template<typename PtrType>bool operator()(PtrType pT1,PtrType pT2) const {return *pT1 < *pT2;}
};
//于是,就可以在下面这样的方法来使用
//与set<string*, StringPtrLess>的行为相同
//把上面的PtrType换成string*就是StringPtrLess了
set<string*, DereferenceLess> ssp;    

第21条:总是让比较函数在等值情况下返回false。

任何一个定义了“严格的弱序化”的函数必须对相同值的两个拷贝返回false

第22条:切勿直接修改set或multiset中的键。

对set和multiset,如果直接对容器中对元素做了修改,要保证该容器仍然是排序的

第23条:考虑用排序的vector替代关联容器。

排序后的vector中存储数据,可能比在标准关联容器中存储同样的数据要耗费更少的内存,而考虑到页面错误的因素,通过二分搜索法来查找一个排序的vector可能比查找一个标准关联容器要更快一些。
但其缺点在于,让vecrot保持有序,是比较困难的一点。

第24条:当效率直观重要时,请在map::operator[]与map::insert之间谨慎作出选择。

使用operator[],如果不存在下标相等的,会先默认构造一个对象,然后赋值,再插入。如果使用insert(),则会节省默认构造和析构和赋值操作符的时间。

第25条:熟悉非标准的散列容器

C++11新标准已经引入unordered_set和unordered_map容器。此章不必了解

4 迭代器

本章讲各种不同的迭代器的不同应用场景

第26条:iterator优先于const_iterator\reverse_iterator及const_reverse_iterator

很多API只接收iterator,而不接收const或reverse版本的,而const和reverse版本的能接收iterator版本的,
因此,尽量使用iterator来代替const_iterator。

第27条:使用distance和advance将容器的const_iterator转换成iterator

不能使用强制转换,因为iterator和const_iterator根本就是两个不同的类。可以使用以下方法来进行const转换:

ConstIter ci;
Iter i(d.begin());
advanca(i,distance(i,ci));

其原理是构造一个iterator,然后移动和const_iterator相同量的偏移位置。但这样的转换需要O(n)的时间复杂度,因此,尽量还是用iterator来歹意const或reverse型的迭代器

第28条:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法

base()成员函数可以得到reverse_iterator对用的iterator,但要区别对待。它可能操作的位置并不是我们所预想的位置。
当我们要将reverse_iterator转换成iterator的时候,我们必须很清楚对该iterator执行什么样的操作。

第29条:对于诸葛字符对输入请考虑使用istreambuf_iterator。

对于非格式化对逐个字符输入过程,使用istreambuf_iterator会有更高的效率。

5 算法

本章介绍一些有用的算法,以及介绍如何避免一些在使用STL算法时的通病

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

当我们需要向一个区间插入值的时候,我们需要确保这个区间是足够大的,或者会随着运行而增大的。容易出现的错误是在容量刚好只足够原来的元素大小,却直接在其后面插入新元素。
不容易出错的方法是使用插入型迭代器,比如ostream_iterator,或者由back_inserter、front_inserter和inserter返回的迭代器。

第31条:了解各种与排序有关的选择。

容器使用建议:

  • 如果需要对vector、string、deque或者数组中的元素执行一次完全排序,那么可以使用sort或者stable_sort
  • 如果有一个vector、string、deque或者数组,并且只需要对等价性最前面的n个元素进行排序,那么可以使用partial_sort
  • 如果有一个vector、string、deque或者数组,并且需要找到第n个位置上的元素,或者,需要找到等价性最前面的n个元素但又不必对这n个元素进行排序,那么,nth_element正是你所需要对函数
  • 如果需要将一个标准序列容器中对元素按照是否满足某个特定的条件区分开来,那么,partition和stable_partition可能正是你所需要的
  • 如果你的数据在一个list中,那么你仍然可以直接调用partition和stable_partition算法;可以用list::sort来替代sort和stable_sort算法。但是,如果你需要获得partial_sort或者nth_element算法的效果,那么,正如前面我所提到的那样,你可以有一些间接的途径来完成这项任务

按照消耗资源较少的算法排前面,则有:

  1. partition
  2. stable_partition
  3. nth_element
  4. partial_sort
  5. sort
  6. stable_sort
    但不必优先考虑性能,应该优先考虑完成功能,并且保证代码但易读性。

第32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase。

remove不是真正意义上的删除。remove的原理是把不需要删除的元素移动到前面,返回一个指向最后一个“不用被删除”的元素的后面,即逻辑结尾。
正确做法是,需要在remove之后,调用erase,区间从返回的逻辑结尾,到原来容器的结尾处。
同理remove_if和unique也需要注意这种情况。

第33条:对包含指针对容器使用remove这一类算法时要特别小心。

对于包含指针的容器进行remove时,需要小心内存泄漏。其中一种可行的办法是提前释放需要删除的对象所指向的内存,然后再执行remove和erase等操作。

第34条:了解哪些算法要求使用排序的区间作为参数。

往往要求用户提供已经排序的区间的算法,是为了能够提供更好的性能。这些算法有:

  • binary_search
  • lower_bound
  • upper_bound
  • equal_range
  • set_union
  • set_intersection
  • set_difference
  • set_symmetric_difference
  • merge
  • inplace_merge
  • includes

第35条:通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较

lexicographical_compare是strcmp的泛化版本,可以比较任何类型的值。strcmp总是比较两个字符的关系来判别是否相等、大于、小于等,而lexicographical_compare可以接受一个判别式,由该判别式来决定两个值是否满足一个用户自定义的准则。用法如下:

bool ciCharLess(char c1,char c2){return tolower(static_cast<unsigned char>(c1))<tolower(static_cast<unsigned char>(c2));
}bool ciStringCompare(const string &s1,const string &s2){return lexicographical_compare(s1.begin(),s1.end(),s2.begin(),s2.end(),ciCharLess);
}

第36条:理解copy_if算法的正确实现。

copy_if并不在STL内,因为copy_if需要传入的函数是一个adaptable的函数对象,而这会给用户带来负担。

第37条:使用accumulate或者for_each进行区间统计。

我们可以使用accumulate来完成我们想要累加的这个目的,并且累加的形式我们可以传入自己的函数/函数对象来进行自定义累加。
for_each同样也能达到效果,但是更推荐使用accumulate,因为这样表达更加的清晰直观。

6 函数子、函数子类、函数及其他

本章介绍如何函数和函数对象(函数子)按照STL期望的方式工作

第38条:遵循按值传递的原则来设计函数子类。

以函数对象为参数的时候一般都是按值传递,传递的实际上是一个函数指针,即使可以以引用的方式传递,也很容易出错,并且有些地方无法通过编译(涉及到引用的引用,C++并不支持)。C++一般都是按值来传递指针。
为了保证函数对象在传递后还能继续工作,需要满足:

  1. 函数对象尽可能地小,否则复制开销非常昂贵
  2. 函数对象必须是单态(不是多态)即不能有虚函数。参数传递过程中可能会发生剥离,去掉派生的部分。(按值传递)
    为了解决上述第二条的问题,可以考虑剥离虚函数部分和数据部分,抽象为一个新类,然后在原来的类中放一个指针,指向这个新类。
template<typename T>
class BPFCImpl:public unary_function<T,void>{private:Widget w;int x;...virtual ~BPFCImpl();virtual void operator()(const T& val) const;
friend class BPFC<T>;
};template<typename T>
class BPFC:public unary_function<T,void>{private:BPFCImpl<T> *pImpl;
public:void operator()(const T& val) const{pImpl->operator()(val);}...
};

第39条:确保判别式是“纯函数”。

因为接受函数子的STl算法可能会先创建函数子的副本,然后存放起来待以后在使用这些副本,如果判别式不是纯函数,程序可能会得到不如预期的结果。

第40条:若一个类是函数子,则应使它可配接。

让函数子可配接的最简单的方法就是从std::unary_function、std::binary_function继承,分别对应1和2个参数的情况。
例:

template<typename T>
class MeetsThreshold:public std::unary_function<Widget,bool>{private:const T threshold;
public:MeetsThreshold(const T&threshold);bool operator()(const Widget&) const;...
};struct WidgetNameCompare:public std::binary_function<Widget,Widget,bool>{bool operator()(const Widget& lhs,const Widget& rhs) const;
};struct PtrWidgetNameCompare:public std::binary_function<const Widget*,const Widget*,bool>{bool operator()(const Widget* lhs,const Widget* rhs) const;
};

为什么要以unary_function、binary_function作为函数子的基类?因为它们提供来函数对象配接器所需要的类型定义。

第41条:理解ptr_fun、men_fun和mem_fun_ref的来由。

在函数/函数对象被传递时,往往都是以非成员函数的方法去调用。
而这几个函数的目的则是为了实现传递函数后仍然能以成员函数的方法去调用。

第42条:确保less与operator<具有相同的语义。

应该尽量避免修改less的行为,因为注意做很可能会误导其他的程序员(修改std)。如果你使用来less,无论是显示地或是隐式地,你都需要确保它与operator<具有相同的意义。如果你希望以一种特殊的方式来排序对象,那么最好创建一个特殊的函数子类,且名字不能是less。

7 在程序中使用STL

本章讲了一些关于在程序中使用STL的综合技巧

第43条:算法调用优先于手写的循环。

出于以下的考虑:

  • 效率:算法通常比程序员自己写的循环效率更高。
  • 正确性:自己写循环比使用算法更容易出错。
  • 可维护性:使用算法的代码通常比手写循环的代码更加简洁明了。
    我们应该优先考虑使用STL中的算法,而不是自己来写细节

第44条:容器的成员函数优先于同名的算法。

出于以下的考虑:

  • 成员函数往往速度快;
  • 成员函数往往与容器(特别是关联容器)结合得更加紧密,这是算法所不能比的。
    当需要在STL算法与容器的荣明成员函数之间作出选择的时候,应该优先考虑成员函数

第45条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range。

想知道什么 对未排序的区间 对排序的区间 对set或map 对multiset或multimap
(使用 算法) (使用成 员函数)
特定的值存在吗 find binary_search count find
特定的值存在吗?如果存在,那第一个有该值的对象在哪里 find equal_range find find或lower_bound
第一个不超过特定值的对象在哪里 find_if lower_bound lower_bound lower_bound
第一个在某个特定值之后的对象在哪里 find_if upper_bound upper_bound upper_bound
具有特定值的对象有多少个 count equal_range(然后distance) count count
具有特定值的对象都在哪里 find(反复调用) equal_range equal_range equal_range

第46条:考虑使用函数对象而不是函数作为STL算法的参数。

出于以下的考虑:

  • 函数对象内联的operator()可能会省去函数调用的开销
  • 函数对象可以避免一些合理但无法通过编译的情况
  • 有助于避免一些微妙的、语言本身的缺陷
    应该优先考虑使用函数对象而不是函数作为STL算法的参数

第47条:避免产生“直写型”(write-only)的代码。

不要把所有代码都写到一行,要考虑到代码的可读性。

第48条:总是包含(#include)正确的头文件。

充足地包含头文件,可以提高代码的可移植性。
为了便于记住需要如何包含头文件,参考以下内容:

  • 几乎所有的标准STL容器都被声明在与之同名的头文件中。
  • 除了4个STL算法意外,其他所有的算法都被声明在中,这4个算法是:accumulate、inner_product、adjacent_difference和partial_sum,它们被声明在头文件中。
  • 特殊类型的迭代器,包活istream_iterator和istreambuf_iterator,被声明在中。
  • 标准的函数子(比如less)和函数子适配器(比如not1、bind2nd)被声明在头文件中。
    一定不要因为可以省略,就不写对应的头文件。

第49条:学会分析与STL相关的编译器诊断信息。

学会分析STL相关的编译器诊断信息是非常有用的,但这取决于你的经验。
一些分析技巧:

  • vector和string的迭代器通常就是指针,所以当错误地使用了iterator时,编译器的诊断信息中可能会引用到指针类型。
  • 如果诊断信息中提到了back_insert_iterator、front_insert_iterator或者insert_iterator,则几乎总是意味这你错误地调用了back_inserter、front_inserter或者inserter。如果你并没有直接调用这些函数,则一定是你所调用的某个函数直接或间接地调用了这些函数。
  • 类似地,如果诊断信息中提到了binder1st或者binder2nd,那么你可能是错误地使用了bind1st和bind2nd
  • 输出迭代器在赋值操作符内部完成其输出或者插入工作,所以,如果在使用这些迭代器的时候犯了错误,那么你所看到的错误信息中可能会提到与赋值操作符有关的内容。
  • 如果你得到的错误消息来源于一个STL算法的内部实现,那么也许是你在调用算法的时候使用了错误的类型。
  • 如果你在使用一个很常见的STL组件,比如vector、string或者for_each算法,但是从错误消息看,编译器好像对此一无所知,那么可能是你没有包含相应的头文件。

第50条:熟悉与STL相关的Web站点。

STL相关网站:

  • SGI STL站点:http://www.sgi.com/tech/stl/
  • STLport站点:http://www.stlport.org
  • Boost站点:http://www.boost.org

写在最后

个人主页:https://me.csdn.net/m0_46415159

本文链接:https://blog.csdn.net/m0_46415159/article/details/108561735

【绝版C++书籍】《Effective STL》读书笔记相关推荐

  1. Effective STL 读书笔记

    Effective STL 读书笔记 标签(空格分隔): 未分类 慎重选择容器类型 标准STL序列容器: vector.string.deque和list(双向列表). 标准STL管理容器: set. ...

  2. Effective STL读书笔记

    第一章容器 条款1:仔细选择你的容器 C++中各种标准或非标容器: 标准STL序列容器:    vector.string.deque和list(双向列表). 标准STL管理容器:    set.mu ...

  3. Effective C++读书笔记 摘自 pandawuwyj的专栏

    Effective C++读书笔记(0)       Start   声明式(Declaration):告诉编译器某个东西的名称和类型,但略去细节.   std::size_t numDigits(i ...

  4. more effective c++和effective c++读书笔记

    转载自http://bellgrade.blog.163.com/blog/static/83155959200863113228254/,方便日后自己查阅, More Effective C++读书 ...

  5. Effective Java读书笔记(二)

    Effective Java 读书笔记 (二) 创建和销毁对象 遇到多个构造器参数时要考虑使用构建器 创建和销毁对象 何时以及如何创建对象? 何时以及如何避免创建对象? 如何确保它们能够适时地销毁? ...

  6. Effective Java 读书笔记(七):通用程序设计

    Effective Java 读书笔记七通用程序设计 将局部变量的作用域最小化 for-each 循环优于传统的 for 循环 了解和使用类库 如果需要精确的答案请避免使用 float 和 doubl ...

  7. Effective C++ 读书笔记 Item1-Item4

    目录 守则01:把C++看做一个语言的集合,而不是单一的语言 守则02:尽量使用const, enum, inline, 减少宏变量#define的使用 守则03: 尽可能使用const关键字 守则0 ...

  8. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  9. Effective Java 读书笔记(一)

    前言: 开个新的坑位,<effective java>的读书笔记,之后有时间会陆陆续续的更新,读这本书真的感触满多,item01和item02就已经在公司的项目代码中看到过了.今天这篇主要 ...

  10. Effective C++读书笔记(一)

    百度博客本来就垃圾,我以前发表的文章也全是废品.就在csdn这里放上我的读书笔记,自娱自乐下. 1 让自己习惯C++ 条款01:视C++为一个语言联邦 C语言同时支持过程形式(procedural). ...

最新文章

  1. 如何让页面停止加载_Axure 案例:数值加载效果
  2. 一个比较牛的Js写的五子棋
  3. python3精要(3)-python对象类型,数字,序列
  4. android 自定义透明 等待 dialog,Android自定义Dialog内部透明、外部遮罩效果
  5. 657. 机器人能否返回原点
  6. hdu 5154 Harry and Magical Computer
  7. Android内核开发 Linux C编程调用内核模块设备驱动
  8. LeetCode刷题——279. 完全平方数
  9. python svm向量_支持向量机(SVM)及其Python实现
  10. oracle循环视频教程,玩转Oracle入门知识和实战教程---韩顺平主讲(全31集)
  11. adb工具的下载及配置
  12. pmon构建过程分析
  13. GO语言之LiteIDE软件的安装与使用
  14. 用Python如何计算两点间距离
  15. 为什么很多宽带的上传速度都很慢??
  16. Python 中的 PIL 库
  17. React 在react中实现鼠标拖拽移动盒子和图片(基于Ant-Design-Pro 4实现)
  18. 互联网晚报 | 5月13日 星期五 | 罗永浩回应被叫行业冥灯;新一轮汽车下乡政策最快将于本月出台;字节跳动鲸鲮操作系统获批...
  19. 基于TPS(Thin Plate Spines)的STN网络的PyTorch实现
  20. 树的先序遍历(双亲表示法)

热门文章

  1. 想自学软件测试,结果被骗去培训机构了。
  2. 【数学建模】面包店老板使日均收入最大化的诀窍
  3. 掌控板ESP32与手机蓝牙通信mind+ appinventor图形编程
  4. 无刷直流电机常用控制方式比较
  5. 给你的导航页加把锁—php简单登录后进入导航
  6. 物联网工业自动化软件市场现状研究分析-
  7. 开花店一年能赚多少钱?
  8. 一位前端 P7 大佬的面试经验分享
  9. 【07月24日】北上资金持股比例排名
  10. 模方使用中的常见问题解答