訪問元素
在 std::vector
中有兩種主要的訪問元素的方法
- 基於索引的訪問
- 迭代器
基於索引的訪問:
這可以使用下標運算子 []
或成員函式 at()
來完成 。
兩者都返回對 std::vector
中相應位置的元素的引用(除非它是一個 vector<bool>
),因此它既可以被讀取也可以被修改(如果向量不是 const
)。
[]
和 at()
的不同之處在於 []
不能保證執行任何邊界檢查,而 at()
確實如此。訪問 index < 0
或 index >= size
的元素是 []
的未定義行為 ,而 at()
丟擲了一個 std::out_of_range
異常。
注意: 為清楚起見,下面的示例使用 C++ 11 樣式初始化,但運算子可以與所有版本一起使用(除非標記為 C++ 11)。
Version => C++ 11
std::vector<int> v{ 1, 2, 3 };
// using []
int a = v[1]; // a is 2
v[1] = 4; // v now contains { 1, 4, 3 }
// using at()
int b = v.at(2); // b is 3
v.at(2) = 5; // v now contains { 1, 4, 5 }
int c = v.at(3); // throws std::out_of_range exception
因為 at()
方法執行邊界檢查並且可以丟擲異常,所以它比 []
慢。這使得 []
成為首選程式碼,其中操作的語義保證索引在邊界內。在任何情況下,對向量元素的訪問都是在恆定時間內完成的。這意味著訪問向量的第一個元素與訪問第二個元素,第三個元素等具有相同的成本(及時)。
例如,考慮這個迴圈
for (std::size_t i = 0; i < v.size(); ++i) {
v[i] = 1;
}
在這裡我們知道索引變數 i
總是處於邊界內,因此在每次呼叫 operator[]
時檢查 i
是否在界限內會浪費 CPU 週期。
front()
和 back()
成員函式允許向量的第一和最後的元件容易參考存取,分別。這些位置經常使用,特殊訪問器比使用 []
的替代方案更具可讀性:
std::vector<int> v{ 4, 5, 6 }; // In pre-C++11 this is more verbose
int a = v.front(); // a is 4, v.front() is equivalent to v[0]
v.front() = 3; // v now contains {3, 5, 6}
int b = v.back(); // b is 6, v.back() is equivalent to v[v.size() - 1]
v.back() = 7; // v now contains {3, 5, 7}
注意 : 在空向量上呼叫 front()
或 back()
是未定義的行為 。在呼叫 front()
或 back()
之前,你需要使用 empty()
成員函式(檢查容器是否為空)來檢查容器是否為空。使用’empty()
‘測試空向量的一個簡單示例如下:
int main ()
{
std::vector<int> v;
int sum (0);
for (int i=1;i<=10;i++) v.push_back(i);//create and initialize the vector
while (!v.empty())//loop through until the vector tests to be empty
{
sum += v.back();//keep a running total
v.pop_back();//pop out the element which removes it from the vector
}
std::cout << "total: " << sum << '\n';//output the total to the user
return 0;
}
上面的例子建立了一個帶有 1 到 10 的數字序列的向量。然後它將向量的元素彈出,直到向量為空(使用’empty()
’)來防止未定義的行為。然後計算向量中的數字之和並顯示給使用者。
Version => C++ 11
該 data()
方法返回指向由 std::vector
使用的原料記憶體在內部儲存它的元件。在將向量資料傳遞給需要 C 樣式陣列的遺留程式碼時,最常使用此方法。
std::vector<int> v{ 1, 2, 3, 4 }; // v contains {1, 2, 3, 4}
int* p = v.data(); // p points to 1
*p = 4; // v now contains {4, 2, 3, 4}
++p; // p points to 2
*p = 3; // v now contains {4, 3, 3, 4}
p[1] = 2; // v now contains {4, 3, 2, 4}
*(p + 2) = 1; // v now contains {4, 3, 2, 1}
Version < C++ 11
在 C++ 11 之前,可以通過呼叫 front()
並獲取返回值的地址來模擬 data()
方法:
std::vector<int> v(4);
int* ptr = &(v.front()); // or &v[0]
這是有效的,因為向量總是保證將它們的元素儲存在連續的記憶體位置,假設向量的內容不會覆蓋一元 operator&
。如果是這樣,你將不得不在 pre-C++ 11 中重新實現 std::addressof
。它還假定向量不為空。
迭代器:
迭代器在“Iterating over std::vector
”和 Iterators 文章中有更詳細的解釋。簡而言之,它們的行為類似於向量元素的指標:
Version => C++ 11
std::vector<int> v{ 4, 5, 6 };
auto it = v.begin();
int i = *it; // i is 4
++it;
i = *it; // i is 5
*it = 6; // v contains { 4, 6, 6 }
auto e = v.end(); // e points to the element after the end of v. It can be
// used to check whether an iterator reached the end of the vector:
++it;
it == v.end(); // false, it points to the element at position 2 (with value 6)
++it;
it == v.end(); // true
std::vector<T>
的迭代器實際上是標準的 38s 符合標準,但大多數標準庫不這樣做。不這樣做既改善了錯誤訊息,又捕獲了非可移植程式碼,並且可用於通過非釋出版本中的除錯檢查來檢測迭代器。然後,在釋出版本中,圍繞底層指標的類被優化掉。
你可以將引用或指標持久儲存到向量的元素以進行間接訪問。除非你在 vector
中的元素之前或之前新增/刪除元素,否則這些對 vector
中元素的引用或指標保持穩定並且訪問仍然定義,或者你導致 vector
容量發生變化。這與使迭代器無效的規則相同。
Version => C++ 11
std::vector<int> v{ 1, 2, 3 };
int* p = v.data() + 1; // p points to 2
v.insert(v.begin(), 0); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
v.reserve(10); // p is now invalid, accessing *p is a undefined behavior.
p = v.data() + 1; // p points to 1
v.erase(v.begin()); // p is now invalid, accessing *p is a undefined behavior.