C++标准模板库笔记(C++ Primer plus)
1.除序列外,vector还是可反转容器(reversible container)概念的模型。
它有两个类方法:rbegin()和rend(),前者返回一个指向反转序列的第
一个元素的迭代器,后者返回反转序列的超尾迭代器。这些迭代
器都是reverse_iterator,对这样的迭代器执行递增操作,将导致它
反向遍历可反转容器。
如果dice是一个vector
则:
for_each(dice.begin(),dice.end(),Show); //正向显示dice的内容
for_each(dice.rbegin(),dice.rend(),Show); //反向显示dice的内容
vector模板类是最简单的序列模型,除非其他类型的特殊有点能够更好地满足程序的要求,否则应该默认使用这种类型。
2.deque模板类(在deque头文件中声明)表示双端队列(double-ended queue),通常被简称为deque。
在STL中,其实现类似于vector容器,支持随机访问。其主要区别
在于,从deque对象的开始位置插入和删除元素的时间是固定的,
而不像vector中那样是线性时间的。
虽然vector和deque都提供对元素的随机访问和在序列中部执行线
性时间的插入和删除操作,但vector容器执行这些操作时速度要快些。
3.list模板类(在list头文件中声明)表示双向链表。
区别:list在链表中任一位置进行插入和删除的时间都是固定的,
因此,vector强调的是通过随机访问进行快速访问,而list强调的
是元素的快速插入和删除。
与vector相似,list也是可反转容器。不同的是,list不支持数组表
示法和随机访问,从容器中插入或删除元素之后,链表迭代器指
向元素将不变。(只是修改连接信息,指向某个元素的迭代器仍
然指向该元素。)
list 模板包含了链表专用的成员函数:
[图片上传失败...(image-ab7c24-1567059183323)]insert()和splice()之间的主要区别在于:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移到目标地址。移动后,被移动的容器将置空(splice()方法还有其他原型,用于移动单个元素和元素区间)。原始容器的迭代器将重新定位到新容器,迭代器仍然指向相同的元素。
1.void splice ( iterator position, list& x );
2.void splice ( iterator position, list& x, iterator it );
3.void splice ( iterator position, list& x, iterator first, iterator last );
position 是要操作的list对象的迭代器
1:会在position后把list&x所有的元素到剪接到要操作的list对象
2:只会把it的值剪接到要操作的list对象中
3:把first 到 last 剪接到要操作的list对象中
unique()只能将相邻的相同值压缩为单个值,但使用sort()后,再
使用unique()时,每个值将只占一个位置。
对于 非成员sort()函数,它需要随机访问迭代器,因为快速插入的
代价是放弃随机访问功能,所以不能将非成员函数sort()用于链表。
因此,list容器中包括了一个只能在类中使用的成员版本。
4.C++11新增了容器类forward_list,它实现了单链表。在这种链表
中,每个节点都只链接到下一个节点。因此,forward_list只需要
正向迭代器,是不可反转容器。相比于list,forward_list更简单,更
紧凑,但功能也更少。
5.queue模板类是一个适配器类
ostream_iterator模板也是一个适配器,让输出流能够使用迭代器
接口。queue模板让底层类(默认为deque)展示典型的队列接口。
queue模板的限制比deque更多,它不仅不允许随机访问队列元
素,甚至不允许遍历队列。
6.priority_queue模板类(在queue头文件中声明)是另一个适配
器类,它支持的操作与queue相同。两者的主要区别在于:在
priority_queue中,比重最大的元素被移到队首,内部区别在于,
默认的底层类是vector。
它可以修改用于确定哪个元素被放到队首的比较方式
7.stack 与queue相似,stack也是一个适配器类,它给底层类(默
认是vector)提供了典型的栈接口
stack模板的限制比vector更多,它不仅不允许随机访问栈元素,
甚至不允许遍历栈。
8.array 模板类array在头文件array中定义,它不是STL容器,因为
其长度是固定的。像push_back(),insert()这种调整容器大小的操
作,array都没有,但是它有operator和at()。可将很多标准STL算
法用于array对象,如copy()和for_each()。
9.关联容器(associative container) 将值与键关联在一起,并用
键来查找值。
关联容器的优点在于,它提供了对元素的快速访问。关联容器允
许插入新元素,但不能指定元素的插入位置。原因是关联容器通
常有用于确定数据放置位置的算法,以便能快速检索信息。
关联容器通常是使用某种树实现的。像链表一样,树的节点使得
添加或删除数据项比较简单。但相对于链表,树的查找速度更
快。
STL提供了四种关联容器:set, multiset, map, 和multimap
(set和map头文件)
最简单的关联容器是set,其值和键的类型相同,键是唯一的。
multiset类似于set,只是可能有多个值的键相同。
map中 值与键的类型不同,键是唯一的。但是multimap中一个键
可以对应多个值。
10.无序关联容器 与关联容器一样,也将值与键关联起来,并使用
键来查找值。但底层的差别在于,关联容器是基于树结构的,而
无序关联容器是基于哈希表的,目的是提高元素的添加和删除速
度以及提高查找算法的效率。
11.函数对象: 很多STL算法都使用函数对象——也叫函数符
(functor)。函数符是可以以函数方式与()结合使用的任意对象。这
包括函数名,指向函数的指针和重载了()运算符的类对象。
12.对于所有内置的算术运算符,关系运算符和逻辑运算符,STL
都提供了等价的函数符。
供泛型;其次它们都使用迭代器来提供访问容器中数据的通用表
示。
STL将算法库分成4种:
- 非修改式序列操作(find(),for_each()...)
- 修改式序列操作(transform(),random_shuffle(),copy()...)
- 排序和相关操作(sort(),其它集合操作等等)
- 通用数字运算(比如将区间的内容累积,计算两个容器的内部乘积)
前3组在头文件algorithm中,第4组在numeric中。
有些算法有两个版本:就地版本和复制版本。STL的约定是,复
制版本的名称将以_copy结尾。复制版本将接受一个额外的输出迭
代器参数,该参数指定结果的放置位置。如:void replace_copy(...)
有些函数有这样的版本,即根据将函数应用于容器元素得到的结果
来执行操作。这些版本的名称通常以_if结尾。如:void replace_if(...)
14.string类虽然不是STL的组成部分,但设计它时考虑到了STL,
因此可以使用STL接口。
next_permutation()算法将区间内容转换为下一种排列方式。对于
字符串,排列按照字母递增的顺序进行。如果成功则返回true,
如果区间已经处于最后的序列中,则返回false。比如:
string letters;
...
sort(letters.begin(),letters.end());
while(next_permutation(letters.begin(),letters.end())
cout<
这个将输出字符串的所有排列。
15.有时可以选择使用STL方法或STL函数。通常 方法 是更好的选
择。首先,它更适合于特定的容器;其次,作为成员函数,它可
以使用模板类的内存管理工具,从而在需要时调整容器的长度。
而函数,它不是成员,并不能调整容器的长度。(不过可以让函
数的返回值是指向新的超尾值的迭代器,来提醒容器长度改变了)
16.编写一个程序,让用户输入单词。希望最后得到一个按输入顺
序排列的单词列表、一个按字母顺序排列的单词列表(忽略大小写)
,并记录每个单词被输入的次数。
//set会自动对元素进行排序,并且不会有相同元素出现。
#include
#include
#include
#include
#include
C++提供三个数组模板:vector、valarray和array。
vector模板类支持面向容器的操作,
valarray是面向数值计算的,不是STL的一部分,
array是为替代内置数组而设计的。
若将两个数组各个元素相加,并将和存储到新的的对象中:
vector< double > ved1(10),ved2(10),ved3(10);
array
valarray< double >vad1(10),vad2(10),vad3(10);
对于vector类:
transform(ved1.begin(),ved1.end(),ved2.begin(),ved3.begin(),plus< double >());
对于array类:
transform(vod1.begin(),vod1.end(),vod2.begin(),vod3.begin(),plus< double >());
对于valarray类:
vad3=vad1+vad2; //因为valarray类重载了所有算术运算符。
显然,执行多步运算时,valarray接口的简单性更加明显。
17.initializer_list
使用initializer_list对象,必须包含头文件initializer_list。这个模板
类包含成员函数begin()和end(),可以使用这两个函数来访问列表
元素。它还包含成员函数size(),返回元素数。
可按值或者按引用传递initializer_list对象。这种对象本身很小,通
常是两个指针(一个指向开头,一个指向末尾的下一个元素),
也可能是一个指针和一个表示元素数的整数,所以传递方式对性
能的影响不大。
initializer_list的迭代器类型为const。
initializer_list类的初衷是将一系列值传递给构造函数或其他函数。
18.STL是一个容器类模板,迭代器模板,函数对象模板和算法模
板的集合,它们的设计都是基于泛型编程原则。算法通过使用模
板,从而独立于所存储的对象的类型;通过使用迭代器接口,从
而独立于容器的类型。
19.智能指针
智能指针模板(auto_ptr,unique_ptr和shared_ptr)(头文件
memory)都定义了类似指针的对象,可以将new获得(直接或间
接)的地址赋给这种对象。当智能指针过期时,其析构函数将使
用delete来释放内存。
所有智能指针类都有一个explicit构造函数,该构造函数将指针作
为参数。
三种智能指针都应该避免的一点:
double a=1.0;
shared_ptr pa(&a); //NO!
//因为这样会导致delete运算符用于非堆内存。
为了避免删除同一个对象2次,可以用下面这些方法:
- 重载赋值运算符,使之执行深复制
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针拥有它,然后让赋值操作转让所有权。(这种方法的缺点是当指针丧失所有权后,若我们继续用这个指针去访问对象,会引发异常,因为丧失所有权后的智能指针是空指针。)
- 引用计数(reference counting),跟踪引用特定对象的智能指针数。 比如赋值时,计数加1,指针过期时,计数减1,当最后一个指针过期时,调用delete。
unique_ptr和auto_ptr都是用建立所有权的办法,但是unique_ptr比auto_ptr更严谨,不容易引发异常。假如程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值(比如返回值是非引用变量),编译器不会报错,但是如果源unique_ptr将存在一段时间,编译器会报错。
20.相比于auto_ptr,unique_ptr还有另一个优点:
因为delete和new配对,delete[]和new[]配对。而模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,而不能与new[]一起使用。但unique_ptr可以(shared_ptr也不可以):
std::unique_ptr
21.注意下面show函数里面的参数