标准模板库(STL)介绍

来源:互联网 发布:悍将传世完整源码 编辑:程序博客网 时间:2024/06/10 04:37

标准模板库(STL)介绍

大概是一月份在winter的blog看到他翻译的标准模板(STL)介绍(上),当时看了前面的五六段,感觉不少地方怪怪的,于是留了“...发现这个翻译实在糟糕”的回复,最近重又看到 winter的这篇文章,重新对比着winter贴出的英文和其译文,发现硬伤的确不少,如past-the-end的翻译,或许是winter的一时疏忽吧。由此对译文并不满意,于是决定自己译一遍,当然看winter的译文在先,所以必有不少部分基于winter的译文,在此表示感谢,还有介绍了这样一篇不错的STL好文(至少对初学者而言)。

文中的代码用Vim+InkPot(Vim Themes)+TOhtml(Vim命令)转换得到。

原文 http://linuxgazette.net/issue34/field.html
版权信息:
Copyright ? 1998, Scott Field
Published in Issue 34 of Linux Gazette, November 1998

标准模板库(STL)介绍
Introduction to STL, Standard Template Library

By Scott Field


 

本文介绍了C++语言一个新的扩展,标准模板库,也即STL。

最初打算写篇关于STL的文章时,我得承认当时有些低估了这个主题的深度和广度。毕竟STL涉及了众多内容,而市面上也有许多详细描述STL的书。因此我重新审视和思考了自己最初的想法。为什么我还要写篇这样的文章,我又能提供些什么?哪些是有用的?有必要再来一篇STL文章吗?

当我翻开Musser和Saini的书时,我看到编程时间在自己面前分解(programming time dissolving in front of me),我看到黑夜消逝去而软件项目又重回目标,我看到了可维护的代码。现在已经过去了一年,我用STL编写的软件维护起来仍非常容易。令人惊恐啊,缺了我,别人可以维护的一样好!(注:作者用了shock horror,大概是想突出STL写的代码之可维护性。)

然而,我还依稀记得一开始面对那些技术术语时有多困难。直到买了Musser&Saini一书后,一切才清晰明朗起来,不过在那之前着实苦苦挣扎了一番,我渴望着能找到一些好的例子。

此外,我开始学习STL时,把STL作为C++一部分来描述的第三版Stroustrup还未出版。

因此我想,为STL新手们写篇关于STL在实际编程中的使用或许有些用处。要是手头有些好的例子,我总能学得更快,尤其是对于类似STL这样的新事物。

另外,STL应该易于使用,因此理论上我们应能马上开始使用STL。

什么是STL?STL表示Standard Template Library,即标准模板库。相对这个史上最令人兴奋的工具之一,这个名字也可能是史上最单调的术语之一。STL主要由一簇容器——list, verctor,set,map等等和一簇算法及其它组件组成。这“一簇容器和算法”可是许多世上最聪明的人多年探索的结晶。

STL的目的在于标准化经常使用的组件,这样一来人们就不必重新发明轮子了,可以直接使用现成的标准STL组件。现在STL已是C++的一部分,因此不用再到处搜集,安装,它已经内建在你的编译器中。STL list是较为简单的容器之一,我想从它入手应该能很好的说明STL的实际用法。如果理解了list相关的那些概念,其它容器也就不在话下了。此外,正如我们将看到的,即使是一个简单的list容器,所涉及的内容已多的吓人。

本文描述了:如何定义和初始化list,如何对其元素进行计数,怎样在list中查找、删除元素,及其它非常有用的操作。为完成这些操作,我们将论及两种不同的算法:作用于多种容器的STL泛型算法和只作用于list容器的list成员函数。

为消除不必要的疑惑,这儿先对三种主要的STL组件作一个扼要介绍。STL容器持有对象,包括内建对象(built in object)和类对象(class object),并且保证这些对象的安全,此外还定义了标准接口,通过它我们可以操作这些对象。放在蛋托(egg container)里的鸡蛋不会滚到厨桌上,它们很安全。STL容器(STL container)里的对象也是如此,它们也一样安全无虞。我知道这比喻够老土,不过蛮贴切。

STL算法是我们能应用到容器内对象上的标准算法。这些算法的执行性能卓著,能对容器内对象进行排序、删除、计数、比较,以及查找某个特定对象,把对象合并到另一个容器内,还能进行很多其它很有用的操作。

