Array C-style punya kelemahan mendasar: ukurannya beku sejak deklarasi dan kita harus memikul sendiri pemantauan panjangnya. Kebutuhan nyata program adalah wadah yang tumbuh dan menyusut mengikuti data — daftar belanja yang bertambah, daftar skor yang berkurang. std::vector dari header <vector> adalah jawaban standar C++ untuk kebutuhan itu: array dinamis yang mengelola memori sendiri, membawa informasi ukuran, dan tetap memberi akses elemen secepat array biasa. Pada Chapter ini kita mempelajari operasi-operasi inti vector; satu kalimat konteks: vector termasuk container, keluarga struktur data yang dibahas lebih rinci pada Section Standard Library.

Operasi Dasar vector #

Operasi yang paling sering dipakai: push_back() menambah elemen di akhir, size() menanyakan jumlah elemen, operator[] dan at() mengakses elemen, pop_back() membuang elemen terakhir, dan clear() mengosongkan seluruh isi. Semuanya bekerja tanpa kita menyentuh memori secara manual.

cpp
#include <iostream>
#include <vector>

// pola pemisah: spasi hanya di antara elemen (bukan di akhir baris)
void cetak(const std::vector<int>& v) {
    for (std::size_t i = 0; i < v.size(); ++i) {
        std::cout << (i > 0 ? " " : "") << v[i];
    }
    std::cout << "\n";
}

int main() {
    std::vector<int> skor;  // container kosong, size = 0

    std::cout << "awal        : size = " << skor.size() << "\n";

    // push_back menambah elemen di akhir; ukuran menyesuaikan sendiri
    skor.push_back(70);
    skor.push_back(85);
    skor.push_back(60);
    std::cout << "setelah isi : ";
    cetak(skor);
    std::cout << "size        : " << skor.size() << "\n";

    // akses: operator[] tanpa pemeriksaan, .at() dengan pemeriksaan batas
    skor[0] = 75;
    std::cout << "at(1)       : " << skor.at(1) << "\n";

    // pop_back membuang elemen terakhir
    skor.pop_back();
    std::cout << "pop_back    : ";
    cetak(skor);
    std::cout << "size        : " << skor.size() << "\n";

    // variasi inisialisasi
    std::vector<int> seragam(3, 9);          // 3 elemen, semuanya 9
    std::vector<int> daftar = {1, 2, 3, 4};  // dari initializer list
    std::vector<int> salinan(daftar);        // salinan penuh dari daftar

    std::cout << "seragam     : ";
    cetak(seragam);
    std::cout << "daftar      : ";
    cetak(daftar);
    std::cout << "salinan     : ";
    cetak(salinan);

    // clear mengosongkan container
    skor.clear();
    std::cout << "clear       : size = " << skor.size() << "\n";

    return 0;
}

Output yang Dihasilkan:

text
awal        : size = 0
setelah isi : 70 85 60
size        : 3
at(1)       : 85
pop_back    : 75 85
size        : 2
seragam     : 9 9 9
daftar      : 1 2 3 4
salinan     : 1 2 3 4
clear       : size = 0

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 10 (std::vector<int> skor;): compiler membuat vector kosong bertipe int. Tidak ada elemen, dan size() bernilai 0 — kita tidak pernah perlu mendeklarasikan panjang di depan.
  2. Baris 14-16 (push_back): setiap panggilan menambahkan satu elemen ke akhir. Vector menyesuaikan penyimpanannya sendiri, dan size() ikut bertambah menjadi 3.
  3. Baris 22 (skor[0] = 75;): operator[] mengubah elemen tanpa pemeriksaan batas; baris berikutnya memakai at(1) yang memeriksa batas dan melempar exception bila indeks tidak sah — perbedaan keduanya penting dan dibahas kembali di bawah.
  4. Baris 26 (pop_back()): elemen terakhir (60) dibuang, sehingga isi berubah dari 70 85 60 menjadi 75 85 setelah elemen pertama diperbarui.
  5. Baris 32-34: tiga cara inisialisasi lain: (3, 9) membuat 3 elemen bernilai 9, initializer list {1, 2, 3, 4} mengisi langsung, dan konstruktor salinan meniru seluruh isi daftar.
  6. Baris 44 (clear()): seluruh elemen dibuang; size() kembali ke 0.

operator[] vs at() #

Keduanya mengakses elemen lewat indeks, tetapi perlakuannya terhadap indeks tidak sah berbeda. operator[] tidak melakukan pemeriksaan apa pun: indeks di luar rentang adalah undefined behavior, dan program bisa saja terus berjalan dengan data salah tanpa pesan. Sementara itu at() memeriksa batas setiap kali dipanggil dan melempar std::out_of_range bila indeks tidak sah, sehingga kesalahan terdeteksi di titik kejadian.

cpp Perbedaan perilaku pada indeks tidak sah
std::vector<int> v = {10, 20, 30};

// TIDAK AMAN: tidak ada pemeriksaan, undefined behavior
// std::cout << v[10];  // tidak sah, jangan lakukan

// AMAN: melempar std::out_range saat diakses
try {
    std::cout << v.at(10);
} catch (const std::exception& e) {
    std::cout << "Tertangkap: " << e.what() << "\n";
}

Mekanisme try dan catch di atas baru kita bahas tuntas pada Section Error Handling; untuk saat ini cukup catat bahwa at() memberi jalur pemeriksaan yang aman dengan biaya sedikit lebih besar per akses. Karena itu kebiasaan yang sehat adalah memakai at() selama pengembangan, lalu menurunkannya ke operator[] pada jalur kode yang sudah terbukti dan berada di dalam loop yang panas — pengukurannya akan kita pelajari pada Section Performa dan Optimasi.

Ukuran dan Jenis Elemen #

size() mengembalikan std::size_t, tipe unsigned yang cocok dipakai sebagai tipe variabel loop — hal inilah yang dilakukan fungsi cetak() pada contoh utama. Vector tidak terbatas pada int: kita dapat menyimpan std::string, double, char, atau tipe lain yang bisa disalin, dan penulisan tipenya eksplisit di sudut <>.

cpp
#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> nama = {"Budi", "Siti", "Dewi"};

    nama.push_back("Rina");

    for (std::size_t i = 0; i < nama.size(); ++i) {
        std::cout << i + 1 << ". " << nama[i] << "\n";
    }

    return 0;
}

Output yang Dihasilkan:

text
1. Budi
2. Siti
3. Dewi
4. Rina

Mari kita telaah: Baris 6 mendeklarasikan vector yang elemennya bertipe std::string, Baris 8 menambahkan satu nama baru, dan Baris 10-12 mencetak setiap elemen dengan nomor urut menggunakan nama[i]. Pola ini bekerja sama untuk tipe elemen apa pun — vector menyembunyikan detail penyimpanan, dan kita berurusan dengan antarmuka yang konsisten.

Chapter berikutnya membongkar bagian dalam vector: perbedaan antara size dan capacity, serta bagaimana vector menyusun ulang penyimpanannya saat tumbuh — pengetahuan yang menjelaskan mengapa push_back jarang terasa mahal.