網站首頁 編程語言 正文
一、迭代器失效
主要作用就是讓算法能夠不用關心底層數據結構,其底層實際就是一個指針,或者是對指針進行了封裝。比如:vector的迭代器就是原生態指針T*。因此迭代器失效,實際就是迭代器底層對應指針所指向的空間被銷毀了,而使用一塊已經被釋放的空間,造成的后果是程序崩潰(即如果繼續使用已經失效的迭代器,程序可能會崩潰)。
二、可能引起的迭代器失效的操作
2.1、野指針引起迭代器失效
凡是涉及到擴容操作,都有可能引起迭代器失效,因為vector擴容是分配一個新的數組,然后全部元素移到新的數組中。
下面我們就以Insert函數來舉例說明!
示例1:
void test02()
{
// 在所有的偶數的前面插入2
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
cout << v.size() << ":" << v.capacity() << endl;
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.insert(it, 20);
++it; //這里++是為了解決第二種迭代器失效,防止原地踏步
}
++it;
}
cout << v.size() << ":" << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
程序崩潰!
代碼解釋:如果我們沒有預先分配空間,那么在insert的時候會發生擴容,根據我們模擬實現vector可知,STL標準庫的vector中insert函數是實現了對迭代器的更新,但是形參列表沒有使用輸出型參數,所以我們只有通過返回值來接收新的迭代器!
示例2:
如果我們用返回值來接受新的迭代器,則不會崩潰!
void test02()
{
// 在所有的偶數的前面插入2
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
cout << v.size() << ":" << v.capacity() << endl;
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.insert(it, 20);//stl中的insert如果發生了擴容是實現了對it位置的更新,并用返回值輸出了形參的改變
++it; //這里++是為了解決第二種迭代器失效,防止原地踏步
}
++it;
}
cout << v.size() << ":" << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
6:6
9:9
1 20 2 3 20 4 5 20 6
請按任意鍵繼續. . .
代碼解釋:
STL中的insert如果發生了擴容是實現了對it位置的更新,并用返回值輸出了形參的改變。
示例3:
如果我們預先預留(reserve)了空間,再插入過程中沒發生擴容,那么自然也不會失效了。
void test02()
{
// 在所有的偶數的前面插入2
vector<int> v;
v.reserve(20);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
cout << v.size() << ":" << v.capacity() << endl;
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
//it = v.insert(it, 20);
v.insert(it, 20);
++it;
++it;
}
cout << v.size() << ":" << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
2.2、迭代器指向的位置意義改變
一般vector刪除數據,都不考慮縮容的方案。縮容方案: size() < capacity()/2時,可以考慮開一個size()大小的空間,拷貝數據,釋放舊空間。縮容方案本質是時間換空間。一般設計都不會考慮縮容,因為實際比較關注時間效率,不關注空間效率,因為現在硬件設備空間都比較大,空間存儲也比較便宜。
示例4:
void test03(){
vector<int> v;
cout << v.size() << ":" << v.capacity() << endl;
v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
cout << v.size() << ":" << v.capacity() << endl;
auto pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
{
v.erase(pos);
}
cout << v.size() << ":" << v.capacity() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << *pos << endl; //只要一訪問 系統強制檢查(怎么檢查的不知道!), 就報錯(Linux沒報錯)
*pos = 10;
cout << *pos << endl << endl;
}
代碼解釋:可見代碼確實是實現了刪除,但是程序卻崩了,原因就是erase后pos失效了,pos的意義變了,(但是在不同平臺下對于訪問pos的反應是不一樣的,因此我們使用的時候要特別小心,統一以失效的角度去看待)。但如果不訪問pos指向的內容就不會崩潰!
erase導致的失效:
- erase失效都是意義變了。
- 一般不會有縮容方案,那么erase的失效,一般也不存在野指針的失效。
??????????????????????????
下面我們舉個實例:
要我們刪除容器中所有偶數:
示例5:
void test05()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
v.erase(it);//刪除了就不移動
}
else
{
++it;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
代碼解釋:毫無疑問上訴代碼會崩潰,因為erase后迭代器it所指向的位置失效,(雖然感覺是可以繼續使用的,但在vs下就是不可以使用,在Linux下就可以對這個位置進行訪問),所以下面我們用返回值來更新迭代器。
示例6:
void test05()
{
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(4);
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);//刪除了就不移動
}
else
{
++it;
}
}
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
1 3 5
請按任意鍵繼續. . .
代碼解釋:可見成功的刪除了其中的偶數!
其中緣由:erase刪除pos位置元素后,pos位置之后的元素會往前移動,沒有導致底層空間的改變,理論上講迭代器不會失效,但是如果pos位置剛好是最后一個元素,刪完之后pos剛好是end的位置,而end的位置是沒有有效元素的,那么pos就失效了。因此刪除vector中任意位置元素時,vs均認為該位置上迭代器失效了!
除erase導致意義失效外,insert也可能導致意義失效,但是編譯器卻檢查不出來!!!
示例7:
void test01(){
vector<int> v;
cout << v.size() << ":" << v.capacity() << endl;
v.reserve(10);
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
cout << v.size() << ":" << v.capacity() << endl;
auto pos = find(v.begin(), v.end(), 2);
if (pos != v.end())
{
v.insert(pos, 20);
}
cout << v.size() << ":" << v.capacity() << endl;
cout << *pos << endl;
*pos = 10;
cout << *pos << endl << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
0:0
4:10
5:10
20
101 10 2 3 4
代碼解釋:同樣地,不是因為擴容而引起的意義失效,這個時候我們訪問了pos指向的位置,編譯器卻不報錯,但此時并不意味著一定對,后續如果進一步操作,還是會發生各種各樣的錯誤!
2.3、總結
總結:
- 對于insert和erase造成迭代器失效問題,linux g++平臺檢查很佛系,基本依靠操作系統自身野指針越界檢查機制,windows下vs系列檢查更嚴格,使用一些強制檢查機制,意義變了也可能會檢查出來。
- 雖然g++對于erase迭代器失效檢查時非常佛系的,但是套在實際場景中,迭代器意義變了,也會出現各種問題,所以我們要有正確處理迭代器失效的方式,比如用函數返回值來更新迭代器。
- windows下vs系列對意義失效的檢查很雙標,由insert函數引起的意義失效檢查不出來,而且可以訪問pos位置,但是由erase函數引起的意義失效卻檢查很嚴格,絲毫不準訪問pos位置。(Linux卻可以)
原文鏈接:https://blog.csdn.net/qq_43727529/article/details/125729210
相關推薦
- 2022-08-26 C++?超詳細示例講解list的使用_C 語言
- 2022-06-15 Golang?gin跨域解決方案示例_Golang
- 2022-12-07 C++?IO設備讀寫功能實現詳解_C 語言
- 2022-11-06 MobLink?Android端業務場景簡單說明_Android
- 2022-02-05 ERROR 1205 (HY000): Lock wait timeout exceeded; tr
- 2022-06-21 C++分析講解類的靜態成員變量是什么_C 語言
- 2024-01-14 idea debugger step into功能失效
- 2022-10-14 Redis常見分布鎖的原理和實現_Redis
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支