若干精心勾画的组件共同合作,构筑起STL的基础。这些组件中最关键的是容器、迭代器和算法。
容器用来管理一大群元素。为了适应不同需要,STL提供了不同的容器。总的来说,容器可分为三大类:
下面各小节详细讨论各种容器类:包括容器的典型实现,及其好处和缺点。下一篇文章会谈到容器类的确切行为,描述它们共有和特有的能力,并详细分析其成员函数,并会详细讨论何时使用哪一种容器。
STL内部预先定义好了以下序列式容器:
Vector
Vector将其元素置于一个动态数组中管理。它允许随机访问。在数组尾部附加元素或移除元素都很快速,但是在数组的中段或起始段安插元素就比较费时。
以下例子针对整数类型定义了一个vector,插入6个元素,然后打印所有元素:
// stl/vector1.cpp
#include
#include
using namespace std;
int main()
{
vector<int> coll; // vector container for integer elements
// append elements with values 1 to 6
for(int i=1; i<=6; ++i)
{
coll.push_back(i);
}
// print all elements followed by a space
for(int i=0; i<coll.size(); ++i)
{
cout << coll[i] << ' ';
}
cout << endl;
}
// 程序输出: 1 2 3 4 5 6
Deque
所谓deque,是“double-ended queue”的缩写,即双端队列。是一个动态数组,可以向两端发展,因此无论在尾部或头部插入元素都十分迅速。
以下例子声明了一个元素为浮点数的deque,并在容器头部插入1.1至6.6共6个元素,最后打印出所有元素。
// stl/deque1.cpp
#include
#include
using namespace std;
int main()
{
deque<float> coll; // deque container for floating-point elements
// insert elements from 1.1 to 6.6 each at the front
for(int i=1; i<=6; ++i)
{
coll.push_front(i*1.1); // insert at the front
}
// print all elements followed by a space
for(int i=0; i<coll.size(); ++i)
{
cout << coll[i] << ' ';
}
cout << endl;
// 程序输出如下:6.6 5.5 4.4 3.3 2.2 1.1
}
你也可以使用成员函数push_back()在deque尾端附加元素。Vector并未提供push_front(),因为其时间效率不佳。一般而言,STL容器只提供具备良好时间效率的成员函数,所谓“良好”通常意味着其复杂度为常量或对数,以免程序员调用性能很差的函数。
Array
一个array对象乃是在某个固定大小的array内管理元素。因此,只能改变元素值,不可以改变元素个数。你必须在建立时就指明其大小。
下面的例子定义出了一个array,元素是string:
// stl/array1.cpp
#include
#include
#include
using namespace std;
int main()
{
// array container of 5 string elements:
array<string, 5> coll = {"hello", "world" };
// print each element with its index on a line
for(int i=0; i<coll.size(); ++i)
{
cout << i << ": " << coll[i] << endl;
}
}
// 整个程序输出如下:
0: hello
1: world
2:
3:
4:
List
从历史角度看,我们只有一个list类。然而自C++11开始,STL竟提供了两个不同的list容器:class list<>和class forward_list<>。
list<>由双向链表实现而成。这意味着list内的每个元素都以一部分内存指示其前驱元素和后继元素。List不提供随机访问,因此如果你要访问第10个元素,你必须沿着链表依次走过前9个元素。
List的优势是:在任何位置上执行安插或删除动作都非常迅速,因为只需改变链接就好。这表示在list中段处移动元素比在vector和deque快得多。
以下例子产生一个空list,用以放置字符,然后将‘a’至‘z’的所以字符插入其中,利用循环每次打印并移除集合的第一个元素,从而打印出所有元素:
// stl/list1.cpp
#include
#include
using namespace std;
int main()
{
list<char> coll; // list container for character elements
// append elements from 'a' to 'z'
for(char c='a'; c <= 'z'; ++c)
{
coll.push_back(c);
}
// print all elements:
// -use range-based for loop
for(auto elem : coll)
{
coll << elem << ' ';
}
cout << endl;
}
注意,elem永远是当前正被处理的元素的一个拷贝。虽然你可以改动它,但其影响只限于“针对此元素而调用的语句”,coll内部并没有任何东西被改动。如果你想改动传入的集合的元素,你必须将elem声明为一个非常量的reference:
for(auto& elem : coll)
{
... // any modification of elem modifies the current element in coll
}
在C++11之前,打印所有元素的另一种做法(不使用迭代器)是逐一地打印而后移第一元素,直到此list中不再有任何元素:
// print all elements
// -while there are elements
// -print and remove the first element
while(! coll.empty()) {
cout << coll.front() << ' ';
coll.pop_front();
}
cout << endl;
Forward list
自C++11之后,C++标准库提供了另一个list容器:forward list。forward_list<>是一个由元素构成的单向链表。与list<>不同的是,每个元素有自己的一段内存,为了节省内存,它只指向下一元素。
因此,forward list原则上就是一个受限的list,不支持任何“后退移动”或“效率低下”的操作。基于这个原因,它不提供成员函数如push_back()乃至size()。
下面是forward list的一个小例子:
#include
#include
using namespace std;
int main()
{
// create forward-list container for some prime numbers
forward_list<long> coll = {2, 3, 5, 7, 11, 13, 17};
// resize two times
// -note:poor performance
coll.resize(9);
coll.resize(10, 99);
// print all elements
for(auto elem : coll)
{
cout << elem << ' ';
}
cout << endl;
关联式容器依据特定的排序准则,自动为其元素排序。元素可以是任何类型的value,也可以是key/value对,其中农key可以是任何类型,映射至一个相关value,而value也可以是任意类型。排序准则以函数形式呈现,用来比较value,或比较key/value中的key。默认情况下所有容器都以操作符<进行比较,不过你也可以提供自己的比较函数,定义出不同的排序准则。
通常关联式容器由二叉排序树实现出来。优点是,能很快找出一个具有某特定value的元素,因为它具备对数复杂度。然而,它的一个缺点是,你不能直接改动元素的value,因为那会破坏元素的自动排序。
STL定义的关联式容器有:
Set和Multiset实例
下面是一个使用multiset的例子:
#include
#include
#include
using namespace std;
int main()
{
multiset<string> cities {
"Braunschweig", "Hanover", "Frankfurt", "New York",
"Chicago", "Toronto", "Paris", "Frankfurt"
};
// print each element
for(const auto & elem : cities) {
cout << elem << " " ;
}
cout << endl;
// insert additional values:
cities.insert( {"London", "Munich", "Hanover", "Braunschweig"} );
// print each element
for(const auto & elem : cities) {
cout << elem << " ";
}
cout << endl;
}
// 第一次输出如下:
Braunschweig Chicago Frankfurt Frankfurt Hanover New York Paris Toronto
// 第二次输出如下:
Braunschweig Braunschweig Chicago Frankfurt Frankfurt Hanover Hanover London Munich New York Paris Toronto
// multiset允许元素重复
Map和Multimap实例
下面的例子示范了如何使用map和multimap:
#include
#include
#include
using namespace std;
int main()
{
multimap<int, string> coll;
coll = { {5, "tagged"},
{2, "a"},
{1, "this"},
{4, "of"},
{6, "strings"},
{1, "is"},
{3, "multimap"} };
// print all element values
for(auto elem : coll) {
cout << elem.second << ' ';
}
cout << endl;
}
// 最终,程序输出如下:
// this is a multimap of tagged strings
无序容器常以hash table实现出来,内部结构是一个“由linked list组成”的array。通过某个hash函数的运算,确定元素落于这个array的位置。Hash函数运算的目标是:让每个元素的落点(位置)有助于用户快速访问。
根据关联式容器的分类方法,STL定义出下面这些无序容器
Unordered Set/Multiset实例
#include
#include
#include
using namespace std;
int main()
{
unordered_multiset<string> cities {
"Braunschweig", "Hanover", "Frankfurt", "New York", "Chicago", "Toronto", "Paris", "Frankfurt"
};
// print each element
for(const auto & elem : cities) {
cout << elem << " ";
}
cout << endl;
// insert additional values:
cities.insert( {"London", "Munich", "Hanover", "Braunschweig"} );
// print each element
for(const auto & elem : cities) {
cout << elem << " ";
}
cout << endl;
}
// 打印出来的次序可能不同于程序中所给的次序,因为其次序是不明确的。
次序究竟会不会变化,或变成怎样,取决于rehashing策略,而它在某种程度上可被程序员影像。例如你可以保留足够空间,使得直到出现一定量的元素才发生rehashing。此外,为确保你在处理所有元素的同时还可以删除元素,C++ standard保证,删除元素不会引发rehashing。但是删除之后的一次插入动作就有可能引发rehashing。