22.泛型编程中
STL 算法通过 iterator_traits 在编译时检测迭代器的“标签”(Tag),来决定是否允许编译。
概念(concept)、改进(refinement)和模型(model)
STL 文献使用术语概念(concept)来描述一系列的要求,因此存在输入迭代器概念、正向迭代器概念等。概念可以具有类似继承的关系,如双向迭代器继承了正向迭代器的功能;由于不能用 C++ 继承机制表达这种关系(如把正向迭代器实现为类、把双向迭代器实现为常规指针),有些 STL 文献使用术语改进(refinement)来表示这种概念上的继承——双向迭代器是对正向迭代器概念的一种改进。概念的具体实现被称为模型(model),因此指向 int 的常规指针是一个随机访问迭代器模型,也是一个正向迭代器模型,因为它满足该概念的所有要求。
将指针用作迭代器
指针满足所有的迭代器要求。迭代器是 STL 算法的接口,而指针是迭代器,因此 STL 算法可以使用指针来对基于指针的非 STL 容器(如数组)进行操作。STL 的 sort() 接受指向容器第一个元素的迭代器和指向超尾的迭代器作为参数。
copy() 算法
copy() 可以将数据从一个容器复制到另一个容器中,以迭代器方式实现,因此它可以从一种容器复制到另一种容器,甚至可以在数组之间复制。copy() 的前两个迭代器参数表示要复制的范围,最后一个迭代器参数表示要将第一个元素复制到什么位置;前两个参数最好是输入迭代器,最后一个参数最好是输出迭代器。
copy() 将覆盖目标容器中已有的数据,同时目标容器必须足够大以容纳被复制的元素,因此不能用 copy() 将数据放到空矢量中(除非采用插入迭代器技巧)。
ostream_iterator 模板
ostream_iterator 模板是输出迭代器概念的一个模型,也是一个适配器(adapter)——一个类或函数,可以将一些其他接口转换为 STL 使用的接口。通过包含头文件 <iterator> 并声明 std::ostream_iterator<int, char> out_iter(std::cout, " ") 来创建。第一个模板参数指出被发送给输出流的数据类型,第二个指出输出流使用的字符类型(另一个可能值是 wchar_t);构造函数第一个参数指出要使用的输出流(可以是文件输出流),最后一个字符串参数是发送给输出流的每个数据项后显示的分隔符。
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; int main() { vector<int> v = {10, 20, 30, 40}; // 1. 创建适配器:绑定到 cout,分隔符是空格 ostream_iterator<int> out_it(cout, " "); // 2. 使用 copy 算法:将 v 的内容"复制"到 out_it copy(v.begin(), v.end(), out_it); // 输出结果:10 20 30 40 return 0; }copy算法全程不知道自己在写屏幕。它只管傻傻地“赋值给迭代器”,而ostream_iterator把这个赋值动作“翻译”成了cout <<。
istream_iterator 模板
头文件 <iterator> 还定义了 istream_iterator 模板,使 istream 输入可用作迭代器接口,它是输入迭代器概念的一个模型。可以使用两个 istream_iterator 对象定义 copy() 的输入范围。与 ostream_iterator 相似,它也使用两个模板参数:第一个指出要读取的数据类型,第二个指出输入流使用的字符类型。
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; int main() { vector<int> nums; cout << "请输入若干整数(空格分隔,输入非整数或 Ctrl+Z/Ctrl+D 结束): " << endl; // 定义输入范围:从 cin 开始,到流结束哨兵为止 istream_iterator<int> in_it(cin); // 起始迭代器(绑定 cin) istream_iterator<int> end_it; // 结束迭代器(默认构造,哨兵) // 将输入流中的所有 int 拷贝到 vector 中 copy(in_it, end_it, back_inserter(nums)); // 输出结果验证 cout << "你输入了 " << nums.size() << " 个数字: "; for (int n : nums) cout << n << " "; return 0; }其他预定义迭代器:reverse、back_insert、front_insert、insert
头文件 <iterator> 还提供了其他一些专用的预定义迭代器类型:reverse_iterator、back_insert_iterator、front_insert_iterator 和 insert_iterator。
反向迭代器(reverse_iterator):执行递增操作将导致它被递减;“rbegin() 返回指向超尾的反向迭代器,rend() 返回指向第一个元素的反向迭代器”。
back_insert_iterator(前置插入迭代器)、front_insert_iterator(后置插入迭代器) 和 insert_iterator(插入迭代器去):back_insert_iterator 将元素插入到容器尾部,front_insert_iterator 插入到容器前端,insert_iterator 插入到构造函数参数指定位置的前面;三个插入迭代器都是输出迭代器概念的模型。
std::copy
// 标准库 std::copy 的简化伪代码 template <class InputIt, class OutputIt> OutputIt copy(InputIt first, InputIt last, OutputIt d_first) { while (first != last) { // 1. 只要没走到终点 *d_first = *first; // 2. 把源的值,赋值给目标位置 ++first; // 3. 源指针往前走 ++d_first; // 4. 目标指针往前走 } return d_first; }| 场景分类 | 源(前两个参数) | 目标(第三个参数) | 典型用途 |
|---|---|---|---|
| ① 容器 → 容器 | v1.begin(), v1.end() | v2.begin()(需保证 v2 空间足够大) 或back_inserter(v2)(自动扩容) | 复制数据、数组拷贝 |
| ② 容器 → 流 | v.begin(), v.end() | ostream_iterator<T>(cout, " ") | 打印到屏幕 / 写入文件 |
| ③ 流 → 容器 | istream_iterator<T>(cin), istream_iterator<T>() | back_inserter(v) | 从键盘 / 文件读取数据 |
| ④ 流 → 流 | istream_iterator<T>(cin), istream_iterator<T>() | ostream_iterator<T>(cout, " ") | 直接搬运输入到输出(极简过滤器) |
| ⑤ 原始数组 → 容器/流 | arr, arr + n | back_inserter(v)或ostream_iterator | 兼容 C 风格老代码 |
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; int main() { // ----- 准备工作 ----- vector<int> src = {10, 20, 30}; vector<int> dst(3); // 预留 3 个位置 // ① 容器 → 容器(必须保证 dst 有足够空间,否则越界崩溃) copy(src.begin(), src.end(), dst.begin()); // dst 变成 {10, 20, 30} // ② 容器 → 容器(自动扩容,推荐!) vector<int> dst2; copy(src.begin(), src.end(), back_inserter(dst2)); // dst2 变成 {10, 20, 30} // ③ 容器 → 流(打印到屏幕) copy(src.begin(), src.end(), ostream_iterator<int>(cout, " ")); // 输出:10 20 30 // ④ 流 → 容器(从键盘读取) cout << "请输入 3 个整数: "; vector<int> input; copy(istream_iterator<int>(cin), istream_iterator<int>(), back_inserter(input)); // 如果输入 "1 2 3",input 变成 {1, 2, 3} // ⑤ 流 → 流(直接把输入复制到输出,一个字符过滤器) cout << "你输入的又原样输出: "; copy(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, " ")); // ⑥ 原始数组 → 容器(C 风格兼容) int arr[] = {100, 200, 300}; vector<int> vec_from_arr; copy(arr, arr + 3, back_inserter(vec_from_arr)); // vec_from_arr 变成 {100, 200, 300} return 0; }容器概念(container concept)与容器类型
STL 具有容器概念和容器类型。
容器概念是具有名称(如容器、序列容器、关联容器等)的通用类别;
容器类型是可用于创建具体容器对象的模板。
容器概念像是概念化的抽象基类:所有容器都有 begin/end,但在实际 C++ 代码中,它们根本不使用继承(没有虚函数表),只是大家刚好都长这样。” 这叫鸭子类型(Duck Typing)——只要你有 begin() 和 end(),编译期就认为你是容器。
容器是存储其他对象的对象,被存储对象必须是同一种类型;存储的数据为容器所有,即容器过期时存储的数据也过期(但若数据是指针,则它指向的数据不一定过期)。
类型必须是可复制构造(copy constructible)和可赋值的(assignable)。
基本容器不能保证其元素都按特定顺序存储,也不能保证元素的顺序不变,但对概念进行改进后可以增加这样的保证。C++11 改进这些概念,添加了术语可复制插入(CopyInsertable)和可移动插入(MoveInsertable)。