c++

EFFECTIVE+STL:50条有效使用STL的经验总结

c++

Posted by YiMiTuMi on February 20, 2021

EFFECTIVE+STL中文版:50条有效使用STL的经验总结

1.vector是默认应使用的序列类型。

2.当需要频繁的在序列中间做插入和删除操作时,应使用list。

3.当大多数插入和删除操作发生在序列的头部和尾部时,deque是应考虑的数据结构。

4.连续内存容器(基于数组的容器):把它的元素存放在一块或多块(动态分配的)内存中,每块内存中存有多个元素。当有新元素插入或已有的元素被删除时,同一内存块中的其他元素要向前或向后移动,以便为新元素让出空间,或者填充被删除元素所留下的空隙。这种移动影响到效率和异常安全性。标准的有:vector、string和deque。非标准的rope。

5.基于节点的容器:在每一个(动态分配的)内存中之存放一个元素。容器中元素的插入或删除只影响到指向节点的指针,而不影响节点本身的内容,所以当有插入和删除操作时,元素的值不需要移动。表示链表的容器时基于节点的:list、slist,所有的标准关联容器也是,非标准的哈希容器使用不同的基于节点的实现。

6.在容器任意位置插入新元素,选序列容器,关联容器不行。

7.如果关心容器中元素是如何排序的要避免哈希容器,否则可以考虑哈希容器。

8.哈希容器、slist和rope不是标准c++的一部分。

9.支持随机访问迭代器:vector、deque、string 和 rope

10.使用双向迭代器要避免slist和哈希容器

11.查找速度:哈希容器 -> 排序的vector -> 标准关联容器

12.提供事务能力要使用基于节点的容器。只有list对多个元素的插入操作提供了实物语义。(连续内存也可以,但是付出性能代价)

13.基于节点的容器插入和删除操作从来不会使迭代器、指针和引用无效(除非他们指向了一个你正在删除的元素),连续内存容器容易使迭代器、指针和引用无效。

14.deque是唯一的、迭代器可能变为无效而指针和引用不会变为无效的STL标准容器。(当插入操作发生在容器末尾是,它的迭代器有可能会变为无效的。)

15.STL是以泛化原则为基础的:

1)数组被泛化为:“已包含的对象的类型为参数的容器”。

2)函数被泛化为:“已使用的迭代器的类型为参数”的算法。

3)指针被泛化为:“以其指向的对象的类型为参数”的迭代器。

16.你无法编写独立于容器的代码,但是,客户代码可能可以(通过类等)。

17.向容器中加入对象时,存入容器的是你所指定的对象的拷贝。从一个容器中取出一个对象时,你所得到的是容器中所保存的对象的拷贝。进去的是拷贝,出来的也是拷贝。这就是STL的工作方式。

18.在存在继承关系的情况下,拷贝动作会导致剥离。(几乎总是错的,不可取)如果向一个存放基类对象的容器中,插入派生类的对象,那么在派生类对象通过基类的拷贝构造函数被拷贝到容器时,它所特有的部分(派生类中的信息)将会丢失。

19.使拷贝动作高效、正确,并防止剥离问题发生的一个简单方法是使容器包含指针而不是对象。拷贝指针时不会有任何剥离现象发生。拷贝指针速度非常快,并且总是会按照你期望的方式进行(他拷贝构成指针的每一位)。

20.数组创建出n个数组对象,而容器创建出能容纳n个对象的空间,并没有创建任何一个对象。

21.调用empty而不是检查size()是否为0,empty(内敛函数返回size是否为0)对所有的标准容器都是常数时间操作,而对一些list实现,耗费线性时间。

22.把一个区间从一个list链接到另一个list可以通过常数时间来完成,不需要拷贝任何数据。

23.v1.assign(v2.begin() + v2.size() / 2, v2.end());

24.assign可以把不同容器相同类型对象的元素进行拷贝,而operator=不行。

v1.assign(s2.begin(), s2.end());

25.1)通过使用区间函数,通常可以少些一点代码。2)使用区间函数通常会得到意图清晰和更加直接的代码。3)表现出更高的效率。

