C++輸入流疊代器(STL輸入流疊代器)詳解

2020-07-16 10:04:30
輸入流疊代器是一個可以在文字模式下從流中提取資料的輸入疊代器,這意味著不能用它處理二進位制流。

一般用兩個流疊代器來從流中讀取全部的值:指向要讀入的第一個值的開始疊代器,指向流的末尾的結束疊代器。在輸入流的檔案結束狀態(End-Of-File,EOF)被識別時,就可以確定結束疊代器。

定義在 iterator 標頭檔案中的 istream_iterator 模板會用提取運算子 >> 從流中讀入 T 型別的值。對於這些工作,必須有一個從 istream 物件讀取 T 型別值的 operator>>() 函數的過載版本。因為 istream_iterator 是一個輸入疊代器,所以它的範例是一個單向疊代器;它只能被使用一次。預設情況下,我們認為這種流包含的是 char 型別的字元。

可以通過傳入一個輸入流物件到建構函式來生成一個 istream_iterator 物件。istream_iterator 物件有拷貝建構函式。下面是一個生成輸入流疊代器的範例:
std::istream_iterator<string> in {std::cin}; // Reads strings from cin
std::istream_iterator<string> end_in;// End-of-stream iterator
預設建構函式會生成一個代表流結束的物件,也就是當 EOF 被識別時的那種物件。

雖然預設情況下,這種流被認為包含的是 char 型別的字元,但能夠定義輸入流疊代器來讀取包含其他型別字元的流。例如,下面展示了如何定義輸入流疊代器來讀取一個包含 wchar_t 字元的流:
std::basic_ifstream<wchar_t> file_in{"no_such_file.txt"};// File stream of wchar_t
std::istream_iterator<std::wstring, wchar_t> in {file_in}; // Reads strings of wchar_t
std::istream_iterator<std::wstring, wchar_t> end_in; // End-of-stream iterator
第一條語句定義了一個 wchar_t 字元的輸入檔案流。為了讀這個檔案,第二條語句定義了一個流疊代器。流中的字元型別是由 istream_iteratot 的第二個模板型別引數指定的,在這個範例中是 wchar_t。當然,指定從流中讀入的物件的型別的第一個模板型別引數必須是 wstring,它是 wchar_t 字元的字串型別。

istream_iterator 物件有下面這些成員函數:
  • opemtor*() 會返回一個流中當前物件的參照,可以運用這個運算子多次以重讀相同的值。
  • operator->() 會返回流中當前物件的地址。
  • apemtor++() 會從底層輸入流中讀取一個值,並將它儲存到一個疊代器物件中,返回一個疊代器物件的參照。因此,表示式 *++in 的值為最新的被儲存的值。這不是一般的用法,因為它可能會跳過流中的第一個值。
  • operator++(int) 會從底層輸入流讀取一個值,並將它儲存到一個疊代器物件中,為使用 operator*() 或 operator->() 存取做準備。在流中的新值被儲存之前,這個函數會返回疊代器物件的一個代理。這意味著在讀和儲存底層輸入流中的最新值之前,表示式 *in++ 的值是儲存在疊代器中的物件。

也有非成員函數,operator==() 和 operator!=() 可以比較相同型別的物件。兩個輸入疊代器是相等的,前提是它們都是同一個流的疊代器或者都是流的結束疊代器;否則,它們就不相等。

疊代器和流疊代器

輸入流疊代器和常規疊代器在資料項序列的關聯方式上是不同的,明白這一點很重要。常規疊代器指向的是陣列或容器中的元素。遞增一個常規疊代器會改變它所指向的元素;這對指向同一個序列的元素的其他疊代器沒有影響。這一點和流疊代器不同。

當用流疊代器來從標準輸入流讀取資料時,顯然要考慮會發生什麼;當流疊代器指向檔案時,就可能不那麼明顯,但仍然可以應用。如果生成了兩個和同一個流相關的輸入流疊代器,初始時它們都指向第一個資料項。如果用一個疊代器來讀取流,另一個不會再參照第一個資料值。當從標準輸入流讀取時,值會被第一個疊代器消耗。這是因為這個疊代器在讀取一個值時,會修改流物件。輸入流疊代器不僅會改變它所指向的元素(在參照時得到的結果),也會改變底層流中確定下一次讀操作從哪裡開始的位置。

因此,給定流的兩個或兩個以上的輸入流疊代器會指向從這個流中得到的下一個資料項。這意味著兩個輸入流疊代器所確定的序列只能由開始疊代器和流的結束疊代器組成。我們無法生成兩個指向同一個流中兩個不同值的流疊代器,這並不是說不能用輸入流疊代器來存取資料項。在適當時,我們會看到可以這麼做。

