C++ vector插入元素(資料)詳解

2020-07-16 10:04:29
通過使用成員函數 emplace(),可以在 vector 序列中插入新的元素。物件會在容器中直接生成,而不是先單獨生成物件,然後再把它作為引數傳入。

emplace() 的第一個引數是一個疊代器,它確定了物件生成的位置。物件會被插入到疊代器所指定元素的後面。第一個引數後的引數,都作為插入元素的建構函式的引數傳入。例如:
std::vector<std::string> words {"first", "second"};
// Inserts string(5,'A') as 2nd element
auto iter = words.emplace(++std::begin(words),5,'A');
//Inserts string ("$$$$") as 3rd element
words.emplace(++iter, "$$$$");
這段程式碼執行後,vector 中的字串物件如下:
"first" "AAAAA" "$$$$" "second"

在 emplace() 的第一個引數的後面,可以使用盡可能多的引數,只要它們是被插入物件的建構函式所需要的。在上面的程式碼片段中,第一次呼叫 emplace() 會得到一個由建構函式 string(5,'A') 生成的字串物件。emplace() 會返回一個指向橫入元素的疊代器,被用來在插入元素的後面,插入一個新的元素。

成員函數 insert() 可以在 vector 中插入一個或多個元素。第一個引數總是一個指向插入點的 const 或 non-const 疊代器。元素會被迅速插入到第一個引數所指向元素的前面,如果第一個引數是一個反向疊代器,元素會被插入到疊代器所指向元素的後面。如果選擇使用 insert() 來插入元素,稍後會分別闡述每一種可能的情況。會先定義一個 vector,然後列出一個相繼呼叫 insert() 的列表:
std::vector<std::string> words { "one","three","eight"} //Vector with 3 elements
下面介紹一些使用 insert() 插入單詞的方式:

1) 插入第二個引數指定的單個元素

auto iter = words.insert(++std::begin(words), "two");
在這個範例中,插入點是由 begin() 返回的疊代器遞增後得到的。它對應第二個元素,所以新元素會作為新的第二個元素插入,之前的第二個元素以及後面的元素,為了給新的第二個元素留出空間,都會向後移動一個位置。這裡有兩個 insert 過載版本,它們都可以插入單個物件,其中一個的引數是 constT& 型別,另一個是 T&&類 型——右值參照。因為上面的第二個引數是一個臨時物件,所以會呼叫第二個函數過載版本,臨時物件會被移動插入而不是被複製插入容器。

執行完這條語句後,words rector 容器包含的字串元素為:
"one" "two" "three" "eight"

返回的疊代器指向被插入的元素 string(”two”)。需要注意的是,在使用同樣引數的情況下,呼叫 insert() 沒有呼叫 emplace() 高效。在 insert() 呼叫中,建構函式呼叫 string("two")  生成了一個物件,作為傳入的第二個引數。在 emplace() 呼叫中,建構函式用第二個引數直接在容器中生成了字串物件。

2) 插入一個由第二個和第三個引數指定的元素序列

std:: string more[] {"five", "six", "seven" }; // Array elements to be inserted
iter = words.insert(--std::end(words) , std::begin(more), std::end(more));
第二條語句中的插入點是一個疊代器,它是由 end() 返回的疊代器遞減後得到的。對應最後一個元素,因此新元素會被插入到它的前面。執行這條語句後,words vector 容器中的字串物件為:
"one" "two" "three" "five" "six" "seven" "eight"

返回的疊代器指向插入的第一個元素"five"。

3) 在 vector 的末尾插入一個元素

iter = words.insert(std::end(words), "ten");
插入點是最後一個元素之後的位置,因此新元素會被新增到最後一個元素之後。執行完這條語句後,words vector 容器中的字串物件如下:
"one" "two" "three" "five" "six" "seven" "eight" "ten"

返回的疊代器指向插入的元素"ten”。這和上面的情況 1) 相似;這表明,當第一個引數不指向元素而是指向最後一個元素之後的位置時,它才發揮作用。

4) 在插入點插入多個單個元素。第二個引數是第三個引數所指定物件的插入次數

iter = words.insert(std::cend(words)-1, 2, "nine");
插入點是最後一個元素,因此新元素 string("nine") 的兩個副本會被插入到最後一個元素的前面。

執行完這條語句後,words vector 容器中的字串物件如下:
"one" "two" "three" "five" "six" "seven" "eight" "nine" "nine" "ten"

返回的疊代器指向插入的第一個元素"nine"。注意,範例中的第一個引數是一個 const 疊代器,這也表明可以使用 const 疊代器。

5) 在插入點,插入初始化列表指定的元素。第二個引數就是被插入元素的初始化列表

iter = words.insert(std::end(words), {std::string {"twelve"},std::string {"thirteen"}});
插入點越過了最後一個元素,因此初始化列表中的元素被新增到容器的尾部。執行完這條語句後,words vector 容器中的字串物件如下:
"one" "two" "three" "five" "six" "seven" "eight" "nine" "nine" "ten" "twelve" "thirteen"

返回的疊代器指向插入的第一個元素"twelve"。初始化列表中的值必須和容器的元素型別相匹配。T 型別值的初始化列表是std::initializer_list<T>,所以這裡的 list 型別為 std::initializer_list<std::string>。前面的 insert() 呼叫中以單詞作為引數的地方,引數型別是 std::string,所以單詞作為字串物件的初始值被傳入到函數中。

記住,所有不在 vector 尾部的插入點都會有開銷,需要移動插入點後的所有元素,從而為新元素空出位置。當然,如果插入點後的元素個數超出了容量,容器會分配更多的記憶體,這會增加更多額外開銷。

vector 的成員函數 insert(),需要一個標準的疊代器來指定插入點;它不接受一個反向疊代器——這無法通過編譯。如果需要查詢給定物件的最後一個元素,或者在它的後面插入一個新的元素,就需要用到反向疊代器。這裡有一個範例:
std::vector<std::string> str { "one", "two", "one", "three"};
auto riter = std::find(std::rbegin(str), std::rend(str) , "one");
str.insert(riter.base(), "five");
fmd() 演算法會在頭兩個引數所指定的一段元素中,搜尋第三個引數指定的元素,返回第一個找到的元素,因此會找到 String("one")。它會返回一個疊代器,這個疊代器和用來指定搜尋範圍的疊代器有相同的型別,是一個指向匹配元素的反向疊代器。如果沒有找到匹配的元素,那麼它就是指向第一個元素之前位置的疊代器 rend(str)。使用反向疊代器意味著 fmd()會找到最後匹配的元素;使用標準迭代器會找到第一個匹配的元素,如果沒有匹配的元素,會返回 end(str)。

呼叫 riter 的成員函數 base() 可以得到一個標準迭代器,從序列反方向來看,它指向 riter 前的一個位置,也是朝向序列結束的方向。因為 riter 指向第三個元素,也就是“one”,所以 riter.base() 指向第 4 個元素“three”。如果使用 riter.base() 作為 insert() 的第一個引數,“five”將被插入到這個位置之前,也就是 riter 所指向元素的後面。執行完這些語句後,str 容器會包含下面 5 個字串元素:
"one", "two", "one", "five", "three"

如果想把插入點變成 fmd() 返回位置的前一個位置,需要將 insert() 的第一個引數變為 iter.base()-1。