26.使用单元素的成员函数比使用区间的成员函数需要更多地调用内存分配,更频繁地拷贝对象。

27.把一个int数组插入到vector前面

int data[numValues];
vectour<int> v;
v.insetr(v.begin(), data, data + numValues);

28.区间成员函数优先于与之对应的单元素成员函数。

1)不必要的函数调用,即多次调用insert插入单个元素和调用1次insert区间一次插入多个元素。

2)元素频繁地移动位置,每插入一个元素就会导致后面的元素向后移位,而STL的工作方式就是元素的拷贝,每插入一次元素会导致后面的每一个元素调用一次拷贝构造函数和赋值操作符即n*n次,用区间一次插入多个元素只需要让后面每个元素移动一次即n次。

3)内存分配问题,vector中当他的内存已满就会分配内存多次插入多个元素会导致vector多次分配内存,使用区间在开始插入数据可以知道需要多少内存,所以只需要一次就可以分配完成。

4)链表问题,当有元素插入链表中我们要为每一个元素设定其前向指针和后向指针,而区间只需要设定开头和结尾的元素就可以。

29.push_front和push_back都向容器中插入单一元素。

30.关联容器的erase返回一个迭代器将会导致不可接受的性能负担。

31.c++分析会尽可能地解释为函数声明。

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

33.指针容器在自己被析构时会析构所包含的每一个元素,但指针的“析构函数”不做任何事情!它当然不会调用delete。

34.string没有虚析构函数,而从没有虚析构函数的类进行公有继承是c++的一项重要禁忌。

35.永远都不要错误的认为:你可以通过创建auto_ptr的容器使指针被自动删除。

36.切勿创建包含auto_ptr的容器对象。

37.连续内存容器删除特定值使用erase-remove

c.erase(remove(c.begin(), c.end(), 1963), c.endl));

38.list删除特定元素

c.remove(1963);

39.关联容器删除特定元素

c.erase(1963);

40.删除使判别式返回true的方法

//判别式
bool badValue(int );
//vector、string和deque
c.erase(remove_if(c.begin(), c.end(), badValue), c.end());
//list
c.remove_if(badValue);
//关联容器
//可以将不删除的保存到另一个临时容器中
remover_copy_if(c.begin(), c.end(), inserter(goodvelues, goodValues.end()), badValue);
c.swap(goodValues);

41.慎重选择删除元素的方法

(1)要删除容器中特有特定值的所有对象

1)如果容器是vector、string或deque,则使用erase-remove习惯用法。

2)如果容器是list,则使用list:remove。

3)如果容器是一个标准关联容器,则使用它的erase成员函数。

(2)要删除容器中满足特定判别式(条件)的所有对象。

1)如果容器是vector、string或deque,则使用erase-remove_if习惯用法。

2)如果容器是list,则使用list:remove_if。

3)如果容器是一个标准关联容器,则使用remove_copy_if和swap,或者写一个循环来遍历容器中的元素,记住当把迭代器传给erase时,要对他进行后缀递增。(他会使当前迭代器失效确保迭代器把旧值给他,在迭代器失效前获得后面的值。)

(3)要在循环内部做某些(除了删除对象之外的操作)操作。

1)如果容器是一个标准序列容器,则写一个循环来遍历容器中的元素,记住每次调用erase时,要用它的返回值更新迭代器。

2)如果容器是一个标准关联容器,则写一个循环来遍历容器中的元素,记住当把迭代器传给erase时,要对迭代器做后缀递增。

42.在C++中,没有办法仿冒引用。这需要重载operator.(点操作符),而这种重载是被禁止的。

43.自定义分配子:

1)你的分配子是一个模板,模板参数T代表你为它分配内存的对象类型。

2)提供类型定义pointer和reference,但是始终让pointer为T*,reference为T&。

3)千万别让你的分配子拥有随对象而不同的状态。通常,分配子不应该有非静态的数据成员。

4)传给分配子的allocate成员函数的是那些要求内存的对象的个数,而不是所需要的字节数。这些函数返回T(指针)(通过pointer类型定义),即使尚未有T对象被构造出来。

5)一定要提供嵌套的rebind模板,因为标准容器依赖该模板。

