再探迭代器(插入迭代器、流迭代器、反向迭代器、移動迭代器)

除了爲每一個容器定義的迭代器以外,標準庫在頭文件iterator中還定義了額外幾種迭代器。這些迭代器包括如下幾種。ios

  • 插入迭代器:這些迭代器被綁定到一個容器上,可用來向容器插入元素
  • 流迭代器:這些迭代器被綁定到輸入或輸出上,可用來遍歷全部關聯的IO流
  • 反向迭代器:這些迭代器向後而不是向前移動。除了forward_list以外的標準庫容器都有反向迭代器
  • 移動迭代器:這些專用的迭代器不是拷貝其中的元素,而是移動它們。

 

1 插入迭代器

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

插入迭代器操做

 it=t            在it指定的當前位置插入值t。假定c是it綁定的容器,依賴於插入迭代器的不一樣種類,此賦值分別調用c.push_back(t)、c.push_front(t)或c.insert(t,p),其中p爲傳遞給inserter的迭            代器位置數組

*it,++it,it++        這些操做雖然存在,但不會對it作任何事情。每一個操做都返回it函數

插入迭代器有三種類型,差別在於元素插入的位置:spa

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

注意:只有在容器支持push_front的狀況下,咱們纔可使用front_inserter。相似的,只有在容器支持push_back的狀況下,咱們才能使用back_inserter指針

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

*it=val;對象

其效果與下面代碼同樣blog

it=c.insert(it,val);//it指向新加入的元素ci

++it; //遞增it使它指向原來的元素

front_inserter生成的迭代器的行爲與inserter生成的迭代器徹底不同。當咱們使用front_inserter時,元素老是插入到容器第一個元素以前,即便咱們傳遞給inserter的位置原來指向第一個元素,只要咱們在此元素以前插入一個新元素,此元素就再也不是容器的首元素了:

list<int> lst={1,2,3,4};

list<int> lst2,lst3;  //空list

//拷貝完成以後,lst2包含4 3 2 1

copy(lst.begin(),lst.end(),front_inserter(lst2));

//拷貝完成以後lst3包含1 2 3 4 

copy(lst.begin(),lst.end(),inserter(lst3,lst.begin()));

當調用front_inserter(c)時,咱們獲得一個插入迭代器,接下來會調用push_front.當每一個元素被插入到容器c中時,它變爲c的新的首元素。所以,front_inserter生成的迭代器會將插入的元素序列的順序顛倒過來,而inserter和back_inserter則不會。

 

2 iostream迭代器

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

 

istream_iterator操做

當建立一個流迭代器時,必須指定迭代器將要讀寫的對象類型。一個istream_iterator使用>>來讀取流。所以,istream_iterator要讀取的類型必須定義了輸入運算符。當建立一個istream_iterator時,咱們能夠將它綁定到一個流。固然,咱們還能夠默認初始化迭代器,這樣就建立了一個能夠看成尾後值使用的迭代器。

istream_iterator<int> int_it(cin); //從cin讀取int

istream_iterator<int> int_eof; //尾後迭代器

ifstream in("afile"); 

istream_iterator<string> str_in(in); //從「afile讀取字符串

下面是一個用istream_iterator從標準輸入流讀取數據,存入一個vector的例子:

istream_iterator<int> in_iter(cin); //從cin讀取int

istream_iterator<int> eof;  //istream尾後迭代器

while(in_iter!=eof)

  //後置遞增運算讀取流,返回迭代器的舊值

  //解引用迭代器,得到從流讀取的前一個值

  vec.push_back(*in_iter++);

此循環從cin讀取int值,保存在vec中。在每一個循環步中,循環體代碼檢查in_iter是否等於eof。eof被定義爲空istream_iterator,從而能夠看成尾後迭代器來使用。對於一個綁定到流的迭代器,一旦其關聯的流遇到文件尾或遇到IO錯誤,迭代器的值就與尾後迭代器相等。

咱們能夠將程序重寫爲以下形式,這體現了istream_iterator更有用的地方:

istream_iterator<int> in_iter(cin),eof; //從cin讀取int

vector<int> vec(in_iter,eof);  //從迭代器範圍構造vec

本例中咱們使用了一對錶示範圍的迭代器來構造vec,這兩個迭代器是istream_iterator,這意味着元素範圍是經過從關聯的流中讀取數據得到的。這個構造函數從cin讀取數據,直至遇到文件尾或者遇到一個不是int的數據爲止。從流中讀取的數據被用來構造vec。

