1. 程式人生 > 其它 >迭代器(一)插入迭代器和流迭代器

迭代器(一)插入迭代器和流迭代器

技術標籤:# C++語法

一、什麼是迭代器?

迭代器設計思維是STL的關鍵所在。迭代器(iterators)是一種抽象的設計概念,現實程式語言中沒有直接對應這個概念的實物,在《Design Patterns》對迭代器的定義如下:提供一種方法,使之能夠依序巡防某個聚合物(容器)所含的各個元素,而又無需暴露該聚合物的內部表達形式。STL的中心思想在於,將資料容器(containers)和演算法(algorithms)分開,彼此獨立設計,最後再以貼膠著劑將他們撮合起來[1]。本文討論的重點不在於迭代器的實現,而在於如何使用,如何利用迭代器加速、巧妙的完成專案設計任務。

二、迭代器種類

簡單來說迭代器總是與容器進行繫結,以便靈活的操作容器。除了為每個容器定義的迭代器外,標準庫在標頭檔案iterator

中還定義了額外幾種迭代器:

  • 插入迭代器(insert iterator),向指定容器插入元素;
  • 流迭代器(stream iterator),遍歷相關的IO流;
  • 反向迭代器(reverse iterator),與普通迭代器前進方向相反(除了forward_list,其他容器都有反向迭代器);
  • 移動迭代器(move iterator),專門用來移動其中的元素的。

2.1 插入迭代器

插入迭代器與普通迭代器的不同在於其作用在於向容器新增元素,而不在於訪問、取值。因此,我們可以對插入迭代器做以下操作++ -- *,但是其效果都是返回同一個插入迭代器。對迭代器的賦值等效於往特定位置插入指定的值,插入迭代器按照插入的位置可以分為:

  • back_inserter 使用push_back將元素追加到迭代器所指位置之後;
  • front_inserter 使用push_front將元素追加到迭代器所致位置之前;
  • inserter 使用insert將元素插入到指定迭代器之前。

我們可以通過關鍵字std::back_inserter、std::front_inserter 和std::inserter ,來獲得一個序列的指定插入迭代器內容。格式是std::back_inserter(ivec),值得注意的是不是所有的容器都支援所有的插入迭代器,如我們的vector就不支援front_inserter,只有當對應插入迭代器的方法在此迭代器是有用的才可以使用。

list<int> ilst{ 1,2,3 };
back_inserter(ilst)=4;//1 2 3 4
front_inserter(ilst) = 0;//0 1 2 3 4
vector<double> dvec{2,3,4};
back_inserter(dvec)=5;//2 3 4 5
front_inserter(dvec)=1;//error,vector不支援push_front
inserter(dvec,dvec.begin())=1;//1 2 3 4 5
inserter(dvec,dvec.end())=6;//1 2 3 4 5 6

可以看出雖然vector不能使用push_front,即不能使用std::front_inserter,沒所謂,因為該序列所有位置都可以插入,不過沒有front_inserter直觀和方便(還要指定一個引數)。插入迭代器+STL演算法可以完成很多有意思的事情,std::copy是插入而不是覆蓋

list<double> dlst{ 2,3,4,5,6 };
list<double> dlst2{ 8,88,888 };
copy(dlst2.begin(), dlst2.end(), dlst.begin());//8 88 888 5 6,覆蓋了原來的 2 3 4 
copy(dlst2.begin(), dlst2.end(), front_inserter(dlst));//8 88 888 2 3 4 5 6,
//														插入,因為每次都是在同一個位置2的前面插入,所以是8 88 888順序

2.2 流迭代器

雖然流不是一個容器,為了支援泛型演算法,標準庫支援兩種流物件的讀寫,分別是:

  • 輸入流迭代器 (istream_iterator)
  • 輸出流迭代器(ostream_iterator)
2.2.1 輸入流迭代器

和插入迭代器不同,插入迭代器直接呼叫函式獲取迭代器,而不需要給定型別,流迭代器的定義是一個類模板,需要指定迭代器型別,如istream_iterator<int>內容為int的輸入流迭代器,建構函式是IO流物件。除了上述不同外,流迭代器支援的操作更為豐富:

建構函式或操作含義
istream_iterator<T> in(is)is流構造流迭代器
istream_iterator<T> end尾後流迭代器
in1==in2 in1!=in2讀取型別相同,如果都是尾後迭代器或相同輸入流則相等;反之不等
*in返回流對應的值
in->*(in).mem
++in in++讀取元素的型別定義的>>運算子從輸入流讀取下一個值

看看實際的例子:

istream_iterator<int> int_iter(cin);//int型且與cin流繫結
istream_iterator<int> eof;//預設構造為尾後迭代器
while(in_iter!=eof)
{
	vec.push_back(*in_iter++);
}

說到這裡你可能覺得流迭代器似乎沒有什麼優勢,看完下面等效表達例子,或許能改變你的想法:

istream_iterator<int> in_iter(cin),eof;
vector<int> vec(in_iter,eof);//

非常簡潔!其實流迭代器還有一個重要的目的:實現對演算法的支援。來看看這個例子:

istream_iterator<int> in(cin),eof;
cout<<accumulate(in,eof,0)<<endl;

你甚至不需要額外定義一個vector儲存流的內容就完成了計算。此外有一點需要注意,流迭代器允許惰性求值,也就是真正使用的時候才真正讀取。

2.2.1 輸出流迭代器
建構函式或操作含義
ostream_iterator<T> out(os);out將型別為T的值寫到輸出流os中
ostream_iterator<T> out(os,d);out將型別為T的值寫到輸出流os中,每個值加上額外的字元陣列c
out=val用<<符號將val寫到out繫結的ostream中,val型別必須和out可寫型別相容
*out ++out out++存在但是沒有任何效果

我們可以使用輸出流迭代器輸出序列:

vector<int> ivec{ 1,2,3,4,5,6,7,8,9 };
ostream_iterator<int> out_iter(cout," ");
for(auto e:vec)
	*out_iter++=e;//*和++可以忽略(out_iter=e),但是建議帶上,程式更加清晰
cout<<endl;

看看效果:
在這裡插入圖片描述
如果你喜歡迴圈,你可以使用copy演算法讓程式更加簡潔:

vector<int> ivec{ 1,2,3,4,5,6,7,8,9 };
ostream_iterator<int> out_iter(cout," ");
copy(vec.begin(),vec.end(),out_iter);//其實一點都簡潔,好在沒有* ++而且是單行,字元反正也多
cout<<endl;

[1] 侯捷. STL原始碼剖析[M]. 華中科技大學出版社, 2002.