STL迭代器类似于指向容器内对象的指针,STL算法就是借助迭代器在容器上进行操作的。迭代器为算法设定了界限,依据的是容器的范围和其它途径,比如有些迭代器只允许算法读取元素,有些则允许算法写元素,而另外一些则读写皆可。迭代器还决定了在容器内操作的方向。

调用容器的成员函数begin()可以取得指向该容器内第一个元素的iterator,而调用容器的end()函数则可得到最后元素的下一个位置(在此停止处理)(past the end)。

STL的全部内容差不多也就是,容器、算法和允许算法作用于容器内元素上的迭代器。这些算法以可测量的、标准的方法操作对象,并借助迭代器掌握容器的确切范围。一旦做到这点,这些算法就决不会“亡命天涯”(run off the edge 越出边界)。还有其它一些组件能增强这些核心组件类型的功能,比如函数对象。我们也会论及一些函数对象相关的例子。眼下,开始咱们的STL list之旅吧。



定义一个list

如下方法可以定义一个STL list

#include <string>

#include <list>
int main (void) {
list<string> Milkshakes;
}

就这样,我们已经定义了一个list。够简单的吧?通过list Milkshakes这一句,我们先是实例化(instantiated)了模板类list,随后创建了该型别的(注:即实例化后的模板类)对象。不过先别拘泥于这些,现阶段你只需知道这样就定义了一个字符串list(a list of strings)。你还需要包含提供了STL list类的头文件。我在自己的Linux系统上用GCC 2.7.2编译了这些测试程序,如下:


g++ test1.cpp -otest1