istream_iterator操做

istream_iterator<T> in(is);     in從輸入流is讀取類型爲T的值

istream_iterator<T> end;     讀取類型爲T的值的istream_iterator迭代器,表所尾後位置

in1==in2             in1和in2必須讀取相同類型。若是它們都是尾後迭代器,或綁定到相同的輸入,則兩個相等

in1!=in2

*in               返回從流中讀取數據

in->mem             與(*in).mem的含義相同

++in,in++            使用元素類型所定義的>>運算符從輸入流中讀取下一個值。與以往同樣,前置版本返回一個指向遞增後迭代器的引用,後置版本返回舊值

 

使用算法操做流迭代器

因爲算法使用迭代器操做來處理數據,而流迭代器又至少支持某種迭代器操做,所以咱們至少能夠用某些算法來操做流迭代器。下面是一個例子,咱們能夠用一對istream_iterator來調用accumulate:

istream_iterator<int> in(cin),eof;

cout<<accumulatre(in,eof,0)<<endl;

此調用會計算出從標準輸入讀取的值的和。若是輸入爲:

1 3 7 9 9 

輸出爲29

 

istream_iterator容許使用懶惰求值

當咱們將一個istream_iterator綁定到一個流時,標準庫並不保證迭代器當即從流讀取數據。具體實現能夠推遲從中讀取數據,直到咱們使用迭代器時才真正讀取。標準庫中的實現所保證的是,在咱們第一次解引用迭代器以前,從流中讀取數據的操做已經完成了。對於大多數程序來講,當即讀取仍是推遲讀取並無什麼差異。可是,若是咱們建立了一個istream_iterator,沒有使用就銷燬了,或者咱們正在從兩個不一樣的對象同步讀同一個流,那麼什麼時候讀取可能就很重要了。

 

ostream_iterator操做

咱們能夠對任何輸出運算符(<<運算符)的類型定義ostream_iterator。當建立一個ostream_iterator時,咱們能夠提供(可選的)第二參數,它是一個字符串,在輸出每一個元素後都會打印此字符串。此字符串必須是一個C風格字符串(即,一個字符串字面值或者一個指向以空字符結尾的字符數組的指針)。必須將ostream_iterator綁定到一個指定的流。不容許空的或表示尾後位置的ostream_iterator。

ostream_iterator操做

ostream_iterator<T> out(os);      out將類型爲T的值寫到輸出流os中

ostream_iterator<T> out(os,d);      out將類型爲T的值寫到輸出流os中,每一個值後面都輸出一個d。d指向一個空字符串結尾的字符數組

out=val                用<<運算符將val寫入到out所綁定的ostream中。val的類型必須與out可寫的類型兼容

*out,++out,out++              這些運算符是存在的,但不對out作任何事情。每一個運算符都返回out

咱們可使用ostream_iterator來輸出值的序列:

ostream_iterator<int> out_iter(cout," ");

for(auto e:vec)

  *out_iter++=e;   //賦值語句實際上將元素寫到cout

cout<<endl;

此程序將vec中的每一個元素寫到cout,每一個元素加一個空格,每次向out_iter賦值時,寫操做就會被提交。

值得注意的是,當咱們向out_iter賦值時,能夠忽略解引用和遞增運算。即,循環能夠重寫成下面的樣子:

for(auto e:vec)

  out_iter=e;//賦值語句將元素寫道cout

cout<<end;

運算符*和++實際上對ostream_iterator對象不作任何事情,所以忽略它們對咱們的程序沒有任何影響。可是,推薦第一種形式。在這種寫法中,流迭代器的使用與其餘迭代器的使用保存一致。若是想將此循環改成操做其餘迭代器類型,修改起來很是容易。並且,對於讀者來講,此循環的行爲也更爲清晰。

能夠經過調用copy來打印vec中的元素,這比編寫循環更爲簡單:

copy(vec.begin(),vec.end(),out_iter);

cout<<endl;

 

使用流迭代器處理類類型

咱們能夠爲任何定義了輸入運算符(>>)的類型建立istream_iterator對象。相似的,只要類型有輸出運算符(<<),咱們就能夠爲其定義ostream_iterator。因爲Sales_item既有輸入運算符也有輸出運算符,所以可使用IO迭代器。例如:

    istream_iterator<Sales_item> item_iter(cin),eof;
    ostream_iterator<Sales_item> out_iter(cout,"\n");
    Sales_item sum=*item_iter++;
    while(item_iter!=eof)
    {
        if(item_iter->isbn()==sum.isbn())
            sum+=*item_iter++;
        else
        {
            out_iter=sum;
            sum=*item_iter++;
        }
    }
    out_iter=sum;

此程序使用item_iter從cin讀取Sales_item交易記錄,並將和寫入cout,每一個結果後面都跟一個換行符。定義了本身的迭代器後,咱們就能夠用item_iter讀取第一條交易記錄,用它的值來初始化sum.

 

3 反向迭代器

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

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

下面的循環是一個使用反向迭代器的例子,它按逆序打印vec中的元素:

vector<int> vec={0,1,2,3,4,5,6,7,8,9};

//從尾元素到首元素的反向迭代器

for(auto r_iter=vec.crbegin;r_iter!=vec.crend();++r_iter)

  cout<<*r_iter<<endl;  //打印9,8,7,6,5,4,3,2,1,0

雖然顛倒遞增和遞減運算符的含義可能使人混淆,但這樣作是咱們能夠用算法透明地向前或向後處理容器。例如,能夠經過向sort傳遞一對反向迭代器來將vector整理爲遞減序:

sort(vec.begin(),vec.end()); 

sort(vec.rbegin(),vec.rend());

 

反向迭代器須要遞減運算符

咱們只能從既支持++也支持--的迭代器來定義反向迭代器。畢竟反向迭代器的目的是在序列中反向移動。出了forward_list以外,標準容器上的其餘迭代器都既支持遞增運算又支持遞減運算。可是,流迭代器不支持遞減運算,由於不可能在一個流中反向移動。所以,不可能從一個forward_list或一個流迭代器建立反向迭代器。

 

反向迭代器與其餘迭代器間的關係

假定有一個名爲line的string,保存着一個逗號分隔的單詞列表,咱們但願打印line中的第一個單詞,使用find能夠很容易地完成這一任務:

//在一個逗號分隔的列表中查找一個元素

auto comma=find(line.cbegin(),line.cend(),',');

cout<<string(line.cbegin(),comma)<<endl;

若是line中有逗號,那麼comma將指向這個逗號;不然,它將等於line.cend().當咱們打印從line.cbegin()到comma之間的內容時,將打印到逗號爲止的序列,或者打印整個string(若是其中不含逗號的話)。

若是但願打印最後一個單詞,能夠改用反向迭代器:

//在一個逗號分隔的列表中查找最後一個元素

auto rcomma=find(line.crbegin(),line.crend(),',');

因爲咱們將crbegin和crend傳遞給find,find將從line的最後一個字符開始向前搜索。當find完成後,若是line中有逗號,則rcomma指向最後一個逗號——即,它指向反向搜索中找到的第一個逗號。若是line中沒有逗號,則rcomma指向line.crend()

但咱們試圖打印找到的單詞時,看起來下面的代碼是顯然的方法

//錯誤:將逆序輸出單詞的字符

cout<<string(line.crbegin(),rcomma)<<endl;

但它會生成錯誤的輸出結果。例如,若是咱們的輸入是

FIRST,MIDOLE,LAST

則這條語句會打印TSAL!

問題所在:咱們使用的是反向迭代器,會反向出來string。所以,上述輸出語句從crbegin開始反向打印line中內容。而咱們但願按正常順序打印從rcomma開始到line末尾間的字符。可是,咱們不能直接使用rcomma。由於它是一個反向迭代器,意味着它會反向朝着string的開始位置移動。須要作的是,將rcomma轉換回一個普通迭代器,能在line中正向移動。咱們經過調用reverse_iterator的base成員函數來完成這一轉換,此成員函數會返回其對應的普通迭代器

//正確:獲得一個正向迭代器,從逗號開始讀取字符直到line末尾

cout<<string(rcomma.base(),line.cend())<<endl;

rcomma和rcomma.base()指向了不一樣的元素,line.crbegin()和line.cend()也是如此。這些不一樣保證了元素範圍不管是正向處理仍是反向出來都是相同的。

從技術上講,普通迭代器與反向迭代器的關係反映了左閉合區間的特徵。關鍵點在於[line.crbegin(),rcomma)和[rcomma.base(),line.cend())指向line中相同的元素範圍。

相關文章
相關標籤/搜索