44.STL多线程期望(并不能依赖):

1)多个线程读是安全的:多个线程可以同时读同一个容器的内容,并且保证是正确的。自然地,在读的过程中,不能对容器有任何写入操作。

2)多个线程对不同的容器做写入操作是安全的:多个线程可以同时对不同的容器做写入操作。

45.C++保证,如果有异常被抛出,局部对象会被析构。

vector和string

46.vector和string优先于动态分配的数组。

47.new来动态分配内存承担的责任:

1)必须确保以后会有人用delete来删除所分配的内存。如果没有随后的delete,那么你的new将会导致一个资源泄露。

2)必须确保使用了正确的delete形式。如果分配了单个对象,则必须使用“delete”;如果分配了数组必须使用“delete []”。如果你使用了不正确的delete形式,那么结果将是不确定的。

3)必须确保只delete了一次。如果一次分配被多次delete,结果同样是不确定的。

48.在多线程环境中使用了引用计数的string,那么注意一下因支持线程安全而导致的性能问题是很有意义的。

49.string是basic_string的类型定义,wstring是basic\_string的类型定义。

50.使用vector来代替string从而避免string引用计数在多线程中影响效率的问题,因为vector的实现不允许使用引用计数。

51.size():该容器中有多少个元素;capacity():该容器利用已分配的内存可以容纳多少个元素,是容器所能容纳的总数,而不是还能容纳的数量;resize():强迫容器改变到包含n个元素的状态;reserver()强迫容器把它的容量变为至少是n,前提是n不小于当前的大小。

52.使用reserve以避免不必要的重新分配:

1)若能确切的预计容器中最终会有多少数量,则可以使用reserve,如v.reserve(n);

2)先预留足够大的空间,然后,当把所有数据都加入以后,再去除多余的容量。

53.每个string实现都包含如下信息:

1)字符串的大小(size),它包含的字符的个数。

2)用于存储该字符串中字符的内存的容量(capacity)。

3)字符串的值(value)。还可能包含 1)他的分配子的一份拷贝。建立在引用计数上还可能包括 2)对值的引用计数。

54.4种string实现的方式:

1)每个string对象包含其分配子的一份拷贝、该字符串的大小、他的容量、以及一个指针,该指针指向一块动态分配的内存,其中包含了引用计数和字符串的值。使用默认分配子的string的对象其大小是一个指针的4倍。

2)建立string对象指针指向的对象中包含了该字符串的大小、容量和引用计数,以及一个指向一块动态分配的内存的指针,该内存中存放了字符串的值,还包含一些与多线程环境下的同步控制相关的额外数据。

3)string对象的大小总是与指针的相同,而该指针指向一块动态分配的内存,其中包含了与字符串相关的一切数据:大小、容量、引用计数和值。

4)string内部包含一块内存,最大可容纳15个字符的字符串。因此小的字符串可以完整地存放在该string对象中,这一特性通常被称为“小字符串优化”特性。当一个string的容量超过15时,该内存的开头部分被当作一个指向一块动态分配的内存的指针,而该值就放在这块内存中。

55.引用计数,比如只有当字符串被频繁拷贝是,引用计数才有用,而有些应用并不经常拷贝内存,这就不值得使用引用计数了。

56.string设计灵活性:

1)string的值可能会被引用计数,也可能不会。(很多实现在默认情况下会使用引用计数,但他们通常提供了关闭默认选择的方法,往往是通过预处理宏来做到这一点。)

2)string对象大小的范围可以是一个char*指针的大小的1倍到7倍。

3)创建一个新的字符串值可能需要零次、一次或两次动态分配内存。

4)string对象可能共享,也可能不共享其大小和容量信息。

5)string可能支持,也可能不支持对单个对象的分配子。

6)不同的实现对字符内存的最小分配单位有不同的策略。

57.begin的返回值是一个迭代器,不是指针;当你需要一个指向vector中的数据的指针时,你永远不应该使用begin,如果为了某正原因请使用&*v.begin,因为这和&v[0]产生同样的指针。