注意头文件iostream.h已被包含在上面其中一个STL头文件内,这就是有些例子不见其踪影的原因。(注:不过良好的编程习惯应该是明确的包含需要的头文件,而不应该依赖其它头文件包含了所需的头文件;毕竟不是所有的编译器实现的都一样。此外应该使用标准C++的头文件,而不是,在最后一句#include之后,应加上using namespace std;,以下例子同)

好了,我们已经有了一个list,可以开始用它来持有东西了。我们将向这个list添加一些字符串。有个很重要的概念,即这个list的值型别(value type),value type指的是该list所持有的对象之型别。本例中,这个list的value type是string(字符串),因为该list持有strings(字符串)。



list成员函数push_back和push_front插入元素到list

#include <string>
#include <list>

int main (void) {
list<string> Milkshakes;
Milkshakes.push_back("Chocolate");
Milkshakes.push_back("Strawberry");
Milkshakes.push_front("Lime");
Milkshakes.push_front("Vanilla");
}
现在我们得到一个含有四个字符串的listlist成员函数push_back()把一个对象放置到list的尾部,而list成员函数 push_front()则把对象放置在list的头部。我常用push_back()插入一些出错消息到list中,然后用push_front()插入一个标题到list中,以便在出错消息前先打印该标题。



list成员函数empty()

获知一个list是否为空很有用。如果一个list为空,则list成员函数empty()返回true。Empty是个看似简单的概念。我常以如下方式使用之。在一个程序中,我会从头到尾的使用push_back()把出错消息放到list中。随后通过调用empty()我便能知道该程序是否有报错。如果我为指示性信息(informational messages)、警告和严重错误三者分别定义一个list,那么我只用empty()就能轻而易举的知道出现了哪种错误。

我可以在程序整个运行过程中填充这些lists,并在输出这些lists之前,加个标题让它们变得漂亮些,或将它们排序归类。

下面就是我的做法:

/*
|| Using a list to track and report program messages and status
*/
#include <iostream.h>

#include <string>
#include <list>

int main (void) {
#define OK 0
#define INFO 1
#define WARNING 2

int return_code;

list<string> InfoMessages;
list&lt:string> WarningMessages;

// during a program these messages are loaded at various points
InfoMessages.push_back("Info: Program started");
// do work...
WarningMessages.push_back("Warning: No Customer records have been found");
// do work...

return_code = OK;

if (!InfoMessages.empty()) { // there were info messages
InfoMessages.push_front("Informational Messages:");
// ... print the info messages list, we'll see how later
return_code = INFO;
}

if (!WarningMessages.empty()) { // there were warning messages
WarningMessages.push_front("Warning Messages:");
// ... print the warning messages list, we'll see how later
return_code = WARNING;
}

// If there were no messages say so.
if (InfoMessages.empty() && WarningMessages.empty()) {
cout << "There were no messages " << endl;
}

return return_code;
}




for循环处理list中的元素

我们总想要迭代遍历list以便,例如打印list中的所有对象,来查看在list上施加了各种不同操作后的实际效果。要一个元素一个元素的迭代遍历list,可按如下方法做:

/*
|| How to print the contents of a simple STL list. Whew!
*/
#include <iostream.h>
#include <string>
#include <list>

int main (void) {
list<string> Milkshakes;
list<string>::iterator MilkshakeIterator;

Milkshakes.push_back("Chocolate");
Milkshakes.push_back("Strawberry");
Milkshakes.push_front("Lime");
Milkshakes.push_front("Vanilla");

// print the milkshakes
Milkshakes.push_front("The Milkshake Menu");
Milkshakes.push_back("*** Thats the end ***");
for (MilkshakeIterator=Milkshakes.begin();
MilkshakeIterator!=Milkshakes.end();
++MilkshakeIterator) {
// dereference the iterator to get the element
cout << *MilkshakeIterator << endl;
}
}

本例程中,我们定义了一个iterator,MilkshakeIterator,并将其指向list的第一个元素。只要调用 Milkshakes.begin()即可,该函数返回指向list头的iterator。然后拿MilkshakeIterator和list的 Milkshakes.end()返回值进行比较,当这两个值相等时退出循环。

容器的成员函数end()返回一个iterator,它指向容器最后元素的下一个位置。到达此位置后,我们便停止处理,并且不能提领(dereference)由容器的end()函数返回的iterator。只要记住,这个位置表示我们已经处于容器最后元素的下一个位置,应该停止处理元素。所有STL容器都遵循这一点。

在上面的例子里,每次进入for循环之后,我们就提领这个iterator取得其指向的字符串,并将其打印输出。

在STL编程中,每个算法都会用到一个或多个iterators,我们可以用它们访问容器内的对象。将iterator指向需要访问的对象,然后提领这个iterator,我们便可以访问这个指定对象。

list容器不支持给list iterator加个数就能跳到容器内的另一个对象处,记住这一点。也就是说,我们无法让Milkshakes.begin()+2指向list中的第三个对象,因为STL list内部是以双向链表(a double linked list)结构实现的,而双向链表并不支持随机存取。不过STL的vector和deque容器支持随机存取。

上面的例程会打印输出list的全部内容。任何读过该例程的人都能立即明白其工作流程,它使用了标准iterators和一个标准list容器。这个例程里头并没有多少程序员个人的东西,或者自个儿打造的list实现,不过是标准C++而已。这可是一大进步。即使STL的这一简单用法就可令我们的软件更加标准。



用STL泛型算法for_each处理list中的元素

即使用了STL list和iterator,为了迭代遍历一个容器,我们还是得对iterator赋初值、检测并加一(注:指for(...)中的部分)。而STL泛型算法for_each则能让我们免于这些杂务。

/*
|| How to print a simple STL list MkII
*/
#include <iostream.h>

#include <string>
#include <list>
#include <algorithm>

PrintIt (string& StringToPrint) {
cout << StringToPrint << endl;
}

int main (void) {
list<string> FruitAndVegetables;
FruitAndVegetables.push_back("carrot");
FruitAndVegetables.push_back("pumpkin");
FruitAndVegetables.push_back("potato");
FruitAndVegetables.push_front("apple");
FruitAndVegetables.push_front("pineapple");

for_each (FruitAndVegetables.begin(), FruitAndVegetables.end(), PrintIt);
}
这个例程中,我们使用了STL泛型算法for_each()来迭代遍历一个iterator区间(range),并对每个对象调用函数PrintIt ()。我们不再需要给任何iterator赋初值、检测或加一,for_each()让我们的代码具备了很好的模块化。我们要给对象施加的操作已经很好的打包到一个函数里,并且除掉了那个循环,现在我们的代码变得更清晰了。

for_each算法引入了一个新概念,iterator区间(an iterator range),它由起始iterator和结束iterator(end iterator)界定。起始iterator确定从何处开始处理,而end iterator表示何处应停止处理,但并不包含在该区间内。(注:是一个半闭半开的区间,[ ... ))



用STL泛型算法count()对list内元素进行计数

STL泛型算法count()和count_if()对容器内对象的出现(occurrences of objects)进行计数。和for_each()一样,count()和count_if()算法也要给定一个iterator range。

让我们来算一下学生考试成绩list中最好成绩的数量,该list的value type是int。

/*
|| How to count objects in an STL list
*/
#include <list>

#include <algorithm>

int main (void) {
list<int> Scores;

Scores.push_back(100); Scores.push_back(80);
Scores.push_back(45); Scores.push_back(75);
Scores.push_back(99); Scores.push_back(100);

int NumberOf100Scores(0);
count (Scores.begin(), Scores.end(), 100, NumberOf100Scores);

cout << "There were " << NumberOf100Scores << " scores of 100" << endl;
}
这里的count()算法算的是等于某一个特定值的对象个数。在上面的例子中,该算法拿list内每个整型对象和100进行比较,每次只要容器对象等于100,变量NumberOf100Scores便加一。该例程的输出如下:


There were 2 scores of 100



用STL泛型算法count_if()对list内元素进行计数

和count()相比,count_if()是一个有趣的多的版本。它引入了一个新的STL组件,函数对象(function object)。count_if()的参数之一是函数对象。函数对象是一个至少定义了operator()的类。一些STL算法接受函数对象作为其参数,并对每个正被处理容器对象调用传入函数对象的operator()。

专门为和STL算法配合使用的函数对象已指定自己的函数调用操作符返回true或flase,函数对象也因此被称作判定函数(predicate function)。来个例子就能解释这一点。count_if()用传入的函数对象来进行比count()更为复杂的评估——某个对象是否应被计数。下面的例子将对牙刷的销售量进行计数。我们假定销售记录包含一个4字符的产品代码和该产品的描述。

/*
|| Using a function object to help count things
*/
#include <string>

#include <list>
#include <algorithm>

const string ToothbrushCode("0003");

class IsAToothbrush {
public:
bool operator() ( string& SalesRecord ) {
return SalesRecord.substr(0,4)==ToothbrushCode;
}
};

int main (void) {
list<string> SalesRecords;

SalesRecords.push_back("0001 Soap");
SalesRecords.push_back("0002 Shampoo");
SalesRecords.push_back("0003 Toothbrush");
SalesRecords.push_back("0004 Toothpaste");
SalesRecords.push_back("0003 Toothbrush");

int NumberOfToothbrushes(0);
count_if (SalesRecords.begin(), SalesRecords.end(),
IsAToothbrush(), NumberOfToothbrushes);

cout << "There were "
<< NumberOfToothbrushes
<< " toothbrushes sold" << endl;
}

该程序的输出是:
There were  2 toothbrushes matching code 0003 sold

这个例子说明了如何向函数对象传入信息。你可以定义任何自己喜欢的构造函数,你也可以在函数对象里做任何自己喜欢的处理,当然,这都得在编译器容忍的范围内。

你可以发现函数对象的确扩展了基本的计数算法。

到现在为止,我们已学习了:

* 定义一个list
* 添加元素到list
* 如何获知list为空
* 如何用for循环迭代遍历一个list
* 如何用STL泛型算法for_each迭代遍历一个list
* list成员函数begin()和end()及它们的含义
* iterator区间的概念和区间的最后一个位置并不会被处理这一事实
* 如何用STL泛型算法count()和count_if()计数一个list内的对象
* 如何定义函数对象

这些例子是我特意选来说明常用的list操作。如果理解了这些基本原理,你便能毫无困难的有效使用STL,不过还得提醒你要做些练习。下面我们将学些更复杂的操作,包括list成员函数和STL泛型算法。
用STL泛型算法find()在list中查找对象

我们如何在list中查找对象呢?STL泛型算法find()和find_if()达成此任务。和for_each()、count()和count_if()一样,这些算法需要传入一个iterator range,它确定了list或其它容器的哪一部分要进行处理。照常第一个iterator指定从何处开始处理,第二个iterator指定在何处停止处理,并且不用处理后者所确定的位置。

下面的例子说明了find()是如何工作的:

/*
|| How to find things in an STL list
*/
#include <string>
#include <list>
#include <algorithm>

int main (void) {
list<string> Fruit;
list<string>::iterator FruitIterator;

Fruit.push_back("Apple");
Fruit.push_back("Pineapple");
Fruit.push_back("Star Apple");

FruitIterator = find (Fruit.begin(), Fruit.end(), "Pineapple");

if (FruitIterator == Fruit.end()) {
cout << "Fruit not found in list" << endl;
}
else {
cout << *FruitIterator << endl;
}
}

该例程的输出是:


Pineapple

如果find()没有查找到指定的对象,则返回最后元素的下一位置(past the end)iterator Fruit.end();否则返回一个iterator,指向查找到的list对象。



用STL泛型算法find_if()在list中查找对象

还有一个比find()功能更强大的版本,find_if()。下面的例子即演示了find_if(),该算法以函数对象作为其参数之一,并用它来进行更为复杂的评估——某个对象是否“已被找到”。

假设我们手头有一个记录list,这些记录由事件和按年月日存储的日期组成。我们想查找1997年发生的第一件事。

/*
|| How to find things in an STL list MkII
*/
#include <string>
#include <list>

#include <algorithm>

class EventIsIn1997 {
public:
bool operator () (string& EventRecord) {
// year field is at position 12 for 4 characters in EventRecord
return EventRecord.substr(12,4)=="1997";
}
};

int main (void) {
list<string> Events;

// string positions 0123456789012345678901234567890123456789012345
Events.push_back("07 January 1995 Draft plan of house prepared");
Events.push_back("07 February 1996 Detailed plan of house prepared");
Events.push_back("10 January 1997 Client agrees to job");
Events.push_back("15 January 1997 Builder starts work on bedroom");
Events.push_back("30 April 1997 Builder finishes work");

list<string>::iterator EventIterator =
find_if (Events.begin(), Events.end(), EventIsIn1997());

// find_if completes the first time EventIsIn1997()() returns true
// for any object. It returns an iterator to that object which we
// can dereference to get the object, or if EventIsIn1997()() never
// returned true, find_if returns end()
if (EventIterator==Events.end()) {
cout << "Event not found in list" << endl;
}
else {
cout << *EventIterator << endl;
}
}


该例程的输出是:

10 January 1997 Client agrees to job




用STL泛型算法search在list中查找序列

在STL容器中有些字符处理起来比较容易,不过下面我们来看看一个处理起来较困难的字符序列。下面定义一个持有字符的STL list


list<char> Characters;


现在我们已经得到一个坚石般的字符序列,它自个儿知道如何管理自己的内存,它也知道起始和结束的确切位置。这个字符序列很有用处,我没用这话夸过以null结尾的字符数组吧。

好,再向这个list添加一些我们喜欢的字符:

 

  Characters.push_back('');
Characters.push_back('');
Characters.push_back('1');
Characters.push_back('2');


我们得到了几个null字符呢?

  int NumberOfNullCharacters(0);
count(Characters.begin(), Characters.end(), '', NumberOfNullCharacters);
cout << "We have " << NumberOfNullCharacters << endl;


再来查找一下字符'1':

  list<char>::iterator Iter;
Iter = find(Characters.begin(), Characters.end(), '1');
cout << "We found " << *Iter << endl;


这个例子意在说明STL容器允许你以更标准的方式处理null字符。接下来用STL search算法在容器中搜索两个nulls。

如你猜想的那样,STL泛型算法search()在容器中搜索,只不过搜索的目标是一个元素序列,而非find()和find_if()那样搜索的是单个元素。

/*
|| How to use the search algorithm in an STL list
*/
#include <string>

#include <list>
#include <algorithm>

int main ( void ) {

list<char> TargetCharacters;
list<char> ListOfCharacters;

TargetCharacters.push_back('');
TargetCharacters.push_back('');

ListOfCharacters.push_back('1');
ListOfCharacters.push_back('2');
ListOfCharacters.push_back('');
ListOfCharacters.push_back('');

list<char>::iterator PositionOfNulls =
search(ListOfCharacters.begin(), ListOfCharacters.end(),
TargetCharacters.begin(), TargetCharacters.end());

if (PositionOfNulls!=ListOfCharacters.end())
cout << "We found the nulls" << endl;
}


该例程的输出是:


We found the nulls

search算法在一个序列中查找另一个序列首次出现的位置。这个例子里,我们在ListOfCharacters中搜寻TargetCharacters第一次出现的位置,TargetCharacters是一个包含两个null字符的list

search的参数包括:两个指定了搜索区间(a range to search)的iterators,还有两个指定了搜索的目标区间(a range to search for)的iterators。由此可知,我们是在ListOfCharacters的整个区间里查找TargetCharacters list的整个区间。

如果找到TargetCharacters,search会返回一个iterator,指向ListOfCharacters中和其匹配的序列的第一个字符。如果找不到,search返回最后元素的后一个位置ListOfCharacters.end()。



list成员函数sort()排序一个list

要排序一个list,我们使用list的成员函数sort()而非泛型算法sort()。到目前为止我们用的所用算法都是泛型算法。不过STL中,有些情况下出于需要或更好的性能,容器也会自己提供特定算法的具体实现。

本例中的list容器有自己的sort算法,因为泛型sort算法只能对可随机访问其内部元素的容器进行排序。由于list容器以链接表的结构实现,故不能随机存取list中的元素。需要提供能对链接表排序的专门sort()成员函数。

你会发现这种情况对STL而言司空见惯。由于各种各样的原因,容器会提供特别的附加函数,或是因为效率的需要,或是因为利用容器结构的一些特性能够获得不同寻常的性能。

/*
|| How to sort an STL list
*/
#include <string>
#include <list>
#include <algorithm>

PrintIt (string& StringToPrint) { cout << StringToPrint << endl;}

int main (void) {
list<string> Staff;
list<string>::iterator PeopleIterator;

Staff.push_back("John");
Staff.push_back("Bill");
Staff.push_back("Tony");
Staff.push_back("Fidel");
Staff.push_back("Nelson");

cout << "The unsorted list " << endl;
for_each(Staff.begin(), Staff.end(), PrintIt );

Staff.sort();

cout << "The sorted list " << endl;
for_each(Staff.begin(), Staff.end(), PrintIt);
}

输出如下:


The unsorted list
John
Bill
Tony
Fidel
Nelson
The sorted list
Bill
Fidel
John
Nelson
Tony



list成员函数insert()插入元素到list

list成员函数push_front()和push_back()分别在list的头部和尾部添加元素,你也可以用insert()在list的任何位置添加对象。

insert()可以添加一个对象、一个对象的多个拷贝,或者一个对象区间(a range of objects)。下面是插入对象到list中的一些例子:

/*
|| Using insert to insert elements into a list.
*/
#include <list>

int main (void) {
list<int> list1;

/*
|| Put integers 0 to 9 in the list
*/
for (int i = 0; i < 10; ++i) list1.push_back(i);

/*
|| Insert -1 using the insert member function
|| Our list will contain -1,0,1,2,3,4,5,6,7,8,9
*/
list1.insert(list1.begin(), -1);

/*
|| Insert an element at the end using insert
|| Our list will contain -1,0,1,2,3,4,5,6,7,8,9,10
*/
list1.insert(list1.end(), 10);

/*
|| Inserting a range from another container
|| Our list will contain -1,0,1,2,3,4,5,6,7,8,9,10,11,12
*/
int IntArray[2] = {11,12};
list1.insert(list1.end(), &IntArray[0], &IntArray[2]);

/*
|| As an exercise put the code in here to print the lists!
|| Hint: use PrintIt and accept an interger
*/
}

注意insert()函数是在你指定的iterator所指位置处添加一个或多个元素。插入的元素会出现在list中指定的iterator所在位置的元素前。



List构造函数

此前我们都是按如下方式定义一个list

  list<int> Fred;
你也可以这样定义一个list,同时初始化其元素:

  // define a list of 10 elements and initialise them all to 0
list<int> Fred(10, 0);
// list now contains 0,0,0,0,0,0,0,0,0,0
或者,你也可以定义一个list,然后用取自另一个STL容器的区间来初始化该list,这个STL容器不用非得是个list,只要有相同的value type。

  vector<int> Harry;
Harry.push_back(1);
Harry.push_back(2);

// define a list and initialise it with the elements in Harry
list<int> Bill(Harry.begin(), Harry.end());
// Bill now contains 1,2



list成员函数删除list中的元素

list成员函数pop_front()删除list中的第一个元素,pop_back()删除的是最后一个元素。成员函数erase()删除某个iterator所指的元素。还有一个erase()函数能删除一个区间的元素。

/*
|| Erasing objects from a list
*/
#include <list>

int main (void) {
list<int> list1; // define a list of integers

/*
|| Put some numbers in the list
|| It now contains 0,1,2,3,4,5,6,7,8,9
*/
for (int i = 0; i < 10; ++i) list1.push_back(i);

list1.pop_front(); // erase the first element 0

list1.pop_back(); // erase the last element 9

list1.erase(list1.begin()); // erase the first element (1) using an iterator

list1.erase(list1.begin(), list1.end()); // erase all the remaining elements

cout << "list contains " << list1.size() << " elements" << endl;
}

输出如下:


list contains 0 elements



list成员函数remove()删除list中的元素

list成员函数remove()可以删除list中的对象:

/*
|| Using the list member function remove to remove elements
*/
#include <string>
#include <list>

#include <algorithm>

PrintIt (const string& StringToPrint) {
cout << StringToPrint << endl;
}

int main (void) {
list<string> Birds;

Birds.push_back("cockatoo");
Birds.push_back("galah");
Birds.push_back("cockatoo");
Birds.push_back("rosella");
Birds.push_back("corella");

cout << "Original list with cockatoos" << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);

Birds.remove("cockatoo");

cout << "Now no cockatoos" << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);

}

