你好。
這個論壇經常能看到關於程式的問題,在這些程式中,程式設計師希望在執行時儲存一系列元素,但又不知道提前會儲存多少。
解決這個問題的經典 C 語言方法是動態分配一個數組,並根據需要“調整”它的大小,方法是分配一個新陣列並從前一個數組複製元素。然而,這種策略不僅對新手程式設計師來說實現起來很麻煩,而且需要手動進行記憶體管理,這會帶來記憶體洩漏的風險。
為此,本文將介紹標準模板庫 (STL) 類模板
std::vector,作為可調整大小陣列問題的潛在解決方案。std::vector 提供了成員函式來處理涉及陣列大小調整的常見任務,在許多情況下可以作為陣列的“即插即用”替代品,並且是儲存布林值的一個方便的大小最佳化。
如果你(讀者)熟悉以下內容,本文可能會更容易理解
- 陣列的使用(C 或 C++ 風格)。
- 函式的使用。
- 類的例項化和成員的使用。
- 類模板的例項化(可選)。
絕對基礎知識
許多初學者的一個誤解是,std::vector 就像數學或物理學中的 n 維向量。雖然這是一個可以理解的誤解,但最好將 std::vector 視為一小段程式碼(一個包裝器),它管理一個可以改變大小的陣列。
讓我們從建立 vector 開始。像任何標準庫元素一樣,需要包含一個頭檔案才能使用 vector。這個標頭檔案命名得相當直觀:就是“vector”。
#include <vector>
例項化一個 vector,需要做的就是這樣
std::vector<value_type> variable_name;
這會建立一個空的 vector。要讓 vector 以某個初始大小開始,這樣做也行
std::vector<value_type> variable_name(number_of_elements);
該 vector 中的每個元素都將初始化為其預設值。如果程式設計師希望將它們全部初始化為除預設值以外的某個值,那麼還有另一種選擇
std::vector<value_type> variable_name(number_of_elements, value);
初始化 vector 的所有方法列表可以在
這裡 找到。
Vector 的用法和陣列類似。它們支援 [] 運算子進行元素訪問,就像陣列一樣(它們的索引也相同,請記住索引的範圍是 [0, size-1]),因此在許多情況下可以作為陣列的“即插即用”替代品。然而,有一種表示法不起作用,那就是
*(ptr_to_first_element_of_array_this_name_is_really_long+offset)
謹此警告。
部分成員函式選集
Vector 提供了一個成員函式來獲取它們包含的元素數量,即
std::vector::size。它的返回型別 size_t 是一個無符號整數,足夠大,可以表示任何物件的大小(以位元組為單位)。在 32 位系統上,它至少有 32 位。在 64 位系統上,它至少有 64 位。
1 2
|
for(size_t i = 0; i < a_vector.size(); ++i)
std::cout << a_vector[i] << std::endl;
|
或者,如果你只想測試 vector 是否為空,
std::vector::empty 函式返回一個布林值,如果 vector 中沒有元素,則為 true,否則為 false。
1 2 3 4
|
if(a_vector.empty())
std::cout << "The vector wishes to be an Equalist." << std::endl;
else
std::cout << "This vector wishes to become Mendeleev." << std::endl;
|
除了 [] 運算子,vector 還提供了
std::vector::at 成員函式。它接受與運算子相同的引數,並像運算子一樣返回一個引用。然而,區別在於它會檢查提供的索引是否小於 vector 的大小。如果不是,它會丟擲異常,而 [] 運算子則可能做任何事情。通常,它要麼訪問程式未保留的記憶體,要麼導致段錯誤,這可能會導致程式崩潰。因此,at() 的速度會略慢一些,但如果出現問題,更容易除錯。
1 2
|
a_vector[a_vector.size()]; //Herp. Undefined.
a_vector.at(a_vector.size()); //Herp. Exception.
|
為了方便起見,vector 還提供了一些函式來獲取索引為 0 的元素(vector 的開頭)和索引為 size-1 的元素(vector 的末尾)。它們的命名直觀。
1 2
|
an_int_vector.front() = 3; //Sets the first element equal 5... I mean 3.
a_char_vector.back() = '\n'; //Sets the last element equal to a newline.
|
向 vector 的末尾新增一個新元素非常簡單。Vector 提供了
std::vector::push_back 函式,它接受一個元素,該元素被複制(或移動)到 vector 的末尾(記住:末尾 = 最大索引),並使其大小增加一。
1 2 3
|
a_vector_of_ints.push_back(7); //Add 7 onto the end of the vector.
a_vector_of_ints.push_back(3); //Add 3 onto the end of the vector, after 7.
a_vector_of_ints.push_back(-2); //Add -2 onto the end of the vector, after 3.
|
.
同樣,vector 還有一個
std::vector::pop_back 函式,它不接受任何引數,並刪除 vector 的最後一個元素,使其大小減一。如果適用,這會銷燬被刪除的元素。
1 2
|
a_vector_with_elements.pop_back(); //Remove the last element from the vector.
a_vector_with_elements.pop_back(); //Remove the new last element from the vector.
|
.
清空 vector 中的所有元素也很容易。呼叫一次
std::vector::clear 會刪除並銷燬 vector 中的所有元素,將其大小設定為 0。
1 2 3
|
a_vector_with_elements.clear(); //Now a misnomer!
if(!a_vector_with_elements.empty())
std::cout << "This line should never print." << std::endl;
|
為了方便地調整 vector 的大小,可以使用
std::vector::resize。它接受兩個引數,儘管第二個引數有一個預設值。第一個引數是要將 vector 調整到的元素數量。如果這個值小於當前大小,那麼末尾多餘的元素(索引更大)將被銷燬。第二個引數是在第一個引數大於當前大小時,用於初始化新元素的值。
1 2 3 4
|
std::vector<Bunny> bunnies(20);
bunnies.resize(50); //More bunnies!
bunnies.resize(70, montyPythonKillerRabbit); //More killer bunnies!
bunnies.resize(20); //Herp, ran out of carrots (and humans).
|
如果需要交換 vector 的內容,還有另一個簡單的函式
std::vector::swap。它接受一個以引用方式傳遞的 vector 作為引數,並交換這兩個 vector 的內容。因此,傳遞的 vector 不應該是 const 的。
1 2 3 4
|
a_vector.swap(a_different_vector); //Vector contents are swapped.
a_vector.swap(a_different_vector); //Vectors are back to the way they were.
a_different_vector.swap(a_vector); //Same as line 1.
a_different_vector.swap(a_vector); //Same as line 2.
|
這些並不是 vector 的所有成員函式。還有其他一些可能也很有趣,其中一些需要關於迭代器的一些先備知識。而這……是另一篇文章的主題。
vector<bool>
當 vector 儲存 bool 值時,它們的行為略有不同。
通常,一個 bool 值會佔用一個位元組的記憶體。這通常是相當浪費的(用 8 位來儲存 1 位),而 C++ 標準庫的實現被允許在內部進行更改以減少浪費。這可能對效能有微小的影響。
更重要的是,這意味著 operator []、at()、front() 和 back() 實際上並不返回對布林值的引用(除非 vector 是 const 的)。相反,它們返回一個成員類的例項,該類例項的行為方式與布林值引用相同,即
std::vector<bool>:reference。雖然它們可以隱式轉換為 bool,但需要注意的是,它們本身並不是 bool。如果你使用 <type_traits> 標頭檔案進行任何操作,這一點至關重要。
引用類還提供了 flip() 成員函式來翻轉例項所引用的布林值。
bool_vec.at(3).flip();
儘管本文沒有討論迭代器,但對於知道迭代器的人來說,這個特化的迭代器在內部也不同。非 const 迭代器將返回該引用類的例項。否則,它們在正常使用中的行為應該與普通迭代器相同。
此外,
std::vector<bool>::swap 有一個額外的靜態版本,功能不同。這個靜態版本可以用來切換 std::vector<bool> 中兩個位的值。請注意,作為引數,它接受前面提到的 bool 引用,這意味著這實際上只適用於在同一 vector 中或在不同 vector 之間交換位值。
vector_1::flip(vector_1.front(),vector_2.back()); // 交換!
最後,增加了一個額外的成員函式:
std::vector<bool>::flip。它的唯一目的是翻轉 vector 中的所有值。
a_vector_of_false_values.flip(); // 現在名不副實!
如果出於任何原因不想使用此特化,可以考慮改用 std::vector<char> 並簡單地為其元素賦值布林值。
結論
Vector 並不是解決順序資料儲存的萬能解決方案,但它們作為方便的可調整大小的陣列相當強大。
-Albatross
技術細則:本文旨在作為一篇適合初學者程式設計師的非技術性文章,為此,可能會對所使用的模板引數做出假設,並可能使用技術上不精確的語言。