在刪除選項中仔細選擇

2021-06-15 22:10:32 字數 4086 閱讀 9079

假定你有乙個標準stl容器,c,容納int,

containerc;
而你想把c中所有值為1963的物件都去掉。令人吃驚的是,完成這項任務的方法因不同的容器型別而不同:沒有一種方法是通用的。

c.erase(remove(c.begin(), c.end(), 1963),		// 當c是vector、string

c.end()); // 或deque時,

// erase-remove慣用法

// 是去除特定值的元素

// 的最佳方法

這方法也適合於list,但是,正如條款44解釋的,list的成員函式remove更高效:

c.remove(1963);		// 當c是list時,

// remove成員函式是去除

// 特定值的元素的最佳方法

當c是標準關聯容器(即,set、multiset、map或multimap)時,使用任何叫做remove的東西都是完全錯誤的。這樣的容器沒有叫做remove的成員函式,而且使用remove演算法可能覆蓋容器值(參見條款32),潛在地破壞容器。(關於這樣的破壞的細節,參考條款22,那個條款也解釋了為什麼試圖在map和multimap上使用remove肯定不能編譯,而試圖在set和multiset上使用可能不能編譯。)

不,對於關聯容器,解決問題的適當方法是呼叫erase:

c.erase(1963);		// 當c是標準關聯容器時

// erase成員函式是去除

// 特定值的元素的最佳方法

這不僅是正確的,而且很高效,只花費對數時間。(序列容器的基於刪除的技術需要線性時間。)並且,關聯容器的erase成員函式有基於等價而不是相等的優勢,條款19解釋了這一區別的重要性。

讓我們現在稍微修改一下這個問題。不是從c中除去每個有特定值的物體,讓我們消除下面判斷式(參見條款39)返回真的每個物件:

bool badvalue(int x);	// 返回x是否是「bad」
對於序列容器(vector、string、deque和list),我們要做的只是把每個remove替換為remove_if,然後就完成了:

c.erase(remove_if(c.begin(), c.end(), badvalue),	// 當c是vector、string

c.end()); // 或deque時這是去掉

// badvalue返回真

// 的物件的最佳方法

c.remove_if(badvalue); // 當c是list時這是去掉

// badvalue返回真

// 的物件的最佳方法

對於標準關聯容器,它不是很直截了當。有兩種方法處理該問題,乙個更容易編碼,另乙個更高效。「更容易但效率較低」的解決方案用remove_copy_if把我們需要的值拷貝到乙個新容器中,然後把原容器的內容和新的交換:

assoccontainer

c; // c現在是一種

... // 標準關聯容器

assoccontainer

goodvalues; // 用於容納不刪除

// 的值的臨時容器

remove_copy_if(c.begin(), c.end(), // 從c拷貝不刪除

inserter(goodvalues, // 的值到

goodvalues.end()), // goodvalues

badvalue);

c.swap(goodvalues); // 交換c和goodvalues

// 的內容

對這種方法的缺點是它拷貝了所有不刪除的元素,而這樣的拷貝開銷可能大於我們感興趣支付的。

我們可以通過直接從原容器刪除元素來避開那筆帳單。不過,因為關聯容器沒有提供類似remove_if的成員函式,所以我們必須寫乙個迴圈來迭代c中的元素,和原來一樣刪除元素。

看起來,這個任務很簡單,而且實際上,**也很簡單。不幸的是,那些正確工作的**很少是躍出腦海的**。例如,這是很多程式設計師首先想到的:

assoccontainer

c;...

for (assoccontainer

::iterator i = c.begin(); // 清晰,直截了當

i!= c.end(); // 而漏洞百出的用於

++i) // 不要這麼做!

唉,這有未定義的行為。當容器的乙個元素被刪時,指向那個元素的所有迭代器都失效了。當c.erase(i)返回時,i已經失效。那對於這個迴圈是個壞訊息,因為在erase返回後,i通過for迴圈的++i部分自增。

assoccontainer

c;...

for (assoccontainer

::iterator i = c.begin(); // for迴圈的第三部分

i != c.end(); // 是空的;i現在在下面

/*nothing*/ ) // 作為***增加i;

// 對於好的值,

// 只增加i

這種呼叫erase的解決方法可以工作,因為表示式i++的值是i的舊值,但作為***,i增加了。因此,我們把i的舊值(沒增加的)傳給 erase,但在erase開始執行前i已經自增了。那正好是我們想要的。正如我所說的,**很簡單,只不過不是大多數程式設計師在第一次嘗試時想到的。

現在讓我們進一步修改該問題。不僅刪除badvalue返回真的每個元素,而且每當乙個元素被刪掉時,我們也想把一條訊息寫到日誌檔案中。

對於關聯容器,這說多容易就有多容易,因為只需要對我們剛才開發的迴圈做乙個微不足道的修改就行了:

ofstream logfile;					// 要寫入的日誌檔案

assoccontainer

c;...

for (assoccontainer

::iterator i = c.begin(); // 迴圈條件和前面一樣

i !=c.end();)

else ++i;

}

現在是vector、string和deque給我們帶來麻煩。我們不能再使用erase-remove慣用法,因為沒有辦法讓erase或 remove寫日誌檔案。而且,我們不能使用剛剛為關聯容器開發的迴圈,因為它為vector、string和deque產生未定義的行為!要記得對於那 樣的容器,呼叫erase不僅使所有指向被刪元素的迭代器失效,也使被刪元素之後的所有迭代器失效。在我們的情況裡,那包括所有i之後的迭代器。我們寫i++,++i或你能想起的其它任何東西都沒有用,因為沒有能導致迭代器有效的。

我們必須對vector、string和deque採用不同的戰略。特別是,我們必須利用erase的返回值。那個返回值正是我們需要的:一旦刪除完成,它就是指向緊接在被刪元素之後的元素的有效迭代器。換句話說,我們這麼寫:

for (seqcontainer

::iterator i = c.begin();

i != c.end();) // 賦給i來保持i有效

else

++i;

}

這可以很好地工作,但只用於標準序列容器。由於論證乙個可能的問題(條款5做了),標準關聯容器的erase的返回型別是void[1]

。對於那些容器,你必須使用「後置遞增你要傳給erase的迭代器」技術。(順便說說,在為序列容器編碼和為關聯容器編碼之間的這種差別是為什麼寫容器無關**一般缺乏考慮的乙個例子——參見條款2。)

為了避免你奇怪list的適當方法是什麼,事實表明對於迭代和刪除,你可以像vector/string/deque一樣或像關聯容器一樣對待list;兩種方法都可以為list工作。

如果我們觀察在本條款中提到的所有東西,我們得出下列結論:

如你所見,與僅僅呼叫erase相比,有效地刪除容器元素有更多的東西。解決問題的最好方法取決於你是怎樣鑑別出哪個物件是要被去掉的,儲存它們的 容器的型別,和當你刪除它們的時候你還想要做什麼(如果有的話)。只要你小心而且注意了本條款的建議,你將毫不費力。如果你不小心,你將冒著產生不必要低 效的**或未定義行為的危險。

[1] 這僅對帶有迭代器實參的erase形式是正確的。關聯容器也提供乙個帶有乙個值的實參的erase形式,而那種形式返回被刪掉的元素個數。但這裡,我們只關心通過迭代器刪除東西。

條款9 在刪除選項中仔細選擇

假定你有乙個標準stl容器,c,容納int,containerc 而你想把c中所有值為1963的物件都去掉。令人吃驚的是,完成這項任務的方法因不同的容器型別而不同 沒 有一種方法是通用的。見條款32 c.erase remove c.begin c.end 1963 當c是vector string...

刪除右鍵選單中的選項

右鍵選單中的有些選項你並不常用,或者,有些軟體已被刪除,但其右鍵選單中的選項卻仍占用著螢幕空間。要刪除這些無用的右鍵選單項,請按下述方法操作 1.單擊windows的 開始 選單,單擊 執行 在 開啟 框中鍵入 regedit 單擊 確定 按鈕,開啟 登錄檔編輯器 視窗。2.展開 hkey clas...

刪除下拉列表中的選項

一 介紹 刪除下拉列表中的單個選項可以用select物件的remove focus 方法和selectedindex屬性來實現。1 remove 方法 該方法用於在下拉列表中刪除指定的option物件。myselect.remove index myselect 當前要刪除選項的select物件的名...