第一条:慎重选择容器类型

1、C++容器:先混个眼熟

序列容器:array、vector、string、deque、list、forward_list
有序关联容器:set、map、multiset、multimap
无序关联容器:unordered_set、unordered_map、unordered_multiset、unordered_multimap
容器适配器:stack、queue、priority_queue

2、C++容器:简介
2.1 序列容器

序列容器实现了可以顺序访问的数据结构。

array:静态连续容器;
vector:动态连续容器,超出容器大小会自动分配内存;
deque:双端队列;
list:双链表;
forward_list:单链表;

2.2 有序关联容器

关联容器实现可以快速搜索的排序数据结构(O(log n)复杂度)。

set:只有键,没有值的集合,按键排序,并且键是唯一的;
map:键值对的集合,按键排序,并且键是唯一的;
multiset:只有键,没有值的集合,按键排序,键可以不唯一;
multimap:键值对的集合,按键排序,键可以不唯一;

2.3 无序关联容器

无序关联容器实现了可以快速搜索的未排序(哈希)数据结构(O(1)最好,O(n)最坏情况的复杂性)。

unordered_set:只有键,没有值的集合,按键哈希散列,并且键是唯一的;
unordered_map:键值对的集合,按键哈希散列,并且键是唯一的;
unordered_multiset:只有键,没有值的集合,按键哈希散列,键可以不唯一;
unordered_multimap:键值对的集合,按键哈希散列,键可以不唯一;

2.4容器适配器

容器适配器为顺序容器提供了不同的接口。
stack:先进后出;
queue:先进先出;
priority_queue:优先级队列

3、选择容器

默认序列容器:vector,先考虑vector是否符合要求,如果不符合再选择其它的;
容器大小从始至终不会变,选择array:例如记录一年中每个月的收入;
需要在序列中间做插入和删除操作,选择list;
需要在序列头部、尾部做插入和删除操作时,选择deque;

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

1、禁止对容器进一步泛化

每一种容器是针对一类场景的泛化,不要试图进一步泛化容器。比如:针对当下场景使用vector容器,写代码时就围绕vector接口来写,不要试图写出能够兼容list、deque的代码。虽然这么做出发点是好的,但是最终总是误入歧途。

2、建议封装容器到类中

要想减少再替换容器类型时所需要修改的代码,可以把容器隐藏到一个类中,并尽量减少与容器相关的外部可见的接口。

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

1、使用智能指针

使拷贝动作高效、正确,并防止剥离问题发生的一个简单办法是使容器包含指针而不是对象,最佳选择是使用智能指针。

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

理由很的简单:empty对所有的标准容器都是常数时间操作,而对一些list实现,size耗时线性增长。

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

1、区间成员函数

区间成员函数像STL算法一样,使用两个迭代器参数来确定该成员操作所执行的区间。

2、区间成员函数的优点

代码量少、表达清晰直接,易写易懂。

3、何时使用区间成员函数

1)通过利用插入迭代器的方式来限定目标区间的copy调用,几乎都应该被替换为对区间成员函数的调用;
2)所有标准容器都提供了使用区间创建的构造函数;
3)所有标准容器都提供了使用区间插入的insert函数;
4)所有标准容器都提供了使用区间删除的erase函数;
5)所有标准容器都提供了使用区间赋值的assign函数

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

请使用命名的迭代器对象,这消除二义性,维护代码的人更容易理解。
这里举例一个容易犯错的代码:将变量定义写成了函数声明。

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

STL容器很智能,但没有智能到知道是否该删除自己所包含的指针所指向的空间。为了避免内存泄漏,建议使用引用计数形式的智能指针对象。

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

不多说了,很多年以前就不再使用auto_ptr了。

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

1、删除特定值

对于连续容器,使用erase-remove:v.erase(remove(v.begin(), v.end(), 1987), c.end());
对于非连续容器list,使用remove函数;
对于关联容器,使用erase;

2、删除满足特定条件的对象

对于连续容器,使用erase-remove_if;
对于非连续容器,使用list::remove_if
对于关联容器,使用remove_copy_if和swap或者遍历使用erase,注意参数使用后缀递增;

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

一个类型为T的对象,它的默认分配子allocator的两个类型定义分别是allocator::pointer和allocator::reference。
其他待完善,没有理解分配子的作用。

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

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

对容器成员函数的每次调用,都锁住容器直到调用结束;
在容器返回的每个迭代器的生存期结束前,都锁住容器;

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

不解释了,直接照做就是了。

第十四条:使用reserver来避免不必要的重新分配

