1. 程式人生 > 其它 >C++ STL 系列 ——迭代器介面卡

C++ STL 系列 ——迭代器介面卡

一、什麼是迭代器介面卡

C++ STL 標準庫種的迭代器大致分為 5 種類型,輸入迭代器、輸出迭代器、前向迭代器、雙向迭代器以及隨機訪問迭代器,它們是最基礎的迭代器,對於很多場合,它們並不適合。

迭代器介面卡是對 5 種基礎迭代器進行封裝,對基礎迭代器的成員方法進行整合、修改,甚至新增一些新的成員方法。

迭代器介面卡種類

名稱 功能
反向迭代器(reverse_iterator) 又稱“逆向迭代器”,其內部重新定義了遞增運算子(++)和遞減運算子(--),專門用來實現對容器的逆序遍歷。
安插型迭代器(inserter 或者 insert_iterator) 通常用於在容器的任何位置新增新的元素,需要注意的是,此類迭代器不能被運用到元素個數固定的容器(比如 array)上。
流迭代器(istream_iterator/ostream_iterator) 輸入流迭代器用於從檔案或者鍵盤讀取資料;相反,輸出流迭代器用於將資料輸出到檔案或者螢幕上。
流緩衝區迭代器(istreambuf_iterator/ostreambuf_iterator) 輸入流緩衝區迭代器用於從輸入緩衝區中逐個讀取資料;輸出流緩衝區迭代器用於將資料逐個寫入輸出流緩衝區。
移動迭代器(move_iterator) 此型別迭代器是 C++ 11 標準中新新增的,可以將某個範圍的類物件移動到目標範圍,而不需要通過拷貝去移動。

二、反向迭代器介面卡(reverse_iterator)

反向迭代器底層可以選用雙向迭代器或者隨機訪問迭代器作為其基礎迭代器。不僅如此,通過對 ++(遞增)和 --(遞
減)運算子進行過載,使得:

  • 當反向迭代器執行 ++ 運算時,底層的基礎迭代器實則在執行 -- 操作,意味著反向迭代器在反向遍歷容器;
  • 當反向迭代器執行 -- 運算時,底層的基礎迭代器實則在執行 ++ 操作,意味著反向迭代器在正向遍歷容器。

實現反向迭代器的模板類定義在 標頭檔案,並位於 std 名稱空間中。

#include <iterator>
using namespace std;

類模板定義:

template <class Iterator>
    class reverse_iterator;

Iterator 模板引數指的是模板類中所用的基礎迭代器的型別,只能選擇雙向迭代器或者隨機訪問迭代器。

2.1 建立反向迭代器

  1. 預設建構函式
std::reverse_iterator<std::vector<int>::iterator> my_reiter;
  1. 將基礎迭代器作為引數傳給新建的反向迭代器
// 建立 myvector 容器
std::vector<int> myvector{1,2,3,4,5};
// 建立並初始化 my_reiter 迭代器
std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.end());
  1. 拷貝建構函式
// 建立並初始化一個 vector 容器
std::vector<int> myvector{1,2,3,4,5};
// 呼叫複製建構函式初始化反向迭代器的 2 種方式
std::reverse_iterator<std::vector<int>::iterator> my_reiter(myvector.rbegin());
//std::reverse_iterator<std::vector<int>::iterator> my_reiter = myvector.rbegin();

2.2 類中的成員

過載運算子 功能
operator* 以引用的形式返回當前迭代器指向的元素。
operator+ 返回一個反向迭代器,其指向距離當前指向的元素之後 n 個位置的元素。此操作要求基礎迭代器為隨機訪問迭代器。
operator++ 過載前置 ++ 和後置 ++ 運算子。
operator+= 當前反向迭代器前進 n 個位置,此操作要求基礎迭代器為隨機訪問迭代器。
operator- 返回一個反向迭代器,其指向距離當前指向的元素之前 n 個位置的元素。此操作要求基礎迭代器為隨機訪問迭代器。
operator-- 過載前置 -- 和後置 -- 運算子。
operator-= 當前反向迭代器後退 n 個位置,此操作要求基礎迭代器為隨機訪問迭代器。
operator-> 返回一個指標,其指向當前迭代器指向的元素。
operator[n] 訪問和當前反向迭代器相距 n 個位置處的元素。

三、插入迭代器介面卡(insert_iterator)

根據插入位置的不同,C++ STL 標準庫提供了 3 種插入迭代器

迭代器介面卡 功能
back_insert_iterator 在指定容器的尾部插入新元素,但前提必須是提供有 push_back() 成員方法的容器(包括 vector、deque 和 list)。
front_insert_iterator 在指定容器的頭部插入新元素,但前提必須是提供有 push_front() 成員方法的容器(包括 list、deque 和 forward_list)。
insert_iterator 在容器的指定位置之前插入新元素,前提是該容器必須提供有 insert() 成員方法。

