C++(四)——C++标准模板库

文章目录

      • 1. STL组件(Component)
      • 2. 容器(Container)
            • 2.1 序列式容器(Sequence Container)
            • 2.2 关联式容器(Associative Container)
            • 2.3 无序容器(Unordered Container)

STL(standard template library, 标准模板库)是C++标准库的核心,它深刻影响了标准库的整体结构。STL是一个泛型程序库,提供一些列软件方案,利用先进、高效的算法来管理数据。程序员无须了解STL的原理,便可享受数据结构和算法领域中的这一革新成果。

1. STL组件(Component)

若干精心勾画的组件共同合作,构筑起STL的基础。这些组件中最关键的是容器、迭代器和算法。

  • 容器(Container),用来管理某类对象的集合。每一种容器都有其优点和缺点,所以,为了应付不同的需求,STL准备了不同的容器类型。容器可以使array或Linked list,或者每个元素有一个特别的key。
  • 迭代器(Iterator),用来在一个对象集合内遍历元素。迭代器的主要好处是,为所有各式各样的容器提供了一组很小的共通接口。例如其中一个操作是行进至集合内的下一个元素。至于如何做到当然取决于集合的内部结构。不论这个集合是array或tree或hash table,此一行进动作都能成功,因为每一种容器都提供了自己的迭代器,而这些迭代器了解容器的内部结构,知道该做些什么。
  • 算法(Algorithm),用来处理集合内的元素。它们可以出于不同的目的而查找、排序、修改、使用元素。通过迭代器的协助,我们只需撰写一次算法,就可以将它应用于任意容器,因为所有容器的迭代器都提供一致的接口。

2. 容器(Container)

容器用来管理一大群元素。为了适应不同需要,STL提供了不同的容器。总的来说,容器可分为三大类:

  1. 序列式容器(Sequence container),这是一种有序(ordered)集合,其内每个元素均有确凿的位置——取决于插入时机和地点,与元素值无关。STL提供了5个定义好的序列式容器:array、vector、deque、list和forward_list。
  2. 关联式容器(Associative container),这是一种已排序(sorted)集合,元素位置取决于其value(或key——如果元素是个键值对)和给定的某个排序准则。STL提供了4个关联式容器:set、multiset、map和multimap。
  3. 无序容器(Unordered container),这是一种无序集合(unordered collection),其内每个元素的位置无关紧要,唯一重要的是某特定元素是否位于此集合内。STL内含4个预定义的无序容器:unordered_set、unordered_multiset、unordered_map和unordered_multimap。

下面各小节详细讨论各种容器类:包括容器的典型实现,及其好处和缺点。下一篇文章会谈到容器类的确切行为,描述它们共有和特有的能力,并详细分析其成员函数,并会详细讨论何时使用哪一种容器。

2.1 序列式容器(Sequence Container)

STL内部预先定义好了以下序列式容器:

  • Array(其类名为array)
  • Vector
  • Deque
  • List(单/双 链表)
    以下讨论从vector开始,因为array是TR1新引入的,进入C++标准库的时间比较短,而且它有一些特殊属性,与其他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;
2.2 关联式容器(Associative Container)

关联式容器依据特定的排序准则,自动为其元素排序。元素可以是任何类型的value,也可以是key/value对,其中农key可以是任何类型,映射至一个相关value,而value也可以是任意类型。排序准则以函数形式呈现,用来比较value,或比较key/value中的key。默认情况下所有容器都以操作符<进行比较,不过你也可以提供自己的比较函数,定义出不同的排序准则。

通常关联式容器由二叉排序树实现出来。优点是,能很快找出一个具有某特定value的元素,因为它具备对数复杂度。然而,它的一个缺点是,你不能直接改动元素的value,因为那会破坏元素的自动排序。

STL定义的关联式容器有:

  • Set 元素依据value自动排序,每个元素只能出现一次,不允许重复。
  • Multiset 和set的唯一差别是:元素可以重复。
  • Map 每个元素都是键值对(key-value pair),其中gkey是排序准则的基准。
  • Multimap 与map的唯一差别是:元素可以重复,也就是multimap允许其元素拥有相同的key。Multimap可被当做字典使用。

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
2.3 无序容器(Unordered Container)

无序容器常以hash table实现出来,内部结构是一个“由linked list组成”的array。通过某个hash函数的运算,确定元素落于这个array的位置。Hash函数运算的目标是:让每个元素的落点(位置)有助于用户快速访问。
C++(四)——C++标准模板库_第1张图片
根据关联式容器的分类方法,STL定义出下面这些无序容器

  • Unordered set
  • Unordered multiset
  • Unordered map
  • Unordered multimap
    以上者四种容器与2.2节关联式容器的四个相比,唯一不同就是无序。

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。

你可能感兴趣的:(#,C++笔记,开发语言)