【C++】C++中的迭代器

目錄結構:ios

contents structure [-]

 

 

迭代器類型相似於指針類型,也提供了對對象的間接訪問。就迭代器而言,其對象即是容器中的元素或者string對象中的字符。使用迭代器能夠訪問某個元素,迭代器也能從一個元素移動到另外一個元素。迭代器有有效和無效之分,這一點和指針差很少。有效的迭代器指向某個元素,或者指向容器中尾元素的下一位置。c++

1 迭代器的運算

1.1 迭代器的運算符

下面列舉了一些廣泛迭代器都支持的運算符:算法

運行符‘ 說明
*iter 返回迭代器iter所指元素的引用
iter->men 解引用iter並獲取改元素的名爲men的成員,等價於(*iter).men
++iter 令iter指示容器中的下一個元素
--iter 令iter指示容器中的上一個元素
iter1 == iter2 判斷兩個迭代器是否相等,若兩個迭代器指向同一個元素或者他們是同一個容器的尾後迭代器,則相等;反之,不相等。
iter1 != iter2 判斷兩個迭代器是否不相等。
iter + n 迭代器加上一個整數值仍獲得一個迭代器,迭代器指示的新位置與原來相比向前移動了若干個元素。結果迭代器或者指示容器內一個元素,或者指示容器尾元素的下一位置
iter - n 迭代器減去一個整數值仍獲得一個迭代器,迭代器指示的新位置與原來相比向後移動了若干個元素。結果迭代器或者指示容器內一個元素,或者指示容器尾元素的下以位置
iter += n 等同於 iter = iter + n
iter -= n 等同於 iter = iter - n
iter1 - iter2 兩個迭代器相減的結果就是它們之間的距離,也就是說,將運算符右側的迭代器向前移動若干個元素後就獲得左側的迭代器了。參與運算的兩個迭代器必須指向同一個容器中的元素或者尾容器的下一個元素。
>,>=,<,<= 迭代器的關係運算符,若是某迭代器指向的容器位置在另外一個迭代器所指位置以前,則說明前者小於後者。參與運算的兩個迭代器必須指向同一個容器中的元素或者尾元素的下一元素。


並非全部的迭代器都支持上面這些運算符,關鍵是要看迭代器是否認義了相應的操做符。

下面使用迭代器把string中的第一個字母改爲了大寫形式:數組

string s("some string");
if(s.begin() != s.end()){//確保s不爲空
    auto it = s.begin(); //it表示s第一個字符的大寫字母
    *t = toupper(*t); //將當前字母改爲大寫形式
}


迭代器可使用++運算符來從一個元素移動到另外一個元素。從邏輯上講,迭代器的遞增和整數的遞增相似,整數的遞增是在整數值上加1,迭代器的遞增怎是將迭代器「向前移動一個位置」。下面使用這個特性,也能夠實現上面的效果:dom

for(auto it=s.begin(); it!=s.end() && !isspace(*it); ++it)
    *it = toupper(*it);


解引用迭代器可得到迭代器所指的對象,若是該對象的類型剛好是類,就有可能但願進一步訪問它的成員(迭代器和指針相似,因此這一規則一樣適用與指針)。
例如,對於一個由一串字符串組成的vector對象來講,想要檢查其元素是否爲空,令it是爲iterator對象的迭代器,只須要檢查it所指字符串是否爲空就能夠了,其代碼以下:函數

(*it).empty(); //解引用it,而後調用結果對象的empty成員。
*it.empty();//錯誤:試圖訪問it名爲empty()的成員,但it是一個迭代器,沒有empty()的成員

爲了簡化這種操做,c++語言提供了箭頭運算符(->)。箭頭運算符把解引用和成員訪問兩個操做結合在一塊兒:this

it->empty(); //等同於(*it).empty();

 

1.2 begin和end操做符

//依次輸出text的每一行直至遇到第一個空白符爲止
vector<string> text{"abc","def"};
for(auto it=text.begin(); it!=text.end() && !it->empty(); ++it)//使用it->empty()判斷所指是否爲空
    cout << *it << endl;

指針和迭代器相似,也能夠操做使用->運算符,例如:spa

string s1 = "a string", *p = &s1;
auto n = s1.size();//運行string對象s1的size成員
n = (*p).size();//運行p所值對象的size成員
n = p->size();//等價於(*p).size();

 

2 迭代器的類型有那些

C++的標準庫中定義了5種迭代器類型(C++17及其以後有6種迭代器類型)。這六大類迭代器分別是:LegacyInputIterator(輸入迭代器), LegacyOutputIterator(輸出迭代器), LegacyForwardIterator(前向迭代器), LegacyBidirectionalIterator(雙向迭代器), LegacyRandomAccessIterator(隨機訪問迭代器), and LegacyContiguousIterator(相接迭代器)。

迭代器是按照它們所提供的操做來分類的,這意味着只要有支持迭代器操做符的類型就能夠被用做迭代器,例如:指針支持LegacyRandomAccessIterator迭代器支持的全部的操做,所以指針能夠被用在任何可使用LegacyRandomAccessIterator的地方。

全部的迭代器都具備層次關係(除LegacyOutputIterator和LegacyContiguousIterator),高層類別的迭代器支持低層類別迭代器的全部操做。若是在這個層次列表中的任何一個迭代器又支持LegacyOutputIterator迭代器的話,那麼這個迭代器被稱爲可變迭代器(mutable iterator),不然被稱爲不能夠變迭代器(Non-mutable iterators)或者常量迭代器(constant iterator)。


輸入迭代器(LegacyInputIterator):能夠用於讀取序列中的元素,輸入迭代器只用於順序訪問,並且是單向的。一旦一個輸入迭代器增長一個元素,那麼它以前全部的複製值均可能無效。

前向迭代器(LegacyForwardIterator):能夠用於讀寫元素,前向迭代器只能用於順序訪問,並且是單向的。前向迭代器支持對序列的多遍掃描,forward_list容器上的迭代器都是前向迭代器。

雙向迭代器(LegacyBidirectionalIterator):能夠用於讀寫元素,雙向迭代器支持對序列的雙向順序訪問。支持對序列的多遍掃碼,除了forward_list以外,其餘標準庫都提供符合雙向迭代器要求的迭代器。

隨機訪問迭代器(LegacyRandomAccessIterator):提供了在常量時間內對序列中任何元素的訪問的能力,它支持雙向迭代器的全部功能。

輸出迭代器(LegacyOutputIterator):能夠用於對序列中元素的寫入操做,輸出迭代器是順序訪問,並且是單向的。當一個輸出迭代器,知足LegacyRandomAccessIterator、LegacyBidirectionalIterator、LegacyForwardIterator中的任何一個迭代器時,它被稱爲可變迭代器(mutable iterator)。

相接迭代器(LegacyContiguousIterator):相接迭代器是C++17新添加的一種迭代器,它的元素在邏輯上相鄰以及在內存物理地址上相鄰。例如:指向數組中某個元素的指針就徹底符合相鄰迭代器的要求。

3d

3 經常使用迭代器

3.1 容器的迭代器

C++中的容器都爲本身的類型定義了一種迭代器。例如string,vector,map,list等等。

這類迭代器和它的容器高度耦合。接下來,展現一些迭代器的使用:指針

#include <iostream>
#include <string>
#include <vector>
#include <list>
#include <algorithm> /*for_each*/

using namespace std;

int main(int argc,char* argv[]){
    string str = "abc";
    string::iterator iter1 = str.begin();
    while(iter1 != str.end()){
        cout << *iter1 << " ";
        iter1++;
    }
    cout << "\n";
    
    vector<int> vec{1,2,3,4,5};
    auto iter2 = vec.begin();//iter2 是vector<int>::iterator類型
    for_each(iter2,vec.end(),[](const int i){
        cout << i << " ";
    });
    cout << "\n";
    
    list<string> lt{"hello","word"};
    list<string>::const_iterator iter3 = lt.cbegin();
    while(iter3 != lt.cend()){
        cout << *(iter3++) << " ";
    }
    cout << endl;
return 0;
}

輸出:

a b c
1 2 3 4 5
hello word

 

3.2 插入迭代器

插入器是一種迭代器適配器,它接受一個容器,生成一個迭代器,能實現向給定容器添加元素。當咱們經過一個插入迭代器進行賦值時,該迭代器調用容器操做來向給定容器的指定位置插入一個元素。

插入器有三種,差別表如今插入的位置:

back_inserter 建立一個使用push_back的迭代器。
front_inserter 建立一個使用push_front的迭代器。
inserter 建立一個使用insert的迭代器。此函數接受第二個參數,這個參數必須是一個指向給定容器的迭代器。元素將被插入到給定迭代器所表示的元素以前。


