今天抽空來看看c++標準庫中迭代器的相關知識。我們知道,stl標準庫一共有六大部件:分配器、容器、迭代器、演算法、仿函式、介面卡。其中,迭代器就是用來「聯結」演算法、仿函式與容器的紐帶。除此之外,在設計模式中有一種模式叫迭代器模式,簡單來說就是提供一種方法,在不需要暴露某個容器的內部表現形式情況下,使之能依次訪問該容器中的各個元素,這種設計思維在stl中得到了廣泛的應用,是stl的關鍵所在,通過迭代器,容器和演算法可以有機的粘合在一起,只要對演算法給予不同的迭代器,就可以對不同容器進行相同的操作。(參考:
比如下面這個find函式,展示了容器、演算法和迭代器如何合作:
template
<
typename inputiterator,
typename t>
inputiterator find
(inputiterator first, inputiterator last,
const t &value)
上述的find函式,只需要傳遞容器的迭代器,就可以實現對不同的容器實現相同的演算法,這其實是一種泛型程式設計的思想。
我們來看看在vector中對於iterator的實現:
template
<
typename t,
class
alloc
= alloc >
class
vector
;
在此可以看到iterator在vector中也只是簡單的被定義成了我們傳入的型別引數t的指標。
#ifndef cpp_primer_my_list_h
#define cpp_primer_my_list_h
#include
template
<
typename t>
class
node
node
(t val, node *p =
nullptr):
value
(val)
,next
(p)}
;template
<
typename t>
class
my_list
//過載++、--、*、->等基本操作
//返回引用,方便通過*it來修改物件
t &operator*(
)const
node
*operator
->()
const
list_iterator &
operator++(
) list_iterator operator++(
int)
bool
operator==(
const list_iterator &t)
const
bool
operator!=(
const list_iterator &t)
const};
public
:typedef list_iterator iterator;
//型別別名
my_list()
//從鍊錶尾部插入元素
void
push_back
(const t &value)
else
size++;}
//列印鍊錶元素
void
print
(std::ostream &os = std::cout)
const
public
://操作迭代器的方法
//返回鍊錶頭部指標
iterator begin()
const
//返回鍊錶尾部指標
iterator end()
const
//其它成員函式 insert/erase/emplace};
#endif
//cpp_primer_my_list_h
對於其他的容器迭代器分析,暫略。
在stl中,除了原生指標以外,迭代器被分為五類:
input iterator
顧名思義,input——此迭代器不允許修改所指的物件,即是唯讀的。支援==、!=、++、*、->等操作。
output iterator
允許演算法在這種迭代器所形成的區間上進行只寫操作。支援++、*等操作。
forward iterator
允許演算法在這種迭代器所形成的區間上進行讀寫操作,但只能單向移動,每次只能移動一步。支援input iterator和output iterator的所有操作。
bidirectional iterator
允許演算法在這種迭代器所形成的區間上進行讀寫操作,可雙向移動,每次只能移動一步。支援forward iterator的所有操作,並另外支援–操作。
random access iterator
包含指標的所有操作,可進行隨機訪問(vector容器支援),隨意移動指定的步數。支援前面四種iterator的所有操作,並另外支援it + n、it - n、it += n、 it -= n、it1 - it2和it[n]等操作。
上述五種迭代器的分類和聯絡可參考下圖:
了解了迭代器的型別,我們就能解釋vector的迭代器和list迭代器的區別了。顯然vector的迭代器具有所有指標算術運算能力,而list由於是雙向鍊錶,因此只有雙向讀寫但不能隨機訪問元素。**故vector的迭代器種類為random access iterator,list 的迭代器種類為bidirectional iterator。**我們只需要根據不同的迭代器種類,利用traits程式設計技巧萃取出迭代器種類,然後由c++的過載機制就能夠對不同型別的迭代器採用不同的處理流程了。為此,對於每個迭代器都必須定義型別iterator_category,也就是原始碼中的typedef std::forward_iterator_tag iterator_category;實際中可以直接繼承stl中定義的iterator模板,模板後三個引數都有預設值,因此繼承時只需要指定前兩個模板引數即可。如下所示,stl定義了五個空型別作為迭代器的標籤:
template
<
class
category
,classt,
class
distance
= ptrdiff_t,
class
pointer
=t*,
class
reference
=t&>
class
iterator
;struct input_iterator_tag
;struct output_iterator_tag
;struct forward_iterator_tag:
public input_iterator_tag
;struct bidirectional_iterator_tag:
public forward_iterator_tag
;struct random_access_iterator_tag:
public bidirectional_iterator_tag
;
當使用乙個容器的insert或者erase函式通過迭代器插入、刪除或者修改元素(如map、set,因為其底層是紅黑樹)"可能"會導致迭代器失效,因此為了避免危險,應該重新獲取的新的有效的迭代器進行正確的操作。plus
vector
1、插入操作(insert)和接合操作(splice)不會造成原有的list迭代器失效,這在vector中是不成立的,因為vector的插入操作可能造成記憶體重新配置,導致所有的迭代器全部失效。
2、list的刪除操作(erase)也只有指向被刪除元素的那個迭代器失效,其他迭代器不受影響。(list目前只發現這一種失效的情況)
關聯容器
對於關聯容器(如map, set,multimap,multiset),刪除當前的iterator,僅僅會使當前的iterator失效,只要在erase時,遞增當前iterator即可。這是因為map之類的容器,使用了紅黑樹來實現,插入、刪除乙個結點不會對其他結點造成影響(雖然刪除了乙個元素,整棵樹也會調整,以符合紅黑樹或者二叉樹的規範,但是單個節點在記憶體中的位址沒有變化,變化的是各節點之間的指向關係)。erase迭代器只是被刪元素的迭代器失效,但是返回值為void,所以要採用**erase(iter++)(分三步走,先把iter傳值到erase裡面,然後iter自增,然後執行erase,所以iter在失效前已經自增了)**的方式刪除迭代器。
C STL 迭代器失效
2 刪除 當進行刪除操作 erase,pop back 後,指向刪除點的迭代器全部失效 指向刪除點後面的元素的迭代器也將全部失效。刪除點之前的迭代器仍有效。二 deque迭代器的失效情況 1 插入 1 在deque容器首部或者尾部插入元素不會使得任何迭代器失效。但是指向存在的元素的引用和指標不會失效...
C STL迭代器失效
迭代器失效就是說,對容器進行了一些操作後,先前的迭代器無法進行解引用操作去訪問容器的元素。迭代器失效可能會造成程式崩潰,如下圖 如果插入元素導致vector達到最大容量,那麼會重新分配記憶體並將老的元素拷貝到新的記憶體中。元素的位址都改變了,顯然迭代器和引用都將失效 如果插入元素沒有引起重新分配記憶...
C STL 迭代器失效問題
之前看 c primier 的時候,也解到在順序型視窗裡insert erase會涉及到迭代器失效的問題,並沒有深究。今天寫程式的時候遇到了這個問題。最初我的程式是醬紫的,別說話,我知道這樣是有問題的,可這樣是最直觀的想法 int arr vector int a arr,arr sizeof ar...