3.1 back_insert_iterator 迭代器

back_insert_iterator 迭代器可用於在指定容器的末尾處新增新元素。其底層使用的是 push_back() 成員方法。

C++ STL 標準庫中,提供有 push_back() 成員方法的容器包括 vector、deque 和 list。

#include <iterator>
using namespace std;

定義 back_insert_iterator 插入迭代器的方式僅有一種:

std::back_insert_iterator back_it(container);

  • Container : 指定插入的目標容器的型別
  • container : 指定目標容器
std::vector<int> foo;
// 建立一個可向 foo 容器尾部新增新元素的迭代器
std::back_insert_itertor<std::vector<int>> back_it(foo);

// back_insert_iterator 迭代器模板類對賦值運算子進行了過載
back_it = 5;           // 將 5 插入到 foo 的末尾
// 相當於
pos = foo.insert(pos,value);
++pos;

C++ STL 標準庫為了方便使用者建立 back_insert_iterator 型別的插入迭代器,提供了 back_inserter() 函式

template
back_insert_iterator back_inserter(Container& x);

std::back_insert_iterator<std::vector<int>> back_it = back_inserter(foo);

3.2 front_insert_iterator 迭代器

功能:向目標容器的頭部插入新元素。底層藉助目標容器的 push_front() 方法,C++ STL 標準庫中提供 push_front() 方法的容器僅有 deque、list 和 forward_list。

#include <iterator>
using namespace std;
//建立一個 forward_list 容器
std::forward_list<int> foo;

//建立一個前插入迭代器
//std::front_insert_iterator< std::forward_list<int> > front_it(foo);
std::front_insert_iterator< std::forward_list<int> > front_it = front_inserter(foo);

//向 foo 容器的頭部插入元素
front_it = 5;

3.3 insert_iterator 迭代器

功能:向容器任意位置插入元素。底層藉助 insert() 方法實現,STL 標準庫中所有容器都提供 insert() 成員方法。insert_iterator 是唯一可用於關聯式容器的插入迭代器。

#include <iterator>
using namespace std;

語法格式:

std::insert_iterator insert_it(container,it);

  • Container : 目標迭代器的型別
  • container : 目標迭代器
  • it : 基礎迭代器

C++ STL 標準庫中還提供有 inserter() 函式,可以快速建立 insert_iterator 型別迭代器。

std::list<int> foo(2,5);

// 定義一個基礎迭代器,用於指定要插入新元素的位置
std::list<int>::iterator it = ++foo.begin();

// 建立一個 insert_iterator 迭代器
// std::insert_iterator<std::list<int>> insert_it = inserter(foo, it);

// 向 foo 容器中插入元素
insert_it = 1;

四、流迭代器

  • 將繫結到輸入流物件的迭代器稱為輸入流迭代器(istream_iterator),其可以用來讀取輸入流中的資料;
  • 將繫結到輸出流物件的迭代器稱為輸出流迭代器(ostream_iterator),其用來將資料寫入到輸出流中。

4.1 輸入流迭代器(istream_iterator)

輸入流迭代器用於直接從指定的輸入流中讀取元素,其本質就是一個輸入迭代器,其只能進行 ++p、p++、*p 操作,迭代器之間只能使用 == 和 != 運算子。

輸入迭代器底層是通過過載 ++ 運算子實現的,該運算子內部會呼叫 operator >> 讀取資料。

#include <iterator>
using namespace std;

建立輸入流迭代器的方式:

  1. 預設建構函式,建立一個具有結束標誌的輸入流迭代器
std::istream_iterator<double> eos;
  1. 建立一個可用來讀取資料的輸入流迭代器
std::istream_iterator<double> iit(std::cin);  // 此方式呼叫的建構函式會自行嘗試去指定流種讀取一個指定型別的元素
  1. 通過已建立好的 istream_iterator 迭代器為新建 istream_iterator 迭代器初始化
std::istream_iterator<double> iit2(iit1);
// 用於接受輸入流中的資料
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;
}

只有讀取到 EOF 流結束符時,程式中的 iit 才會和 eos 相等。

4.2 輸出流迭代器(ostream_iterator)

輸出流迭代器用於將資料寫到指定的輸出流(如 cout)中,本質上屬於輸出迭代器,能執行 ++p、p++、*p=t 以及 *p++ = t 等類似操作。

輸出迭代器底層是通過過載賦值運算子實現,即每個賦值給輸出流迭代器的元素都會被寫入到指定的輸出流中。

#include <iterator>
using namespace std;

