在for迴圈中為STL容器插入與刪除元素的注意事項

2021-10-03 16:50:59 字數 4288 閱讀 2536

std::vector<

int>a(

5,2)

;for

(auto b : a)

// a 中的元素不變

for(

auto

&b : a)

// a 中的元素變為

for(

const

auto

&b : a)

三種用途有顯著的差別,

第一種b

bb複製了a

aa中元素的值,因此修改b

bb的值無法改變a

aa中元素的值;

第二種b

bb是作為a

aa中元素的別名,因此修改b

bb的值等價於修改a

aa中元素的值;

第三種b

bb也是作為a

aa中元素的別名,但加上了con

st

const

cons

t修飾符保證了b

bb的值不會被修改,因此在非必要的情況下,推薦使用第三種用法。

那就是應當避免在迴圈體中對a

aa進行插入、刪除等操作,因為對容器本身的操作會改變迭代器,並且編譯器不會給出提醒報錯,使得這樣的錯誤難以被發現。雖然都明白這一點,但實際使用的時候還是很容易在一堆for迴圈中逐漸迷失自己。。。

這裡具體討論一下可能會出現的問題和解決的辦法,情況如下:

一、刪除操作

有部分文章說明,在這情況下執行會出錯,如下**仔細除錯大概就能理解了。

std::vector<

int> a =

;for

(auto b : a)

// 程式報錯

這是一段會執行出錯的**,執行除錯可以發現,程式執行過程如下:a1

2345

6step123

4566

step224

5666

step324

6666

step424

6666

step524

黃色標記了a

aa中被刪除的部分,以及對應的物理位置上的資料,可以發現:

前三次迴圈依次刪除了1,3

,5

1,3,5

1,3,

5三個元素,因此可以推測出,該用法for

forfo

r迴圈實際上記錄了當前迭代元素的物理位置,當前的第0,1

,2

0, 1, 2

0,1,

2位置;

第四次迴圈時讀取了上一步被刪除的位置3

33的元素6

66,因此又刪除了a

aa中元素666;

第五次迴圈時讀取了已被刪除的位置4

44的元素6

66,std

::fi

nd

std::find

std::f

ind返回了陣列的end

()

end()

end(

),但陣列的end

()

end()

end(

)是不能被刪除的,所以程式報錯。

這種問題並不是c++11帶來的新特性,如下一段**,在a

aa中有偶數個元素時並不會報錯,但奇數個就會報錯。

std::vector<

int> a =

;for

(auto it = a.

begin()

; it != a.

end(

); it++

)// 最後 a 中的元素為 ,但如果輸入奇數個元素則會報錯

實際上還有很多情況會出現隱藏的 bug,如下的這種**始終維持了a

aa的容器大小不變(刪除元素也不會真正釋放記憶體空間),這種情況完全可以正常的編譯執行而不被發現。

std::vector<

int> a =

;for

(auto b : a)

// 最後 a 中的元素為

解決方法!

如果我想要在迭代的同時刪除當前的元素,需要一次性完成,除去拷貝另乙份資料之外,最好的辦法就是使用自帶的返回值.

如下一段**就是刪除所有奇數的示例

std::vector<

int> a =

;for

(auto it = a.

begin()

; it != a.

end();

)else

}// 最後 a 中的元素為

二、插入操作

插入操作與刪除操作有顯著的不同,如果進行了這種操作,一般情況下,無論是編譯還是執行 bug 都很難被發現。

std::vector<

int> a =

;for

(auto b : a)

// 最後 a 中的元素為 ;

上述**很奇怪地只迴圈了六次,而如果使用傳統的迭代器寫法則會變為死迴圈。實際上前者只在迴圈開始的時候計算了一次陣列的末尾位置(曾經就被這點狠狠地坑過!!!!),而後者每次迴圈都會重複計算末尾位置。因此如果想要容器內每個元素都被處理且包括新加入的元素,還是要老老實實地用迭代器。

std::vector<

int> a =

;for

(auto it = a.

begin()

; it != a.

end(

); it++

)// 死迴圈

但是

使用迭代器在陣列中插入元素是高危行為,陣列每次插入元素後需重新計算迭代器!

(除非已經事先佔據了空間)

std::vector<

int> a =

;for

(auto it = a.

begin()

; it != a.

end(

); it++

)// 執行報錯

上述**會報錯,而且除錯過程顯示,有時會在陣列的開頭加入了一系列奇怪的0

00或其他數字,原因是 insert() 方法會通過將陣列拷貝到新的位置來實現,使用原先迭代器指向的位置此時已毫無意義。push_back() 存在類似的問題,那就是如果儲存空間不夠了,stl是完全有可能將陣列拷貝到另外乙個更大的空間的,這種情況下,原先的迭代器將毫無意義!!!

std::vector<

int> a =

;for

(auto it =

0; it != a.

size()

; it++

)}

如果將陣列換成集合容器,情況又有所不同,集合的插入操作並不會使得迭代器失效,而刪除操作如果刪去了迭代器指向的元素,迭代器則會失效。

一、刪除操作

如下神奇的**,它不會報錯,執行也不會出錯!!!這和集合的紅黑樹儲存結構有關,有的情況下恰好不會報錯,但如果a

=a=\

a=,則會報錯

std::set<

int> a =

;for

(auto b : a)

// 最後 a 中的元素為

std::set<

int> a =

;for

(auto b : a)

// 執行報錯

和之前的陣列一樣,下面的**可以正確的執行

std::set<

int> a =

;for

(auto it = a.

begin()

; it != a.

end();

)else

}// 最後 a 中的元素為

二、插入操作

下面的這部分**恰恰是死迴圈,應該與紅黑樹的插入操作有關,原理暫沒搞明白,但是集合插入刪除並不會改變集合最大元素的位置,因此這裡的範圍迭代與使用迭代器的迭代並不會出現較大差異。

std::set<

int> a =

;for

(auto b : a)

// 死迴圈

如果插入比最大值小的元素的情況則不會出現死迴圈

std::set<

int> a =

;for

(auto b : a)

// 最後 a 中的元素為 ;

STL 在迴圈中使用erase報錯的情況

之前想寫個程式,利用iterator遍歷list,然後用if語句刪除特定值 像下面這樣 for list int iterator iter lst.begin iter lst.end iter cout iter 但是不是正確結果,甚至有時候還會崩潰。對於節點式容器 map,list,set 元...

在for迴圈中break與continue的區別

break的作用是跳出當前迴圈塊 for while do while 或程式塊 switch continue用於結束迴圈體中其後語句的執行,並跳回迴圈程式塊的開頭執行下一次迴圈 舉例子說明吧 public void exampleone system.out.println 我還要搞事情 i s...

Python else在for迴圈中的運用

一直以來,感覺else語句在for迴圈中沒有什麼用,但是在編寫 木桶排序 演算法時發現,else還是蠻有用的,特此記錄一下。number 11 while true try input list map int,input 請輸入乙個整數序列 split break except 錯誤的處理 pri...