设为首页 加入收藏

TOP

STL Containers
2012-09-28 16:57:34 来源: 作者: 【 】 浏览:584
Tags:STL Containers

甚麽是 STL

STL 是标准样版程式库(Standard Template Library)的缩写,在经过前篇对样版函式与样版之后,你应该可以理解到 STL 其实就是资料结构与演算法的 template 版本,适用于各种资料型别,STL 当中的样版类别也称为 STL 容器(containers),包括阵列(array)、堆叠(stack)、串列(linked list)...等等,STL 当中的样版函式也称为演算法(algorithms),包括寻找、取代、排序...等等,它们都宣告在 std 命名空间当中,请见下面的例子,vector 样板类别是 STL 当中的阵列类别。

#include <vector>
#include <stdio.h>
using namespace std;

void main(void)
{
    // 以 float 型别具现化 STL 的 vector 样板类别:
    vector<float> grades;

    // 以 vector 的 push_back( ) 把数值 add 到 grades 中,自动增长记忆体空间:
    grades.push_back(78.0f); grades.push_back(44.0f);
    grades.push_back(89.0f); grades.push_back(55.0f);
    grades.push_back(45.0f); grades.push_back(75.0f);
    grades.push_back(94.0f); grades.push_back(74.0f);
    grades.push_back(83.0f); grades.push_back(65.0f);

    // 以 iterator 巡览整个 vector,这是 STL 的标准用法:
    float maxGrade = 0.0f;
    vector<float>::iterator iter;
    for (iter = grades.begin(); iter != grades.end(); ++iter)
    {   if (*iter > maxGrade) {  maxGrade = *iter;  }  }

    printf("Best grade is %f\n", maxGrade);
}
在这个例子当中,vector<float> 这个类别乃是以 float 型别具现化(realize) vector 这个样板类别,并且利用 push_back( ) 函式(类似前篇例子中的 add( ))来增加资料,push_back( ) 同样会在内部记忆体不足的时候自动增长记忆体空间,其实 vector 和前篇的 ValueArray 样板类别非常相似,到目前为止的,你应该都还感到熟悉。

在 vector 样板类别当中比较令你陌生的,应该就是接下来的 iterator 迴圈了,iter 又可以 ++ 又可以 * 运算子取数值,那麽 iter 到底是个整数变数还是指标呢?概念上来说都不是,在  STL 的容器类别当中另外定义了 iterator 类别来代替 for (int i=0; i<size; ++i) 之类的 i 变数,虽然 iterator 不是个整数,但也覆写(override)它的运算子,使它的行为看起来像整数,又能像指标的 * 运算子一般地取得容器内的资料,这麽作的目的在于希望提供使用者一套他们熟悉的方法,能在不触碰容器类别内部资料成员的前提下(不须传出内部记忆体位址),存取容器内的资料。

在本篇不会详细地逐个介绍 STL 的容器类别,本篇的宗旨仅在于建立你对 STL 容器类别的概念,接下来以 vector 为例说明之。

STL Vector

STL 当中的 vector 样板类别如同前篇的 ValueArray 样板类别一般,是会自动增长空间的阵列类别,这可能是被使用得最广泛的 STL 容器类别,而叠代子(iterator) 则是用来纪录巡览位置的资料结构。如上例一般,vector <typename T> 类别以 push_back( ) 从尾端增加资料,并且提供了 [ ] 运算子取得任意索引的数值,以 size( )指示元素个数。同时也提供了 begin( ) 函式取得开始巡览的叠代子,以 end( )判断是否结束,换言之,上例中巡览整个阵列内容的的迴圈,可以叠代子(iterator)的方式撰写如下:

// 以 iterator 巡览整个 vector,这是 STL 的标准用法:
float maxGrade = 0.0f;
vector<float>::iterator iter;
for (iter = grades.begin(); iter != grades.end(); ++iter)
{   if (*iter > maxGrade) {  maxGrade = *iter;  }  }

printf("Best grade is %f\n", maxGrade);
也可以透过 [ ] 运算子和 size( ) 撰写如下,这是你所熟悉的方式:

// 以整数变数 i 巡览整个 vector,这是 vector 的标准用法:
float maxGrade = 0.0f;

for (int i=0; i<grades.size(); ++i)
{   if (grades[i] > maxGrade) {  maxGrade = grades[i];  }  }

printf("Best grade is %f\n", maxGrade);
既然如此,为甚麽要有 iterator 这种东西呢?通通都用 i 不就好了吗?这是因为以 vector 来说,由于它内部储存资料的方式同样是连续的记忆体空间,所以它可以提供 [ ] 运算子,但是例如 STL 串列(list)类别以及其他的一些 STL 容器类别,它们虽然不是用连续空间来储存资料所以没有 [ ] 运算子,但它们同样地各自提供了 iterator 类别,同样可以前者的 iterator 迴圈巡览容器内所有的资料。

因此 iterator 的设计可说是为各种不同资料结构的 STL 容器类别,提供了一套共同的资料存取方式,各种容器类别的叠代子(iterator) 均支援 ++ 运算子来移动位置,并可以 * 运算子取回目前位置的数值。

