导图社区 effective stl
effective stl的知识点脑图,不需要从头到尾看书,只需要这个脑图
编辑于2019-07-06 01:38:59effective stl
第一条:慎重选择容器类型
顺序容器vector、list、deque区别
vector向量(封装数组):连续内存存储,高效的随机访问([]和at),低效的随机删除和插入(要移动所有数据);相对数组,size是动态的;capacity是容量,size是当前数据个数
list列表(封装链表):非连续存储,双向链表,前向和后向指针,高效随机删除和插入操作,但低效的随机访问能力(不支持at和[])(因为要遍历),相对vector占用内存多
deque队列(封装数组):内存是否连续?包括vetor所有功能,同时具备高效的头尾插入删除,也就是队列头进尾出,兼顾了list和vector。deque=double-ended queue双端队列是一种具有队列和栈性质的数据结构,双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。
选择题:1、如果需要高效的随机访问at/[],则vector2、如果需要高效的随机删除和插入(不仅仅是两端),则list3、如果只需要在两端进行删除和插入,并要求高效随机访问,则deque4、如果既要随机访问,又要高效插入和删除,则只能在vector和list间做个权衡
https://blog.csdn.net/greek_1999/article/details/81608787
关联容器set、map、mul_set、mul_map区别(红黑树)
set集合:所有元素根据元素键值自动排序,元素的键值key就是实值value,实值就是键值,不允许有相同的键值,底层通过红黑树实现,通过中序遍历,可返回键值由小到大的排序
map映射:所有元素根据键值key自动排序;map的所有元素都是pair,同时拥有key和value;不允许有重复的key,不允许修改key,但允许修改value;底层通过红黑树实现
mul_set和mul_map相对于set和map,允许key值重复
https://blog.csdn.net/u010710458/article/details/79644312https://blog.csdn.net/u013006553/article/details/78174789
容器适配器stack、queue、priority_queue
适配器并不是一种容器,没有提供与元素保存形式有关的真正数据结构,可以让程序员选择一种合适的底层数据结构。容器适配器不支持迭代器
stack,堆栈,允许在底层数据结构的一端执行插入和删除操作,也就是先入后出,堆栈能用任何序列容器实现(vector、list和deque),默认采用deque,因为vector的内存利用率比较低
queue,队列,允许在底层数据结构的末尾插入元素,也允许从前面删除元素,也就是先入后出,头删尾插。能用list和deque实现,默认采用deque实现。(不用vector,因为vector尾部插入可采用push_back,时间复杂度为O1(),头部删除用erase,时间复杂度为O(n),效率极低)
priority_queue,优先级队列,能够按照有序的方式在底层数据结构中执行插入,优先级高的元素会从底层数据结构最先删除。优先级队列是通过堆排序实现的。底层结构采用vector,因为需要用到堆排序。
https://blog.csdn.net/ZYZMZM_/article/details/88769890
哈希容器hash_map、hash_set、hash_mulmap、hash_mulset
hash_map基于哈希表实现,查找时间复杂度几乎为常数,key->hash值->桶->比较函数查找,要存储hash值,典型的空间换时间。
map是基于红黑树,200万数据最多可能需要比较21次才能找到
hash_map和map区别:hash_map需要hash函数,等于函数;map只需要比较函数(小于函数)。存储结构不一样,hash_map采用哈希表,map采用红黑树。
hash_map查找速度在常数级别,map的查找速度在logn级别,当数据量小时,则hash_map不一定比map快,因为还有哈希函数的耗时。总之,只有当数据量达到一定级别时,才考虑hash_map。另外,如果对内存有限制的话,则慎重考虑hash_map
hash_map不是标准的,可能是STL加入标准C++库时,hash_map系列还未完全实现出来。为了考虑平台的移植性,例如g++编译器,要慎重使用这些非标准容器、
其他哈希容器与普通容器区别,同hash_map与map区别
http://blog.chinaunix.net/uid-20773165-id-1847822.html
第二条:不要试图编写独立于容器类型的代码
应用层编写依赖于特定容器类型操作的代码,则在后期需要更换容器类型时则会产生很大的影响
可以通过typedef类型定义进行封装,也可以通过定义将容器封装起来,然后通过类提供的接口对容器进行操作
第三条:确保容器内的对象拷贝正确而高兴
push_back、insert是拷贝对象进容器,pop是将对象从容器中拷贝出,copy in/copy out;对于vector、deque、string进行删除和插入操作时,也会执行拷贝;执行排序等算法时,也会进行拷贝。
如果对象的拷贝构造和拷贝赋值函数有特殊性要求,则需要自定义确保性能
基类对象的容器,放入派生类对象,则派生类锁特有的部分则会发生剥离。避免这个的好办法则是存放智能指针。
容器相对于数组更加聪明,可以动态扩充大小
第四条:调用empty而不是size()来判断是否为0
对于标准容器,empty是常数时间操作,size对于list类型耗费线性时间
第五条:区间成员函数优先于与之对应的单元素成员函数
使v1内容与v2的后半部分相同的最简单办法
区间赋值:v1.assign(v2.begin()+v2.size()/2,v2.end())
单元素赋值:for(vector<Wigdet>::const_iterator ci=v2.begin()+v2.size()/2;ci!= v2.end(); ci++){v1.push_back(*ci);}
拷贝算法+后向插入迭代器(back_insert_iterator),适用于支持push_back的顺序容器,例如vector、deque、list、string:copy(v2.begin()+v2.size()/2, v2.end(), back_inserter(v1))
拷贝算法+前向插入迭代器(front_insert_iterator),适用于支持push_front的容器,例如list、deque:copy(v2.begin()+v2.size()/2, v2.end(), front_inserter(v1))
拷贝算法+插入迭代器(insert_iterator),适用所有容器都支持insert:copy(v2.begin()+v2.size()/2, v2.end(), inserter(v1))
拷贝算法三个原型(在这个位置安插某个值,原来的值就会后移):iterator insert (iterator position, const value_type& val);void insert (iterator position, size_type n, const value_type& val);template <class InputIterator> void insert (iterator position, InputIterator first, InputIterator last);
v1.insert(v1.begin(), v2.begin(). v2.end()),v1和v2顺序相反v1.insert(v1.end().v2.begin().v2.end()),v1和v2顺序相同
http://www.cplusplus.com/reference/vector/vector/insert/
https://blog.csdn.net/BonChoix/article/details/8062483
https://blog.csdn.net/qq_40788950/article/details/82714436
区间函数1:区间构造函数 所有顺序容器 container::container(iterator position, InputIterator begin, InputIterator end)所有关联容器利用比较函数决定元素安插位置,提供一个省去position参数的函数原型: container::container(InputIterator begin, InputIterator end)
区间函数2:区间删除,顺序容器iterator container::erase(iterator begin, iterator end);关联容器void container::erase(iterator begin, iterator end)
区间函数3:区间赋值void container::assign(iterator begin, iterator end)
优先区间成员函数的理由有如下三条:1、区间成员函数写起来更容易;更能清楚表达意图;表现出了更高的效率。
第六条:当心C++编译器最烦人的分析机制
有一个存有int的文件,把这些整数拷贝到list中。ifstream,从已有文件读;ofstream,向文件写;fstream,打开文件读写。
区间构造,但编译器会认为是一个函数,带有两个参数:ifstream dataFile("ints.dat"); list<int> data(istream_iterator<int>(dataFile), istream_iterator<int>());
编译器认为w是一个函数,而不是构造函数:class Widget{......}; Widget w();
正确的写法:ifstream dataFile("ints.dat");istream_iterator<int> dataBegin(dataFile);istream_iterator<int> dataEnd;list<int> data(dataBegin, dataEnd);
第七条:如果容器中包含了new操作创建的指针,切记在容器对象析构前将指针delete掉
c.begin()返回一个迭代器,指向容器c的第一个元素c.end()返回一个迭代器,指向容器c的最后一个元素的下一个位置c.rbegin()返回一个逆向迭代器,指向容器c的最后一个元素c.rend()返回逆迭代器,指向容器c的第一个元素前面的位置
template <class InputIterator, class Function> Function for_each (InputIterator first, InputIterator last, Function fn);Applies function fn to each of the elements in the range [first,last).
如果容器的元素是指针的话,指针指向对象的析构,容器是不负责任的,有如下方法:
for 循环 delete *i,也不是异常安全的,因为发生异常时,可能中断循环,导致资源泄漏
for_each(v.begin(). v.end(), deleteObject(Wigdet)),对继承体系无法适用
for_each(v.begin(), v.end(), deleteObject()),在operator()层去实例化,则可以对继承体系中任何对象进行delete
最好的解决办法是带有引用计数的智能指针来代替普通指针放入容器,这样就可以避免内存泄漏问题了,例如:shared_ptr
第八条:切勿创建auto_ptr的容器对象
auto_tr<Widget> pw1(new Widget);auto_ptr<Widget> pw2(pw1);//pw1变为了NULLpw1=pw2;//pw2变为了NULL
如果容器元素为auto_ptr对象,则在执行sort等算法时,则会出现很多莫名奇妙的NULL元素
第九条:慎重选择删除元素的方法
STL通过iterator将container和algorithm分离,并通过function提供高可定制化。iterator可以看作是一种契约,algorithm对iterator进行操作,因为algorithm对container知之甚少。
a、容器成员函数erase,是物理上的真正删除b、算法函数remove,是逻辑上的删除,将被删除元素移动到容器末尾,然后返回新的末尾,此时容器的size不变c、部分容器函数remove,是物理上真正删除
删除容器内特定的值:a、删除顺序容器(连续内存)(vector、deque、string)特定值,方法如下:c.erase(remove(c.begin(),c.end(),1963), c.end())b、删除链表特定值,线性时间c.remove(1963)c、删除标准关联容器(set、map、multimap、multiset)特定值,对数时间c.erase(1963)
删除使判定式返回true的元素:例如判定是 bool badValue(int x)a、如果vector、deque和string,采用算法函数remove_ifc.erase(remove_if(c.begin(),c.end(),badValue), c.end())b、如果list,采用容器成员函数c.remove_if(badValue)c、如果关联容器,采用简单粗暴方式,copy和swap,采用算法函数remove_copy_if(c.begin(),c.end(),inserter(goodValues,goodValues.end()),badValue);c.swap(goodValues)
插入迭代器:a、back_inserter(container):返回尾部插入型迭代器,内部会调用容器container的push_back()方法来将数据插入容器的尾部b、front_inserter(container):返回头部插入型迭代器,内部会调用容器container的push_front()方法来将数据插入容器的头部c、inserter(container,pos):返回通用插入型迭代器,内部会调用容器container的insert(pos)方法将数据插入到pos位置。
其他操作
第十条:了解分配子allocator的约定和限制
new/delete分别对应两个阶段:内存配置操作由alloc::allocate负责;内存释放操作由alloc::deallocate负责;对象构造操作由::construct()负责;对象析构操作由::destroy()负责
STL提供众多泛型容器,程序员在使用时只需关心何时往容器内塞对象,而不用关心如何管理内存,需要用多少内存。STL通过分配子allocator来完成内存管理
STL的allocator是一个由两级分配器构成的内存管理器,当申请的内存大小大于128byte时,启动第一级分配器通过malloc直接向系统的堆空间分配;当申请的内存大小小于128byte时,启动第二级分配器,从一个预先分配好的内存池中取一块内存给用户
内存池是由16个不同大小(8的倍数,8-128byte)的空闲列表组成,allocator会根据用户申请内存大小从空闲列表取表头块给用户。(从内存池申请内存,代价很小,只是指针操作)
STL内存管理http://www.cnblogs.com/dynas/p/6953355.html
第十一条:理解自定义分配子的合理用法
自定义分配子的理由:也许你认为STL allocator太慢,产生太多内存碎片,或许你发现allocator<T>对线程安全采取了措施,但你的程序时单线程的,不想花费同步开销
在自定义allocator时,注意rebind的作用:给定了类型T的分配器,根据相同的策略得到另外一个类型U的分配器那么,allocator<U>=allocator<T>::rebind<U>:other
第十二条:切勿对STL的线程安全性有不切实际的依赖
标准C++相当狭小和古旧,在这个纯净的世界,不存在内存映像文件或共享内存。没有窗口系统,没有网络,没有数据库,也没有其他进程,所有的可执行都是静态连接。
STL线程安全性随实现而异,对于STL的一个实现,最多期望如下:多线程对同一个容器读是安全的;多线程对不同的容器做写操作是安全的。
第十三条:尽量使用vector和string代替动态分配的数组
动态分配的数组,需要自己malloc/free,而vector和string可以动态自动管理内存
vecotr和string是完备的序列容器,可以使用STL算法、begin()、end()和size()等成员函数
string为了避免不必要的内存分配和字符拷贝,背后实现采用了引用计数,在多线程环境下,则需要付出同步的成本。
多环境下使用采用了引用计数的string,替代方案有三种:1、尝试在STL中关闭此开关2、自己实现一个不采用引用计数的string3、采用vector<char>来替代string,此方法虽然不能使用string的成员函数,但是还有STL的算法可以用
第十四条:使用reserve避免不必要的重新分配
容器通过自动增长内存来容纳加入的元素对象,当需要更多空间时,则调用如下步骤:1、分配一块大小是当前容量的某个倍数的新内存,一般为2;2、把容器的元素从旧内存拷贝到新内存;3、析构掉旧内存中的对象;4、释放旧内存。每当上述步骤发生时,都会导致vector、string的指针、迭代器或引用失效。
reserve可以减少重新分配的次数,避免内存重新分配以及指针、迭代器或引用失效带来的开销。
size()函数表示容器内实际的元素数量;capacity()函数返回利用容器分配的内存可以容纳的元素数量;resize(n)强迫容器改变为只包含n个元素的状态,多的析构掉,少的话则用默认构造函数填充;reserve(n)强迫容器将其容量变为至少是n,但容器内的元素个数size()不会变.
vector<int> v;for(int i=0;i<1000;i++){v.push_back(i);}v可能会发生2-10次的内存再分配。通过v.reserve(1000)则避免此种情况
如上,已知大小,可以通过reserve预留,也可以在未知大小时,先预留,然后最后去掉多余的容量
第十五条:注意string实现的多样性
每个string实现包含如下信息:字符串大小;容量;值;分配子的拷贝(可选);对值的引用计数。
不同的实现可能会使用引用计数,也可能不会;不同的实现,string的大小也不一样;不同的实现,字符内存分配策略也不一样。
第十六条:了解如何把vector和string数据传递给旧的API
C API=>STL:利用数组和vector的内存兼容性,可以通过vector作为中介,将C API类型转换为任意STL容器size_t fillArray(double* pOut, double* pIn, size_t arraySize);vector<double> vd(maxDoubleNumber);vd.resize(fillArray(&vd[0], pIn, vd.size()));//其中pIn为double类型的数组名list<double> lt(vd.begin(),vd.end());//将double*转换为listdeque<double> dq(vd.begin(),vd.end());//将double*转换为dequeset<double> st(vd.begin().vd.end())//将double*转换为set
STL=>C API:vector<int> v;&v[0]为int*类型;string s; s.c_str()为char*类型
第十七条:使用“swap技巧”去除多余的容量
避免vector占用不再需要的内存,可以采用:vector<ElemType>(v).swap(v);vector<ElemType>(v)创建临时变量,为v的拷贝,不会有多余的容量,临时变量和v交换,则v不含有多余的容量
同样的技巧适用于string:string(s).swap(s)
这一技术并不保证去除全部多余的容量,实际上只意味着"在容量当前的大小确定的情况下,使容量在该实现下变为最小"
对于swap操作,不仅两个容器的内容被交换,同时它们的迭代器、指针和引用也将被交换(string除外)
第十八条:避免使用vector<bool>
原因1:vector<bool>不是真正的容器vector<bool> vb; 不支持operator[]操作,也就是&vb[0]编译不通过
原因2:vector<bool>中存放的不是真正的bool,而是bit位
替代方案1:deque<bool>,当然不是连续内存存储;替代方案2:bitset,不是标准容器,但是是STL一部分,在编译期就确定了大小
第十九条:理解相等和等价的区别
相等的概念是基于operator==的,如果表达式x==y返回为真,则x与y相等;x与y有相等的值不一定意味着x和y的所有数据成员都有相同的值,取决于==的定义find对相同的定义是相等,是以operator==为基础的
等价关系是以"在已排序的区间对象值的相对顺序"为基础的;对于两个对象x,y 如果按照关联容器c的排列顺序, 每个都不在另一个的前面,那么称这两个对象按照c的排列顺序有等价的值;set::insert对相同的定义是等价,是以operator<为基础的。
每个关联容器都通过key_comp成员函数使得判别式可被外部使用。所以,如果下面表达式为true,两个对象就是等价的:!c.key_comp()(x,y) && !c.key_comp()(y,x)
effective stl 第19条:理解相等(equality)和等价(equivalence)的区别https://blog.csdn.net/u014110320/article/details/52601904STL 理解相等和等价的区别https://blog.csdn.net/hutianyou123/article/details/78425165
第二十条:为包含指针的关联容器指定比较类型
关联容器key是排序的,内部是按照平衡二叉树(红黑树)进行排序的,并且是基于等价进行比较,如果元素为指针,采用默认比较key_comp,则会乱序,解决方法是自定义比较类,为了避免定义多个比较类,则可以定义模板比较类
为包含指针的关联容器指定比较类型https://www.cnblogs.com/tntboom/p/4562175.html
第二十一条:总是让关联容器比较函数在等值情况下返回false
关联容器的key是不允许重复的,是基于等价比较的,set<int, less_equal<int> > s; 则是采用<=进行等价比较,则if(!(except1)&&!(except2)) 10<=10成立为true、10>=10成立为true,则返回false,则不会将第二个10 insert进去,但如果自定义的等价比较式返回true,则会插入第二个10,则破坏了关联容器的数据结构存储结构了。
第二十二条:切勿修改set或multiset中的key
STL有的实现允许修改关联容器的key,有的不允许
如果不关心移植性,恰好当前的STL实现允许修改key,则修改
如果要求移植性,则不要修改,方案是:a、找到要修改的元素;b、拷贝一份;c、修改;d、删除原来的元素;e、insert修改的元素
第二十三条:考虑用排序的vector替代关联容器
非标准hash容器查找时间复杂度为常数时间,vector为线性时间,关联容器是指数时间
用户使用场景如果是无规律的插入、删除和查找等,则关联容器是最合适的,对数时间复杂度也可控
如果用户使用场景非常有规律,插入->查找->重组(删除、插入)->查找,并且数据量特别大,则可以用vector,然后排好序进行查找。 因为一个元素,关联容器会比vector多三个指针空间,内存换页会带来很大的开销。
第二十四条:当考虑效率时慎重考虑map::operate[]和map::insert
第二十五条:熟悉非标准的哈希容器
第二十六条:iterator优于const_iterator、reverse_iterator、const_reverse_iterator
存在隐式转换 terator -> const_iterator、iterator -> reverse_iterator、const_iterator -> const_reverse_iterator
显示转换 reverse_iterator -> base() -> iterator、const reverse_iterator -> base() -> const iterator
对于insert erase等函数,只能接受iterator型的迭代器作为参数,对于其他的是不行的
除了reverse有时真的需要,在大多数时候用iterator比const iterator更优
第二十七条:用 distance 和 advance 把 const_iterator 转化成 iterator
deque/list/set/multiset/map/multimap对应的iterator与const_iterator(reverse_iterator和const_reverse_iterator)是完全不同的类,他们之间的关系或许比string和complex(double)之间的关系还要远
强制转换,以及const_cast、reinterpret_cast和static_cast等都无法编译通过
可以使用advance和distance将const_iterator 转换成 iterator;distance 返回两个指针指向同一个容器的 iterator 之间的距离;advance 则用于将一个 iterator 移动到指定的距离 deque<int> d; IntDeque::iterator i(d.begin()); IntDeque::const_iterator ci; ....(将ci移动到d的某个位置) advance( i, distance<ConstIter>(i, ci) ); //则i指向了ci指向的位置,实现了const_iterator向iterator转换的过程
第二十八条:正确理解由reverse_iterator的base()成员函数所产生的iterator的用法
reverse_iterator和它对应的base iterator之间特有的偏移量,就像rbegin()和rend()与相关的begin()和end()一样
容器的插入和删除必须是iterator,而不能是reverse_iterator,必须先通过base函数将reverse_iterator转换成iterator,然后用iterator来完成工作
要实现在一个reverse_iterator ri指出的位置上插入新元素,在ri.base()指向的位置插入就行了
要实现在一个reverse_iterator ri指出的位置上删除元素,就应该删除ri.base()的前一个元素
https://www.cnblogs.com/cappuccino/p/3563802.html 了解如何通过reverse_iterator的base得到iterator
第二十九条:对于逐个字符的输入请考虑istreambuf_iterator
对于格式化输入/输出,采用istream_iterator/ostream_iterator,缺点是效率低下,原因是通过operator>>实现,每次调用都会构造和析构内部岗哨对象以及各种错误检查
对于格式化输入istream_iterator,因为是通过operator>>实现的,默认会忽略空格,可以通过is::skipws禁止忽略输入中的空格
对于非格式化输入/输出,不关注内容和格式,采用istreambuf_iterator/ostreambuf_iterator,可以大幅提升效率(40%)
https://www.cnblogs.com/liangjf/p/10634078.html
第三十条:确保目标区间足够大
transform( values.begin(), values.end(), results.end(), transmogrify ); 用values[0]调用transmogrify放到*(results.end()),用values[1]调用transmogrify放到*(results.end()+1),但是results.end()已经不存在对象,所以此个函数使用有问题,要确保目标区间足够大
解决方法是插入迭代器:transform( values.begin(), values.end(), back_inserter(results), transmogrify ); back_inserter 返回的迭代器会调用 push_back(任何标准序列容器:vector、string、deque和list) front_inserter 利用了 push_front(deque 和 list),在容器的前端插入 inserter 允许在容器的任意位置插入
第三十一条:了解各种与排序有关的选择
稳定性排序保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。实际应用场景是按照某个字段排序,两个元素相等,但另外一个非排序字段不等,非稳定性排序则把顺序打乱了
如果需要对vector、string、deque或者数组的元素执行一次完全排序,那么可以使用sort或者stable_sort
如果有一个vector、string、deque或者数组,并且只需要对等价性最前面的n个元素进行排序,那么可以使用partial_sort partial_sort(partSortExapmle.begin(), partSortExapmle.begin() + 20, partSortExapmle.end(), qualityCompare);
如果有一个vector、string、deque或者数组,并且需要找到第n个位置上的元素,或者,需要找到等价性最前面的n个元素蛋不必对这n个元素进行排序,那么nth_element正式你需要的函数 nth_element(partSortExapmle.begin(), partSortExapmle.begin() + 19, partSortExapmle.end(), qualityCompare);//注意是19
如果需要将一个标准序列容器中的元素按照是否满足某个特定的条件分开来,那么,partition和stable_partition可能正式你需要的 partition算法可以将满足条件的数据放在容器的前边,并返回指向第一个不满条件元素位置的迭代器 vector<int>::iterator goodEnd = partition(partSortExapmle.begin(), partSortExapmle.end(), hasAcceptableQuality);
如果你的数据是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort来代替sort和stable_sort。如果你需要partial_sort或nth_element提供的效果,你就必须间接完成这个任务。
消耗资源从少到多:partition < stable_partial < nth_element < partial_sort < sort < stable_sort https://www.cnblogs.com/liangjf/p/10634083.html
第三十二条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase
remove不是真正意义上的删除,因为它做不到。remove并不知道它的具体容器类型,只接受迭代器类型;
remove移动了区间中的元素,其结果是,“不要被删除”的元素移动到了区间的前面(保持原有的相对顺序),它返回的一个迭代器只是最后一个“不用被删除”的元素之后的元素,这个返回值相当于该区间的“新的逻辑结尾”;用remove从容器中删除元素,而容器中的元素数目却不会因此而减少
如果要删除元素,要将remove和erase配合使用 vector<int> v; v.erase(remove(v.begin(),v,end(),99),v.end());
事实上remove和erase的配合是如此紧密,以至他们被合并起来融入到了list的remove成员函数中。这是stl中唯一一个名为remove并且确实删除了容器中元素的函数(其他都是erase函数);
第三十三条:对包含指针的容器使用remove这一类算法时要特别小心
或者通过引用计数的智能指针,或者调用remove算法之前手动删除指针并将他们置为空,或者用你自己发明的其他技术
指导原则:对包含的指针的容器使用remove类算法需要特别警惕,如果你不留意这条警告,其后果就会造成资源泄露
第三十四条:了解哪些算法要求使用排序的区间作为参数
binary_search:如果找到返回true,否则返回false lower_bound在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置 upper_bound:在first和last中的前闭后开区间进行二分查找,返回大于val的第一个元素位置。如果所有元素都小于val,则返回last的位置 equal_range:对于给定的范围[first,last), 返回所有值和val等价的元素的范围. set_union:求两个有序的区间的并集 set_intersection:求两个有序的区间的交集 set_difference:求两个有序的区间的差集 set_symmetric_difference:求两个有序的区间的对称差集(两个集合的并集减去交集) merge:合并两个有序的区间(从两个的有序列(两个有序列可以在同一容器内)归并第三个容器.) inplace_rangge:合并两个有序的区间(一个容器内分两个有序的部分归并到本身的容器.) includes:测试排序范围是否包括另一个排序范围
不一定要求排序的区间,但通常情况下会与排序区间一起使用 unique:在范围内删除连续重复的副本(并非真正意义上的删除,和remove类似) unique_copy:复制范围删除重复的 unique和unique_copy使用相等来判断,而要求排序区间的算法均以等价性来判断
第三十五条:了解copy_if 的正确 实现
STL算法中带有copy的有11个:copy、copy_backward、replace_copy、reverse_copy、replace_copy_if、unique_copy、remove_copy、rotate_copy、remove_copy_if、partial_sort_copy、unintialized_copy
copy_if 在STL中没有实现,复制区间满足某个判别式的所有元素,需要自己来实现。 注:C++11中已经自带copy_if算法
第三十六条:用accumulate或for_each来统计区间
一般统计用count和count_if,用于统计区间有多少数或等于某个值的元素个数
区间进行统计一般用accumulate和for_each float product = accumulate(vf.begin(), vf.end(),1.0f, multiplies<float>());
区别:accumulate的名字表示它是一个产生区间统计的算法,for_each听起来好像你只是要对区间的每个元素进行一些操作; accumulate直接返回那些我们想要的统计值,而for_each返回一个函数对象,我们必须从这个对象中提取想要的统计信息
Effective STL学习笔记-条款37 https://blog.csdn.net/gx864102252/article/details/78650541
第三十七条:遵循按值传递的原则来设计函数子类
函数传递是按照指针传递的,C++和C标准都遵循这个原则:函数指针是按值传递的
按值传递也就是不能按指针(函数指针的指针)或引用传递,那么就是不支持多态,不能使用虚函数
将所需的数据和虚函数从函数子类中分离出来,放到一个新的类中,然后在函数子类中包含一个指针,指向这个新类的对象。这样就避免了使用虚函数
第三十八条:确保判别式是纯函数
一个判别式(predicate)是一个返回值为bool类型(或者可以隐式地转换为bool类型)的函数
一个纯函数(pure function)是指返回值仅仅依赖于其参数的函数。纯函数所能访问的数据应该仅局限于参数以及常量(在函数生命期内不会被改变)
判别式类(predicate class)是一个函数子类,它的operator()函数是一个判别式,也就说是,它的operator()返回true或者false
一个精心设计的判别式类应该保证其operator()函数完全独立于mutable数据成员、非const的局部static对象、非const的类static对象、名字空间域中的非const对象,以及非const的全局对象
第三十九条:若一个类是函数子,则应使其可配接
bind1st和bind2nd函数用于将一个二元函数对象(binary functor,bf)转换成一元函数对象(unary functor,uf)
not1是构造一个与谓词结果相反的一元函数对象,not2是构造一个与谓词结果相反的二元函数对象
not1,not2,bind1st和bind2nd详解 https://www.cnblogs.com/blueoverflow/p/4737122.html
第四十条:理解ptr_fun、mem_fun和mem_fun_ref的由来
在x对象上调用f函数,C++有三种不同语法实现:f(x)、x.f()、p->f(),其中p=&x
vector<Widget> vw; for_each(vw.begin(), vw.end(), test);等同于f(x)
vector<Widget> vw; for_each(vw.begin(), vw.end(),&Widget::test);则编译不通过,因为for_each的实现无法知道去掉x.f()
list<Widget*> lpw; for_each(lpw.begin(), lpw.end(),&Widget::test);则编译不通过,因为for_each的实现无法知道去调用p->f()
https://blog.csdn.net/jxlczjp77/article/details/1751860
第四十一条:确保less<T>与operator<具有相同的语义
如果使用了less,无论是显式还是隐式,你都需要确保她与operator<有相同的意义。否则可能存在误导别的程序员。
第四十二条:算法调用优先于手写的循环
从效率、正确性、可维护性这几个维度考虑,算法调用要优于手写的循环。当然前提是多知道些算法。
第四十三条:容器的成员函数优先于同名的算法
成员函数更快
成员函数与容器结合的更紧密,算法函数则是面向迭代器的
set的find成员函数是对数时间,而find算法是线性时间
将容器中元素清除可以通过算法remove、remove_if和unique,然后调用erase;而对于list容器,直接调用相应的成员函数即可
sort算法不能用于list容器,所以list要用自己的成员函数sort
第四十四条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range
容器或一对迭代器标识区间来查找,要正确区分count、find、binary_search、lower_bound、upper_bound和equal_range
第四十五条:考虑使用函数对象而不是函数指针作为STL算法的参数
主要考虑函数对象在编译期间就已经内联inline展开,因此在运行期间就少了一次函数指针调用过程,效率高
什么叫函数对象(仿函数) https://blog.csdn.net/xyblog/article/details/50377942 百度百科-函数对象 https://bbs.csdn.net/topics/350142727 为什么说,stl的"函数对象"效率比"函数指针"高?
第四十六条:避免产生“直写型”(write-only)的代码
代码被阅读的次数肯定要比编写的次数要多
第四十七条:总是包含(#include)正确的头文件
第四十八条:学会分析与STL相关的编译诊断信息
第四十九条:熟悉与STL相关的Web站点
(1)www.boost.org (2)www.stlport.org (3)www.sgi.com/tech/stl/