reserver成员函数能使你把重新分配的次数减少到最低限度。
size:容器中有多少元素;注意:不等于容器容纳多少元素;
capacity:容器已经分配的内存可以容纳多少元素;
resize:强迫容器改变到包含n个元素的状态。
reserve:强迫容器把它的容器变为至少是n,如果n不小于当前的大小,会重新分配;如果小于,什么也不做。

第十五条:注意string实现的多样性

不同的string实现以不同的方式来组织下列信息:字符串的大小、容量capacity、字符串的值、对值的引用计数。

第十六条:了解如果把vector和string数据传给旧的API

vector返回C API:

vector<int> v;
if(!v.empty()){doSomething(&v[0], v.size());
}

注意:不要用v.begin()代替&v[0]

string返回C API:s.c_str();

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

class Contestant{... ...};
vector<Contestant> contestants;
... ...
vector<Contestant>(contestants).swap(contestants);
string s;
... ...
string(s).swap(s);

第十八条:避免使用vector

首先,它不是一个STL容器;
其次,它并不存储bool

代替方法:

deque<bool>
或者使用bitset

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

相等:operator==
等价:!(x<y) && !(y<x),对于两个对象x和y,如果按照关联容器c的排列顺序,每个都不在另一个的前面,那么称这两个对象按照c的排列顺序是等价的。

第二十条:为包含指针的关联容器指定比较类型

如果什么也不做,默认是对指针的地址做排序。必须自己编写比较函数子类。
比较函数模板如下:

struct DereferenceLess {tumplate<typename PtrType>bool operator()(PtrType pT1, PtyType pT2) const{return *pT1 < *pT2;}
}

第二十一条:总是让比较函数在等值情况下返回false

如果比较函数在等值情况下返回true,两个相等的值,可能不等价。
比如使用less_equal(operator<=)作为比较函数,x=y=10时,!(x<=y) && !(y<=x)的结果为false,即最终会得出:x!=y

第二十二条:切勿直接修改set或multiset的值

所有的标准关联容器是按照一定顺序来存放的,如果修改了会打破容器的有序性。

第二十三条:考虑用排序的vector替代关联容器

如果元素少并且几乎不会有插入删除操作,可以考虑使用排序的vector替代关联容器,无论是空间还是时间都是最优的。

第二十四条:当效率至关重要时,请在map::operator[]和map::insert之间谨慎做出选择

map::operator[]的设计目的是为了提供“添加和更新”的功能;
在作为“添加”操作时,insert比operator[]效率高;
当作为“更新”操作时,优先使用operator[]

第二十五条:熟悉非标准的散列表

非标准的散列表:hast_set、hast_multiset、hash_map、hash_multimap
注意这里说的是旧版本的STL

第二十六条:iterator优先于const_iterator、reverse_iterator及const_reverse_iterator。

原因:
1、容器中的instert和crase函数的形参只接受iterator类型,不接受const_iterator、reverse_iterator及const_reverse_iterator。
2、从iterator到const_iterator,或者从reverse_iterator到const_reverse_iterator之间都存在隐式转换,但是反过来技术上可以实现,但不推荐,效率不能保证。
3、在同一个表达式中混用iterator和const_iterator,比如比较操作,会发生隐式转换,有时会出现问题。

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

distance:用于取得两个迭代器之间的距离;
advance:用于将一个迭代器移动指定的距离。

typedef deque<int> IntDeque;
typedef IntDeque::iterator Iter;
typedef IntDeque::const_iterator ConstIter;IntDeque d;
ConstIter ci;
...
Iter i(d.begin()); /迭代器 i 指向容器 d 起始位置
advance(i, distance<ConstIter>(i, ci)); /注意这里指明distance所使用的类型为ConstIter

效率问题:对于随机访问的迭代器(如vector、string、deque产生的迭代器)而言,执行时间是常数时间;对于其他标准容器以及散列表的迭代器而言,执行时间是一个线性时间,因此推荐二十六条,尽量用iterator代替const_iterator

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

1、插入操作:如果要在一个迭代器reverse_iterator ri指定的位置上插入一个新元素,则只需在ri.base()位置处插入元素即可。
2、删除操作:如果要在一个迭代器reverse_iterator ri指定的位置上删除一个元素,要先递增ri,然后在调用base()函数,例如:v.erase((++ri).base());

第二十九条:对于逐个字符的输入请考虑使用istreambuf_iterator

对比istream_iterator,它在默认请看下会跳过空白字符,并且效率低。
读取一个文本文件的内容到一个string对象中,推荐的方案如下:

ifstream inputFile("test.txt");
string fileData((istreambuf_iterator<char>(inputFile)), istreambuf_iterator<char>());