58.数组指针实际是指向首元素的指针,所以vector的元素指针可以用&v[0]来获得(因为vecter和数组一样,其中的元素存储都在连续的内存中)。唯一麻烦的地方就是v可能是空的,则&v[0]则试图产生一个指针,而指针指向的东西并不存在,所以要提前判断vector是否为空。

59.string可以用c_str来返回一个指向string的指针,而指针可用于c(不需要担心string为空的情况)。string不能用&s[0]的原因是:1)string中的数据不一定储存在连续的内存中。2)string的内部表示不一定是以空字符结尾的。

60.从string和vector中去除多余的容量,即“shrink to fit”(压缩至适当大小)。

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

string s;
string(s).swap(s);

解释:表达式vector(contestants)创建一个临时的矢量,他是contestants的拷贝:这是由vector的拷贝构造函数来完成的。然而,vector的拷贝构造函数只为所拷贝的元素分配所需要的内存,所以这个临时矢量没有多余的容量。然后我们把临时矢量中的数据和contestants中的数据做swap操作,contestants具有了被去除之后的容量,即原先临时变量的容量,而临时变量的容量则变成了原先的contestants臃肿的容量。最后(语句结尾)临时矢量被析构了,从而释放了原先的内存。

这种放法并不意味着“使容量尽量小”,他意味着在“在容器当前大小确定的情况下,使容器在该实现下变为最小”。(string和vector一般会保留多余的容量)

61.避免使用vector。vector有两点不对,1)他不是一个STL容器。2)他并不存储bool。

62.一个对象要成为STL容器,就必须满足c++标准的所有条件。其中:如果c是包含对象T的容器,而且c支持operator[],那么 T *p = &c[0]; 必须能被编译。即如果你用operator[]取得了Container中的一个对象,那么你可以通过取它的地址得到一个指向该对象的指针。

63.vector是一个假的容器,它并不真的存储bool,相反,为了节省空间,他储存的是bool的紧凑表示。即8位的字节可以容纳8个“bool”。你可以创建一个指向bool的指针,而指向单个位的指针则是不允许的,而bool只占其中一位。所以vector::operator[]不能返回一个指向单个位的引用,而这样的引用是不存在的。所以c++使用了一个代理对象。

vector<bool> v;
bool *pb = &v[0]; 所以实际上&v[0]返回的是代理对象类型的指针,而不是bool*类型的。

64.可以使用deque 和 bitset来代替vector

关联容器

65.等价关系是以“在一排序的区间中对象值的相对顺序”为基础的。如果两个值中的任何一个(按照一定的排序准则)都不在另一个的前面,那么这两个值(按照这一准则)就是等价的。即Poss和poss是不相等的,但是在不区分大小写的排序中他们是等价的。

//价差等价性
!(a <= b) && !(b <= a)

66.因为存在等价和相等的存在,意味着等价不一定相等,所以优先使用成员函数就及其重要,而不是与之对应的非成员函数。比如find成员函数因为考虑等价而可以根据Poss找到poss,而非成员函数find则不会。

67.为包含指针的关联容器指定比较类型。如果把指针作为参数他会根据指针的值进行排序,而不是所指的值进行排序。

68.

set<string*> ssp;

是下面代码的缩写:

set<string*, less<string*>> ssp;

set<string*, less<string*>, allocator<string*>> ssp;
//类型,默认比较函数,分配子

所以我们需要编写自己的比较函数的子类如

set<string*, newLess> ssp;

69.set模板的三个参数每一个都是一个类型,随意我们不能将一个函数带入。set不需要一个函数,他需要一个类型,并在内部用它创建一个函数。

模板:
class DereferenceLess
{
	template<typename PtrType>
	
	bool operator () (PtrType pT1, PtrType pT2) const
	{
		return *pT1 < *pT2;
	}
}

70.总是让比较函数在等值情况下返回false。比较函数的返回值表明的是按照该函数定义的排列顺序,一个值是否在另一个之前。相等的值从来不会有前后顺序关系,所以,对于相等的值,比较函数应当始终返回false。除非你的比较函数对相等的值总是返回false,否则你会破环所有的标准关联容器,不管他们是否允许存储重复的值。对于关联容器排序的比较函数必须为它们所比较的对象定义一个“严格的弱序化”。

