態(tài)數(shù)組到高效容器實(shí)戰(zhàn))
1. 從“動(dòng)態(tài)數(shù)組”到“瑞士軍刀”為什么你需要重新認(rèn)識(shí)vector如果你剛開始接觸C或者從C語言轉(zhuǎn)過來第一次聽說std::vector可能會(huì)覺得它就是個(gè)“動(dòng)態(tài)數(shù)組”。沒錯(cuò)這確實(shí)是它的核心身份但如果你只把它當(dāng)成一個(gè)能自動(dòng)變長的數(shù)組來用那可就太浪費(fèi)了。在實(shí)際的C項(xiàng)目中vector更像是一把“瑞士軍刀”是標(biāo)準(zhǔn)庫容器中使用頻率最高、最值得信賴的工具之一。從游戲開發(fā)中管理成千上萬的游戲?qū)ο蟮胶蠖朔?wù)里處理海量的用戶請(qǐng)求數(shù)據(jù)再到算法競賽中快速實(shí)現(xiàn)各種數(shù)據(jù)結(jié)構(gòu)vector的身影無處不在。我剛開始寫C時(shí)也習(xí)慣用new和delete手動(dòng)管理數(shù)組直到被內(nèi)存泄漏和越界訪問折磨得焦頭爛額。后來全面轉(zhuǎn)向vector才真正體會(huì)到RAII資源獲取即初始化和標(biāo)準(zhǔn)庫帶來的安全感與便利。它不僅僅幫你管理內(nèi)存更提供了一整套高效、安全的方法來操作數(shù)據(jù)。這篇文章我就以一個(gè)過來人的視角帶你深入vector的常用函數(shù)不光是告訴你“怎么用”更要講清楚“為什么這么用”以及“實(shí)際中容易踩哪些坑”。無論你是剛?cè)腴T的新手還是想鞏固基礎(chǔ)的開發(fā)者相信都能從中找到實(shí)用的干貨。2. vector的基石構(gòu)造、賦值與容量管理在揮舞vector這把瑞士軍刀之前你得先知道怎么把它從工具箱里拿出來并且了解它的“尺寸”和“容量”到底有什么區(qū)別。這是很多初學(xué)者混淆的地方也是后續(xù)高效使用的基礎(chǔ)。2.1 多種多樣的“出生”方式vector提供了豐富的構(gòu)造函數(shù)讓你能在不同場景下優(yōu)雅地初始化它。#include vector #include iostream int main() { // 1. 默認(rèn)構(gòu)造創(chuàng)建一個(gè)空的vector std::vectorint vec1; // 此時(shí)vec1.size() 0, vec1.capacity() 由實(shí)現(xiàn)定義通常為0 // 2. 指定元素個(gè)數(shù)和初始值構(gòu)造 std::vectorint vec2(5, 100); // 創(chuàng)建包含5個(gè)元素的vector每個(gè)元素都是100 // vec2 {100, 100, 100, 100, 100} // 3. 通過迭代器范圍構(gòu)造強(qiáng)大且常用 int arr[] {1, 2, 3, 4, 5}; std::vectorint vec3(arr, arr 5); // 使用原生指針作為迭代器 // vec3 {1, 2, 3, 4, 5} std::vectorint vec4(vec3.begin(), vec3.begin() 3); // 復(fù)制vec3的前3個(gè)元素 // vec4 {1, 2, 3} // 4. 列表初始化C11及以上最直觀 std::vectorint vec5 {10, 20, 30, 40, 50}; // vec5 {10, 20, 30, 40, 50} // 5. 拷貝構(gòu)造 std::vectorint vec6(vec5); // vec6是vec5的一個(gè)副本 // vec6 {10, 20, 30, 40, 50} return 0; }實(shí)操心得對(duì)于已知的少量初始數(shù)據(jù)優(yōu)先使用列表初始化vec5的方式代碼最清晰。當(dāng)需要從其他容器甚至是數(shù)組或容器的一部分復(fù)制數(shù)據(jù)時(shí)迭代器范圍構(gòu)造是利器。指定個(gè)數(shù)和值的構(gòu)造vec2在需要?jiǎng)?chuàng)建大量相同默認(rèn)值元素時(shí)很高效比如初始化一個(gè)全零的矩陣。2.2 賦值操作不僅僅是等號(hào)創(chuàng)建之后如何給一個(gè)已存在的vector賦予新值除了還有assign成員函數(shù)。std::vectorint vec {1, 2, 3}; std::vectorint other {4, 5, 6, 7}; // 1. 使用 操作符拷貝賦值 vec other; // vec現(xiàn)在的內(nèi)容和other完全一樣 {4,5,6,7} // 注意這會(huì)釋放vec原有的內(nèi)存并分配足夠容納other內(nèi)容的新內(nèi)存。 // 2. 使用assign成員函數(shù)更靈活 vec.assign(3, 99); // 將vec內(nèi)容替換為3個(gè)99。 vec {99, 99, 99} vec.assign(other.begin(), other.end()); // 用other的迭代器范圍賦值。 vec {4,5,6,7} vec.assign({10, 20, 30}); // 用初始化列表賦值C11。 vec {10,20,30}為什么需要assign操作符要求右邊也是一個(gè)vector對(duì)象。而assign允許你直接用元素個(gè)數(shù)值、迭代器范圍或初始化列表來覆蓋當(dāng)前內(nèi)容無需先構(gòu)造一個(gè)臨時(shí)的vector對(duì)象在某些場景下更高效、更直接。2.3 容量capacity與大小size關(guān)鍵區(qū)別與內(nèi)存管理這是vector最核心的概念之一直接關(guān)系到性能和內(nèi)存使用。size(): 返回當(dāng)前vector中實(shí)際擁有的元素?cái)?shù)量。你通過push_back添加的就是這些元素。capacity(): 返回當(dāng)前vector已分配的內(nèi)存底層數(shù)組能夠容納的元素?cái)?shù)量上限。這個(gè)值總是大于等于size()。reserve(n):預(yù)分配內(nèi)存。它確保vector的容量至少為n。如果當(dāng)前容量小于n則會(huì)重新分配一塊至少能容納n個(gè)元素的內(nèi)存如果當(dāng)前容量已經(jīng)大于等于n則什么也不做。它不會(huì)改變size()也不會(huì)創(chuàng)建或銷毀任何元素。resize(n, val):改變vector的size()。如果n大于當(dāng)前size()則在末尾添加新元素新元素的值由第二個(gè)參數(shù)val指定如果省略則使用值初始化對(duì)于int是0如果n小于當(dāng)前size()則末尾多余的元素會(huì)被銷毀。它可能會(huì)改變capacity()如果需要擴(kuò)容。std::vectorint vec; std::cout 初始狀態(tài): size vec.size() , capacity vec.capacity() std::endl; // 輸出可能為: size0, capacity0 vec.reserve(100); // 預(yù)分配至少100個(gè)元素的空間 std::cout reserve(100)后: size vec.size() , capacity vec.capacity() std::endl; // 輸出可能為: size0, capacity100 (size沒變) for(int i 0; i 10; i) { vec.push_back(i); } std::cout 添加10個(gè)元素后: size vec.size() , capacity vec.capacity() std::endl; // 輸出可能為: size10, capacity100 (capacity沒變因?yàn)轭A(yù)分配夠了) vec.resize(5); // 將大小調(diào)整為5銷毀后5個(gè)元素 std::cout resize(5)后: size vec.size() , capacity vec.capacity() std::endl; // 輸出可能為: size5, capacity100 (size變了capacity通常不變) vec.resize(20, -1); // 將大小調(diào)整為20新增的15個(gè)元素用-1填充 std::cout resize(20, -1)后: size vec.size() , capacity vec.capacity() std::endl; // 輸出可能為: size20, capacity100 (size變了capacity可能仍為100如果100夠用)核心避坑點(diǎn)reservevsresize務(wù)必分清兩者的用途reserve是性能優(yōu)化工具當(dāng)你事先知道或能估算出大致要存入多少元素時(shí)使用reserve一次性分配足夠內(nèi)存可以避免push_back過程中多次“分配新內(nèi)存-拷貝舊數(shù)據(jù)-釋放舊內(nèi)存”的昂貴操作。這是提升vector性能最有效的手段之一。resize是邏輯大小調(diào)整工具當(dāng)你需要立即讓vector擁有特定數(shù)量的元素比如初始化一個(gè)固定大小的數(shù)組或者清空尾部元素時(shí)使用它。一個(gè)常見的性能陷阱std::vectorint data; // 錯(cuò)誤示范在循環(huán)中讓vector自己增長 for (int i 0; i 1000000; i) { data.push_back(i); // 可能導(dǎo)致多次重新分配效率低下。 } // 正確示范預(yù)先分配 std::vectorint data2; data2.reserve(1000000); // 一次性分配足夠內(nèi)存 for (int i 0; i 1000000; i) { data2.push_back(i); // 幾乎無重新分配開銷效率極高。 }對(duì)于百萬級(jí)別甚至更多的數(shù)據(jù)預(yù)先reserve帶來的性能提升是數(shù)量級(jí)的。2.4 內(nèi)存釋放的誤區(qū)clear()、shrink_to_fit()與“交換技法”如何釋放vector占用的內(nèi)存這里有幾個(gè)微妙之處。clear(): 清空所有元素將size()設(shè)置為0。但它不保證釋放內(nèi)存capacity()通常保持不變。這意味著vector仍然持有那塊內(nèi)存以備后續(xù)添加元素之用。shrink_to_fit()(C11): 這是一個(gè)“請(qǐng)求”請(qǐng)求vector將capacity()減少到與size()匹配。標(biāo)準(zhǔn)不強(qiáng)制要求實(shí)現(xiàn)必須釋放內(nèi)存但主流實(shí)現(xiàn)通常會(huì)照做。它是一個(gè)非綁定的請(qǐng)求。“交換技法” (Swap Trick)在C11之前這是強(qiáng)制釋放內(nèi)存的可靠方法。std::vectorint vec(1000); // size1000, capacity1000 vec.clear(); std::cout clear()后: size vec.size() , capacity vec.capacity() std::endl; // 輸出: size0, capacity1000 (內(nèi)存沒還) vec.shrink_to_fit(); // 請(qǐng)求釋放多余內(nèi)存 std::cout shrink_to_fit()后: size vec.size() , capacity vec.capacity() std::endl; // 輸出: size0, capacity0 (或一個(gè)很小的值內(nèi)存很可能被釋放) // 交換技法 (C11前常用現(xiàn)在仍可作為明確意圖的表達(dá)) std::vectorint().swap(vec); // 用一個(gè)臨時(shí)空vector和vec交換內(nèi)容。臨時(shí)vector析構(gòu)時(shí)釋放了大內(nèi)存。 // vec現(xiàn)在是一個(gè)全新的、capacity很小的空vector。什么時(shí)候該釋放內(nèi)存如果一個(gè)vector在某個(gè)階段裝了大量數(shù)據(jù)之后這些數(shù)據(jù)不再需要且很長時(shí)間內(nèi)或永遠(yuǎn)不會(huì)再需要同等量級(jí)的內(nèi)存那么使用shrink_to_fit()或交換技法來釋放內(nèi)存是合理的尤其是在內(nèi)存受限的嵌入式環(huán)境或長期運(yùn)行的服務(wù)中。否則保留一定的容量clear后可以避免后續(xù)添加元素時(shí)的重復(fù)分配這是一種空間換時(shí)間的權(quán)衡。3. 元素的訪問與遍歷安全與效率的權(quán)衡拿到了數(shù)據(jù)怎么讀、怎么寫vector提供了多種訪問方式各有適用場景和風(fēng)險(xiǎn)。3.1 隨機(jī)訪問[]與at()的抉擇vector支持高效的隨機(jī)訪問時(shí)間復(fù)雜度是O(1)。operator[](下標(biāo)運(yùn)算符): 和數(shù)組一樣快但不進(jìn)行邊界檢查。如果下標(biāo)越界行為是未定義的(Undefined Behavior, UB)通常會(huì)導(dǎo)致程序崩潰或更詭異的數(shù)據(jù)錯(cuò)誤。at(index): 功能相同但進(jìn)行邊界檢查。如果下標(biāo)越界它會(huì)拋出一個(gè)std::out_of_range異常。std::vectorint vec {10, 20, 30}; // 使用 [] int val1 vec[1]; // val1 20 高效 vec[2] 99; // 修改元素 vec {10, 20, 99} // int val_danger vec[5]; // 危險(xiǎn)下標(biāo)越界未定義行為可能崩潰或讀取垃圾值。 // 使用 at() int val2 vec.at(1); // val2 20 vec.at(2) 100; // vec {10, 20, 100} try { int val_safe vec.at(5); // 下標(biāo)越界拋出 std::out_of_range 異常 } catch (const std::out_of_range e) { std::cerr 訪問越界: e.what() std::endl; // 程序可以優(yōu)雅地處理錯(cuò)誤 }選擇建議追求極致性能且能100%保證索引不越界的場景例如在已知范圍的循環(huán)內(nèi)使用[]。這是C哲學(xué)的一部分不為你不需要的檢查付費(fèi)。索引來自外部輸入、計(jì)算結(jié)果不確定或者代碼安全穩(wěn)定性優(yōu)先的場景使用at()。多一次檢查的成本換來的是程序的健壯性。在調(diào)試階段即使使用[]也可以考慮開啟編譯器的邊界檢查選項(xiàng)如GCC的-D_GLIBCXX_DEBUG。3.2 首尾元素訪問front()與back()這兩個(gè)函數(shù)提供了快速訪問首尾元素的方法代碼意圖更清晰。std::vectorint vec {1, 2, 3, 4, 5}; int first vec.front(); // first是vec[0]的引用值為1 int last vec.back(); // last是vec[4]的引用值為5 vec.front() 100; // vec {100, 2, 3, 4, 5} vec.back() 500; // vec {100, 2, 3, 4, 500}注意在vector為空時(shí)調(diào)用front()或back()是未定義行為。使用前務(wù)必檢查!vec.empty()。3.3 遍歷的多種姿勢從下標(biāo)到范圍for循環(huán)遍歷是容器最常用的操作之一。std::vectorint vec {1, 2, 3, 4, 5}; // 方法1傳統(tǒng)下標(biāo)循環(huán) (需要知道元素類型可修改元素) for (std::size_t i 0; i vec.size(); i) { std::cout vec[i] ; // vec[i] * 2; // 可以修改 } // 方法2迭代器循環(huán) (更通用是STL算法的基石) for (std::vectorint::iterator it vec.begin(); it ! vec.end(); it) { std::cout *it ; // *it * 2; // 可以修改 } // C11后可以用auto簡化 for (auto it vec.begin(); it ! vec.end(); it) { std::cout *it ; } // 方法3常量迭代器 (只讀遍歷) for (std::vectorint::const_iterator cit vec.cbegin(); cit ! vec.cend(); cit) { std::cout *cit ; // *cit * 2; // 錯(cuò)誤不能修改 } // 方法4基于范圍的for循環(huán) (C11最簡潔) for (int value : vec) { // 值拷貝修改value不影響vec std::cout value ; } for (int ref : vec) { // 引用可以修改vec中的元素 std::cout ref ; ref * 2; } for (const int cref : vec) { // 常量引用只讀且避免拷貝開銷 std::cout cref ; }經(jīng)驗(yàn)之談只讀遍歷優(yōu)先使用基于范圍的for循環(huán)常量引用(for (const auto elem : vec))代碼簡潔且高效。需要修改元素使用基于范圍的for循環(huán)引用(for (auto elem : vec)) 或迭代器。需要索引位置使用傳統(tǒng)下標(biāo)循環(huán)。迭代器是理解STL算法的關(guān)鍵在配合algorithm頭文件中的函數(shù)如std::sort,std::find時(shí)是必須的。4. 動(dòng)態(tài)增刪在尾部、在中間、在開頭vector的“動(dòng)態(tài)”特性主要體現(xiàn)在元素的增刪上。但需要注意的是由于其底層是連續(xù)數(shù)組在不同位置操作的效率差異巨大。4.1 尾部操作push_back、emplace_back與pop_back這是vector最高效的操作均攤時(shí)間復(fù)雜度為O(1)。push_back(const T value)/push_back(T value): 在尾部添加一個(gè)元素。接受一個(gè)已存在的對(duì)象拷貝或移動(dòng)。emplace_back(Args... args)(C11): 在尾部原位構(gòu)造一個(gè)元素。它接受構(gòu)造T類型對(duì)象所需的參數(shù)直接在vector的內(nèi)存中構(gòu)造對(duì)象避免了臨時(shí)對(duì)象的創(chuàng)建和拷貝/移動(dòng)。pop_back(): 移除尾部最后一個(gè)元素。注意它不返回被移除的元素如果需要獲取尾元素請(qǐng)先使用back()。#include string #include vector struct Person { std::string name; int age; Person(const std::string n, int a) : name(n), age(a) { std::cout 構(gòu)造 Person: name std::endl; } Person(const Person other) : name(other.name), age(other.age) { std::cout 拷貝構(gòu)造 Person: name std::endl; } }; int main() { std::vectorPerson people; Person bob(Bob, 30); people.push_back(bob); // 調(diào)用拷貝構(gòu)造函數(shù) // 輸出: 構(gòu)造 Bob - 拷貝構(gòu)造 Bob people.push_back(Person(Alice, 25)); // 調(diào)用移動(dòng)構(gòu)造函數(shù)如果存在 // 輸出: 構(gòu)造 Alice - (可能)移動(dòng)構(gòu)造 Alice people.emplace_back(Charlie, 28); // 直接在vector內(nèi)存中構(gòu)造無需臨時(shí)對(duì)象 // 輸出: 構(gòu)造 Charlie (只有這一次) // 移除尾部元素 if (!people.empty()) { // Person lastPerson people.back(); // 如果需要先保存 people.pop_back(); // 移除Charlie調(diào)用其析構(gòu)函數(shù) } return 0; }核心建議對(duì)于非平凡類型如自定義類、std::string等優(yōu)先使用emplace_back。它能直接傳遞構(gòu)造參數(shù)避免創(chuàng)建臨時(shí)對(duì)象再拷貝/移動(dòng)性能更優(yōu)。這是C11后最重要的優(yōu)化習(xí)慣之一。4.2 任意位置插入與刪除insert與erase在非尾部位置操作因?yàn)樾枰苿?dòng)后續(xù)的所有元素以保持連續(xù)性所以時(shí)間復(fù)雜度是O(n)其中n是移動(dòng)的元素?cái)?shù)量。insert(iterator pos, const T value): 在迭代器pos指向的位置之前插入一個(gè)新元素。返回指向新插入元素的迭代器。erase(iterator pos): 刪除迭代器pos指向的元素。返回指向被刪除元素之后位置的迭代器。erase(iterator first, iterator last): 刪除[first, last)區(qū)間內(nèi)的所有元素。std::vectorint vec {10, 20, 30, 40}; // 在第三個(gè)元素值為30之前插入99 auto it vec.insert(vec.begin() 2, 99); // vec {10, 20, 99, 30, 40} // it 指向新插入的99 // 刪除剛才插入的99 it vec.erase(it); // vec {10, 20, 30, 40} // it 現(xiàn)在指向30原99位置的下一個(gè) // 刪除一個(gè)區(qū)間比如刪除20和30 vec.erase(vec.begin() 1, vec.begin() 3); // vec {10, 40}重要陷阱迭代器失效在vector中插入或刪除元素可能會(huì)導(dǎo)致所有指向該vector的迭代器、引用和指針失效特別是插入引起重新分配時(shí)。這是一個(gè)極易出錯(cuò)的地方。std::vectorint vec {1, 2, 3, 4, 5}; auto iter vec.begin() 2; // iter 指向3 vec.push_back(6); // 可能導(dǎo)致重新分配iter 現(xiàn)在可能失效了 // int val *iter; // 危險(xiǎn)未定義行為iter可能指向已釋放的內(nèi)存。 vec.insert(vec.begin(), 0); // 在開頭插入所有迭代器包括iter肯定失效 // int val2 *iter; // 同樣危險(xiǎn)安全操作法則插入/刪除后立即更新迭代器。insert和erase的返回值就是更新后的、有效的迭代器應(yīng)該用它來替代舊的迭代器。std::vectorint vec {1, 2, 3, 4}; for (auto it vec.begin(); it ! vec.end(); /* 注意這里不遞增 */) { if (*it % 2 0) { // 刪除所有偶數(shù) it vec.erase(it); // erase返回下一個(gè)有效迭代器賦值給it } else { it; // 只有沒刪除元素時(shí)才手動(dòng)遞增迭代器 } } // vec {1, 3}避免在循環(huán)中混用索引和修改容器大小的操作除非你非常小心地處理索引。上面的迭代器方法更安全。如果需要在循環(huán)中插入多個(gè)元素考慮先記錄位置循環(huán)結(jié)束后再批量插入或者使用“從后往前”處理的方式可以減少元素移動(dòng)的次數(shù)。4.3 清空與判空clear(): 如前所述清空所有元素size變0capacity通常不變。empty(): 檢查vector是否為空size() 0。這是一個(gè)高效的操作應(yīng)該用它來檢查而不是判斷size() 0雖然結(jié)果一樣但empty()意圖更清晰。std::vectorint vec {1, 2, 3}; if (!vec.empty()) { // 安全地操作vec例如訪問vec.front() } vec.clear(); // 清空 // 現(xiàn)在 vec.empty() 為 true5. 進(jìn)階技巧與實(shí)戰(zhàn)中的“坑”掌握了基本函數(shù)我們來看看一些能讓你代碼更優(yōu)雅、更高效的進(jìn)階用法以及那些只有踩過才知道的“坑”。5.1 使用data()獲取底層數(shù)組指針data()成員函數(shù)返回一個(gè)指向底層數(shù)組的指針。這在需要與C語言API或某些需要裸指針的庫如OpenGL、某些數(shù)學(xué)庫交互時(shí)非常有用。std::vectorfloat vertices {0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}; // 假設(shè)有一個(gè)C函數(shù)需要浮點(diǎn)數(shù)組指針void process_floats(float* arr, int count); process_floats(vertices.data(), vertices.size()); // 安全高效的傳遞方式 // 對(duì)比舊的錯(cuò)誤做法 // process_floats(vertices[0], vertices.size()); // 當(dāng)vertices為空時(shí)vertices[0]行為未定義 // process_floats(vertices.begin(), vertices.size()); // 迭代器不能當(dāng)指針用雖然某些實(shí)現(xiàn)可能行但不標(biāo)準(zhǔn)重要提示在vector為空時(shí)data()可能返回nullptr也可能返回一個(gè)非空但不可解引用的指針C11起要求為可解引用但操作未定義。最安全的做法是在傳遞data()給C接口前檢查vector是否為空。5.2swap不僅僅是交換內(nèi)容swap成員函數(shù)用于交換兩個(gè)vector的內(nèi)容。它的效率非常高通常是O(1)復(fù)雜度因?yàn)樗唤粨Q內(nèi)部指針等元數(shù)據(jù)而不交換實(shí)際的元素。快速清空并釋放內(nèi)存前面提到的交換技法 (std::vectorT().swap(v))。轉(zhuǎn)移所有權(quán)在C11移動(dòng)語義普及前swap常被用來實(shí)現(xiàn)高效的“轉(zhuǎn)移”操作。縮小容量與一個(gè)容量更小的vector交換可以間接縮小容量。std::vectorint a(100, 1); // 容量很大 std::vectorint b(10, 2); // 容量較小 a.swap(b); // 高效交換 // 現(xiàn)在 a 的 size10, capacity較小 b 的 size100, capacity很大。5.3 與算法庫algorithm的強(qiáng)力結(jié)合vector作為序列容器與標(biāo)準(zhǔn)庫算法是天作之合。迭代器讓它們無縫銜接。#include algorithm #include vector #include iostream int main() { std::vectorint vec {5, 2, 8, 1, 9, 3}; // 排序 std::sort(vec.begin(), vec.end()); // vec {1, 2, 3, 5, 8, 9} // 查找 auto it std::find(vec.begin(), vec.end(), 5); if (it ! vec.end()) { std::cout 找到5位置索引: (it - vec.begin()) std::endl; } // 反轉(zhuǎn) std::reverse(vec.begin(), vec.end()); // vec {9, 8, 5, 3, 2, 1} // 累加 int sum std::accumulate(vec.begin(), vec.end(), 0); std::cout 總和: sum std::endl; // 刪除特定元素例如刪除所有偶數(shù) - 使用“擦除-移除”慣用法 vec {1, 2, 3, 4, 5, 6}; auto new_end std::remove_if(vec.begin(), vec.end(), [](int x) { return x % 2 0; }); // 將所有偶數(shù)移到末尾 vec.erase(new_end, vec.end()); // 真正刪除末尾的“垃圾”元素 // vec {1, 3, 5} return 0; }“擦除-移除”慣用法是STL中一個(gè)經(jīng)典模式。std::remove或std::remove_if并不直接刪除元素而是將不需要的元素移動(dòng)到容器末尾并返回一個(gè)指向新的邏輯結(jié)尾的迭代器。隨后再用erase刪除從該迭代器到原結(jié)尾的所有元素。這樣做比在循環(huán)中調(diào)用erase更高效因?yàn)閑rase在循環(huán)中會(huì)導(dǎo)致多次元素移動(dòng)。5.4 存儲(chǔ)自定義對(duì)象與內(nèi)存管理當(dāng)vector存儲(chǔ)的是自定義類對(duì)象時(shí)你需要了解其生命周期。class MyClass { public: int id; MyClass(int i) : id(i) { std::cout 構(gòu)造 id std::endl; } ~MyClass() { std::cout 析構(gòu) id std::endl; } // 拷貝構(gòu)造和拷貝賦值運(yùn)算符對(duì)于vector管理內(nèi)存至關(guān)重要 MyClass(const MyClass other) : id(other.id) { std::cout 拷貝構(gòu)造 id std::endl; } }; int main() { std::vectorMyClass vec; vec.reserve(3); // 預(yù)分配內(nèi)存避免后續(xù)push_back時(shí)多次重新分配和拷貝 vec.emplace_back(1); // 原位構(gòu)造 vec.emplace_back(2); vec.emplace_back(3); std::cout --- 刪除第二個(gè)元素 --- std::endl; vec.erase(vec.begin() 1); // 刪除id2的對(duì)象會(huì)調(diào)用其析構(gòu)函數(shù)并且后面的元素會(huì)向前移動(dòng)可能觸發(fā)拷貝賦值 std::cout --- 清空vector --- std::endl; vec.clear(); // 對(duì)所有剩余元素調(diào)用析構(gòu)函數(shù) std::cout --- main函數(shù)結(jié)束vec析構(gòu) --- std::endl; return 0; // vec離開作用域其析構(gòu)函數(shù)被調(diào)用會(huì)對(duì)其管理的所有MyClass對(duì)象調(diào)用析構(gòu)函數(shù)但此時(shí)vec已空 }關(guān)鍵點(diǎn)vector在重新分配內(nèi)存、erase元素、clear或自身銷毀時(shí)會(huì)自動(dòng)調(diào)用其存儲(chǔ)對(duì)象的析構(gòu)函數(shù)。如果你的對(duì)象管理著動(dòng)態(tài)內(nèi)存例如有new出來的指針你必須確保在析構(gòu)函數(shù)中正確釋放或者遵循“三/五法則”提供正確的拷貝控制成員拷貝構(gòu)造、拷貝賦值、移動(dòng)構(gòu)造、移動(dòng)賦值、析構(gòu)否則會(huì)導(dǎo)致資源泄漏或雙重釋放。在現(xiàn)代C中使用智能指針如std::unique_ptr,std::shared_ptr來管理成員資源是更安全的選擇。5.5 性能考量與選擇vector的時(shí)機(jī)優(yōu)勢緩存友好數(shù)據(jù)連續(xù)存儲(chǔ)CPU預(yù)取效率高訪問速度快。隨機(jī)訪問O(1)通過索引訪問元素是常數(shù)時(shí)間。尾部增刪高效push_back/pop_back均攤O(1)。劣勢中間/頭部增刪慢insert/erase需要移動(dòng)元素O(n)。重新分配成本高當(dāng)容量不足需要擴(kuò)容時(shí)需要分配新內(nèi)存、拷貝/移動(dòng)所有舊元素、釋放舊內(nèi)存。何時(shí)選擇vector需要頻繁隨機(jī)訪問元素。元素的存儲(chǔ)順序很重要。大部分增加/刪除操作發(fā)生在序列的末尾。你需要一個(gè)可動(dòng)態(tài)增長但絕大多數(shù)情況下數(shù)據(jù)量穩(wěn)定的數(shù)組。何時(shí)考慮其他容器需要頻繁在序列中間或開頭插入/刪除元素 → 考慮std::list雙向鏈表或std::deque雙端隊(duì)列。需要頻繁按關(guān)鍵字查找 → 考慮std::set集合或std::map映射。需要實(shí)現(xiàn)先進(jìn)先出(FIFO)或后進(jìn)先出(LIFO) → 考慮std::queue或std::stack它們通常默認(rèn)用deque作為底層容器但提供特定接口。vector是C標(biāo)準(zhǔn)庫的基石理解其函數(shù)和行為細(xì)節(jié)是寫出高效、健壯C代碼的關(guān)鍵一步。從簡單的數(shù)據(jù)存儲(chǔ)到復(fù)雜的數(shù)據(jù)處理熟練運(yùn)用vector及其配套的算法能讓你在C編程中事半功倍。記住預(yù)分配reserve是性能朋友迭代器失效是隱藏的敵人而emplace_back和算法庫則是讓你代碼更現(xiàn)代的利器。