第三十条:确保目标区间足够大

对于vector、string、deque、list容器在尾部插入对象时,需要使用back_inserter函数;
其中deque和list容器还可以使用front_inserter在头部插入对象。
为了提供插入操作的性能,对于vector和string容器可以使用reserve来提前分配好内存。

第三十一条:了解各种与排序有关的选择

按照性能由高到底排序:
1、partition:把满足特定条件的元素放到前面;
2、stable_partition:把满足特定条件的元素放到前面,并且是稳定的排序(在遇到相等的对象时,还会按照出场顺序排序);
3、nth_element:找出部分最优的对象,可以不按照顺序,比如列出前十个,而且这十个可以不用排序;
4、partial_sort:部分排序;
5、sort:全部排序
6、stable_sort:稳定版本的全部排序,不仅全部排好序,而且在遇到相等的对象时,还会按照出场顺序排序。
注意:list::sort是稳定排序。

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

注意:remove不能删除容器中元素。
因为从容器中删除元素的唯一方法是调用该容器的成员函数,而remove并不知道它操作的元素所在的容器,所以remove不可能从容器中输出删除元素。
remove实际功能:把不用被删除的元素放到容器前部,把需要被删除的元素放到容器尾部。
如果需要真正删除元素,需要使用erase和remove配合

vector<int> v;
...
v.erase(remove(v.begin(), v.end(), 99), v.end());

同理unique也需要和erase配合使用。

注意:list::remove会真正删除元素,并且比使用erase-remove配合使用更高效,list::unique也会真正删除元素。

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

对指针容器使用erase-remove组合来删除时,很可能造成内存泄漏。
其实在执行remove后,还没有执行erase之前已经发生内存泄漏。因为remove在将不需要删除的指针元素移动到容器头部时,会覆盖掉需要删除的指针元素,造成内存泄漏;
再执行erase时,也会造成内存泄漏,因为还没释放内存,就删除了指针。
同理:remove_if和unique也会造成内存泄漏。

解决方法有两种:
1、使用带有引用计数的智能指针
2、使用partition代替remove

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

需要排序后才能使用的算法:

// 使用二分法查找的算法,需要先排序
binary_search
lower_bound
upper_bound
equal_range// 集合操作,为了提高效率(为了保证线性时间效率),需要先排序
set_union
set_intersection
set_defference
set_symmetric_defference// 合并操作,为了提高效率
merge
inplace_merge// 判断一个区间中的所有的对象是否都在另一个区间中,为了提高效率
includes

第三十五条:通过mismatch或lexicographical_compace实现简单的忽略大小写的字符串比较

第三十六条:理解copy_if算法的正确实现

STL中包含copy的算法

copy
copy_backward
replace_copy
replace_copy_if
reverse_copy
unique_copy
remove_copy
remove_copy_if
rotate_copy
partial_sort_copy
uninitialized_copy

但是就是没有copy_if算法,需要自己实现

template<typename InputIterator,typename OutputIteratortypename Predicate>
OutputIterator copy_if(InputIterator begin,InputIterator end,OutputIterator destBegin,Predicate p)
{while (begin != end) {if (p(*begin))*destBegin++ = *begin;++begin;}return destBegin;
}

第三十七条:使用accumulate或者for_each进行区间统计

count:统计一个区间中有多少个元素;
count_if:统计满足某个条件的元素个数;
min_element:获取区间中最小值;
max_element:获取区间中最大值;

accumulate:对区间进行自定义的操作,比如计算一个容器中字符串长度的总和。

/计算和
list<double> ld;
...
double sum = accumulate(ld.begin(), ld.end(), 0.0);/ 计算容器中字符串长度的总和
string::size_type
stringLengthSum(string::size_type sumSoFar ,const string& s)
{return sumSoFar + s.size();
}set<string> ss;
...//插入一些字符串
string::size_type lengthSum = accumulate(ss.begin(), ss.end(), static_cast<string::size_type>(0),stringLengthSum);
};

第三十八:遵循按值传递的原则来设计函数子类

1、在C和C++的标准库函数中,如果需要函数作为参数,需要使用函数指针,并且函数指针是按值传递的(被复制);
2、在STL中,函数对象在函数之间来回传递也是按值传递(被复制);
3、因为函数对象是按值传递,即需要来回复制,因此函数对象要尽可能的小;
4、函数对象必须是单态的(不能是多态),也就是说,它们不能有虚函数,因此在传递过程中,会出现剥离问题(slicing problem):在对象复制过程中,派生部分可能会被去掉,而仅保留了基类部分。

第三十九:确保判别式是“纯函数”。