71.切勿直接修改set或multiset中的键。

72.map或multimap的键值是const Key,而set或multiset的键值是key。

73.如果你不关系可移植性,并且你的STL实现允许你这么做,只要注意不改变元素中的键部分,即元素中能够影响容器有序的部分。如果你重视可移植性,就要确保set和mulitiset中的元素不能被修改。至少不能未经过强制类型转换就修改。

74.强制类型转换set,它将取得i所指的对象,并告诉编译器把类型转换的结果当作一个指向(非const的)string的引用,然后对该对象调用setTitle。

iter->setTitle("111"); //有些编译器实现将认为这一行为不合法
						//以为*iter为const

const_cast<string&>(*i).setTitle("111"); //转换掉*i的const性质

75.通过强制转换到引用类型,我们避免了创建新的对象。这样,类型转换的结果将会指向已有的对象。

76.安全的修改set、multiset、map、multimap中的元素。1)找到你想修改的容器的元素。2)为将要被修改的元素做一份拷贝。在map和multimap的情况下,不要把该拷贝的第一个部分声明为const。3)修改该拷贝。4)把该元素从容器中删除。5)把新的值插入到容器中。

78.对set和multiset,如果你直接对容器中的元素做了修改,那么你要保证该容器仍然使排序的。

79.对于许多应用,哈希容器可能提供的常数时间查找能力优于set、multiset、map、multimap的确定对数时间查找能力。

80.使用数据结构的过程:1)设置阶段:创建一个新的数据结构,并插入大量元素。2)查找阶段:查询该数据结构以找到特定的信息。3)重组阶段:改变数据结构的内容,或许使删除、查找和添加。该阶段结束又回到第二阶段。

81.可以考虑排序的vector代替关联容器。

82.在排序的vector中存储数据结构可能比在标准关联容器中存储同样的数据要耗费更少的内存。(关联容器使用了树结构,所以至少要为数据结构分配3个指针的地址,即指向左右节点的和根节点的。)而考虑到页面错误的因素,通过二分搜索法来查找一个排序的vector可能比查找一个标准关联容器要更快。

83.对数据结构的使用方式使:查找操作几乎从不跟插入和删除操作混在一起时,在考虑使用排序的vector而不是关联容器才是合理的。

84.当使用vector来模仿map<k, v>时,存储在vector中的数据必须是pair<k, v>,而不是pair<const k, v>。map和multimap总是保持自己的元素是排序的,但他们排序只看元素的键部分,所以对vector进行排序时,也需要这么做。

85.map的operator[]函数与vector、deque和string的operator[]函数和内置数组的operator[]没有关系,map::operator[]的设计目的是为了提供“添加和更新的功能”。m[k] = v; 检查键k是否已经在map中了。如果没有,它就被加入,并以v作为相应的值。如果k已经在映射表中了,则与之关联的值被更新为v。

86.当向映射表中添加元素时,要优先选用insert,而不是operator[],insert插入时会节省三个函数的调用(构造函数、析构函数和拷贝函数)。当更新已经在映射表中的元素的值时,要优先选择operator[],insert在会多调用一个pair构造函数和一个pair析构函数。

87.C++标准保证,如果“提示”是正确的,则插入操作将耗费常数时间,而不是对数时间。

88.SGI的哈希容器通过测试两个对象是否相等,而不是是否等价来决定容器中的两个对象是否有相同的值。

迭代器

89.iterator优先于const_iterator、reverse_iterator以及const_reverse_iterator。

90.我们没有办法从const_iterator转换得到iterator,也无法从const_reverse_iterator得到reverse_iterator。

91.1)有些版本的insert和erase函数要求使用iterator。如果你需要调用这些函数,那你就必须使用iterator。const和reverse型的迭代器不能满足这些函数的要求。2)想要隐式地将一个const_iterator转换成iterator是不可能地。3)从reverse_iterator转换而来地iterator在使用之前可能需要相应的调整。

92.使用distance和advance将容器的const_iterator转换成iterator。

93.const_iterator到iterator之间不存在隐式转换。

94.STL实现为了便于调试,通常只会在Release模式下才使用指针来表示vector和string的迭代器。

