Effective STL 读书笔记
第二章 vector和string
第13条:vector和string优先于动态分配的数组
使用new来动态分配内存,需要承担以下责任
1.确保有配套的delete调用
2.确保delete调用形式正确,单个对象使用delete,数组使用delete[]
3.delete只调用一次
每当需要动态分配一个数组时,都应该考虑使用vector和string来替代,原因是
它们自己管理内存
它们可以使用全部stl算法
它们和旧代码可以相互转化
只有一种情况使用动态的数组取代string,关于string的引用计数技术
·多线程中使用引用计数的string,在避免内存分配和字符拷贝所节省下的时间比不过在背后同步控制上的时间
规避方法:
使用不使用引用计数的string
考虑使用vector<char>
而不是string
第14条:使用reserve来避免不必要的重新分配
stl容器的自动增长机制
当需要更多空间时,就调用realloc类似的操作
1.分配 一块当前容量的某个倍数的新内存。一般是2倍
把容器的所有元素从旧的内存拷贝到新的内存
析构旧内存中的对象
释放旧内存
这些操作十分费时,这些步骤发生时,容器所有的指针、迭代器和引用都无效
4个函数
size(),告诉你容器有多少元素,不会告诉你该容器为自己所包含的元素分配多少内存
capacity(),告诉你该容器已经分配的内存可以容纳多少个元素,是容器可以容纳的元素最大总数,不是还能容纳的多少元素,还能容纳的多少元素,capacity-size
resize(n),强迫容器改变到包含n个元素的状态,n如果比当前size要小,尾部多余元素会析构,如果n比当前size要大,通过默认构造函数创建的新元素将会被添加到容器的末尾,如果n比当前的capacity要大,在添加元素之前,将先重新分配内存
reserve(n),强迫容器把它容量变为至少是n,前提是n不小于当前大小,这通常会导致重新分配,
避免重新分配的关键是,尽早的使用reserve把容器的容器设为足够大的值
第15条:注意String实现的多样性
string大小和实现不同而不同
size信息
capacity容量
value值
可能包括:分配子的拷贝
建立在引用计数之上的string:对值的引用计数
string类型的区别
string的值可能会被引用计数
大小的范围值时一个char*指针大小的1-7倍
创建新的字符串子可能需要零次、一次或者两次动态分配
string类型可能共享、也可能不共享其大小和容量信息
string 可能支持,也可能不支持针对单个对象的分配子
不同的实现对字符内存的最小分配单位有不同的策略
第16条:了解如何把 vector 和 string 数据传给旧的 API。
vector v表示数组的指针 &v[0]
string s.ctr() 表示char*
事实上,先让C API把数据写到一个vector中,再把数据拷贝到STL容器中,这思想总是可行的
可以先把其他容器转换为vector再使用
第17条:使用swap技巧除去多余的容量
shrink to fit思想(压缩至适当大小)
vector<contestant>
(contestants).swap(contestants);
vector<contestant>
(contestants)创建一个临时的vector,是contestants的拷贝,只为所拷贝的元素分配了所需要的内存,swap后临时变量的容量是原先contestants的容量,临时变量执行完后被析构,而contestants拥有合适的内存值
vector<contestant>
().swap(v);// 清除v并把它的容量变为最小
swap不仅两个容器的内容被交换,同时它们的迭代器、指针和引用也将被交换
第18条:避免使用vector<bool>
它不是一个STL容器
STL容器的一个条件:
T *p = &c[0] 如果operator[取得了Container<t>
中的一个T对象,那么你可以通过取它的地址得到一个指向该对象的指针。
它不存储bool
它存储的是bool的紧凑表示,而非真正的bool,使用了bitfield思想
使用deque<bool>
或bitset代替
第三章 关联容器
equivalence(等价)而不是equality(相等)来对待自己的内容
第19条:理解相等(equality)和等价(equivalence)的概念
相等:以operator==为基础 :find
等价:以operator< insert>
相等不一定所有数据成员都有相同值
等价关系:"在已排序的区间中对象值的相对顺序",针对oprator <
!(w1 < w2) && !(w2 < w1)这两个值就是等价的
用户判别式(predicate):比较函数
标注关联容器通过key_comp成员函数可被外部使用
!c.key_comp()(x,y) && !c.key_comp()(y,x) // 在c的排列顺序中,x不在y前,y也不在x之前
例子:自定义set<string>
不区分大小写
使用set的find成员函数,查找含义仅大小写不同的string的set里面,可以成功查找(基于等价),使用非成员的find算法就不会成功(基于相等)
标注关联容器使用等价的原因
容器总是保持排列顺序的,那么必须要实现比较相对大小,如果使用相等,需要多定义一个操作符
第20条:为包含指针的关联容器制定比较类型
set<string*>
ssp;
是下面代码的缩写:set<string*,less<string*>
> spp;
最精确的:set<string*,less<string*>
,allocator<string*>
> spp;
定义比较函数子类
set 比较不需要函数,而是需要一个类型,在内部通过它创建一个函数
其他容器包含的对象与指针的行为类似:比如智能指针和迭代器
第21条:总是让比较函数在等值情况下返回false
比较函数的返回值表明的按照该函数定义的排列顺序,相等的值从来不会有前后顺序关系,比较函数应当始终返回false
第22条:切勿直接修改set或multiset中的键
为什么set或者multiset中的元素不能是const的
针对对象,如果用对象中某个参数表示key,其他参数表示value,如果设置const,则无法改变value
更改键部分(key part):这部分信息会影响容器的排序性,可能破坏容器
强制类型转换是危险的,只要您能避免使用它就应用避免使用
第23条:考虑用排序的vector替代关联容器
需要可提供快速查找功能的数据结构时,可以选择关联容器
考虑查找速度,非标准的哈希容器是值得的
标准关联容器比vector效率还低的情况并不少见
标准关联容器通常被实现为平衡二叉查找树
平衡二叉树对插入、删除和查找的混合操作做了优化,总的来说就是没办法预测针对这棵树的下个操作是什么
常见的数据结构过程
设置阶段:几乎所有操作都是插入和删除
查找阶段:几乎所有操作都是查找
重组阶段:改变改数据结构,再插入新的的数,和第1阶段类似
这样的情况下,vector可能比关联容器提供了更好的性能,必须是排序的vector
排序的vector性能强的原因
大小问题:关联容器中一个widget伴随的空间至少是3个指针,更非内存
但是需要对每个元素都需要排序
第24条:当效率至关重要时,请在map::operator[]与map::insert之间谨慎做出选择
map的operator[]与众不同,它的设计目的是提高"添加和更新"
如果键k没有的map中,需要先初始化一个对象给其拷贝赋值,效率低,应该使用insert
第25条:熟悉非标注的哈希容器
目前应该有std::map等实现
第四章 迭代器
第26条:Iterator优先于const_iterator、reverse_iterator以及const_reverse_iterator
四个迭代器
iterator 相当于T*,
const_iterator相当于const T*
iterator、const_iterator 递增效果:头部到尾部
reverse_iterator、const_reverse_iterator相当于T、const T,递增效果:尾部到头部
类似的参数基本参数类型为:Iterator
不同迭代器的转换:base()转换
const 转普通的Iterator不能直接得到
第27条:使用distance和advance将容器的const_iterator转换成iterator
const_iterator无法强制转换为Iterator
第28条:正确理解由reverse_iterator的base()成员函数所产生的Iterator的用法
例子
vector<int>
v = {1,2,3,4,5};
vector<int>
::reverse_iterator ri =
find(v.rbegin(),v.rend(),3); vector<int>
::iterator i (ri.base()
图例
对于插入来说:直接使用ri和ri.base()是等价的
对于删除来说:如果需要删除ri所指的元素,必须删除i前面的元素:v.erase(--ri.base())
对于vector和string的很多实现来说,Iterator和const_iterator是以内置指针的方式来实现的,这样--ri.base()的表达式就无法通过编译。必须使用 v.erase((++ri).base())
第29条:对于逐个字符的输入请考虑使用istreambuf_iterator
istream_iterator<char>
对象使用oprator>>从输入流中读取单个字符:每调用一次operator>>操作符,都需要执行许多附近的操作
istreambuf_iterator<char>
直接从流的缓冲区中读取下一个字符
第五章 算法
第30条:确保目标区间足够大
在vector尾部添加对象
失败案例
成功案例
如果插入的目标容器是vector和string,预先调用reserve,可以提高插入操作的性能。
reserve和insert建议同时调用
需要牢记的是:如果使用的算法需要一个目标区间,那么必须确保目标区间足够大,或者确保它会随着算法的运行而增大。
要在算法执行过程中增大目标区间,使用插入型迭代器
第31条:了解各种与排序有关的选择
nth_element算法:
排序一个区间,使得位置n上的元素正好是全排列情况下的第n个元素,当nth_element返回时,所有按全排列(sort的结果)排在位置n之前的元素也都排在位置n之前,而所有按照全排序规则排在位置n之后的元素则都排在位置n之后。
例子:将最好的20个元素放在容器前列,而不关心他们的具体排序
nth_element,没有对位置1-20中的元素排序
用途:找到一个区间的中间值,或者特定百分比上的值
排序算法的稳定性:排序后,等价的值,先后次序稳定
sort、stable_sort和partial_sort
partition算法
sort、stable_sort、partial_sort和nth_element算法都要求随机访问迭代器
list是唯一一个需要排序却无法使用这些排序算法的容器,只能sort完全排序
对于标准关联容器的元素进行排序无意义
partion和stable_partion只要求双向迭代器就能完成工作
总结
完全排序使用sort
等价性前n个元素排序,使用partial_sort
需要找到前n个元素但又不进行排序,nth_element
标准容器需要按照满足某个特定的条件区分开,使用partion和stable_partion
list中只能实现list::sort,使用其他算法需要别的转化
第32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase
用remove删除容器元素,容器中的元素数目不会因此减少
remove不是真正意义上的删除,因为它做不到
例子
调用remove之前:
调用之后
remove只是移动了区间中的元素,把不用删除的元素移到了区间的前部
真正删除值
list.remove() 唯一一个命名为remove而确实删除了容器元素的函数
类似的函数:remove_if和unique,同时unique和list::unique和remove的关系一致
第33条:对包含指针的容器使用remove这一类算法时要特别小心
例子:
对于vector<widget*>
v
删除操作有问题,在remove_if中就出现
调用之前
remove_if调用之后
erase执行完
容器中存放的是指向动态分配对象指针的时候
避免使用remove和类似算法,使用partition算法时不错的选择
如果是智能指针,问题就不存在
第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
需要用二分法查找数据
binary_search、lower_bound/upper_bound、equal_range
如果区域时排序好的,承诺对数时间的查找效率
只有接受随机迭代器的时候,才能保证此效率
如果不支持随机迭代器,只能保证线性时间
提供线性时间效率的集合操作
set_union/set_intersection、set_difference/set_symmetric_difference
不使用排序区间,无法保证在线性时间完成
提供线性时间的合并和排序联合操作
merge/inplace_merge
读入两个排序的区间、然后合并为一个新的排序区间
不使用排序算法,变得很慢
includes
对于未排序区间有很好行为
unique和unique_copy
unique如果想要删除区间的重复元素,必须保证所有相等的元素都是连续存放的
这样需要
一个区间被排序的含义
区间可能有不同的排序比较函数,算法也有比较函数,需要保证两者有一致的行为
正确用法
第35条:通过mismatch或lexicograhical_compare实现简单的忽略大小写的字符串比较
该条例实现简单的英语字符串的比较
忽略大小写的字符串比较功能
实现类似strcmp接口
实现与operator<类似接口>
strcmp类似接口
第一种实现
比较函数
c1、c2的char强制转换为unsigned char