使用 vector 样板类别必须 #include <vector> 并且援用 std 命名空间,它提供了以下的函式:

push_back(T &)
从尾端增加资料。
 
pop_back( )
从尾端删除资料。
 
erase(iterator i) 或
erase(iterator i, iterator j)
删除单一或指定范围内的资料。
 
clear( ) 清除所有的资料。
begin( )
取得从头开始巡览的iterator。
 
end( )
固定是个无效的iterator数值。
 
size( )
得知目前的元素数量。
 
at(i) 或 [i]
取得位于 i 索引位置的资料。
STL 的容器类别

以上介绍了最简单也最常用的 vector 类别,STL 所有的容器类别可分为三类:

序列容器类 (sequence)

vector: 阵列
- 连续空间。
 
list: 串列
- 非连续空间,无[ ]运算子。
- 插入/删除有效率。
 
deque:
- 双向 queue。
特性容器类 (adapters)

stack: 堆叠
- 先进后出。
 
queue: 储列
- 先进先出。
 
priority_queue: 优先权储列。
关联式容器类 (associative)

map: 序对
- (key, value) 的集合。
- key 不可重複。
 
multimap
- key 可重複的 map。
- 比较类似 database。
 
set: 集合
- 数值不可重複。
- 自动排序。
 
multiset
- 数值可重複的 set。
- 自动排序。
以上这些容器类别的特性,就如同它们所採用的资料结构一般,至于详细的函式与使用方法,你可以到需要使用到的时候再查阅MSDN或其他STL相关书籍,除此之外,如前所述地,除了特性容器类 stack, queue, priority_queue必须要透过特定函式存取资料,以维持先进后出之类的容器特性以外,其他容器均有以下四种叠代子(iterator)。

iterator : 顺向从 begin( ) 到 end( ) 为止。
reverse_iterator : 逆向从rbegin( )到rend( )为止。
这种 iterator 的 ++ 会由后向前走。
const_iterator: 顺向,* 取出的数值是唯读的。
const_reverse_iterator: 逆向,* 取出数值是唯读的
这四种叠代子(iterator)都有 ++, ==, * 三种运算子。

STL Map

必须注意的是各容器类别所提供的叠代子(iterator)乃是它们各自定义的资料结构,并不是共通的,只不过它们都提供了++, ==, * 三种运算子。这是当然的,毕竟没有一种 iterator 资料结构可以用来走访所有种类的容器类别,在此以 STL 另一种常用的容器类别 map 来举例。

map 是一种可以 key 来查询 value 的容器,所以内部都是 (key, value) 的序对,在程式当中可以当作简单的资料表(data sheet)或速查表(look-up table)来使用,在这个例子当中,你可以看到 map 的 [ ] 运算子和 iterator 和 vector 截然不同,map 没有 push_back( ) 函式,反而是覆写了 [ ] 运算子来作为加入(或替换)资料项目的方法,此外 map 的 iterator 有 first 和 second 两个栏位,这也是 vector 的 iterator 没有的。

#include <map>
#include <stdio.h>
using namespace std;

void main(void)
{
    // 以 int (key) 与 float (value) 型别具现化 STL 的 map 样板类别:
    map<int, float> grades;

    // map 没有 push_back( ),它藉由覆写 [] 运算子来新增资料,[] 内的数值是 key 值:
    grades[11] = 78.0f;  grades[12] = 44.0f;  grades[13] = 89.0f;  grades[14] = 55.0f;
    grades[15] = 45.0f;  grades[16] = 75.0f;  grades[17] = 94.0f;  grades[18] = 74.0f;
    grades[19] = 83.0f;  grades[20] = 65.0f;

    // 以 iterator 巡览整个 map:
    map<int, float>::iterator iter;
    int bestStudentID = 0;
    float maxGrade = 0.0f;

    for (iter = grades.begin(); iter != grades.end(); ++iter)
    {   if ((*iter).second > maxGrade)
        {   bestStudentID = (*iter).first;   // 这是 key 值
            maxGrade = (*iter).second;  }  // 这是 value 值
    }

    printf("Best student is No. %d whose grade is %f\n", bestStudentID, maxGrade);
}
map 的优点在以 key 作查询很有效率,查询方法只要简单地使用 [ ] 运算子就可以了,例如:

// 查询学号为 15 的学生的成绩:
printf("Grade of Student No. %d is %f", 15, grades[15]);
使用 STL 的程式写法是不是很优美呢?但优美背后的代价就是必须熟知运算子覆写与编译器原理,否则有不明错误或隐藏 bug,还有必须熟知资料结构原理,否则用错容器类别会严重伤害程式效率,所以许多人都说 STL 是高手的工艺品。

本篇重点回顾

您知道了吗
STL是把资料结构与演算法给样板化了。
STL提供10种容器,可分为三类,各适用于不同存取需求。
STL的容器提供 iterator 来巡览内容并且存取数值。
STL的 iterator 提供 ++, ==, * 三种运算子。
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇诺基亚推出广告交易平台 吸引WP8.. 下一篇pthread的执行绪函式库

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: