今天看書剛剛看的,就記錄下來吧。這可能是老生常談了,權(quán)且作為一個(gè)警醒的例子吧。
大家都知道STL有兩個(gè)非常重要的組成部分,容器和算法。
算法就是一個(gè)個(gè)的函數(shù),通過迭代器和容器關(guān)聯(lián)在一起,完成一些工作。
算法和容器的分離為程序設(shè)計(jì)提供了很大的靈活性,但是也帶來了一些負(fù)面效果,下面我講的這個(gè)問題就是一個(gè)例子。
STL的算法里有一個(gè)remove函數(shù),而list自身也有一個(gè)remove函數(shù),功能都是一樣的,移除某一個(gè)元素,那我們應(yīng)該使用哪一個(gè)呢?
看一下下面這段程序
1 list<int> numbers; 2 3 for ( int number = 0; number <= 6; number ++ ) { 4 numbers.push_front(number); 5 numbers.push_back(number); 6 } 7 8 copy(numbers.begin(), numbers.end(), 9 ostream_iterator<int>(cout, " ")); 10 cout << endl; 11 12 // remove algorithm will remove element but not erase the element from container 13 // it will return the logical desination of container 14 list<int>::iterator endOfNumbers = remove(numbers.begin(), numbers.end(), 3); 15 16 copy(numbers.begin(), numbers.end(), 17 ostream_iterator<int>(cout, " ")); 18 cout << endl;輸出是什么呢?
第一行肯定是6 5 4 3 2 1 0 0 1 2 3 4 5 6,那么第二行會(huì)輸出什么?
如果是沒有仔細(xì)看過STL的人肯定會(huì)認(rèn)為remove(number.begin(), numbers.end(), 3)會(huì)移除所有值為3的元素。所以輸出是:6 5 4 2 1 0 0 1 2 4 5 6。
但是,我們看一下它真正的輸出:
6 5 4 2 1 0 0 1 2 4 5 6 5 6
你可能會(huì)非常驚訝,為什么最后會(huì)多出5和6兩個(gè)數(shù)呢?
我們來講一下remove算法的原理。
remove算法工作時(shí)并不是直接把元素刪除,而是用后面的元素替代前面的元素,也即是說如果我對1234這個(gè)序列remove 2,返回的序列是 1344(3被復(fù)制到2的位置,4被復(fù)制到3的位置)。
這樣上面的例子就好解釋了,那兩個(gè)3的元素并沒有被移除,而是用后面的元素覆蓋了前面的元素。多出的那兩個(gè)數(shù)沒有被移除掉而已。
那么我們應(yīng)該如何真正完成移除呢?remove函數(shù)會(huì)返回一個(gè)迭代器,那個(gè)迭代器是這個(gè)序列的邏輯終點(diǎn),也即是我代碼里的endOfNumbers,它指向倒數(shù)第二個(gè)5上。
于是我們要利用list的erase函數(shù)完成元素移除
numbers.erase(endOfNumbers, numbers.end());
這樣我們就完成了我們的工作,稍稍有點(diǎn)曲折……
其實(shí)我們可以把這兩步放在一起,比如如果我想接著移除所有值為2的元素
numbers.erase(remove(numbers.begin(), numbers.end(), 2), numbers.end());
這樣我們就可以一步到位了。
但是這樣好么?
不好。
大家會(huì)發(fā)現(xiàn),remove函數(shù)的原理是復(fù)制而不是指針的移動(dòng)(因?yàn)楹瘮?shù)操縱的是迭代器,而C++的迭代器沒有定義刪除操作),這樣會(huì)帶來一個(gè)問題:我們使用list是因?yàn)樗男薷牡男史浅8撸淖円幌轮羔樉涂梢粤恕6@里我們復(fù)制了元素,如果在vector中,可能還是高效的,因?yàn)関ector無論如何都要復(fù)制,而對于list就不是如此了,會(huì)極度降低我們的效率。
那我們怎么辦呢?
答案是使用list自己的remove函數(shù)
我們可以這樣刪除所有值為1的元素。
也即是說,如果要?jiǎng)h除list中的元素,我們應(yīng)該使用list的remove成員函數(shù),而不是remove算法!
小結(jié)
我們都知道,STL是一個(gè)效率、復(fù)用性、靈活性折衷的產(chǎn)物,其中效率至關(guān)重要,所以STL已經(jīng)禁止了一些效率低的操作(比如list的隨機(jī)訪問),而鼓勵(lì)你去使用其它的容器。
但是,在算法中,為了靈活性,STL還是會(huì)犧牲一些東西,比如我們這個(gè)例子。
個(gè)人覺得,STL作為C++標(biāo)準(zhǔn)庫的一個(gè)組成部分,特點(diǎn)和C++本身一模一樣,強(qiáng)大而復(fù)雜,有些地方難以理解,很多細(xì)節(jié)需要學(xué)習(xí)注意,我們要學(xué)會(huì)避免陷入某些陷阱之中,比如這個(gè)例子就是一個(gè)效率陷阱。
其它更多的陷阱是錯(cuò)誤處理方面的,STL本身并沒有規(guī)定過多的錯(cuò)誤處理,大部分的錯(cuò)誤處理都交給了我們,理由很簡單:性能至上,如果一個(gè)東西自身沒有錯(cuò)誤檢查,我們可以包裝一個(gè)帶錯(cuò)誤檢查的類;但是如果這個(gè)東西自身就帶了錯(cuò)誤檢查,那么我們就沒有任何方法提升它的效率了。這也是很多C和C++庫的設(shè)計(jì)原則。
所以,很多時(shí)候,需要我們深入細(xì)節(jié),然后再?zèng)Q定到底怎么做。因?yàn)镃++就是如此:有很多路可以走,需要我們自己選擇最好的一條路。