用輸入流的成員函數讀取資料

下面是一些說明如何用成員函數讀取字串的程式碼:
std::cout << "Enter one or more words. Enter ! to end:";
std::istream_iterator<string> in {std::cin}; // Reads strings from cin
std::vector<string> words;
while(true)
{
    string word = *in;
    if(word == "!"") break;
    words.push_back(word);
    ++in;
}
std::cout << "You entered " << words.size() << "words." << std::endl;
迴圈從標準輸入流中讀取單詞,並把它們新增到 vector 容器中,直到按下確認鍵。表示式 *in 的值是從底層流讀到的當前 string 物件。++in 會導致從流中讀取下一個 string 物件,並儲存到這個疊代器中。下面是執行這段程式碼的範例輸出:

Enter one or more words. Enter ! to end:
Yes No Maybe !
You entered 3 words.

下面是一個演示如何用成員函數來讀取數值資料的範例,但不一定要這樣使用:
// Calling istream_iterator function members
#include <iostream>                                   // For standard streams
#include <iterator>                                   // For stream iterators

int main()
{
    std::cout << "Enter some integers - enter Ctrl+Z to end.n";
    std::istream_iterator<int> iter {std::cin};         // Create begin input stream iterator...
    std::istream_iterator<int> copy_iter {iter};        // ...and a copy
    std::istream_iterator<int> end_iter;                // Create end input stream iterator

    // Read some integers to sum
    int sum {};
    while(iter != end_iter)                             // Continue until Ctrl+Z read
    {
        sum += *iter++;
    }
    std::cout << "Total is " << sum << std::endl;

    std::cin.clear();                                   // Clear EOF state
    std::cin.ignore();                                  // Skip characters

    // Read integers using the copy of the iterator
    std::cout << "Enter some more integers - enter Ctrl+Z to end.n";
    int product {1};
    while(true)
    {
        if(copy_iter == end_iter) break;                  // Break if Ctrl+Z was read
        product *= *copy_iter++;
    }
    std::cout << "product is " << product << std::endl;
}
在顯示輸入提示後,我們可以用一個輸入流疊代器從 cin 中讀取 int 型別的值;然後會複製疊代器物件的一個副本。在原始物件 iter 被使用後,我們可以用副本 y_iter 從 cin 讀取輸入,我們只需要一個結束疊代器物件,因為它從沒有改變。第一個迴圈會求出所有用輸入流疊代器讀入的值的和,直到識別出 EOF 狀態,它是通過從流中讀取 Ctrl+Z 標誌設定的。解除參照 iter 可以使它所指向的值變得可用,之後,後遞增運算會移動 iter,使它指向下一個輸入。如果輸入的是 Ctrl+Z,迴圈結束。

在從 cin 讀取更多資料之前,必須呼叫流物件的 dear() 來重置 EOF 標誌;也需要跳過輸入緩衝區中留下的 'n' 字元,這是通過呼叫流物件的 ignore 做到的。第二個迴圈會用 copy_iter 來讀取值並計算它們的 product。第一個迴圈的主要差別是:它是通過比較 copy_iter 和 end_iter 的相等性來終止迴圈的。

下面是這個範例的輸出:

Enter some integers - enter Ctrl+Z to end.
1 2 3 4^Z
Total is 10
Enter some more integers - enter Ctrl+Z to end.
3 3 2 5 4^Z
product is 360

在大多數時候,我們並不會像這樣使用輸入流疊代器。一般來說,只需要用一個流的開始和結束疊代器作為一個函數的引數。我們可能意識到,能夠只用一條語句來替代上述程式中的第一個迴圈及其後面的輸出語句:
std::cout << "Total is " << std::accumulate(iter, end_iter, 0) <<std::endl;
下面是通過用一個輸入流疊代器,將從 cin 得到的浮點值插入到一個容器中的程式碼:
std::vector<double> data;
std::cout <<"Enter some numerical values — enter Ctrl+Z to end.n";
std::copy(std::istream_iterator<double>{std::cin}, std::istream iterator <double>{}, std::back_inserter(data));
copy() 演算法會將任意個數的值附加到 vector 容器中,直到讀到 Ctrl+Z。vector 容器有一個接受序列來初始化元素的建構函式,因此可以用生成容器的語句中的輸入流疊代器來讀取值:
std::cout << "Enter some numerical values - enter Ctrl+Z to end.n";
std::vector<double> data {std::istream_iterator<double>{std::cin},std::istream_iterator<double>{}};
這會從標準輸入流中讀取浮點值,並用它們作為容器中元素的初始值。