输出如下:


Original list with cockatoos
cockatoo
galah
cockatoo
rosella
corella
Now no cockatoos
galah
rosella
corella



用STL泛型算法remove()删除list中的元素

泛型算法remove()和list成员函数remove()的工作方式有所不同。remove的泛型版本不会改变容器的大小。

/*
|| Using the generic remove algorithm to remove list elements
*/
#include <string>
#include <list>

#include <algorithm>

PrintIt(string& AString) { cout << AString << endl; }

int main (void) {
list<string> Birds;
list<string>::iterator NewEnd;

Birds.push_back("cockatoo");
Birds.push_back("galah");
Birds.push_back("cockatoo");
Birds.push_back("rosella");
Birds.push_back("king parrot");

cout << "Original list" << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);

NewEnd = remove(Birds.begin(), Birds.end(), "cockatoo");

cout << endl << "List according to new past the end iterator" << endl;
for_each(Birds.begin(), NewEnd, PrintIt);

cout << endl << "Original list now. Care required!" << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);
}
输出如下:

Original list
cockatoo
galah
cockatoo
rosella
king parrot


List according to new past the end iterator
galah
rosella
king parrot


Original list now. Care required!
galah
rosella
king parrot
rosella
king parrot

泛型的remove()算法返回一个iterator指向该list的新结尾(new end)。从起始处到新结尾(但不包括这个新结尾)的区间包含了施加remove算法之后剩下的元素。然后你可以用list成员函数erase来删除从新结尾到旧结尾(old end)之间的区间。



用STL泛型算法stable_partition()和list成员函数splice()分割一个list

我们将以一个稍微比较复杂的例子来结束这篇文章。这个例子演示说明STL泛型算法stable_partition()和list成员函数splice ()的一个变种。注意函数对象的用法,还有代码中并没有出现循环。控制是通过一系列简单的语句完成的,这些语句调用了STL算法。

stable_partition()是个很有趣的函数,它重新排列list内元素使得那些满足一定条件的元素排在其它元素之前。同时它保持两组元素的相对顺序。来个例子就能一清二楚了。

splice把另一个list的元素连接到一个list里,同时删除源list中的元素(注:即前面的另一个list

这个例子里我们要从命令行接收一些标志(flags)和四个文件名,这些文件名必须以一定顺序出现。借助stable_partition()我们可以和文件名相关的、放在任何位置的标志,然后把它们组合在一起,同时无需关心文件名参数的顺序。

归功于现成易用的计数和查找算法,我们能够按需调用这些算法以确定程序里哪些标志已被设定。我发现用容器来管理少量类似的动态数据变量非常方便。

/*
|| Using the STL stable_partition algorithm
|| Takes any number of flags on the command line and
|| four filenames in order.
*/
#include <string>
#include <list>
#include <algorithm>

PrintIt ( string& AString ) { cout << AString << endl; }

class IsAFlag {
public:
bool operator () (string& PossibleFlag) {
return PossibleFlag.substr(0,1)=="-";
}
};

class IsAFileName {
public:
bool operator () (string& StringToCheck) {
return !IsAFlag()(StringToCheck);
}
};

class IsHelpFlag {
public:
bool operator () (string& PossibleHelpFlag) {
return PossibleHelpFlag=="-h";
}
};

int main (int argc, char *argv[]) {

list<string> CmdLineParameters; // the command line parameters
list<string>::iterator StartOfFiles; // start of filenames
list<string> Flags; // list of flags
list<string> FileNames; // list of filenames

for (int i = 0; i < argc; ++i) CmdLineParameters.push_back(argv[i]);

CmdLineParameters.pop_front(); // we don't want the program name

// make sure we have the four mandatory file names
int NumberOfFiles(0);
count_if(CmdLineParameters.begin(), CmdLineParameters.end(),
IsAFileName(), NumberOfFiles);

cout << "The "
<< (NumberOfFiles == 4 ? "correct " : "wrong ")
<< "number ("
<< NumberOfFiles
<< ") of file names were specified" << endl;

// move any flags to the beginning
StartOfFiles =
stable_partition(CmdLineParameters.begin(), CmdLineParameters.end(),
IsAFlag());

cout << "Command line parameters after stable partition" << endl;
for_each(CmdLineParameters.begin(), CmdLineParameters.end(), PrintIt);

// Splice any flags from the original CmdLineParameters list into Flags list.
Flags.splice(Flags.begin(), CmdLineParameters,
CmdLineParameters.begin(), StartOfFiles);

if (!Flags.empty()) {
cout << "Flags specified were:" << endl;
for_each(Flags.begin(), Flags.end(), PrintIt);
}
else {
cout << "No flags were specified" << endl;
}

// parameters list now contains only filenames. Splice them into FileNames list.
FileNames.splice(FileNames.begin(), CmdLineParameters,
CmdLineParameters.begin(), CmdLineParameters.end());

if (!FileNames.empty()) {
cout << "Files specified (in order) were:" << endl;
for_each(FileNames.begin(), FileNames.end(), PrintIt);
}
else {
cout << "No files were specified" << endl;
}

// check if the help flag was specified
if (find_if(Flags.begin(), Flags.end(), IsHelpFlag())!=Flags.end()) {
cout << "The help flag was specified" << endl;
}

// open the files and do whatever you do

}


假设命令行输入如下:


test17 -w linux -o is -w great

则输出如下:


The wrong number (3) of file names were specified
Command line parameters after stable partition
-w
-o
-w
linux
is
great
Flags specified were:
-w
-o
-w
Files specified (in order) were:
linux
is
great



结束语

我们只谈及了你用list能做的事,甚至没有论及如何存储用户定义类的对象,尽管那不是太难。

如果你理解了本文介绍的算法背后的种种概念,那么其它算法用起来应该不在话下。使用STL最重要的一点是掌握基本原理。

STL的关键乃是iterator。STL算法将iterators作为其参数,还有iterator ranges,有时是一个区间,有时则是两个。STL容器提供了iterators,那正是我们使用list<int>:: iterator,或list<char>::iterator,或list<string>::iterator的缘由。

Iterators有一个定义良好的层次结构,它们具有各不相同的“权力”(powers)。有些iterators提供对容器的只读访问,有些则只写;有些只能向前迭代,有些则是双向的;有些iterators提供对容器的随机访问。

STL算法要求一个特定“权力”的iterator。如果容器不提供那种权力的iterator,那么这个算法便无法通过编译。例如,list容器只提供了双向(bidirectional)iterators,而泛型sort()算法要求可随机访问的iterators。这就是我们需要专门的list成员函数sort()的原因。

要真正正确的使用STL,你需要认真学习各种不同的iterators。你需要熟知哪些类型的iterators是由哪些容器提供的,然后需要熟知那些算法要求何种类型的iterators。当然,你得需要理解手头能有何种类型的iterators。



在开发中(field)使用STL

过去一年里我已经用STL编写了数个商业C++程序,期间STL的确让我省事不少,而且几乎消除了逻辑错误。

最大的程序大概有5000行,而最惊人的应该是它的速度。这个程序能在大约20秒内读取并详尽处理一个1-2Mb大的事务文件。它在Linux下用GCC 2.7.2开发,现运行在HP-UX机器上,使用了超过50个函数对象以及大量容器,其大小不等,如小小的list,最大的map则有超过14,000个元素。

这个程序的函数对象形成了一个层次结构,上层函数对象调用低层函数对象。我广泛的使用了STL算法for_each(),find(),find_if(),count()和count_if()。我几乎把程序的所有内部结构简化至STL算法的调用。

STL有助于自动把代码组织成清晰的控制和支持两个模块。通过细致的编写函数对象并给它们取上有意义的名字后,我便设法把它们搁置到一边,然后集中精力处理软件中的控制流程。

关于STL编程还有很多东西要学,我希望你乐于学习这些例子。

参考文献里的两本书在网上都有最新的勘误表,你可以自己校正它们。

Stroustrup一书在每章后面都有建议栏,这些建议都很棒,尤其值得初学者一看。整本书也比先前版本更加通俗易懂些,当然也变得更厚。书店里还有许多其它论述STL的书,自己去找找看,并祝好运!:)
原创粉丝点击