Menu

Penjelasan Iterator C++: begin, end, dan Jenis Iterator

Cara kerja iterator C++ sebagai pointer umum ke dalam container: begin() dan end(), dereference, memajukan iterator, varian const/reverse, serta jebakan invalidasi dan dereference end() yang menyebabkan undefined behavior.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Apa Sebenarnya Iterator Itu

Setiap container standar, yaitu vector, string, map, set, list, menyimpan elemennya dengan cara yang berbeda di dalam. vector adalah blok yang bersambung, map adalah balanced tree, list adalah node yang tertaut. Namun kamu bisa melakukan perulangan pada semuanya dengan cara yang sama. Yang memungkinkan hal itu adalah iterator: objek kecil yang "menunjuk" ke satu elemen dan tahu cara melangkah ke elemen berikutnya.

Anggap iterator sebagai pointer yang diperluas. Kamu mendapatkannya dari begin(), membaca elemen yang ditunjuknya dengan *, dan memajukannya dengan ++. Potongan-potongan itu tersusun seperti ini:

v.begin() mengembalikan iterator ke elemen pertama; *it memberimu elemen itu; ++it berpindah ke elemen berikutnya. Ketiganya, yaitu dereference, memajukan, dan membandingkan, adalah seluruh model mentalnya.

begin(), end(), dan Range Setengah Terbuka

Separuh gambaran lainnya adalah end(). Yang penting, end() tidak menunjuk ke elemen terakhir; ia menunjuk ke posisi satu setelah elemen terakhir. Ini adalah range "setengah terbuka" yang disengaja, [begin, end): begin termasuk, end adalah tanda berhenti.

Desain itu membuat loop standarnya rapi, karena kamu berjalan sampai iterator sama dengan end():

Perhatikan it != v.end(), bukan it < v.end(). Sebagian besar iterator container (seperti map atau list) tidak mendukung <, hanya == dan !=, jadi != adalah pilihan yang portabel. Dan auto menghemat kamu dari menulis vector<int>::iterator secara manual, karena compiler menyimpulkannya.

Kasus container kosong tertangani dengan sendirinya: saat container kosong, begin() == end(), jadi isi loop tidak pernah berjalan. Tidak perlu penanganan khusus.

Jangan Pernah Dereference end()

Bug iterator yang paling umum adalah melakukan dereference pada end(). Karena ia menunjuk satu posisi setelah elemen terakhir, *v.end() membaca memori yang bukan milikmu. Ini undefined behavior, artinya crash atau sampah diam-diam, bukan error yang ramah:

vector<int> v = {1, 2, 3};
cout << *v.end();   // UNDEFINED BEHAVIOR - end() is not an element

Jebakan yang sama mengenai fungsi pencarian. std::find mengembalikan end() kalau tidak menemukan nilainya, jadi kamu harus mengecek sebelum melakukan dereference:

Selalu bandingkan iterator yang dikembalikan dengan end() sebelum melakukan dereference. Lupa menulis if ini adalah salah satu sumber crash paling sering di kode STL buatan pemula.

const, cbegin, dan Reverse Iterator

Container menyediakan beberapa jenis iterator, tergantung kebutuhanmu:

  • begin() / end(): iterator baca/tulis biasa (*it = ... berfungsi).
  • cbegin() / cend(): const_iterator; kamu bisa membaca lewatnya tetapi tidak bisa mengubah elemennya.
  • rbegin() / rend(): reverse iterator yang berjalan dari belakang ke depan; ++ sebenarnya bergerak mundur.

Reverse iterator adalah cara yang rapi untuk melakukan perulangan secara terbalik tanpa perhitungan indeks yang merepotkan:

Dengan reverse iterator kamu tetap menulis ++it untuk maju; iterator-nya menangani arah "mundur" secara internal. Pakai cbegin()/cend() (atau referensi const ke container) saat sebuah loop hanya perlu membaca, supaya compiler mencegahmu menulis secara tidak sengaja.

Iterator Map Menghasilkan Pair