95.reverse_iterator调用base()成员函数转换成iterator时会出现偏移。(一般是iterator落后一位)

96.insert插入会将新元素插到迭代器指定位置的元素的前面,所以如果要在一个reverse_iterator ri指定的位置上插入一个新元素,则只需要在ri.bese()的位置处插入元素即可。(ri从右向左遍历所以插入元素实际是在迭代器指向位置的右边,而iterator是在插入的左边,iterator又落后ri一个位置的偏移量)对插入操作而言,ri和ri.base()是等价的。

97.如果要在一个reverse_iterator ri指定的位置上删除一个元素,则需要在ri.base()前面的位置上执行删除操作。(因为有一个位置的偏移量)例:v.erase((++ri).base());可以。v.erase(–ri.base());错误。

98.C和C++都规定了从函数返回的指针不应该被修改。

99.对于逐个字符的输入请考虑使用istreambuf_iterator。

100.istream_iterator使用operator»函数来完成实际的读操作,默认情况下operator»函数会跳过空白字符串。

使用 inputfile.unsetf(ios::skipws);可以禁止忽略空格。

101.istream_iterator内部使用的operator»函数实际上执行了格式化的输入,这意味着每调用一次operator»操作符,他都要执行许多附加操作:1)一个内部的sentry对象的构造和析构(sentry是在调用opertor»的过程中进行设置和清理行为的特殊iostream对象)。2)检查那些可能会影响其行为的流标志(如skipws)。3)检查所有可能会发生的读取错误。4)如果遇到错误,还需要检查输入流的异常屏蔽标志以决定是否抛出相应的异常。

102.istream_iterator对象使用operator>>从输入流中读取单个字符,而istreambuf\_iterator则直接从流的缓冲区中读取下一个字符。(istreambuf_iterator对象从一个输入流istream s中读取下一个字符的操作是通过s.rdbuf()->sgetc()来完成的。)

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

103.如果你需要从一个输入流中逐个读取字符,就不必使用格式化输入。所以使用istreambuf_\iterator取代istream_iterator可以获得明显的性能改善。对于非格式化的逐个字符输入过程,总应该考虑使用istreambuf_iterator。对于非格式化的逐个字符输出过程,也应该考虑使用ostreambuf_iterator。

104.ostreambuf_iterator可以避免使用ostream_iterator带来的额外负担,但同时也损失了格式化输出的灵活性。

算法

105.back_inserter(生成一个迭代器来指定目标区间的实际位置)返回的迭代器将使得push_back被调用。同front_inserter。

106.verctor并没有提供push_front方法,所以无法针对vector使用front_inserter。

107.push_back插入头部可以通过反向遍历要插入的数据来确保顺序。

108.vector和string预先调用reserve,从而可以提高插入操作的性能。 reserve只是增加了容器的容量,并没有改变容器的大小。

109.确保目标区间足以容纳算法的结果。

110.用其中一个容器覆盖另一个容器时,要确保两个容器中的元素一样多,否则就必须使用resize来保证这一点。

if (result.size() < values.size())
{
	results.resize(values.size());
}

111.如果所使用的算法需要指定一个目标区间,那么必须确保目标区间足够大,或者确保他会随着算法的运行而增大。要在算法执行过程中增大目标区间,要使用插入型迭代器,比如ostream_iterator、或者由back_inserter、front_inserter和inserter返回的迭代器。

112.排序可用sort算法,部分排序可以使用partial_sort。

113.nth_element用于排序一个区间,它使得位置n上的元素正好是全排序情况下的第n个元素(这里的n由我们指定)。而且,当nth_element返回的时候,所有按全排序规则(即sort的结果)排在位置n之前的元素也都被排在n之前,而所有按全排序规则排在位置n之后的元素则都被排在位置n之后。还可以用来找到一个区间的中间值,或者找到某个特定百分比上的值。

114.nth_element和partial_sort基本上完全相同。在效果上唯一不同之处在于:partial_sort对位置1-20中的元素进行了排序,而nth_element没有对它们进行排序。

115.在稳定的排序算法中,如果区间中的两个元素由等价的值,那么在排序之后,它们的相对位置不会发生变化。非稳定算法并不会保证这一点。