1、判别式函数(predicate function):是一个返回值为bool类型(或者可以隐式地转换为bool类型)的函数。
2、判别式类(predicate class):是一个函数类,它的operator()函数是一个判别式。
3、纯函数(pure function):是指返回值仅仅依赖于其参数的函数。
4、在STL中容器使用的比较函数都是判别式,比如:find_if以及各种与排序相关的算法,这些算法要求每执行一次都要保证结果是唯一,不会受其他参数的干扰,比如全局变量、静态变量等。

第四十条:若一个类是函数类,则应使它可配接

1、STL函数配接器not1、not2、bind1st、bind2nd等都要求一些特殊的类型定义,提供这些必要的类型定义的函数对象被称为可配接的(adaptable)函数对象。
2、特殊的类型定义:argument_type、first_argument_type、second_argument_type、result_type
3、继承 std::unary_function、std::binary_function来完成上述的特殊的类型定义

第四十一条:理解ptr_fun、mem_fun和mem_fun_ref的来由

STL算法只支持非成员函数,不支持成员函数(无法通过编译),如果要使用成员函数需要使用ptr_fun、mem_fun或者mem_fun_ref来将成员函数封装到一个函数类中

第四十二条:确保less与operator<具有相同的语义

1、不要特化一个位于std名字空间中的模板
2、operator<是std::less默认实现方式,不要修改std::less的行为,因为这样做很可能会误导其它的程序员。

第四十三条:算法调用优先于手写的循环

1、先看一个手写循环和使用算法的例子
一个支持重画的类Widget

class Widget{public:...void redraw() const;...
}

使用手写循环来重画容器list中的所有的Widget:

list<Widget> lw;
...
for (list<Widget>::iterator i=iw.begin(); i!=iw.end(); ++i){i->redraw();
}

使用for_each循环算法来完成

for_each(lw.begin(), lw.end()),mem_fun_ref(&Widget::redray));

mem_fun_ref:封装成员函数,使它可以用在算法中,因为算法只能使用非成员函数

2、使用算法的优点
效率高、可维护性好。
理由:略,一定要用起来

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

1、列举出这些函数
关联容器:count、find、lower_bound、upper_bound、equal_range
list容器:remove、remove_if、unique、sort、merge、reverse

2、理由
速度更快、成员函数通常与容器结合的更加紧密

第四十五条:正确区分count、find、binary_search、lower_bound、upper_bound和equal_range

1、如果容器的区间是排序好的使用:binary_search、lower_bound、upper_bound和equal_range,
这些算法是对数时间的效率。
2、如果容器的区间不是排序好的使用:count、count_if、find、find_if,
这些算法是线性时间的效率。

count:区间中是否存在某个特定的值?如果存在的话,有多少个拷贝?
find:区间中有这样的值吗?如果有,它在哪里?
两者还有一个区别:find找到后就返回,效率高;count要遍历一遍容器的区间

binary_search:测试一个排序的容器区间中是否存在某一个特定的值,返回true或者false;
equal_range:查找一个值在容器区间中的位置;
lower_bound:查找一个值在容器区间中第一次出现的位置,如果没有,则返回适合插入该值的位置;

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

使用函数对象比直接使用函数作为STL算法的参数,更高效。
原因是:编译器可以对函数对象做内敛优化,而函数作为参数,其实是指针,编译器不会对指针做优化。

第四十七条:避免产生“直写型”(wirte-only)的代码

1、不要写过于复杂的嵌套函数调用
2、注意添加代码的注释

