C++ STL流迭代器(stream_iterator)用法详解

流迭代器也是一种迭代器适配器,不过和之前讲的迭代器适配器有所差别,它的操作对象不再是某个容器,而是流对象。即通过流迭代器,我们可以读取指定流对象中的数据,也可以将数据写入到流对象中。

通常情况下,我们经常使用的 cin、cout 就属于流对象,其中 cin 可以获取键盘输入的数据,cout 可以将指定数据输出到屏幕上。除此之外,更常见的还有文件 I/O 流等。关于什么流,更详细的介绍可阅读《C++流类和流对象》一文。

介于流对象又可细分为输入流对象(istream)和输出流对象(ostream),C++ STL 标准库中,也对应的提供了 2 类流迭代器:
  • 将绑定到输入流对象的迭代器称为输入流迭代器(istream_iterator),其可以用来读取输入流中的数据;
  • 将绑定到输出流对象的迭代器称为输出流迭代器(ostream_iterator),其用来将数据写入到输出流中。

接下来,就分别讲解这 2 个流迭代器的用法。

C++ STL输入流迭代器(istream_iterator)

输入流迭代器用于直接从指定的输入流中读取元素,该类型迭代器本质上就是一个输入迭代器,这意味着假设 p 是一个输入流迭代器,则其只能进行 ++p、p++、*p 操作,同时输入迭代器之间也只能使用 == 和 != 运算符。

实际上,输入流迭代器的底层是通过重载 ++ 运算符实现的,该运算符内部会调用operator >>读取数据。也就是说,假设 iit 为输入流迭代器,则只需要执行 ++iit 或者 iit++,即可读取一个指定类型的元素。

值得一提的是,istream_iterator 定义在<iterator>头文件,并位于 std 命名空间中,因此使用此迭代器之前,程序中应包含如下语句:
#include <iterator>
using namespace std;

第二行代码不是必需的,但如果不用,则程序中在创建该类型的迭代器时,必须手动注明 std 命名空间(强烈建议初学者使用)。


创建输入流迭代器的方式有 3 种,分别为:
1) 调用 istream_iterator 模板类的默认构造函数,可以创建出一个具有结束标志的输入流迭代器。要知道,当我们从输入流中不断提取数据时,总有将流中数据全部提取完的那一时刻,这一时刻就可以用此方式构建的输入流迭代器表示。

例如:
std::istream_iterator<double> eos;
由此,即创建了一个可读取 double 类型元素,并代表结束标志的输入流迭代器。

2) 除此之外,还可以创建一个可用来读取数据的输入流迭代器,比如:
std::istream_iterator<double> iit(std::cin);
这里创建了一个可从标准输入流 cin 读取数据的输入流迭代器。值得注意的一点是,通过此方式创建的输入流迭代器,其调用的构造函数中,会自行尝试去指定流中读取一个指定类型的元素。

3) istream_iterator 模板类还支持用已创建好的 istream_iterator 迭代器为新建 istream_iterator 迭代器初始化,例如,在上面 iit 的基础上,再创建一个相同的 iit2 迭代器:
std::istream_iterator<double> iit2(iit1);
由此,就创建好了一个和 iit1 完全相同的输入流迭代器。

下面程序演示了输入流迭代器的用法:
#include <iostream>
#include <iterator>
using namespace std;
int main() {
    //用于接收输入流中的数据
    double value1, value2;
    cout << "请输入 2 个小数: ";
    //创建表示结束的输入流迭代器
    istream_iterator<double> eos;
    //创建一个可逐个读取输入流中数据的迭代器,同时这里会让用户输入数据
    istream_iterator<double> iit(cin);
    //判断输入流中是否有数据
    if (iit != eos) {
        //读取一个元素,并赋值给 value1
        value1 = *iit;
    }
    //如果输入流中此时没有数据,则用户要输入一个;反之,如果流中有数据,iit 迭代器后移一位,做读取下一个元素做准备
    iit++;
    if (iit != eos) {
        //读取第二个元素,赋值给 value2
        value2 = *iit;
    }
    //输出读取到的 2 个元素
    cout << "value1 = " << value1 << endl;
    cout << "value2 = " << value2 << endl;
    return 0;
}
程序执行结果为:

请输入 2 个小数: 1.2 2.3
value1 = 1.2
value2 = 2.3

注意,只有读取到 EOF 流结束符时,程序中的 iit 才会和 eos 相等。另外,Windows 平台上使用 Ctrl+Z 组合键输入 ^Z 表示 EOF 流结束符,此结束符需要单独输入,或者输入换行符之后再输入才有效。

C++ STL输出流迭代器(ostream_iterator)

和输入流迭代器恰好相反,输出流迭代器用于将数据写到指定的输出流(如 cout)中。另外,该类型迭代器本质上属于输出迭代器,假设 p 为一个输出迭代器,则它能执行 ++p、p++、*p=t 以及 *p++=t 等类似操作。

其次,输出迭代器底层是通过重载赋值(=)运算符实现的,即借助该运算符,每个赋值给输出流迭代器的元素都会被写入到指定的输出流中。

值得一提的是,实现 ostream_iterator 迭代器的模板类也定义在<iterator>头文件,并位于 std 命名空间中,因此在使用此类型迭代器时,程序也应该包含以下 2 行代码:
#include <iterator>
using namespace std;

ostream_iterator 模板类中也提供了 3 种创建 ostream_iterator 迭代器的方法。

1) 通过调用该模板类的默认构造函数,可以创建了一个指定输出流的迭代器:
std::ostream_iterator<int> out_it(std::cout);
由此,我们就创建了一个可将 int 类型元素写入到输出流(屏幕)中的迭代器。

2) 在第一种方式的基础上,还可以为写入的元素之间指定一个分隔符,例如:
std::ostream_iterator<int> out_it(std::cout,",");
和第一种写入方式不同之处在于,此方式在向输出流写入 int 类型元素的同时,还会附带写入一个逗号(,)。

3) 另外,在创建输出流迭代器时,可以用已有的同类型的迭代器,为其初始化。例如,利用上面已创建的 out_it,再创建一个完全相同的 out_it1:
std::ostream_iterator<int> out_it1(out_it);

下面程序演示了 ostream_iterator 输出流迭代器的功能:
#include <iostream>
#include <iterator>
#include <string>
using namespace std;
int main() {
    //创建一个输出流迭代器
    ostream_iterator<string> out_it(cout);
    //向 cout 输出流写入 string 字符串
    *out_it = "https://www.xinbaoku.com/stl/";
    cout << endl;

    //创建一个输出流迭代器,设置分隔符 ,
    ostream_iterator<int> out_it1(cout, ",");
    //向 cout 输出流依次写入 1、2、3
    *out_it1 = 1;
    *out_it1 = 2;
    *out_it1 = 3;
    return 0;
}
程序输出结果为:

https://www.xinbaoku.com/stl/
1,2,3,


在实际场景中,输出流迭代器常和 copy() 函数连用,即作为该函数第 3 个参数。比如:
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>    // std::copy
using namespace std;
int main() {
    //创建一个 vector 容器
    vector<int> myvector;
    //初始化 myvector 容器
    for (int i = 1; i < 10; ++i) {
        myvector.push_back(i);
    }
    //创建输出流迭代器
    std::ostream_iterator<int> out_it(std::cout, ", ");
    //将 myvector 容器中存储的元素写入到 cout 输出流中
    std::copy(myvector.begin(), myvector.end(), out_it);
    return 0;
}
程序执行结果为:

1, 2, 3, 4, 5, 6, 7, 8, 9,

有关 copy() 函数,由于不是本节重点,这里不再介绍,后续章节会做详细讲解。