116.partial_sort、nth_element和sort都属于非稳定的排序算法。

117.stable_sort的算法可以提供稳定排序特性。

118.partition算法可以把所有满足某个特定条件的元素放在区间的前部,返回一个指向第一个不满足条件元素的迭代器。

119.sort、stable_sort、partition和nth_element算法都要求随机访问迭代器,所以只能被应用于vector、string、deque和数组。

120.对标准关联容器中的元素进行排序没有实际意义,因为这样的容器总是使用比较函数来维护内部元素的有序性。

121.list是唯一需要排序却无法使用这些排序算法的容器,list特别提供了sort成员函数。(list::sort执行的是稳定排序)

122.排序选择:

  1)如果需要对vector、string、deque或者数组中的元素执行一次完全排序,那么可以使用sort或者stable_sort。

  2)如果有一个vector、string、deque或者数组,并且需要对等价性最前面的n个元素进行排序,那么可以使用Partial_sort。

  3)如果有一个vector、string、deque或者数组,并且需要找到第n个位置上的元素,或者,需要找到等性价最前面的n个元素但又不必对这n个元素进行排序,那么,nth_element正是你所需要的函数。

  4)如果需要将一个标准序列容器中的元素按照是否满足某个特定的条件区分开来,那么,partition和stable_partition可能正是你所需要的。

  5)如果你的数据在一个list中,那么仍然可以直接调用partition和stable_partition算法,你可以用list::sort和stable_sort算法。

123.标准的非STL容器priority_queue,它总是保持其元素的顺序关系。但他并不支持迭代器。

124.稳定的排序算法要比那些忽略稳定性的算法更为耗时。

125.其中消耗资源较少的算法排序:

1)partition 
2)stable_partition 
3)nth_element
4)partial_sort
5)sort
6)stable_sort

125.建议对算法的选择应该更多地基于你所需要完成的功能,而不是算法的性能。

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

127.remove需要一对迭代器来指定所要操作的元素区间。他并不接受容器作为参数,所以remove并不知道这些元素被存放在哪个容器中,也不可能推断出是什么容器。从容器中删除元素的唯一方法是调用该容器的成员函数,而remove并不知道他操作的元素所在的容器,所以remove不可能从容器中删除元素。

128.用remove从容器中删除元素,而容器中的元素数目却不会减少。remove不是真正意义上的删除,因为它做不到。

129.remove移动了区间中的元素,其结果是,“不用被删除”的元素移动到了区间的前部(保持原来的相对顺序)。他返回一个迭代器指向最后一个“不用被删除”的元素之后的元素。这个返回值相当于该区间“新的逻辑结尾”。但要被删除的元素依旧存在。把“不用被删除”不用被删除的元素放在v.begin()和newEnd之间,“需要被删除”的元素放在newEnd和v.end()之间,当我们需要的是被删除元素不能留在vector中。所以我们要在最后调用erase来删除newEnd和v.end()之间的元素。

v.erase(remove(v.begin(), v.end(), 99), v.end());

130.List有自己的remove成员函数,这是STL中唯一一个名为list::remove并且确实删除了容器中元素的函数。

131.remove和remove_if相同,但是unique也和remove行为相似,所以在调用unique之后再调用erase删除。list::unique也会真正的删除元素同list::remove。

132.对包含指针的容器使用remove这一类算法时要特别小心。

133.删除容器中的指针并不能删除该指针所指的对象。

134.remove移动数据会导致“要被删除”的指针被那些“不会被删除”的指针覆盖。没有任何指针指向“要被删除”的数据,所以他们永远也不会被删除。

135.当容器中存放的时指向动态分配的对象的指针的时候,应该避免使用remove和类似的算法(remove_if和unique)。

136.如果容器中存放的不是普通指针,而是具有引用计数功能的智能指针,那么困难就不再存在,可以直接使用erase-remove习惯用法。

137.有些算法要求排序的区间,即区间中的值是排过序的。

138.用于查找的算法binary_search、lower_bound、upper_bound和equal_range要求排序的区间,因为他们采用二分法查找数据。这些算法承诺了对数时间的查找效率,但前提是,必须提供已经排序好的数据。只有接受了所及访问迭代器的时候,才能保证对数时间的效率,否则执行的过程依旧是线性的。