第四十八条:总是包含(#include)正确的头文件

1、有些标准头文件可以省略,也可以通过编译,但是考虑移植性,强烈建议不要省略;
2、标准的STL容器都被声明在与之同名的头文件中:vector、list等,特殊的:multiset和set都在<set>中,map和multimap都在<map>中
3、算法被声明在<algorithm>中,除了下面四个
4、accumulate、inner_product、adjacent_difference、partial_sum被声明在<numeric>中
5、特殊类型的迭代器被声明在<iterator>中
6、标准的函数类和函数配接器被声明在<functional>中:如not1、bind2nd

第四十九条:学会分析与STL相关的编译器诊断信息

1、std::basic_string<… …>很长的一个信息,翻译成string就好;
2、vector和string的迭代器通常就是指针,所以错误的使用iterator错误信息中会有:double *
3、如果错误消息源于某一个STL算法的内部实现,那么也许在调用算法的时候,使用了错误的类型。

第五十条:熟悉与STL相关的web站点

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

【C++】Effective STL:50条有效使用STL的经验相关推荐

  1. Effective STL 50条有效使用STL的经验笔记

    Scott Meyers大师Effective三部曲:Effective C++.More Effective C++.Effective STL,这三本书出版已很多年,后来又出版了Effective ...

  2. Effective STL中文版:50条有效使用STL的经验(双色)

    <Effective STL中文版:50条有效使用STL的经验(双色)> 基本信息 作者: (美)梅耶(Meyers,S.) 译者: 潘爱民 陈铭 邹开红 出版社:电子工业出版社 ISBN ...

  3. 读 S. Meyers 之 《Effective STL 中文版:50条有效使用 STL 的经验》

    S. Meyers, 潘爱民, 陈铭, 邹开红. Effective STL 中文版:50条有效使用 STL 的经验. ISBN: 978-7-121-20125-7 STL (Standard Te ...

  4. 社交网络经验50条 看微博微信实战经验

    人心变幻莫测,运营如何把握社交网络? 1.大部分,不,是绝大部分都被社交网络平台是企业自媒体这一伪证给坑害了.于是,它们忘却了社交网络的基础,而着急使用自媒体,于是变成了一场自欺的狂欢.无论是什么,都 ...

  5. c++容器使用50条总结

    第1章 容器 第1条:慎重选择容器类型. 标准STL序列容器:vector.string.deque和list. 标准STL关联容器:set.multiset.map和multimap. 非标准序列容 ...

  6. 50条大牛C++编程开发学习建议

    每个从事C++开发的朋友相信都能给后来者一些建议,但是真正为此进行大致总结的很少.本文就给出了网上流传的对C++编程开发学习的50条建议,总结的还是相当不错的,编程学习者(不仅限于C++学习者)如果真 ...

  7. [转]学习c++的50条忠告

    学习c++的50条忠告(初学者必看)转自http://www.rayoko.com/article/101.htm 1.把C++当成一门新的语言学习(和C没啥关系!真的.): 2.看<Think ...

  8. 大牛C++编程开发学习建议50条

    每个从事C++开发的朋友相信都能给后来者一些建议,但是真正为此进行大致总结的很少.本文就给出了网上流传的对C++编程开发学习的50条建议,总结的还是相当不错的,编程学习者(不仅限于C++学习者)如果真 ...

  9. 【C++ STL学习笔记】C++ STL序列式容器(array,vector,deque,list)

    文章目录 C++ STL容器是什么? 迭代器是什么,C++ STL迭代器(iterator)用法详解 迭代器类别 迭代器的定义方式 C++序列式容器(STL序列式容器)是什么 容器中常见的函数成员 C ...

最新文章

  1. C++和python先学哪个
  2. CentOS上使用OpenStack的一些问题
  3. 腾讯云TStack,带着“数据中心”游云南
  4. 【转】log4net使用详解
  5. 一张图理清ASP.NET Core启动流程
  6. 突发!Spring 也沦陷了。。。
  7. exchange 2010 允许 relay设定
  8. 配置中心Nacos与Apollo比较
  9. unity网站服务器,Unity基础网络服务器通信
  10. 渗透测试/应急演练过程中metasploit制作木马连接失败问题排查
  11. 【学习体会】Lighttools8.4.0:软件基本使用+光度学基本概念+系统初始设置
  12. AndroidStudio 集成 OpenCV
  13. python3报错: takes 1 positional argument but 2 were given 问题解决。
  14. java工程师怎么找兼职,快来看鸭~
  15. android studio try again,完美解决Android Studio在gradle上的各种问题
  16. PKUSC 模拟赛 day1 下午总结
  17. android 大量代码中 grep 太慢,ReactNative 性能 - 闪电教程JSRUN
  18. 情人节 玫瑰花表白源码
  19. 2020联发科技笔试面试经验
  20. LED圆柱屏、波浪屏、飘带屏等异形屏是由柔性软模组构成的创意LED显示屏

热门文章

  1. STM32控制OLCD显示中英文(NB-IoT专栏—基础篇6)
  2. TensorFlow(1)TensorFlow基础(整体介绍)
  3. db2 linux 导入数据_「软件资料」-「软件使用」-Linux 导入、导出 MySQL 数据库命令...
  4. 实时的激光雷达点云压缩
  5. 运行ORB-SLAM笔记_编译篇(一)
  6. 基于Keras的CNN/Densenet实现分类
  7. 数字太大了,计算加法、减法会报错,结果不正确?怎么办?用JavaScript实现大数据(超过20位的数字)相加减运算。
  8. Windows DOS窗口查看历史执行过的命令的三种方式
  9. C和C++混合编程的Makefile的编写!
  10. s9.16作业,员工信息表