Topik ini membahas iterator, mekanisme umum pustaka standar untuk mengunjungi elemen container satu demi satu. Pembahasannya mencakup pasangan begin() dan end(), penulisan perulangan dengan iterator secara manual, padanan range-based for di balik layar, serta peran iterator pada algoritma seperti std::sort. Setelah membaca topik ini, hubungan antara range-based for dan iterator beserta batas-batas keduanya sudah dapat dijelaskan.
Rentang dari begin sampai end#
Setiap container pustaka standar menyediakan pasangan member function begin() dan end(). Member function begin() mengembalikan iterator yang menunjuk elemen pertama, sedangkan end() mengembalikan iterator penanda yang berada tepat setelah elemen terakhir, bukan pada elemen itu sendiri. Keduanya membentuk rentang setengah terbuka (half-open range), yaitu rentang yang memuat elemen awal tetapi tidak memuat penanda akhirnya. Pada container kosong, begin() dan end() menunjuk tempat yang sama karena tidak ada elemen sama sekali.
#include <iostream>
#include <vector>
int main() {
std::vector<int> nilai = {70, 85, 90, 60};
for (std::vector<int>::iterator it = nilai.begin(); it != nilai.end(); ++it) {
std::cout << "Nilai: " << *it << '\n';
}
return 0;
}Nilai: 70
Nilai: 85
Nilai: 90
Nilai: 60Tipe iterator vector empat elemen itu tertulis panjang, yaitu std::vector<int>::iterator, dan pada kode sehari-hari bentuk ini biasanya diperpendek dengan auto. Kondisi perulangannya memakai !=, bukan <, karena keterbandingan dengan < hanya dijamin pada sebagian iterator. Putaran berhenti tepat ketika it mencapai penanda akhir, sehingga elemen terakhir tetap terkunjungi dan penanda end() tidak pernah dibaca isinya.
Meskipun tertulis panjang, tipe iterator perlu dipahami sebagai satu konsep yang sama pada semua container. Vector, std::string, dan container lain menyediakan tipe iterator masing-masing, tetapi operasi yang diperlukan perulangan selalu sama, yaitu perbandingan, penambahan satu langkah, dan dereference. Kesamaan operasi inilah yang memungkinkan satu pola perulangan dipakai untuk semua container tanpa perubahan apa pun.
Perulangan dengan iterator dan padanannya#
Iterasi manual memakai tiga operasi, yaitu *it yang membaca elemen yang ditunjuk (dereference), ++it yang menggeser iterator ke elemen berikutnya, dan perbandingan != end() sebagai syarat berhenti. Range-based for pada topik perulangan sesungguhnya memakai operasi yang sama, sehingga kedua bentuk berikut menghasilkan perhitungan yang identik.
#include <iostream>
#include <vector>
int main() {
std::vector<int> harga = {3500, 12000, 7800, 4400};
int total = 0;
for (std::vector<int>::iterator it = harga.begin(); it != harga.end(); ++it) {
total += *it;
}
std::cout << "Total via iterator : " << total << '\n';
int totalRange = 0;
for (int h : harga) {
totalRange += h;
}
std::cout << "Total via range-for: " << totalRange << '\n';
return 0;
}Total via iterator : 27700
Total via range-for: 27700Kedua total bernilai sama karena range-based for diterjemahkan compiler menjadi perulangan iterator dengan pola yang tetap. Bentuk konseptualnya kurang lebih seperti potongan berikut, yang ditulis untuk memperlihatkan mekanismenya dan bukan untuk dijalankan.
// tidak-dijalankan (ilustrasi bentuk yang dibentuk compiler dari range-based for)
auto&& rentang = harga;
auto mulai = rentang.begin();
auto akhir = rentang.end();
for (; mulai != akhir; ++mulai) {
int h = *mulai;
totalRange += h;
}Nama internal pada padanan itu sesungguhnya dapat berupa apa pun, karena yang diperiksa compiler hanyalah polanya, yaitu pemanggilan begin() dan end(), perbandingan dengan !=, penambahan satu langkah, dan dereference pada setiap putaran. Karena polanya tetap, container apa pun yang menyediakan operasi tersebut otomatis mendukung range-based for. Pemahaman ini sekaligus menjelaskan mengapa range-based for gagal bekerja ketika container diubah strukturnya di tengah perulangan, sebab pasangan iterator yang dipegang perulangan menjadi tidak sah.
Kesimpulannya, range-based for adalah singkatan penulisan, bukan fitur yang bekerja dengan cara berbeda. Apabila indeks tidak diperlukan, bentuk singkatnya lebih disarankan, sedangkan perulangan iterator manual dipakai ketika posisi berjalan perlu dikendalikan, misalnya melompat ke elemen tertentu atau mengunjungi dua container sekaligus.
Operator dereference juga dapat berada di sisi kiri penugasan, sehingga *it = 100; mengubah elemen yang ditunjuk oleh it. Apabila elemen hanya dibaca, iterator konstan const_iterator yang diperoleh melalui cbegin() dan cend() menegaskan larangan penulisan itu secara eksplisit pada tipe iteratornya. Pola ini sejajar dengan konvensi const pada parameter fungsi, yaitu menyatakan niat membaca lewat tipe, bukan lewat komentar.
Iterator sebagai bahasa bersama algoritma#
Algoritma pustaka standar berkomunikasi dengan container melalui pasangan iterator, bukan melalui tipe container-nya. Fungsi std::sort dari header <algorithm> menerima rentang yang akan diurutkan dalam bentuk nilai.begin() dan nilai.end(), lalu mengurutkan elemen di antara keduanya secara menaik pada tempat aslinya. Pola yang sama dipakai std::find, std::reverse, dan algoritma lainnya, sehingga kebiasaan membaca pasangan begin() dan end() langsung terpakai di sana.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> nilai = {85, 60, 90, 70};
std::sort(nilai.begin(), nilai.end());
for (int n : nilai) {
std::cout << n << ' ';
}
std::cout << '\n';
return 0;
}60 70 85 90 Keluaran diakhiri satu spasi karena setiap angka dicetak disertai satu spasi pemisah. Apabila spasi akhir tidak diinginkan, pemisahan yang rapi memerlukan pola pencetakan khusus. Yang penting dari contoh ini bukan pengurutannya, melainkan tanda tangannya: satu fungsi std::sort mampu mengurutkan vector, array, dan container lain karena semuanya menyediakan begin() dan end(). Pembahasan algoritma pustaka standar diperdalam pada modul STL tersendiri.
Algoritma lain memakai pola yang serupa. Fungsi std::find menyusuri rentang dari begin() sampai end() dan mengembalikan iterator yang menunjuk elemen pertama yang cocok, atau end() apabila tidak ada yang cocok. Hasilnya selalu diuji terhadap end() sebelum dipakai, mengikuti kebiasaan yang sama dengan pengujian std::string::npos pada pencarian teks.
Kesalahan umum#
Empat kesalahan berikut paling sering muncul ketika iterator dipakai secara manual.
- Melakukan dereference terhadap
end(). Iterator itu tidak menunjuk elemen, sehingga hasil pembacaannya tidak terdefinisi. Pastikan kondisi!= end()teruji sebelum*itdipakai. - Membandingkan iterator dengan
<pada container yang tidak menjamin keterbandingan tersebut. Gunakan!=agar perulangan tetap sah untuk semua container. - Mengubah struktur container di tengah perulangan.
push_back()dapat memicu realokasi yang menggugurkan iterator yang sedang dipakai, sehingga perulangannya berujung pada undefined behavior. Tangguhkan perubahan struktur sampai perulangan selesai. - Melupakan
++itpada bagian pembaruan. Tanpa penggeseran, iterator tidak pernah mencapaiend()dan perulangan berjalan tanpa henti.
Kesalahan-kesalahan tersebut sulit terlihat pada pembacaan cepat karena sintaksnya sah, mirip dengan pola kesalahan array pada topik pembuka modul ini. Kebiasaan yang menahan keempatnya sekaligus adalah memperlakukan end() sebagai penanda yang tidak pernah dibaca isinya, serta memperlakukan container yang sedang diulang sebagai struktur yang tidak boleh berubah. Kedua perlakuan itu cukup untuk sebagian besar perulangan iterator sehari-hari.
Ringkasan#
begin()danend()membentuk rentang setengah terbuka yang memuat seluruh elemen, dan container kosong ditandai oleh keduanya yang menunjuk tempat yang sama.- Perulangan iterator manual memakai dereference, penambahan, dan perbandingan dengan
end(), sedangkan range-based for adalah singkatan dari pola yang sama. - Algoritma pustaka standar seperti
std::sortmenerima pasangan iterator, sehingga mekanisme ini menjadi bahasa bersama antara container dan algoritma.
Referensi#
- Iterator library — cppreference.com — Gambaran pustaka iterator dan operasi dasarnya.
- std::vector::begin, std::vector::end — cppreference.com — Perilaku begin dan end pada vector.
- Range-for statement — cppreference.com — Kaidah range-based for dan padanan mekanisnya.
- Introduction to iterators — LearnCpp — Pengantar iterator, dereference, dan perulangannya.