C++ STL (一)
STL是什么(STL简介)
本节主要讲述 STL 历史、STL 组件、STL 基本结构以及 STL 编程概述。
STL 历史可以追溯到 1972 年 C 语言在 UNIX 计算机上的首次使用。直到 1994 年,STL 才被正式纳入 C++ 标准中。
STL 组件主要包括容器,迭代器、算法和仿函数。STL 基本结构和 STL 组件对应。
STL 主要由迭代器、算法、容器、仿函数、内存配置器和配接器六部分组成,可帮助程序员完成许多功能完善、形式多样的程序。
STL 组件
STL 是 C++ 标准程序库的核心。STL 内的所有组件都由模板构成,其元素可以是任意型别。程序员通过选用恰当的群集类别调用其成员函数和算法中的数据即可,但代价是 STL 晦涩难懂。
STL 组件主要包括容器,迭代器、算法和仿函数。
容器
容器即用来存储并管理某类对象的集合。例如鱼缸是用来盛放金鱼的容器。
每一种容器都有其优点和缺点。为满足程序的各种需求,STL 准备了多种容器类型,容器可以是 arrays 或是 linked lists,或者每个元素有特别的键值。
迭代器
迭代器用于在一个对象群集的元素上进行遍历动作。对象群集可能是容器,也可能是容器的一部分。
迭代器的主要用途是为容器提供一组很小的公共接口。利用这个接口,某项操作可以行进至群集内的下一个元素。
每种容器都提供了各自的迭代器。迭代器了解该容器的内部结构,所以能够正确行进。迭代器的接口和一般指针类似。
算法
算法用来处理群集内的元素,可以出于不同目的搜寻、排序、修改、使用那些元素。所有容器的迭代器都提供一致的接口,通过迭代器的协助,算法程序可以用于任意容器。
STL 的一个特性是将数据和操作分离。数据由容器类别加以管理,操作则由可定制的算法定义。迭代器在两者之间充当“粘合剂”,以使算法可以和容器交互运作。
STL 的另一个特性即组件可以针对任意型别运作。“标准模板库”这一名称即表示“可接受任意型别”的模板,并且这些型别均可执行必要操作。
在 STL 中,容器又分为序列式容器和关联式容器两大类,而迭代器的功能主要是遍历容器内全部或部分元素的对象。迭代器可划分为 5 种类属,这 5 种类属归属两种类型:双向迭代器和随机存取迭代器。
SIL 中提供的算法包括搜寻、排序、复制、重新排序、修改、数值运算等。
仿函数
STL中大量运用了仿函数。仿函数具有泛型编程强大的威力,是纯粹抽象概念的例证。
STL基本结构
STL 是 C++ 通用库,由迭代器、算法、容器、仿函数、配接器和配置器(即内存配置器)组成。
容器
STL 包含诸多容器类。容器类是可以包含其他对象的类,就像数组和队列堆栈等数据结构包含整数、小数、类等数据成员一样。STL 可以包含常见的向量类、链表类、双向队列类、集合类、图类等,每个类都是一种模板,这些模板可以包含各种类型的对象。
下述代码是常用的 vector 赋值方法:
- vector <int> l;
- for (int i =0;i <100;i++ )
- l.push_back (i);
下述代码采用 map 容器进行二维元素的管理:
- map <string, string, less <string>> cap; //按从小到大序
- cap ["Ark"] ="Little Rock";
- cap ["New"] ="Albany";
map 类似于二维数组,但比二维数组灵活得多。
目前,STL 中已经提供的容器主要如下:
- vector <T>:一种向量。
- list <T>:一个双向链表容器,完成了标准 C++ 数据结构中链表的所有功能。
- queue <T>:一种队列容器,完成了标准 C++ 数据结构中队列的所有功能。
- stack <T>:一种栈容器,完成了标准 C++ 数据结构中栈的所有功能。
- deque <T>:双端队列容器,完成了标准 C++ 数据结构中栈的所有功能。
- priority_queue <T>:一种按值排序的队列容器。
- set <T>:一种集合容器。
- multiset <T>:一种允许出现重复元素的集合容器。
- map <key, val>:一种关联数组容器。
- multimap <key, val>:一种允许出现重复 key 值的关联数组容器。
以上容器设计高效,还提供了接口。程序员可以在任何适当的地方使用它们。
容器可以分为序列式容器和关联式容器两大类。序列式容器主要有 vector、list 和 deque;关联式容器包括 set、map、multiset 和 multimap 等容器模板类。
算法
STL 提供了非常多的数据结构算法。这些算法在命名空间 std 的范围内定义,通过包含头文件 <algorithm> 来获得使用权。
常见的部分算法如下:
- for_each();
- find();
- find_if();
- count();
- count_if();
- replace();
- replace_if();
- copy();
- unique_copy();
- sort();
- equal_range();
- merge();
STL 中的所有算法都是基于模板实现的。
迭代器
通俗来讲,迭代器就是指示器。迭代器技术能够使程序非常快捷地实现对 STL 容器中内容的反复访问。反复访问意味着一次可以访问一个或多个元素。
迭代器为访问容器提供了通用的方法,类似于 C++ 的指针。当参数化类型是 C++ 内部类型时,迭代器即 C++ 指针。
STL 定义了 5 种类型的指示器,并根据其使用方法予以命名。每种容器都支持某种类别的迭代器。常见的迭代器包括输入、输出、前向、双向和随机接入等类别:
- 输入迭代器主要用于为程序中需要的数据源提供输入接口,此处的数据源一般指容器、数据流等。输入迭代器只能从一个序列中读取数值。该迭代器可以被修改和被引用。
- 输出迭代器主要用于输出程序中已经得到的数据结果(容器,数据流)。输出迭代器只能向一个序列写入数据。该迭代器也可以被修改和被引用。
- 双向迭代器既可以用来读又可以用来写,它与前向迭代器相类似。双向迭代器可以同时进行前向和后向元素操作。所有 STL 容器都提供了双向迭代器功能,这既有利于数据的写入和读出,又有利于提供更加灵活的数据操作。
- 有的容器甚至提供了随机接入迭代器。随机接人迭代器可以通过跳跃的方式访问容器中的任意数据,使数据的访问非常灵活。随机访问迭代器具有双向迭代器的所有功能,是功能最强大的迭代器类型。
迭代器的诞生使算法和容器分离成为可能。算法是模板,其类型依赖于迭代器,不会局限于单一容器。不同的 STL 算法需要不同类型的迭代器来实现相应的功能。因为不同类型的 STL 容器支持不同类型的迭代器,所以不能对所有容器使用相同的算法。
仿函数
STL 包含了大量仿函数。仿函数可以理解为函数的一般形式。对于编程来说,仿函数非常重要,并有几种约束。在 C++ 标准中,函数调用一般使用指针,当需要调用函数时,只需要提供函数的地址即可。例如:
- static int cmp (int* i, int* j)
- {
- return (*i - *j);
- }
上述代码定义了一个 cmp() 函数,当需要调用此函数时,只需提供函数的地址即可。例如:
qsort(a,10,sizeof(int), cmp);
此方法的最大缺陷是效率低。为提高效率,STL 定义了仿函数这种概念。下述代码定义了一个仿函数,其特征是使用 operator 实现定义。
- struct three_mul
- {
- bool operator() (int& v)
- {
- return (v%3 ==0)
- }
- }
通过运算符定义显著提高效率。例如,
for_each (myvector.begin (), myvector.end(), three_mul);
内存配置器和配接器
STL 包括底层的内存分配和释放。内存配置器非常少见,在此可以忽略,在后面章节专门介绍。
配接器可以实现不同类之间的数据转换。最常用的配接器有 istream_temtor,它提供了函数复制的接口。配接器对于 STL 技术来说非常重要。
STL 提供了 3 种容器配接器,分别是:
- stack <Container>;
- queue <Container>;
- deque <Container>;
看这样一个程序:
- #include <iostream>
- #include <stack>
- using namespace std;
- int main ()
- {
- stack <int> st; //定义堆栈对象
- for (int i = 0;i <10;i ++ )
- st.push (i); //将数据压入堆钱
- while (!st.empty())
- {
- cout << st.top() << " "; //弹出堆找的第一个元素,并输出
- st.pop(); //弹出堆栈元素
- }
- cout<< endl;
- cin.get(); //任意键退出
- return 0;
- }
程序执行结果为:
9 8 7 6 5 4 3 2 1 0
STL 使用方法
STL 作为 C++ 通用库,主要由迭代器、算法、容器、仿函数、内存配置器和配接器等六大部分组成。程序员使用 STL 容器能够实现多种标准类型且操作便捷的容器。
对于编程人员,标准化组件意味着直接使用现成的组件,不用重复开发。使用 STL 最重要的是掌握基本理论和编程方法,了解 STL 编程技术,必须深刻掌握 STL 容器技术和 STL 迭代器技术。
STL 提供了一组表示容器、迭代器、仿函数和算法的模板:
- 容器是类似数组的单元,可存储 若干个值,且STL容器是同质的,即存储的值类型相同;
- 算法是完成特定任务的处方;
- 迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针;
- 仿函数是类似于函数的对象,可以是类对象或函数指针。
STL 使程序员能够构造各种容器和执行各种操作。
下面以矢量为例,简要讲述矢量模板的使用。
在数学计算和 STL 模板中,vector 对应数组,提供与 valarray 和 ArrayTP 类似的操作。而 STL 为使 vector 矢量具备通用性,在头文件 <vector> 中定义了 vector 模板。具体方法为:创建 vector 模板对象,使用通常的 <type> 表示法指出要使用的类型;然后使用初始化参数决定矢量的大小,并定义矢量动态内存。例如:
- #include <vector>
- using namespace std; //使用命名空间 std
- vector <int> ratings (5); //定义矢量对象 int n;
- cin >> n; //输入矢量大小
- vector <double> scores (n); //定义矢量动态内存
内存分配器是用来管理对象内存的。在 STL 容器模板中,一般都有一个可选的模板参数。例如:
- template <class T, class Allocator = allocator <T>>//矢量模板
- class vector {
- ...
- }
若省略该模板参数的值,则容器模板将默认使用 allocator<T> 类。类 allocator 以标准形式使用 new 和 delete 内存管理方式。
下面举例说明,创建两个 vector 对象:一个是 int 规范;另一个是 string 规范:
- #include <iostream>
- #include <string>
- #include <vector>
- using namespace std;
- const int NUM = 5;
- int main ()
- {
- vector <string>names(NUM); //定义矢量对象
- vector <int> sexs (NUM); //同上
- cout<<"Please Do Exactly As Told You Will enter \n"<<NUM<<" Personal Name and Their Sex.\n";
- int i =0;
- for (i = 0;i <NUM;i++) //输入信息
- {
- cout << "Enter title # " << i +1 << ": ";
- getline (cin, names[i]) ; //获取输入信息
- cout << "Enter sex (0/1) #";
- cin >> sexs [i]; //获取输入信息
- cin.get (); //等待
- }
- cout << "Thank you. You entered the following: \n"<< "name/sex" << endl;
- for (i = 0; i <NUM; i++ ) //输出信息
- {
- cout <<names[i] << "\t" << sexs[i] << endl;
- }
- return 0;
- }
程序执行结果为:
Please Do Exactly As Told You Will enter
5 Personal Name and Their Sex.
Enter title # 1: A
Enter sex (0/1) #1
Enter title # 2: B
Enter sex (0/1) #1
Enter title # 3: D
Enter sex (0/1) #0
Enter title # 4: E
Enter sex (0/1) #1
Enter title # 5: E
Enter sex (0/1) #1
Thank you. You entered the following:
name/sex
A 1
B 1
D 0
E 1
E 1