STL是什么?

27

我不是C++程序员,很难理解网站上给出的解释。我不了解容器或迭代器,并且没有计划在近期学习C++。因此,简单来说:STL是什么?它对我有什么帮助?与Python标准库或glibc相比如何?


1
你不是C++程序员,但你是程序员吗?或者有其他技术思维模式吗?用来解释的语言和比喻也各不相同。 - Javier
1
如果您在不久的将来没有计划使用/学习C++,那为什么需要了解STL呢? - RobS
我会写一些C和Python。我不需要了解STL,但它似乎非常神秘和重要。我想了解它是什么,它的作用是什么,并且至少在与同事和同学交流时有所了解。 - tory
1
这只在C++环境中很重要。它是一个巧妙设计的库,利用一些高级C++特性来克服许多语言的弱点,并提供基本的标准库功能。但对于不了解C++的人来说,解释它将非常困难。 - jalf
“Standard” 的使用方式与 Python 或 C 相似,没有什么神秘的地方。而 “Template” 则是一个重要的区别,C++ 模板是一种特殊的“更通用”的 class(或对象),需要使用 foo<> 尖括号,并且是许多“泛型”库(如 Java、C# 等)的前身。 - ebyrob
PS - 我可能过于简化了,没有提到一个函数可以被“模板化”,即使它不在类中,但这是一个语言特性的例子,可能并非必需。它可以被消除,它解决的问题仍然可以通过一个静态或只有一个函数的类来轻松解决等。我想我不需要在这种情况下打破自我发现的乐趣。如果我是任何一种教授,天佑我,我可能会尽力假装三元运算符?:不存在,而不解释只是“-1”风格。 - ebyrob
6个回答

68
了解STL,你至少需要了解C++的一些方面。我将尽力解释它。STL的结构看似简单,但其优点在于使用它可以简化许多复杂的任务。不过,我将只给出一些非常简单的例子,因为其他任何东西都可能会让不懂C ++的人感到困惑,而且我也不想写一部小说。 ;)
首先是一些历史。STL(标准模板库)是分别开发的,然后提交给C++标准委员会考虑,让他们选择将其纳入语言中。但它不是作为C++标准的一部分开发的,因此它的设计风格与C++标准库的其余部分非常不同。如果我记得我的古代历史,标准委员会花费了很长时间来理解STL并逐渐习惯它。当他们最初看到它时,并不太喜欢它,但过了一段时间,他们意识到它是多么强大和精心设计的。因此,它被纳入语言标准。这一切都发生在上世纪90年代末,当时该语言正在接近ISO标准化。
从本质上讲,STL提供了标准库所期望的最基本功能:存储数据序列和处理这些序列的能力。
每种其他语言都有一个收集/容器部分的标准库,其中包含动态数组的实现(在Java中称为arraylists,在C#中称为List,在C++中称为vector),链表,字典和其他常见数据结构。
它们通常还提供一些遍历这些结构的机制。(例如枚举器或迭代器)
STL在C++中提供了相同的功能,但以不同寻常的优雅方式实现,并带有一些有趣的抽象。
STL清晰地分为三个独立的组件:
  • 容器(如上所述,每种语言都有这些。数组、ArrayList、Dictionary、Set、LinkedList等。任何能够存储对象集合的数据结构在C++中都是一个容器)
  • 算法(每种语言也都以某种形式存在这些。算法是用于处理元素序列的函数。)现在,假设一个序列就是一个容器。那是有点简化了,但我们会解决这个问题。算法有各种各样的用途,从一个for_each()函数,它允许你对序列中的每个元素应用一个函数,到相关的transform()函数,它将一个函数应用于每个元素,并将结果存储到一个单独的序列中(非常类似于函数式语言中的map操作),或者累加(类似于函数式语言中的fold),还有排序或搜索函数,以及允许您复制整个序列的函数。
  • 最后,将容器和算法绑定在一起的粘合剂:迭代器。正如我上面所说的,序列(算法所处理的)并不完全相同于容器。容器中的元素当然构成一个序列,但容器中的前五个元素也构成一个序列。或者容器中的每个其他元素都是一个序列。直接从文件流中读取的数据也可以视为序列。即使是即时生成的数据(例如斐波那契数列)也可以被视为值序列。序列不必映射到容器,甚至不必映射到存在于内存中的数据,尽管这是最常见的用法。

请注意,这三个领域之间没有重叠。容器存储(并拥有)数据,并生成迭代器。迭代器允许您检查、修改和遍历数据。算法操作迭代器范围

从概念上讲,迭代器有两个功能。它指向一些数据,并且可以在序列中移动(根据迭代器类型,不同的移动操作可能可用。几乎所有迭代器都可以移动到下一个元素。有些迭代器也可以移动到前一个元素,而有些可以向后和向前跳过任意距离)。 如果您熟悉C语言,这听起来很像指针,这不是巧合。迭代器被建模为指针的概括,并且实际上,指针也是有效的迭代器。所有STL算法也适用于指针以及“真正”的迭代器。

这意味着任何数据序列都可以由一对迭代器表示:第一个迭代器指向序列中的第一个元素,第二个迭代器指向序列结尾后的一个位置。

这允许以相当简单的语法遍历序列:

std::vector<int> container;
for (iter it = container.begin(); it != container.end(); ++it)
{
  // perform some operations on the iterator (it) or the element it points to (*it)
  ++(*it); // increment the value the iterator points to
}

或者我们可以对这个序列应用一个算法:

std::sort(container.begin(), container.end());

请注意,sort函数并不知道也不关心它正在处理一个向量。它会传递两个迭代器,并且这些迭代器可以是任何类型。它们可以是指向数组的普通指针,链表迭代器或任何其他有效的迭代器类型。

我们可以通过提供自己的比较函数(任何接受两个值并在第一个值严格小于另一个值时返回true的函数)来将排序函数进行泛化。

// sort in descending order, by passing in a custom comparer which uses greater than instead of less than
bool greater(int lhs, int rhs) { return lhs > rhs; }
std::sort(container.begin(), container.end(), greater);

当然,我们也可以仅对向量的前五个元素进行排序:

std::sort(container.begin(), container.begin()+5);

begin()和end()函数只是从容器中获取迭代器的便捷函数,我们不必直接使用它们。

另一个不错的技巧是将流也广义化为迭代器。因此,让我们从文件中读取所有整数,并将它们复制到数组中(数组当然是普通的C类型,因此它们不是适当的容器,也没有迭代器。但指针可以正常工作)。

int arr[1024];
std::ifstream file("something.txt");
// (note, this assumes <= 1024 integers are read)
std::copy(std::istream_iterator<int>(file) // create an iterator pointing to the current position in the file stream
        , std::istream_iterator<int>() // and our "end" iterator. When we reach the end of the stream, testing the two iterators for equality will yield true, and so the operation will halt
        , arr);

STL的独特之处在于其灵活性和可扩展性。它与C代码互操作清晰(指针可以作为合法的迭代器),它可以简单而轻松地扩展(如果您愿意,可以编写自己的迭代器类型)。大多数算法使用比较器的自定义谓词(如上面所示的),您可以定义自己的容器。也就是说,STL的三个支柱中的每一个都可以被覆盖或扩展,因此STL可以说是一种设计策略而不仅仅是一个库。即使您正在使用自己的容器、迭代器和算法,您也可以编写STL代码。由于这三个支柱都与其他支柱干净地分离,因此它们可以比大多数其他语言更容易地交换,而这三个责任在大多数其他语言中则是混在一起并由相同的类共享。

算法不知道它正在操作的序列存储在哪个容器中,如果有的话。它只知道它被传递的迭代器可以被解引用以获取对数据本身的访问。

容器不必支持所有标准算法。它只需能够产生一对迭代器,然后所有功能就会免费提供。

将其与Java进行比较,Java中每个集合类都必须实现自己的搜索、排序和其它所有功能。在C++中,我们只需要一个find()函数的实现。它接受两个迭代器和要查找的值,然后遍历序列寻找该值。因此,它适用于任何容器类型,甚至包括我定义的容器。

STL的另一个显著特点是几乎没有性能损失。C++模板都在编译时替换,产生的代码可以像手动编写C代码一样进行优化。上面的sort函数会失去一些性能,因为我将一个函数指针作为自定义比较器传递给它,这通常无法内联,但如果我们将其定义为这样:

struct greater {
    bool operator()(int lhs, int rhs) { return lhs > rhs; }
};
std::sort(container.begin(), container.end(), greater());
现在我们不再传递函数指针,而是一个对象。成员函数(例如operator())可以被内联。因此,这个排序函数将和你手写的任何C代码一样有效。
而且,它甚至不需要增加排序函数的任何复杂性。实际上,sort函数只有两个重载。一个接受比较器函数,一个不接受。
排序函数不需要知道它是否传递了函数指针或对象。只要语法“X(a,b)”有效,其中X是作为比较器传递的值,a、b是要比较的元素,同一实现的排序函数将起作用。并且由于我的greater对象重载了operator(),这个语法对于这个对象和我们之前传递的函数指针都是有效的。 通过利用这样的技巧,STL代码可以免费获得很多功能。由于C++模板的工作方式,同一函数的实现适用于非常不同的参数类型。

2
这就像是我在SO上读过的最好的答案之一。解释得非常完美! - gonidelis

9

STL是标准模板库,它是C++标准库的一个子集。

STL提供了有用算法和容器的通用实现。

容器提供了一种在程序中存储数据、查找、排序以及执行其他计算的简单方法。


1
STL和Java、C#中的泛型有关系吗? - cdmckay
1
STL类似于泛型,但它只在编译之前生成新代码。泛型(至少在C#中)是在运行时由虚拟机理解的。 - Robert Lewis
3
C++、Java和C#都有泛型,但它们的实现方式各不相同。泛型的思想在于,List、Map或Set的行为不取决于它们包含的对象类型。这就是泛型所允许的。 - duffymo
3
STL并不等同于模板。泛型与模板相似,但只提供了模板功能的一小部分。STL是围绕模板提供的功能构建的库,因此STL与泛型无关。虽然它是基于泛型相关的特性构建的,但STL是一个库,而泛型是一种语言特性。 - jalf
1
在jalf的评论基础上补充一点,可以将C++的模板看作是Java泛型的“增强版”,而基于模板的STL容器则类似于Java中的集合类。除了容器类之外,STL还包括各种模板算法。 - Mr Fooz
显示剩余4条评论

7

标准模板库是在C++标准化之前编写的C++库。它包括排序算法和容器(以及迭代器,用于使用这些功能)等很酷的东西。

1998年标准化后,C++标准库的部分基于STL;它已经发展(通过2003年标准,特别是现在的C++0x)。

  • C++标准库由各种编译器供应商(和他们的朋友)实现,并随您喜爱的工具链一起提供。
  • 实际上,您可能使用的就是它:STL现在几乎已经过时了。
注意许多程序员(包括一些著名的书籍作者)因为习惯仍然使用“STL”一词来代指C++标准库(或其最初基于STL的部分),即使这是不正确的。只要您意识到技术上的区别,就不会有问题。希望这可以帮到您。

3

如果你不是C++程序员,标准模板库的实现对你来说毫无价值。然而,这些思想超越了任何编程语言。

它是一个数据结构库(例如,映射、列表、向量等)。它包括迭代器来抽象遍历这些数据结构,并提供操作它们的算法。其他语言也使用这些思想,因此如果你在一种语言中学习了它们,它们将在其他语言中变得熟悉。


2

STL是标准模板库。它是一个包含函数和类的库,您可以使用它。它具有基本计算机科学的许多基本算法和数据结构。如果您打算继续使用C++,则应该计划学习此库或其他资源库。

这段代码被全球许多人使用,因此您可以放心,代码经过了充分测试并广为人知。


-1

这是一个库,可以使某些事情更容易,例如字符串操作、向量、链表等。STL 可以帮助你避免编写一些底层代码,让你更专注于应用程序代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接