back_inserter函數會獲得一個back_insert_iterator迭代器對象,它定義在<iterator>頭文件中,back_insert_iterator屬於輸出迭代器,back_insert_iterator會調用容器的push_back插入元素。應此,back_inserter函數接受的容器類型,必需具備push_back函數。back_insert_iterator老是插入元素到容器的末尾。

front_inserter函數會獲得一個front_insert_iterator迭代器對象,它定義在<iterator>頭文件中,front_insert_iterator屬於輸出迭代器,front_insert_iterator會調用容器的push_front插入元素。應此,front_inserter函數接受的容器類型,必需具備push_front函數。front_insert_iterator老是插入元素到容器的開端。

inserter函數會獲得一個insert_iterator迭代器對象,它定義在<iterator>頭文件中,insert_iterator屬於輸出迭代器,insert_iterator會調用容器的insert插入元素。應此,inserter函數接受的容器類型,必需具備insert函數。insert_iterator老是插入元素到指向的元素以前。

理解插入器的工做機制很是重要:當調用inserter(c,iter)時,咱們獲得一個迭代器,接下來使用它時,會將元素插入到iter原來所指向的元素以前的位置。即,若是it是由inserter生成的迭代器,則下面的賦值語句:

*it = val;

其效果與下面的代碼是同樣的

it = c.insert(it,val); //it指向c容器中新加入的元素
++it; //遞增it使它指向原來的元素


back_inserter,front_inserter,inserter這三種插入適配器生成的迭代器完成不同。

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

int main()
{
    std::vector<int> v{1};
    std::back_insert_iterator iter = std::back_inserter(v);
    iter = 10;// iter包含1 10
    iter = 20;// iter包含1 10 20

    std::list<std::int> lst = {1,2,3,4};
    std::list<std::int> lst2,lst3; //空list
    //拷貝完成後,lst2包含4 3 2 1
    copy(lst.cbegin(),lst.cend(),front_inserter(lst2));
    //拷貝完成後,lst3包含1 2 3 4
    copy(lst.cbegin(),lst,cend(),inserter(lst3,lst3.begin()));
}


當咱們傳遞給inserter的位置原來指向第一個元素,只要咱們在此元素以前插入一個新元素,此元素就再也不是容器的首元素了。

 

3.3 流迭代器

雖然iostream類型不是容器,但標準庫定義了能夠用於這些IO類型對象的迭代器。istream_iterator讀取輸入流,ostream_iterator向一個輸出流寫入數據。這些迭代器將它們的流看成一個特定類型的元素序列來處理。經過使用流迭代器,咱們能夠用泛型算法從流對象讀取數據以及向其寫入數據。

istream_iterator操做
當建立一個流迭代器時,必需指定迭代器將要讀寫的對象類型。一個istream_iterator使用>>來讀取流。所以,一個istream_iterator讀寫的對象類型必需定義了輸入運算符。

流迭代器的讀取操做其實是在迭代器向前遞增的時候,並不是是在被引用的時候。而第一個讀取的對象是在istream_iterator被構造的時候發生。默認的istream_iterator對象被稱爲尾後迭代器,當一個合法的迭代器到達容器的尾部的時候,它就會和尾後迭代器相等。

#include <iostream>
#include <sstream>
#include <iterator>
#include <string>
#include <vector>

using namespace std;

int main(){
    string str("1 2 3 4 5");
    istringstream sstrm(str);

    istream_iterator<int> iter(sstrm);//從istringstream讀取int
    istream_iterator<int> eof;//尾後迭代器
    while(iter != eof){//當有數據可讀時
        cout << *iter << " ";
        iter++;
    }
    cout << endl;

return 0;
}

結果:

1 2 3 4 5 


ostream_iterator操做

一個ostream_iterator使用<<來向流中寫入數據。所以,一個ostream_iterator讀寫的對象類型必需定義了輸出運算符。

當建立一個ostream_iterator時,構造器提供了第二個(可選的)參數,它是一個字符串,在輸出每一個元素後都會打印此字符串。此字符串必須是C風格字符串(即,一個字符串字面常量或者一個指向以空字符結尾的字符數組的指針)。必需將ostream_iterator綁定到一個指定的流,不容許空的或表示尾後位置的ostream_iterator。

#include <iostream>
#include <sstream>
#include <iterator>

using namespace std;

int main()
{
    ostringstream str;
    //使用ostringstream構建一個ostream_iterator迭代器
    //在每次寫入字符後都會附加一個空格
    ostream_iterator<string> iter(str," ");

    //寫入字符
    iter = "how";
    iter = "are";
    iter = "you";

    cout << str.str() << endl;
}

