C++ vector疊代器及用法

2020-07-16 10:05:23
正如期望的那樣,vector 容器實現了所有可以返回疊代器的成員函數,包括 const 疊代器和 non-const 疊代器,以及反向疊代器。

vector 容器的疊代器是隨機存取疊代器。當然,也可以通過全域性函數獲取它們。vector 有成員函數 push_back(),所以能夠通過使用 back_insert_iterator 來新增新的值。

從前面章節了解到,可以通過呼叫全同的 back_inserter() 函數來獲取一個後向插入疊代器。無法在 vector 容器中使用 front_insert_iterator,這需要 vector 有成員函數 push_front(),但是 vector 容器並沒有定義這個函數。

可以通過演示如何用 copy() 演算法來新增元素,向你展示怎樣在 vector 中使用後向插入疊代器。copy() 的頭兩個引數是兩個疊代器,指定了複製元素的範圍,第三個引數指定了這些元素存放的位置。頭兩個引數要求是輸入疊代器,所以可以接受任何其他類別的疊代器;顯然第三個引數必須是一個輸出疊代器。這裡有一個範例:
std::vector<double> data {32.5, 30.1, 36.3, 40.0, 39.2};
std::cout << "Enter additional data values separated by spaces or Ctrl+Z to end:" << std::endl;

std::copy(std::istream_iterator<double>(std::cin) , std::istream_iterator<double>(),std::back_inserter(data));
std::copy(std::begin(data), std::end(data),std::ostream_iterator<double> (std:: cout," "))
用初始化列表生成 data 容器。第一次呼叫 copy() 時,使用一個 istream_iterator 物件作為第一個引數,它能夠從標準輸入流中讀取 double 型別的值。第二個引數是一個流的結束疊代器,當識別到流結束時,istream_iterator 會變為結束疊代器;當從鍵盤輸入 Ctrl+Z 時, 這也會發生在 cin 中。

copy() 的第三個引數是讀入值的存放位置,是 data 容器的一個 back_insert_iterator,它是由 back_inserter() 函數返回的,因此從 cin 讀出的值都被作為新元素新增到 data 容器的後面。最後一次呼叫 copy(),會將 data 容器的所有元素複製到 cout;這是通過將一個 ostream_iterator 物件作為目的地址來實現的。讓我們使用 vector 容器的疊代器來嘗試一個完整的範例:
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using std::string;
using std::vector;

int main()
{
    vector<string> words;                     // Stores words to be sorted
    words.reserve(10);                        // Allocate some space for elements
    std::cout << "Enter words separated by spaces. Enter Ctrl+Z on a separate line to end:" << std::endl;

    std::copy(std::istream_iterator <string> {std::cin}, std::istream_iterator <string> {},std::back_inserter(words));

    std::cout << "Starting sort." << std::endl;
    bool out_of_order {false};
    while (true)
    {
        for (auto first = start + 1; first != last; ++first)
        {
            if (*(first - 1) > *first)
            { // Out of order so swap them
                std::swap(*first, *(first - 1));
                out_of_order = true;
            }
        }
        if (!out_of_order)                      // If they are in order (no swaps necessary)...
            break;                                // ...we are done...
        out_of_order = false;                   // ...otherwise, go round again.
    }
 
    std::cout << "your words in ascending sequence:" << std::endl;
    std::copy(std::begin(words), std::end(words), std::ostream_iterator < string > {std::cout, " "});
    std::cout << std::endl;

    // Create a new vector by moving elements from words vector
    vector<string> words_copy {std::make_move_iterator(std::begin(words)),std::make_move_iterator(std::end(words))};
    std::cout << "nAfter moving elements from words, words_copy contains:" << std::endl;
    std::copy(std::begin(words_copy), std::end(words_copy),  std::ostream_iterator < string > {std::cout, " "});
    std::cout << std::endl;

    // See what's happened to elements in words vector...
    std::cout << "nwords vector has " << words.size() << " elementsn";
    if (words.front().empty())
    std::cout << "First element is empty string object." << std::endl;

    std::cout << "First element is "" << words.front() << """ << std::endl;
}
範例輸出如下:

Enter words separated by spaces. Enter Ctrl+Z on a separate line to end:
one two three four five six seven eight
^Z

