STL中的remove問題

2021-07-06 09:57:39 字數 4172 閱讀 9932

我將從remove的複習開始這個條款,因為remove是stl中最糊塗的演算法。誤解remove很容易,驅散所有關於remove行為的疑慮——為什麼它這麼做,它是怎麼做的——是很重要的。

這是remove的宣告:

templateforwarditerator remove(forwarditerator first, forwarditerator last,

const t& value);

就像所有演算法,remove接收指定它操作的元素區間的一對迭代器。它不接收乙個容器,所以remove不知道它作用於哪個容器。此外,remove也不可能發現容器,因為沒有辦法從乙個迭代器獲取對應於它的容器。

想想怎麼從容器中除去乙個元素。唯一的方法是呼叫那個容器的乙個成員函式,幾乎都是erase的某個形式,(list有幾個除去元素的成員函式不叫erase,但它們仍然是成員函式。)因為唯一從容器中除去乙個元素的方法是在那個容器上呼叫乙個成員函式,而且因為remove無法知道它正在操作的容器,所以remove不可能從乙個容器中除去元素。這解釋了另乙個令人沮喪的觀點——從乙個容器中remove元素不會改變容器中元素的個數:

vectorv;   // 建立乙個vector用1-10填充它

v.reserve(10); // (呼叫reserve的解釋在條款14)

for (int i = 1; i <= 10; ++i)

cout << v.size(); // 列印10

v[3] = v[5] = v[9] = 99; // 設定3個元素為99

remove(v.begin(), v.end(), 99); // 刪除所有等於99的元素

cout << v.size(); // 仍然是10!

要搞清這個例子的意思,記住下面這句話:

remove並不「真的」刪除東西,因為它做不到。

重複對你有好處:

remove並不「真的」刪除東西,因為它做不到。

remove不知道它要從哪個容器刪除東西,而沒有容器,它就沒有辦法呼叫成員函式,而如果「真的」要刪除東西,那就是必要的。

上面解釋了remove不做什麼,而且解釋了為什麼它不做。我們現在需要複習的是remove做了什麼。

非常簡要地說一下,remove移動指定區間中的元素直到所有「不刪除的」元素在區間的開頭(相對位置和原來它們的一樣)。它返回乙個指向最後乙個的下乙個「不刪除的」元素的迭代器。返回值是區間的「新邏輯終點」。

舉個例子,這是v在呼叫remove前看起來的樣子:

如果我們把remove的返回值存放在乙個叫做newend的新迭代器中:

vector::iterator newend(remove(v.begin(), v.end(), 99));
這是呼叫後v看起來的樣子:

這裡我用問號來標明那些在概念上已經從v中被刪除,但繼續存在的元素的值。

如果「不刪除的」元素在v中的v.begin()和newend之間,「刪除的」元素就必須在newend和v.end()之間——這好像很合理。事實上不是這樣!「刪除的」值完全不必再存在於v中了。remove並沒有改變區間中元素的順序,所以不會把所有「刪除的」元素放在結尾,並安排所有「不刪除的」值在開頭。雖然標準沒有要求,但一般來說區間中在新邏輯終點以後的元素仍保持它們的原值。呼叫完remove後,在我知道的所有實現中,v看起來像這樣:

正如你所見,兩個曾經存在於v的「99」不再在那兒了,而乙個「99」仍然存在。一般來說,呼叫完remove後,從區間中刪除的值可能是也可能不在區間中繼續存在。大多數人覺得這很奇怪,但為什麼?你要求remove除去一些值,所以它做了。你並沒有要求它把刪除的值放在乙個你以後可以獲取的特定位置,所以它沒有做。有問題嗎?(如果你不想失去任何值,你可能應該呼叫partition或stable_partition而不是remove,partition在條款31中描述。)

remove的行為聽起來很可惡,但它只不過是演算法操作的附帶結果。在內部,remove遍歷這個區間,把要「刪除的」值覆蓋為後面要保留的值。這個覆蓋通過對持有被覆蓋的值的元素賦值來完成。

你可以想象remove完成了一種壓縮,被刪除的值表演了在壓縮中被填充的洞的角色。對於我們的vector v,它按照下面的表演:

remove檢測v[0],發現它的值不是要被刪除的,然後移動到v[1]。同樣的情況發生在v[1]和v[2]。

發現v[3]應該被刪除,所以它記錄下v[3]的值應該被覆蓋,然後它移動到v[4]。這類似記錄v[3]是乙個需要填充的「洞」。

發現v[4]的值應該被保持,所以它把v[4]賦給v[3],記錄下v[4]應該被覆蓋,然後移動到v[5]。繼續類似的壓縮,它用v[4]「填充」v[3]而且記錄v[4]現在是乙個洞。

發現v[5]應該被刪除,所以忽略並它移動到v[6]。仍然記得v[4]是乙個等待填充的洞。

發現v[6]是乙個應該保留的值,所以把v[6]賦給v[4]。記得v[5]現在是下乙個要被填充的洞,然後移到v[7]。

在某種意義上類似上面的,檢查v[7]、v[8]和v[9]。把v[7]賦給v[5],v[8]賦給v[6],忽略v[9],因為v[9]的值是要被刪除的。

返回指定下乙個要被覆蓋的元素的迭代器,在這個例子中這個元素是v[7]。

正如條款33所解釋的,事實上當remove在刪除時覆蓋的值是指標時,會有重要的影響。但是對於本條款,知道remove不從容器中除去任何元素因為它做不到就夠了。只有容器成員函式可以除去容器元素,而那是本條款的整個要點:如果你真的要刪除東西的話,你應該在remove後面接上erase。

你要erase的元素很容易識別。它們是從區間的「新邏輯終點」開始持續到區間真的終點的原來區間的元素。要除去那些元素,你要做的所有事情就是用那兩個迭代器呼叫erase的區間形式(參見條款5)。因為remove本身很方便地返回了區間新邏輯終點的迭代器,這個呼叫很直截了當:

vectorv;      // 正如從前

v.erase(remove(v.begin(), v.end(), 99), v.end()); //真的刪除所有

// 等於99的元素

cout << v.size(); // 現在返回7

把remove的返回值作為erase區間形式第乙個引數傳遞很常見,這是個慣用法。事實上,remove和erase是親密聯盟,這兩個整合到list成員函式remove中。這是stl中唯一名叫remove又能從容器中除去元素的函式:

listli;   // 建立乙個list

// 放一些值進去

li.remove(99); // 除去所有等於99的元素:

// 真的刪除元素,

// 所以它的大小可能改變了

坦白地說,呼叫這個remove函式是乙個stl中的矛盾。在關聯容器中類似的函式叫erase,list的remove也可以叫做erase。但它沒有,所以我們都必須習慣它。我們所處於的世界不是所有可能中最好的世界,但卻是我們所處的。(附加一點,條款44指出,對於list,呼叫remove成員函式比應用erase-remove慣用法更高效。)

一旦你知道了remove不能「真的」從乙個容器中刪除東西,和erase聯合使用就變成理所當然了。你要記住的唯一其他的東西是remove不是唯一這種情況的演算法。另外有兩種「類似remove」的演算法:remove_if和unique。

remove和remove_if之間的相似性很直截了當。所以我不會細講,但unique行為也像remove。它用來從乙個區間刪除東西(鄰近的重複值)而不用訪問持有區間元素的容器。結果,如果你真的要從容器中刪除元素,你也必須成對呼叫unique和erase,unique在list中也類似於remove。正像list::remove真的刪除東西(而且比erase-remove慣用法高效得多)。list::unique也真的刪除鄰近的重複值(也比erase-unique高效)。

STL中的remove問題

我將從remove的複習開始這個條款,因為remove是stl中最糊塗的演算法。誤解remove很容易,驅散所有關於remove行為的疑慮 為什麼它這麼做,它是怎麼做的 是很重要的。這是remove的宣告 templateforwarditerator remove forwarditerator ...

STL 中 remove 的運用

by a code rabbit 今天在看別人的解題報告時,發現這麼一行 remove pack 0 pack strlen pack 0 刪除行中的空格頓時想起前幾天寫的乙個函式 void filterspaces char expression new,char expression pos e...

STL中remove 和erase 的用法

示例 initializer listlist t vectorvec list t vector iterator it for it vec.begin it vec.end it cout endl remove vec.begin vec.end 1 for it vec.begin it ...