139.set_union、set_insetsection、set_difference和set_symmetric_difference它们提供了线性时间效率的集合操作,若果区间不是排序的就不会满足线性的效率。

140.merge和inplace_merge实际上实现了合并和排序的联合操作:它们读入两个排序的区间,然后合并成一个新的排序区间,其中包含了原来两个区间的所有元素。

141.includes它用来判断一个区间中的所有对象是否都在另一个区间中。includes总是建设这两个区间是排序的,所以他承诺线性时间的效率。

142.要求排序区间的算法之所以有这样的要求是为了提供更好的性能,而对于未排序的区间它们无法保证有这样的性能。

143.unique和unique_copy它们即使对于未排序的区间也有好好的行为。行为:删除每一组连续相等的元素,仅保留其中的第一个。如果想让unique删除区间中所有重复的元素,那么就必须保证所有相等的元素都是连续存放的。unque使用了与remove类似的方法来删除区间中的元素,而并非真正意义上的删除,所以后面要调有erase。

144.保证你的排序区间的排序方式和算法的默认使用的排序方式是一直的,否则你就必须给算法提供一个排序方式。

145.通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较。

146.mismatch标识出两个区间中第一个对应值补相等的位置,如果两个字符串的长度不一样,那么我们必须把短的字符串作为第一个区间传入。

147.lexicographical_compare是strcmp的一个泛化版本,可以与任何类型的值的区间一起工作,可以接受一个判别式,有判别式来决定两个值是否满足一个用户自定义的准则。

148.strcmp只能与字符数组一起工作,总是通过比较两个字符来判断它们的关系是相等、小于还是大于。

149.不考虑移植性和国际化支持可以把两个string转换成const char*指针,然后调用srtcmp或strcmpi。

150.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;
				}
				return destBegin;
			}
		}

151.使用accumulate或者for_each进行区间统计。

152.count获取一个区间中有多少个元素。

153.count_if统计满足某个判别式的元素个数。

154.min_element、max_element获取区间中的最小值和最大值。

155.每一个标准的STL容器都有一个名为size_type的类型定义,它是容器中用于计数的类型。对于所有的标准容器,size_type都必须是size_t。对于标准的容器,可以把Container::size_type当作是size_t的另一种写法。

156.accumulate的第一种形式:有两个迭代器和一个初始值,返回该初始值加上由迭代器标识的区间中的值的总和:

double sum = accumulate(ld.begin(), ld.end(), 0.0);

第二种形式:两个迭代器带一个初始值和一个任意的统计函数。

float product = accumulate(vf.begin(), vf.end(), 1.0f, multplies<float>()); //计算区间中函数的乘积值

157.accumulate将会计算出一个区间的统计信息,而for_each对区间的每个元素做一个操作。accumulate直接返回我们所要的统计结果,而for_each却返回一个函数对象,必须从这个函数对象中提取出我们所需要的统计信息。

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

158.遵循按值传递的原则来设计函数子类。

159.无论是C还是C++,都不允许将一个函数作为参数传递给另一个函数,必须传递函数指针。C和C++的标准库函数都遵循:函数指针是按值传递的。

160.如果将函数对象按引用来传递的话,有些STL算法的某些实现方式甚至根本不能通过编译。

161.函数对象往往会按值传递和返回:1)函数对象必须尽可能地小。2)函数对象必须是单态地(不是多态的),也就是说,它们不得使用虚函数。因为在参数传递地过程中可能会产生剥离问题。

162.允许函数对象可以很大并且保留多态性,又可以于STL所采用的按值传递函数子的方法:将所需要的数据和虚函数从函数子类中分离出来,放到一个新的类中;然后在函数子类中包含一个指针,指向这个新类的对象。

163.一个判别式是一个返回值为bool类型(或者可以隐士地转换为bool类型)的函数。标准关联容器的比较函数就是判别式。

164.纯函数是指返回值仅仅依赖于其参数的函数。在c++中,纯函数所能访问的数据应该仅限于参数以及常量。