Tidak setiap iterator hanyalah pembungkus tipis di atas pointer. Iterator std::map menelusuri sebuah tree, dan dereference padanya memberimu std::pair berisi key dan value, yang diakses lewat ->first dan ->second (sama seperti pointer, iterator mendukung ->):

Range-based for loop dibangun langsung di atas begin()/end(), jadi untuk perulangan maju yang biasa kamu umumnya akan memakai itu. Iterator eksplisit berguna saat kamu butuh penelusuran mundur, posisi sebuah elemen, atau meneruskan sebuah range ke algoritma.

Jebakan Besar: Iterator Invalidation

Inilah jebakan yang cepat atau lambat menjebak semua orang. Saat kamu mengubah struktur container, iterator yang sudah ada bisa menjadi tidak valid (invalidated): ia menunjuk ke memori yang sudah dibebaskan atau dipindahkan. Memakainya adalah undefined behavior.

Untuk vector, push_back bisa merealokasi seluruh buffer untuk memperbesarnya, sehingga setiap iterator yang masih dipegang menjadi tidak valid. Menghapus elemen di tengah loop bahkan lebih terkenal; ini crash yang klasik:

vector<int> v = {1, 2, 3, 4};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it % 2 == 0)
        v.erase(it);   // BUG - erase invalidates it, then ++it is UB
}

Solusinya: erase mengembalikan iterator yang valid ke elemen setelah elemen yang dihapus. Majukan iterator hanya kalau kamu tidak menghapus apa pun:

Perhatikan bahwa header for tidak punya ++it; isi loop yang memutuskan apakah iterator dimajukan. (Di kode nyata, idiom erase-remove atau std::erase_if dari C++20 melakukan ini dalam satu baris.) Aturan yang perlu diingat: operasi apa pun yang menambah atau menghapus elemen bisa membuat iterator tidak valid, jadi jangan berpegangan pada iterator lama melewati perubahan seperti itu.

Berikutnya: Algoritma

Sekarang setelah kamu bisa menggambarkan sebuah range sebagai pasangan begin/end, kamu sudah membuka seluruh library algoritma STL. Fungsi seperti sort, find, count, dan accumulate tidak peduli container apa yang kamu punya; mereka bekerja pada range iterator, jadi pemanggilan yang sama berfungsi pada vector, array, atau potongan dari salah satunya. Berikutnya kita akan memakai iterator-iterator itu dan membiarkan standard library melakukan perulangan untukmu.

Pertanyaan yang sering diajukan

Apa itu iterator di C++?

Iterator adalah objek yang menunjuk ke sebuah elemen di dalam container dan tahu cara berpindah ke elemen berikutnya. Kamu mendapat yang pertama dengan container.begin() dan penanda satu posisi setelah akhir dengan container.end(). Lakukan dereference dengan *it untuk membaca atau menulis elemennya, dan majukan dengan ++it. Iterator adalah antarmuka bersama yang membuat algoritma STL bisa bekerja pada container apa pun.

Apa perbedaan iterator dan pointer di C++?

Untuk vector atau array, iterator berperilaku hampir persis seperti pointer: kamu melakukan dereference dengan *, memajukannya dengan ++, dan membandingkannya dengan ==/!=. Namun iterator adalah sebuah konsep, belum tentu pointer mentah: iterator map atau list menelusuri tree atau node yang tertaut, jadi ia berupa tipe class yang meng-overload * dan ++. Pointer adalah salah satu jenis iterator; iterator memperluas ide itu ke setiap container.

Apa penyebab iterator invalidation di C++?

Mengubah struktur container bisa membuat iterator yang sudah ada menunjuk ke memori yang sudah dibebaskan atau dipindahkan. Untuk vector, push_back bisa merealokasi dan membuat semua iterator tidak valid; erase membuat iterator di elemen yang dihapus dan sesudahnya tidak valid. Memakai iterator yang tidak valid adalah undefined behavior. Pakai iterator yang dikembalikan erase, atau pesan kapasitas sejak awal, supaya tetap aman.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai