語法書 / AA 競程語法書 下冊 / 第十單元 / iterator:指向容器裡的位置

10.5 iterator:指向容器裡的位置

10.2 教的 push_back 能加元素、pop_back 能移除元素,但動得到的都只有尾端。想在中間插入或移除元素,得先回答一個問題:怎麼跟 vector 說「就是這個位置」?C++ 容器的標準答案是 iterator:一種特殊的型態,它的值是「容器裡的某個位置」。有點像上冊 7.7 的「陣列名稱代表開頭的記憶體位置」——iterator 存的不是元素本身,而是元素住在哪

vector 的 iterator 型態寫作 vector<int>::iterator——中間的兩個冒號 :: 表示「vector<int> 裡面的 iterator 型態」:每種容器都帶著自己的 iterator 型態,所以要這樣指名。兩個最重要的來源:

  • v.begin():指向第一個元素的 iterator。
  • v.end():指向最後一個元素的下一格——一個不裝資料的「結束位置」。像 8.6'\0' 一樣,它本身不是內容,只負責標示「到此為止」。但有個關鍵差別:'\0' 是陣列裡真的讀得到的一個字元,end() 指的位置沒有元素——*v.end() 是錯的(UB),end() 只能當「邊界」用,不能當「最後一個元素」用。順帶一提,空 vector 的 begin() 就等於 end()
圖 10-3:begin() 與 end()——含頭不含尾

拿到 iterator 之後,*it 取出它指向位置的值(這個 * 唸作解參考(dereference),跟乘法無關),it++ 往後移一格:

範例程式碼

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {10, 20, 30, 40};

    vector<int>::iterator it = v.begin();   // it 指向第一個元素
    cout << *it << '\n';                    // * 取出 it 指向位置的值
    it++;                                   // 往後移一格
    cout << *it << '\n';
    it += 2;                                // vector 的 iterator 可以一次跳好幾格
    cout << *it << '\n';
    cout << v.end() - v.begin() << '\n';    // 頭尾距離恰是元素個數
    return 0;
}

執行結果:

10
20
40
4

最後一行值得咀嚼:begin()end() 是「含頭不含尾」的一段,所以兩者的距離恰好是元素個數。順帶一提,「一次跳好幾格」(it += 2v.begin() + i)是 vector 這類容器的 iterator 才有的待遇,不是每種容器都行——Level 0 只用 vector 和 string,放心用。而「從 begin()end() 指定一整段」這個講法,正是下一單元 sort(v.begin(), v.end()) 的語言——那也是 iterator 在 Level 0 最重要的出場。

insert 與 erase

有了「位置」的語言,就能在任意位置動手:

寫法 功能 快慢
v.insert(it, x) it 指的位置插入 x(原本該處起的元素整段右移) O(n)
v.erase(it) 移除 it 指的元素(後面的元素整段左移) O(n)

搭配 v.begin() + i 就是「索引 i 的位置」:

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {10, 20, 30};
    v.insert(v.begin() + 1, 99);       // 在索引 1 的位置插入 99:{10, 99, 20, 30}
    v.erase(v.begin() + 3);            // 移除索引 3 的元素:{10, 99, 20}
    for (int i = 0; i < 3; i++) {
        cout << v[i] << '\n';
    }
    return 0;
}

執行結果:

10
99
20