C++ STL入门

  1、STL的三个基本组件:

  1)容器:以模板类的形式提供。

  STL容器对象释放(如超出作用域)后,进程可能还会占用这部分内存,而不是立即把它归还给系统。

  (1)顺序容器:vector/list/deque。通过元素在容器中的位置顺序存储和访问元素。

  顺序容器适配器(adaptor):queue(FIFO队列)、priority_queue(优先级队列)和stack。

  默认的stack和queue都基于deque容器实现,而priority_queue则在vector容器上实现。在创建适配器时,通过将一个顺序容器指定为适配器的第二个类型实参,可覆盖其关联的基础容器类型:

stack<string, vector<string> > str_stk;

  对于给定的适配器,其关联的基础容器必须满足一定的约束条件。stack适配器可以建立在vector/list/deque上。而queue适配器要求其基础容器必须提供pop_front运算,因此只能建立在list/deque上。priority_queue适配器要求提供随机访问功能(保持堆结构),因此可建立在vector/deque上。

  (2)关联容器:map、set、multimap(同一个“键”可多次出现的map)和multiset。通过“键”存储和读取元素。在迭代遍历关联容器时,可确保按“键”的“升序”访问元素。

  关联容器不提供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; }
};

  VIII、查看元素个数:size()。时间复杂度:在C++98里面是线性的,在C++11里面是常数。

 

  5)priority_queue:优先级队列。

  (1)特性:类似于堆(如小根堆:任意节点的值总是小于等于它的子结点的值,因此堆顶节点是最小的)。

  (2)示例:

#include <queue>

int main()
{
    int a[] = {14, 10, 56, 7, 83, 22, 36, 91, 3, 47, 72, 0};
    // 一个小根堆
    priority_queue<int, vector<int>, greater<int> > pq(a, a + sizeof(a) / sizeof(int));

    // 0 3 7 10 14 22 36 47 56 72 83 91 
    while (!pq.empty())
    {
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;
}

  (3)原理:priority_queue的构造函数/push()/pop()分别调用了<algorithm>中的make_heap()/push_heap()/pop_heap()。以下例子说明这几个_heap函数的作用:

#include <algorithm>

int main ()
{
    int myints[] = {10, 20, 30, 5, 15};
    vector<int> v(myints, myints + 5);

    std::make_heap(v.begin(), v.end());
    // 大顶堆。此时v的元素依次是:30 20 10 5 15

    std::pop_heap(v.begin(), v.end());
    // 交换v[0]和v[v.size() - 1],再把v[0]~v[v.size() - 2]重新做成一个堆
    v.pop_back();
    // 此时v的元素依次是:20 15 10 5

    v.push_back(99);
    std::push_heap(v.begin(), v.end());
    // 把新加入的元素与原堆一起做成一个新堆
    // 此时v的元素依次是:99 20 10 5 15

    std::sort_heap(v.begin(), v.end());
    // 对堆进行排序。此时v的元素依次是:5 10 15 20 99

    return 0;
}

  (4)应用:可以使用priority_queue(实现小根堆)+树实现哈夫曼树。

 

  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/

 

 

不断学习中。。。

posted on 2013-10-15 16:31  han'er  阅读(479)  评论(0编辑  收藏  举报

导航