1、STL的三个基本组件:
1)容器:以模板类的形式提供。
(1)顺序容器:如vector。通过元素在容器中的位置顺序存储和访问元素。
(2)关联容器:如map、set、multimap(同一个“键”可多次出现的map)和multiset(同一个“键”可多次出现的set)。通过“键”存储和读取元素。在迭代遍历关联容器时,可确保按“键”的“升序”访问元素。
关联容器不提供front和back系列操作:包括front()、back()、push_front()、pop_front()、back()、push_back()和pop_back()等。
关联容器不支持以下关于“容器大小”的操作,因为有了键之后,我们才能安排它的内部结构:无法通过容器大小来定义(如set<int> s(10);是错误的);不支持resize()。
2)迭代器:提供了访问容器中元素的方法。
3)算法:用来操作容器中元素的模板函数。
注意,使用通用算法(如find()、max_element())时需要加上#include <algorithm>。
2、容器
1)vector:“动态数组”。
(1)特性:
I、容量(capacity)随着元素的增加而自动增长:0、1、2、4、8、16... ... 使用复制构造一个新vector时,如vector<int> vec2(vec1),vec2的size和capacity等于vec1的size,且其capactiy从复制后的初始值开始double。如复制后vec2.capacity()为3,则之后其容量按6、12、24... ...增长。
每次double容量时,都会把原vector的元素复制到新vector的存储空间。通过平摊分析可知,插入n个元素的所有操作的总时间复杂度还是O(n)。
double容量时,重新分配内存会导致原迭代器失效。
II、使用pop_back()、erase()和clear()等可以改变vector的size,但不能减少其capacity。
若想在vector作用域结束前回收其内存,可以使用swap():vector<int>().swap(vec)。
(2)常用操作:
I、push_back():在vector末尾插入元素。注意vec[900] = 900;这样的形式不仅是不安全的,也不会改变size。
II、erase(iter):删除iter指向的元素,并将其后的元素前移。如13、15、91、30、19,iter指向91,则erase()之后变成13、15、30、19、19,size减1。
III、remove_if(v.begin(), v.end(), Odd());:将不删除的元素复制到v开头。如v的元素有1~7,删除奇数后,v的元素变成2、4、6、4、5、6、7。remove_if()返回指向“v的最后一个有效元素之后的元素”(在这里是第二个4)的迭代器。
IV、reserve():预留指定大小的容量。对于需要执行大量push_back()的vector来说,提前使用reserve()可以减少double容量时的复制操作。reserve()改变的是capacity,而size并没有改变。
V、front():返回第一个元素。
VI、back():返回最后一个元素(v1.at(v1.size() - 1))。
2)map:通常可理解为(键值)关联数组(使用“键”作为下标来获取“值”)。使用红黑树实现。
(1)应用:如字典,单词本身是“键”,而它的解释说明则是“值”。
(2)特性:
I、“键”是唯一的,且不可修改。将自定义类型作为“键”时,需要重载"<"运算符,以定义“键”的排序规则:
class key
{ public: key(int i) : i(i) {} bool operator<(const key &k) const { return i < k.i; } private: int i; };
II、将自定义类型作为“值”时,该类型可能需要有默认构造函数。详见下文关于“下标运算符”的内容。
(3)常用操作:
I、插入:
在说明我们的例子之前,先来熟悉一个标准库类型——pair类型。它也是一种模板类型,包含两个“成员”。可以通过以下方法创建和访问pair对象:
pair<int, string> p1(1, string("hello")); // make_pair(v1, v2):以v1和v2值创建pair对象,其元素类型分别是v1和v2的类型 pair<int, string> p2 = make_pair(2, string("world"));
// first和second是pair的(公有)数据成员,可直接读写 cout << p1.first << "\t" << p1.second << endl;
再来了解一下map定义的类型:
map<K, V>::key_type:用作索引的“键”的类型(此处为K)。
map<K, V>::mapped_type:“键”所关联的“值”的类型(此处为V)。
map<K, V>::value_type(map元素的类型):实际上是pair类型,其first元素是const map<K, V>::key_type类型,second元素是map<K, V>::mapped_type类型。value_type是pair<const K, V>类型的同义词。
关于“插入”操作的例子:
map<int, string> m; // 该版本的insert()的实参可以是:value_type对象、pair对象或make_pair()的返回值
// 返回值:pair类型,包含一个迭代器和一个bool值(表示是否成功插入了元素)。在这个例子中是pair<map<int, string>::iterator, bool> m.insert(map<int, string>::value_type(2, "world")); m.insert(pair<const int, string>(1, "hello")); m.insert(make_pair(3, "hello world"));
II、查找:
// 下标操作符危险的副作用:查找的“键”(key)对应的元素不存在时,会插入一个新元素
// 新元素的“键”为key,“值”使用0或默认构造函数来初始化
// 另一个副作用:不必要的初始化(如m[123] = 4;会先初始化,再赋值) cout << m[123] << endl; // 对于map来说,只能返回0或1 int count = m.count(456); map<int, string>::iterator iter = m.find(456); if (iter != m.end()) { cout << iter -> first << ": " << iter -> second << endl; }
III、删除:erase()
IV、遍历:
// map迭代器进行解引用将得到value_type类型的对象 map<int, double>::value_type vt = *iter;
3)set:map容器是键值对的集合,而set容器只是单纯的“键”的集合(没有定义mapped_type类型;value_type是与key_type相同的类型)。
(1)特性:
I、set不支持下标操作符。
II、与map一样,set容器存储的键必须唯一,而且不能修改。
(2)常用操作:
I、插入
// 与map类似,只带一个参数的insert()返回pair类型对象,包含一个迭代器和一个bool值,迭代器指向拥有该键的元素,而bool值表明是否添加了元素 pair<set<int>::iterator, bool> p = iset.insert(111); cout << *(p.first) << "\t" << p.second << endl; // 使用迭代器对作为参数的insert()返回void类型 iset.insert(ivec.begin(), ivec.end());
II、查找:
// find()返回迭代器 set<int>::iterator iter = iset.find(7); // count()返回0或1 cout << iset.count(8) << endl;
III、集合操作(差集、并集、交集等)
// sa的元素:2, 4, 7, 9, 10 // sb的元素:3, 4, 8, 10, 12 set<int> sa, sb, sc, sd, se; // 差集。输出:2,7,9, set_difference(sa.begin(), sa.end(), sb.begin(), sb.end(), inserter(sc, sc.begin())); copy(sc.begin(), sc.end(), ostream_iterator<int>(cout, ",")); cout << endl; // 交集。输出:4,10, set_intersection(sa.begin(), sa.end(), sb.begin(), sb.end(), inserter(sd, sd.begin())); copy(sd.begin(), sd.end(), ostream_iterator<int>(cout, ",")); cout << endl; // 并集。输出:2,3,4,7,8,9,10,12, set_union(sa.begin(), sa.end(), sb.begin(), sb.end(), inserter(se, se.begin())); copy(se.begin(), se.end(), ostream_iterator<int>(cout, ",")); cout << endl;
4)list:双向循环链表。
(1)特性:
I、大致结构(括号中的数字是元素的值):
以下是验证代码:
// 输出:2, 6, 4, 3, -1074611216, 2, 6, 4, list<int>::iterator iter = l.begin(); for (int i = 0; i < 8; i++) { cout << *(iter++) << "\t"; } cout << endl; // 输出:3, 4, 6, 2, -1074611216, 3, 4, 6, list<int>::reverse_iterator riter = l.rbegin(); for (int i = 0; i < 8; i++) { cout << *(riter++) << "\t"; }
(2)常用操作:
I、插入:push_back()、push_front()、pop_back()、pop_front()
II、查看:front()、back()
III、排序:sort()
IV、去重(在表有序的前提下):unique()。通过Binary Predicate(二元谓词)可以自定义“两个元素在什么情况下相等”:
// 二元谓词。可以用函数或类(匿名对象)实现 bool is_equal(double first, double second) { return (int(first) == int(second)); } class Equality { public: bool operator() (double first, double second) { return (fabs(first - second) < 5.0); } };
int main () { double darray[]={12.15, 2.72, 73.0, 12.77, 3.14, 12.77, 73.35, 72.25, 15.3, 72.25}; list<double> my_list(darray, darray + 10); // 2.72, 3.14, 12.15, 12.77, 12.77, 15.3, 72.25, 72.25, 73.0, 73.35 my_list.sort(); // 2.72, 3.14, 12.15, 12.77, 15.3, 72.25, 73.0, 73.35 my_list.unique(); // 2.72, 3.14, 12.15, 15.3, 72.25, 73.0 my_list.unique(is_equal); // 2.72, 12.15, 72.25 my_list.unique(Equality()); return 0; }
V、逆转链表:reverse()
VI、合并:merge()。如l1.merge(l2);,合并有序的l1和l2,并使合并结果按指定方式排序。合并结果存放到l1,而l2变为空。
VII、删除满足条件的元素:l1.remove_if(Odd());
// 定义了什么是奇数 class Odd { public: bool operator()(int i) { return i % 2 == 1; } };
3、算法
1)查找最大值:iter = max_element(l1.begin(), l1.end());
2)统计等于特定值的元素的个数:count(l1.begin(), l1.end(), 12)
3)统计满足特定条件的元素的个数:count_if(l1.begin(), l1.end(), Odd())
4)查找满足特定条件的元素:iter = find_if(l2.begin(), l2.end(), Odd());
5)排序:sort(vec.begin(), vec.end(), compare);是不稳定排序,stable_sort(vec.begin(), vec.end(), compare);是稳定排序。
参考资料:
《C++ Primer》
http://www.cplusplus.com/reference/list/list/unique/
不断学习中。。。