标准模板库(STL)
STL 的四个组成与泛型编程
标准模板库(Standard Template Library,STL)提供了一组表示容器(container)、迭代器(iterator)、函数对象(function object)和算法(algorithm)的模板。容器是与数组类似的单元,可以存储若干个值,STL 容器是同质的(存储的值的类型相同);算法是完成特定任务(如排序、查找)的处方;迭代器是能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针;函数对象是类似于函数的对象,可以是类对象或函数指针(包括函数名,因为函数名被用作指针)。STL 不是面向对象编程,而是一种不同的编程模式------泛型编程(generic programming)。
泛型编程使 STL 能够构造各种容器(数组、队列、链表)并执行各种操作(搜索、排序、随机排列),且同一套算法可复用于任意满足接口要求的容器。
#include <vector> // 容器模板:vector
#include <algorithm> // 算法模板:sort、for_each、random_shuffle
std::vector<int> v; // 容器:存储同质(int)值
// 算法:对区间 [v.begin(), v.end()) 排序
std::sort(v.begin(), v.end());
// 迭代器:v.begin() 返回指向第一个元素的广义指针
模板类 vector 与分配器
vector 是定义在头文件 <vector>(以前为 <vector.h>)中的模板类,计算中的矢量(vector)对应数组------存储一组可随机访问(random access)的值,即可以用索引直接访问第 N 个元素而不必先访问前面的元素。要创建 vector 模板对象,使用通常的 <type> 表示法指出要使用的类型;vector 模板使用动态内存分配,可以用初始化参数指出需要多少矢量。
把类设计为模板使其成为通用的,可存储任意指定类型;动态内存分配使长度可随初始化参数与后续操作增长
规则(限制):与 string 类相似,各种 STL 容器模板都接受一个可选的模板参数指定使用哪个分配器(allocator)对象管理内存------template<class T, class Allocator = allocator<T>> class vector {...};;若省略该参数,容器默认使用 allocator<T> 类(它使用 new 和 delete)。由于 operator\[\] 被重载,创建对象后可用通常的数组表示法访问元素。
迭代器与容器基本方法
所有 STL 容器都提供一些基本方法:size() 返回容器中元素数目、swap() 交换两个容器的内容、begin() 返回指向容器中第一个元素的迭代器、end() 返回一个表示超过结尾(past-the-end)的迭代器。迭代器是广义指针,可以是指针,也可以是可执行类似指针操作(如解引用 operator*、递增 operator++)的对象。每个容器类都定义了一个合适的迭代器,其类型是一个名为 iterator 的 typedef,作用域为整个类。
规则(限制):超过结尾是一种迭代器,指向容器最后一个元素后面的那个元素,与 C 风格字符串最后一个字符后面的空字符类似------但空字符是一个值,而"超过结尾"是一个指向元素(迭代器);end() 成员标识超过结尾的位置。C++11 的 auto 可省略显式写出迭代器类型。
std::vector<double> scores; // vector<double> 对象
std::vector<double>::iterator pd; // 声明迭代器:typedef,作用域为整个类
pd = scores.begin(); // 令 pd 指向第一个元素
*pd = 10.5; // 解除引用:给第一个元素赋值
++pd; // 递增:令 pd 指向下一个元素
// C++11 自动类型推断
auto pa = scores.begin(); // 编译器推断 pa 为迭代器类型
push_back、erase 与 insert
vector 模板类包含一些只有某些 STL 容器才有的方法。push_back() 将元素添加到矢量末尾,它负责内存管理、增加矢量长度以容纳新成员;erase() 删除矢量中给定区间的元素,接受两个定义区间的迭代器参数;insert() 的功能与 erase() 相反,接受 3 个迭代器参数------第一个指定新元素的插入位置,第二、三个定义被插入区间(通常是另一个容器对象的一部分)。
erase() 与 insert() 的区间都用半开区间 [p1, p2) 指定。vector 提供随机访问功能,因此其迭代器定义了诸如 begin() + 2 的算术操作。向 old.end() 前插入即在矢量最后一个元素后面追加。
std::vector<int> old_v; // 目标矢量
std::vector<int> new_v; // 源矢量
old_v.push_back(5); // 在末尾添加元素,自动增长
// 删除前两个元素(begin 与 begin+1 指向的元素)
old_v.erase(old_v.begin(), old_v.begin() + 2);
// 把 new_v 除第一个元素外的其余元素插入到 old_v 开头
old_v.insert(old_v.begin(),
new_v.begin() + 1, new_v.end());
// 在末尾追加 new_v 的全部元素(插入到 end() 前)
old_v.insert(old_v.end(), new_v.begin(), new_v.end());
非成员算法与成员方法的取舍
STL 从更广泛的角度定义了非成员(non-member)函数来执行常见操作------不是为每个容器类定义 find() 成员函数,而是定义一个适用于所有容器类的非成员函数 find()。例如 for_each()、random_shuffle() 和 sort() 都是代表性的非成员 STL 函数。
这种设计理念省去了大量重复工作------假设有 8 个容器类、需要支持 10 种操作,若每个类都有自己的成员函数需定义 80 个函数,而采用 STL 方式只需定义 10 个非成员函数;定义新容器类时只要遵循正确的指导思想,也能使用已有的 10 个非成员函数。即使有执行相同任务的非成员函数,STL 有时仍会定义成员函数,因为对有些操作而言类特定算法的效率比通用算法高------如 vector 的成员 swap() 效率比非成员 swap() 高,但非成员函数能交换两个不同类型容器的内容。
规则(限制):for_each() 接受 3 个参数(前两个是定义区间的迭代器,最后是指向函数的指针或函数对象),把函数应用于区间中的各个元素,被指向的函数不能修改容器元素的值;random_shuffle() 接受两个指定区间的迭代器参数并随机排列元素,要求容器类允许随机访问;sort() 也要求容器支持随机访问。
#include <algorithm> // for_each、random_shuffle、sort
// for_each:把 ShowReview 应用于区间内每个元素(不修改元素值)
// for_each(books.begin(), books.end(), ShowReview);
// random_shuffle:随机排列区间元素(要求随机访问容器)
// random_shuffle(books.begin(), books.end());
// sort:按类型定义的 < 运算符排序(要求随机访问容器)
// sort(coolstuff.begin(), coolstuff.end());
sort 的两个版本与排序概念
sort() 有两个版本。第一个版本接受两个定义区间的迭代器参数,使用为存储在容器中的类型元素定义的 < 运算符对区间元素进行排序;若容器元素是用户定义的对象,则必须定义能处理该类型对象的 operator<() 函数。第二个版本接受 3 个参数:前两个也是指定区间的迭代器,最后一个是函数指针(或函数对象),返回值可转换为 bool,false 表示两个参数的顺序不正确。
默认按 < 排序不够灵活------需要按降序、或按其他成员(而非 operator< 依据的成员)排序时,用自定义比较函数替代默认比较。
两种排序对应两种排序概念:按 operator< 的全排序(total ordering)中,若 a<b 和 b<a 都不成立,则 a 和 b 必定相同;而自定义比较函数的完整弱排序(strict weak ordering)中并非如此------它们可能相同,也可能只是在某方面相同(如仅 rating 成员相同),此时只能说它们等价(equivalent),而不是相同。
struct Review { // 用户定义类型
std::string title; // 标题成员
int rating; // 评分成员
};
bool operator<(const Review& r1, const Review& r2) { // 全排序:按 title
if (r1.title < r2.title) // 先按标题比较
return true;
if (r1.title == r2.title) // 标题相同时按评分比较
return r1.rating < r2.rating;
return false;
}
bool WorseThan(const Review& r1, const Review& r2) { // 完整弱排序:按 rating
return r1.rating < r2.rating; // 仅评分相同时视为等价
}
// sort(books.begin(), books.end()); // 用 operator< 排序
// sort(books.begin(), books.end(), WorseThan); // 用自定义比较函数排序
基于范围的 for 循环(C++11)
基于范围的 for 循环(range-based for loop)是为用于 STL 而设计的。括号内的代码声明一个类型与容器存储内容相同的变量,然后指出容器的名称;循环体使用指定的变量依次访问容器的每个元素。
// 基于范围的 for:依次访问每个元素
// for (auto x : books) ShowReview(x); // x 推断为 Review,按值传递
// 若要修改元素,使用引用变量
void InflateReview(Review& r) { // 接收引用的函数
++r.rating; // 修改元素内容
}
// for (auto& x : books) InflateReview(x); // 引用方式:可修改容器内容