建立 ostream_iterator 迭代器的方法

  1. 預設建構函式,建立一個指定輸出流的迭代器
std::ostream_iterator<int> out_it(std::cout);
  1. 建立為寫入的元素之間指定分隔符
std::ostream_iterator<int> out_it(std::cout, ",");  // 在向輸出流寫入int元素時,還會附帶逗號,
  1. 用已有同類型的迭代器初始化新建迭代器
std::ostream_iterator<int> out_it1(out_it);
// 建立一個輸出流迭代器
ostream_iterator<string> out_it<cout>;
// 向 cout 輸出流寫入 string 字串
*out_it = "12341234";

// 建立一個輸出流迭代器,設定分隔符
ostream_iterator<int> out_it1(cout, ",");
// 向 cout 輸出流依次寫入 1、2、3
*out_it1 = 1;
*out_it2 = 2;
*out_it3 = 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=0; 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;
}

五、流緩衝區迭代器(streambuf_iterator)

流緩衝區迭代器分為輸入流緩衝區迭代器和輸出流緩衝區迭代器

  • 輸入流緩衝區迭代器(istreambuf_iterator):從輸入流緩衝區中讀取字元元素;
  • 輸出流緩衝區迭代器(ostreambuf_iterator):將連續的字元元素寫入到輸出緩衝區中。

流緩衝區迭代器和流迭代器最大的區別:

  • 流緩衝區迭代器會將元素以字元的形式(包括 char、wchar_t、char16_t 及 char32_t 等)讀或者寫到流緩衝區中,由於不會涉及資料型別的轉換,讀寫資料的速度比流迭代器要快。

5.1 輸入流緩衝區迭代器(istreambuf_iterator)

istreambuf_iterator 輸入流緩衝區迭代器的功能是從指定的流緩衝區中讀取字元元素。

該型別迭代器本質是一個輸入迭代器,只能進行 ++p、p++、*p 操作,同時迭代器之間也只能使用 == 和 != 運算子。

#include <iterator>
using namespace std;

建立輸入流緩衝區迭代器的常用方式:

  1. 預設建構函式,建立一個表示結尾的輸入流緩衝區迭代器,即從流緩衝區讀完資料。
std::istreambuf_iterator<char> end_in;
  1. 指定要讀取的流緩衝區
std::istreambuf_iterator<char> in{std::cin};
// 傳入流緩衝區的地址,rdbuf 函式是獲取指定緩衝區的地址
std::istreambuf_iterator<char> in{std::cin.rdbuf()};
#include <iostream>             // std::cin, std::cout
#include <iterator>             // std::istreambuf_iterator
#include <string>              // std::string

using namespace std;

int main() {
    // 建立結束流緩衝區迭代器
    istreambuf_iterator<char> eos;
    
    // 建立一個從輸入緩衝區讀取字元元素的迭代器
    istreambuf_iterator<char> iit(cin);
    
    string mystring;
    cout << "向緩衝區輸入元素:\n";
    
    //不斷從緩衝區讀取資料,直到讀取到 EOF 流結束符
    while (iit != eos) {
        mystring += *iit++;
    }
    
    cout << "string:" << mystring;

    return 0;
}

5.2 輸出流緩衝區迭代器(ostreambuf_iterator)

ostreambuf_iterator 輸出流緩衝區迭代器用於將字元元素寫入到指定的流緩衝區中。

該型別迭代器本質上是一個輸出迭代器,它僅能執行 ++p、p++、*p=t 以及 *p++=t 等操作。

和 ostream_iterator 輸出流迭代器一樣,istreambuf_iterator 迭代器底層也是通過過載賦值(=)運算子實現的。

#include <iterator>
using namespace std;

常見建立輸出流緩衝區迭代器的方式:

  1. 通過傳遞流緩衝區物件,建立輸出流緩衝區迭代器
std::ostreambuf_iterator<char> out_it(std::cout);
> 尖括號 <> 中用於指定要寫入字元的型別,可以是 char、wchar_t、char16_t 以及 char32_t 等。
  1. 藉助 rdbug() 傳遞流緩衝區的地址,建立輸出流緩衝區迭代器
std::ostreambuf_iterator<char> out_it(std::cout.rdbuf());
#include <iostream>             // std::cin, std::cout
#include <iterator>             // std::ostreambuf_iterator
#include <string>               // std::string
#include <algorithm>            // std::copy

int main() {
    // 建立一個和輸出流緩衝區相關聯的迭代器
    std::ostreambuf_iterator<char> out_it(std::cout); // stdout iterator
    // 向輸出流緩衝區中寫入字元元素
    *out_it = 'S';
    *out_it = 'T';
    *out_it = 'L';

    // 和 copy() 函式連用
    std::string mystring("\nhttp://c.biancheng.net/stl/");
    // 將 mystring 中的字串全部寫入到輸出流緩衝區中
    std::copy(mystring.begin(), mystring.end(), out_it);
    
    return 0;
}