結果:

how are you


因爲istream_iterator使用>>讀取流數據,ostream_iterator使用<<操做符向流寫入數據,因此咱們只須要爲咱們的定義上合適的<<和>>操做符,就可使用istream_iterator和ostream_iterator流迭代器了。

person.h文件

#pragma once
#include <string>
#include <iostream>
class person{
    //聲明友好方法
    friend std::istream& operator>>(std::istream&,person&);
    friend std::ostream& operator<<(std::ostream&,const person&);

    private:
        std::string name;
    public:
        person(){}

        std::string getName() const{
            return this->name;
        }
};
//istream_iterator會調用>>向對象寫數據
std::istream& operator>>(std::istream& is,person& p){
    is >> p.name;
    return is;
};
//ostream_iterator會調用<<向流中寫入數據,因爲只讀person的值,因此聲明爲const person&
std::ostream& operator<<(std::ostream& os,const person& p){
    os << "person name : " << p.name;
    return os;
};

 

personTest.cpp文件

#include "person.h"/*person*/
#include <iterator>/*istream_iterator,ostream_iterator*/
#include <sstream>/*istringstream*/
#include <vector>/*vector*/
#include <algorithm>/*for_each*/
#include <iostream>/*cout,endl*/

using namespace std;

int main(int argc,char* argv[]){
    istringstream issm("green blue");

    //istream_iterator會調用person的>>從流中讀數據
    istream_iterator<person> ist(issm);
    istream_iterator<person> eof;

    vector<person> vec;
    while(ist != eof){
        vec.push_back(*ist);
        ist++;
    }

    //ostream_iterator會調用person的<<向流中寫數據
    ostream_iterator<person> os(cout,"\n");

    for_each(vec.begin(),vec.end(),[&](const person& p){
        os = p;
    });

    cout << endl;
return 0;
}

結果:

person name : green
preson name : blue

 

3.4 反向迭代器

反向迭代器就是在容器中從尾元素向首元素反向移動迭代器。對於反向迭代器,遞增(以及遞減)操做的含義會顛倒過來。遞增反向迭代器(++it)會移動到前一個元素;遞減反向迭代器(--it)會移動到下一個元素。

除了forward_list以外,其它容器都支持反向迭代器。咱們能夠經過調用rbegin,rend,crbegin和crend成員函數來得到反向迭代器。這些成員函數返回指向容器尾元素和首元素以前一個位置的迭代器。與普通迭代器同樣,反向迭代器也有const和非const的版本。

反向迭代器(reverse_iterator)在頭文件<iterator>中,reverse_iterator實際是一個迭代器適配器,它會反轉給它的容器。換句話說,當反轉一個雙向迭代器時,reverse_iterator會產生一個新的迭代器,該迭代器從序列的尾部向頭部移動。

對於一個由迭代器i構成的反轉迭代器r來講,這樣的關係&*r = &*(i - 1)老是成立的



經過這個圖片,咱們能夠看出,反向迭代器並無真正地反轉序列中的元素,它只是從序列尾部向頭部遍歷而已。而且反向迭代器和源迭代器之間只錯位了1個元素。

base函數會返回該反向迭代器對應的源迭代器,好比上面圖片中

反向迭代器的rend對應源迭代器的begin
反向迭代器的1對應源迭代器中2
反向迭代器的2對應源迭代器中3
反向迭代器的3對應源迭代器中4
......
反向迭代器的14對應源迭代器中15
反向迭代器的15對應源迭代器中16
反向迭代器的rbegin對應源迭代器中end

 

有了base函數,咱們就能夠輕鬆在反向迭代器和源迭代器之間切換,下面實現了字符串從尾部查找特定字符,而且截取。

#include <iterator>/*reverse_iterator*/
#include <string>/*string*/
#include <algorithm>/*find*/
#include <iostream>/*cout,endl*/

using namespace std;

int main(){
    string line("first,middle,last");
    reverse_iterator<string::iterator> rcomma = find(line.rbegin(),line.rend(),',');
    //輸出 tsal
    cout << string(line.rbegin(),rcomma) << endl;

    //輸出 last
    cout << string(rcomma.base(),line.rbegin().base()) << endl;
    return 0;
}

第一個輸出語句輸出了tsal,這顯然不是咱們想要的,爲了獲得正確的答案須要獲得它的源迭代器,而後再進行構造新的字符串。

相關文章
相關標籤/搜索