Starting sort.
your words in ascending sequence:
eight five four one seven six three two

After moving elements from words, words_copy contains:
eight five four one seven six three two

words vector has 8 elements
First element is empty string object.
First element is ""

該程式使用流疊代器從標準輸入流讀取單詞,然後將其作為字串物件寫入一個 vector 容器。可以輸入任意個數的單詞。容器會在必要時自動增長。這裡呼叫容器的 reserve()  函數來為 10 個元素分配記憶體。一個好主意是,每次只分配大致需要的記憶體,這會減少小幅度分配記憶體所帶來的開銷。

back_inserter() 生成了一個 back_insert_iterator,它能夠呼叫容器的成員函數 push_back(),來將每一個字串物件作為新元素新增到容器中。

copy() 演算法的頭兩個引數是輸入流疊代器,其中的第二個引數是結束流疊代器。當從鍵盤輸入 Ctrl+Z 時,流疊代器就會匹配到它,這相當於檔案流的 EOF。

這裡對 vector 元素進行排序的程式碼展示了疊代器的使用。稍後你會看到,sort() 演算法可以只用一條語句就完成相同的工作。這裡的排序演算法是十分簡單的氣泡排序,通過遍歷元素來反複排序。在每一趟排序中,如果臨近的元素無序,就會互相交換。swap() 函數定義在 <algorithm.h> 標頭檔案中,可以高效地交換任何型別的元素。如果在一趟排序中,所有元素都沒有交換,那麼所有元素已經是升序序列了。最外層的迴圈是一個由疊代器控制的 for 迴圈。first 的初始值是 begin(words)+1,它指向 vector 的第二個元素。從第二個元素開始, 是為了確保能夠使用 first-1,這樣可以保證兩個連續元素的比較總是合法。當 first 自增到 end(words) 時,一趟排序就會結束。

對 words vector 中的內容排序後的結果,可以通過使用 copy() 演算法將全部元素轉移到輸出流疊代器來顯示。轉移元素的範圍是由 begin() 和 end() 返回的疊代器指定的,所以會輸出全部元素。ostream_iterator 建構函式的引數是資料流向的目的地址,分隔字串會分隔每一個輸出的值。

main() 的最後一部分程式碼展示了如何使用移動迭代器,這裡移動了所有元素。在這個操作之後,從程式輸出可以發現,words 中包含的字串都變成了空字串。移動一個元素會留下一個由無參字串建構函式建立的物件。一般來說,移動一個是類物件的元素,會導致這個元素處于一種不確定的狀態,因此我們不應該再去使用這個物件。

main() 中的排序程式碼其實並不依賴存放元素的容器。它只要求疊代器指定的元素能夠支援排序演算法的運算。STL 有一個 sort() 函數模板,它遠比我們能想出的任何方法都好。有時候,我們也可以定義自己的函數模板,去對能夠排序的任意型別元素進行排序:
template<typename RandomIter>
void bubble_sort(RandomIter start, RandomIter last)
{
    std::cout << "Starting sort." << std::endl;
    bool out_of_order {false};                // true when values are not in order
    while (true)
    {
        for (auto first = start + 1; first != last; ++first)
        {
            if (*(first - 1) > *first)
            { // Out of order so swap them
                std::swap(*first, *(first - 1));
                out_of_order = true;
            }
        }
        if (!out_of_order)                      // If they are in order (no swaps necessary)...
            break;                                // ...we are done...
        out_of_order = false;                   // ...otherwise, go round again.
    }
}
模板型別引數是疊代器型別。因為 for 迴圈中迭代器算術操作的原因,bubble_sort() 演算法需要使用隨機存取疊代器。只要容器可以提供隨機存取疊代器,演算法就可以對這個容器的內容進行排序;這也包括標準陣列和字串物件。如果在前面的 main() 中使用此程式碼, 就可以使用下面的語句替換掉 main() 中對 words 進行排序的部分:
bubble_sort(std::begin(words), std::end(words)); // Sort the words array
定義一個只用疊代器實現操作的函數模板,會使這個函數的用法變得更靈活。任何處理一段元素的演算法都可以用這種方式生成。