C++ STL流疊代器(stream_iterator)用法詳解

2020-07-16 10:05:22
流疊代器也是一種疊代器介面卡,不過和之前講的疊代器介面卡有所差別,它的操作物件不再是某個容器,而是流物件。即通過流疊代器,我們可以讀取指定流物件中的資料,也可以將資料寫入到流物件中。

通常情況下,我們經常使用的 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 = "http://c.biancheng.net/stl/";
    cout << endl;

    //建立一個輸出流疊代器,設定分隔符 ,
    ostream_iterator<int> out_it1(cout, ",");
    //向 cout 輸出流依次寫入 1、2、3
    *out_it1 = 1;
    *out_it1 = 2;
    *out_it1 = 3;
    return 0;
}
程式輸出結果為:

http://c.biancheng.net/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() 函數,由於不是本節重點,這裡不再介紹,後續章節會做詳細講解。