Hampir semua loop iterator dan semua algoritma STL berdiri di atas dua titik: begin() yang menunjuk elemen pertama dan end() yang menandai batas akhir penelusuran. Pasangan ini menggambarkan rentang setengah terbuka [begin, end) — elemen pertama termasuk, elemen yang ditunjuk end() tidak. Chapter ini membahas dua bentuk pemanggilan yang tersedia, yaitu member function milik container dan free function std::begin/std::end, keunggulan masing-masing, dukungan terhadap array C-style, serta varian cbegin/cend untuk akses read-only. Kita juga membongkar makna end() yang sering disalahpahami: ia bukan elemen terakhir, melainkan satu langkah setelahnya.

Member Function vs Free Function #

Setiap container standar menyediakan member begin() dan end(), misalnya v.begin() pada std::vector. Selain itu, header <iterator> menyediakan free function std::begin(v) dan std::end(v) yang, untuk container, hanya memanggil member yang sama sehingga hasilnya identik. Lalu apa bedanya dan kapan masing-masing lebih tepat?

Keunggulan free function terletak pada satu hal penting: ia bekerja juga untuk array C-style mentah yang tidak punya member function. std::begin(arr) pada int arr[5] mengembalikan pointer int* ke elemen pertama, dan std::end(arr) mengembalikan pointer arr + 5. Dengan begitu, kode berbasis iterator tetap bisa ditulis tanpa peduli data disimpan di std::vector atau array biasa. Free function juga menjadi titik ekstensi: kita bisa menyediakan overload begin/end untuk tipe buatan sendiri sehingga tipe kita bisa ikut memakai algoritma STL.

cpp
#include <iostream>
#include <iterator>
#include <vector>

int main() {
    std::vector<int> data = {5, 10, 15};

    // Member function: data.begin() dan data.end()
    for (auto it = data.begin(); it != data.end(); ++it) {
        if (it != data.begin()) std::cout << ' ';
        std::cout << *it;
    }
    std::cout << '\n';

    // Free function std::begin/std::end: bekerja juga untuk array C-style
    int arr[] = {2, 4, 6, 8};
    for (auto it = std::begin(arr); it != std::end(arr); ++it) {
        if (it != std::begin(arr)) std::cout << ' ';
        std::cout << *it;
    }
    std::cout << '\n';

    // end - begin = jumlah elemen (hanya untuk iterator random access)
    std::cout << "Jumlah elemen arr: " << (std::end(arr) - std::begin(arr)) << '\n';

    // cbegin/cend: iterator const, elemen tidak boleh diubah
    for (auto it = data.cbegin(); it != data.cend(); ++it) {
        if (it != data.cbegin()) std::cout << ' ';
        std::cout << *it;
    }
    std::cout << '\n';

    // Pola loop kosong: pada container kosong, begin() == end()
    // sehingga badan loop tidak pernah dijalankan
    std::vector<int> kosong;
    int hitung = 0;
    for (auto it = kosong.begin(); it != kosong.end(); ++it) {
        ++hitung;
    }
    std::cout << "Loop pada vector kosong berjalan: " << hitung << " kali\n";
    return 0;
}

Output yang Dihasilkan:

text
5 10 15
2 4 6 8
Jumlah elemen arr: 4
5 10 15
Loop pada vector kosong berjalan: 0 kali

Bedah baris demi baris:

  1. Baris 9-13: iterasi dengan member data.begin() dan data.end(). Pembanding it != data.begin() dipakai hanya untuk memberi spasi pemisah sebelum setiap angka setelah yang pertama.
  2. Baris 17-21: iterasi array C-style memakai std::begin(arr) dan std::end(arr). Sintaksnya sama persis dengan container — inilah manfaat utama free function.
  3. Baris 24: selisih std::end(arr) - std::begin(arr) menghasilkan jumlah elemen, yaitu 4. Operasi pengurangan iterator hanya sah untuk kategori random access, dan pointer array termasuk kategori ini.
  4. Baris 28-32: cbegin() dan cend() mengembalikan iterator const; elemen tetap terbaca, tetapi penulisan *it = ... akan ditolak compiler.
  5. Baris 37-42: pada vector kosong, begin() dan end() bernilai sama sehingga loop berjalan nol kali tanpa perlu pemeriksaan khusus — pola aman yang berlaku untuk semua container.

end() = One Past the Last #

Makna end() adalah titik yang paling sering ditanyakan pemula: end() tidak menunjuk elemen apa pun, melainkan satu langkah setelah elemen terakhir (one past the last). Dereference end() adalah undefined behavior — kita hanya boleh membandingkannya, bukan membacanya. Desain ini sengaja dipilih karena membuat rentang setengah terbuka [begin, end) memiliki sifat yang rapi:

  • Rentang kosong dinyatakan dengan satu kondisi saja: begin() == end(). Tidak ada kasus khusus.
  • Jumlah elemen dihitung sebagai end() - begin() pada iterator random access, tanpa penyesuaian plus-minus satu.
  • Algoritma yang menambahkan elemen di posisi end() — misalnya std::copy dengan tujuan back_inserter — punya tempat alami untuk menulis.

Memilih Bentuk yang Tepat #

Sebagai aturan praktis: di dalam loop dan kode umum, pakai bentuk yang paling ringkas dan jelas. Member v.begin() lazim untuk kode container tunggal, sedangkan std::begin(v)/std::end(v) lebih tepat ketika kita menulis kode generik yang harus menerima array C-style maupun container. Untuk data yang tidak boleh berubah, pilih cbegin()/cend() (atau member pada object const) agar compiler menjadi penjaga — setiap usaha mengubah elemen melalui iterator const langsung menjadi compile error.

Dengan begin dan end sudah kokoh, kita siap membawa pasangan ini ke tingkat berikutnya: menyerapkannya ke algoritma siap pakai dari header <algorithm>, yang pola pemakaiannya kita bahas pada chapter berikutnya.

Rujukan resmi: std::begin, std::end dan member begin/end pada vector .