六、move_iterator 移動迭代器用法詳解

C++11 新增。move_iterator 迭代器介面卡,又可簡稱為移動迭代器,其可以實現以移動而非複製的方式,將某個區域空間中的元素移動至另一個指定的空間。

先看一段程式碼

#include <iostream>
#include <vector>
#include <list>
#include <string>

using namespace std;

int main()
{
  // 建立並初始化一個 vector 容器
  vector<string> myvec{ "STL","Python","Java" };
  // 再次建立一個 vector 容器,利用 myvec 為其初始化
  vector<string>othvec(myvec.begin(), myvec.end()); // 複製

  cout << "myvec:" << endl;
  // 輸出 myvec 容器中的元素
  for (auto ch : myvec)
      cout << ch << " ";

  cout << endl << "othvec:" << endl;
  // 輸出 othvec 容器中的元素
  for (auto ch : othvec)
      cout << ch << " ";

  return 0;
}

上述程式碼在初始化 othvec 容器時是通過複製 myvec 容器中的元素實現的,如果想把 myvec 容器中的元素全部移動到 othvec 容器中,可以採用移動迭代器。

move_iterator 雖然沒有明確要求基礎迭代器,但該模板類中某些成員方法的底層實現,需要此基礎迭代器為雙向迭代器或者隨機訪問迭代器。

常見 move_iterator 移動迭代器:

  • 預設建構函式,建立一個不指向任何物件的移動迭代器
    // 將 vector 容器的隨機訪問迭代器作為新建移動迭代器底層使用的基礎迭代器
    typedef std::vector<std::string>::iterator Iter;
    // 呼叫預設建構函式,建立移動迭代器
    std::move_iterator<Iter>mIter;
    
  • 建立 move_iterator 迭代器的同時初始化
    // 建立一個 vector 容器
    std::vector<std::string> myvec{ "one","two","three" };
    // 將 vector 容器的隨機訪問迭代器作為新建移動迭代器底層使用的基礎迭代器
    typedef std::vector<std::string>::iterator Iter;
    // 建立並初始化移動迭代器
    std::move_iterator<Iter>mIter(myvec.begin());
    
  • 通過已有移動迭代器初始化新建同類型迭代器
    std::move_iterator<Iter> mIter2(mIter);
    // 還可以使用 = 運算子,它們等價
    std::move_iterator<Iter> mIter2 = mIter;
    
  • make_move_iterator() 函式
    typedef std::vector<std::string>::iterator Iter;
    std::vector<std::string> myvec{ "one","two","three" };
    // 將 make_move_iterator() 的返回值賦值給同類型的 mIter 迭代器
    std::move_iterator<Iter>mIter = make_move_iterator(myvec.begin());
    

對一開始的程式進行修改,使用移動迭代器

#include <iostream>
#include <vector>
#include <list>
#include <string>

using namespace std;

int main()
{
  // 建立並初始化一個 vector 容器
  vector<string> myvec{ "STL","Python","Java" };
  // 再次建立一個 vector 容器,利用 myvec 為其初始化
  vector<string>othvec(make_move_iterator(myvec.begin()), make_move_iterator(myvec.end()));
  cout << "myvec:" << endl;
  
  // 輸出 myvec 容器中的元素

  for (auto ch : myvec)
    cout << ch << " ";        // 沒有元素輸出

  cout << endl << "othvec:" << endl;
  
  // 輸出 othvec 容器中的元素
  for (auto ch : othvec)
    cout << ch << " ";

  return 0;
}

七、輔助函式

為了方便使用者操作這些迭代器,C++ STL 標準庫中還提供有一些輔助函式

迭代器輔助函式 功能
advance(it, n) it 表示某個迭代器,n 為整數。該函式的功能是將 it 迭代器前進或後退 n 個位置。
distance(first, last) first 和 last 都是迭代器,該函式的功能是計算 first 和 last 之間的距離。
begin(cont) cont 表示某個容器,該函式可以返回一個指向 cont 容器中第一個元素的迭代器。
end(cont) cont 表示某個容器,該函式可以返回一個指向 cont 容器中最後一個元素之後位置的迭代器。
prev(it) it 為指定的迭代器,該函式預設可以返回一個指向上一個位置處的迭代器。注意,it 至少為雙向迭代器。
next(it) it 為指定的迭代器,該函式預設可以返回一個指向下一個位置處的迭代器。注意,it 最少為前向迭代器。