STL

来源:互联网 发布:长春网络广播电视台 编辑:程序博客网 时间:2024/05/01 03:07

大概是一月份在 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");

}

 

现在我们得到一个含有四个字符串的 list 。 list 成员函数 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 的书,自己去找找看,并祝好运! :)

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/yzm365487848/archive/2010/01/12/5182559.aspx

原创粉丝点击