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.
#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:
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 = 0Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 10 (
std::vector<int> skor;): compiler membuat vector kosong bertipeint. Tidak ada elemen, dansize()bernilai 0 — kita tidak pernah perlu mendeklarasikan panjang di depan. - Baris 14-16 (
push_back): setiap panggilan menambahkan satu elemen ke akhir. Vector menyesuaikan penyimpanannya sendiri, dansize()ikut bertambah menjadi 3. - Baris 22 (
skor[0] = 75;):operator[]mengubah elemen tanpa pemeriksaan batas; baris berikutnya memakaiat(1)yang memeriksa batas dan melempar exception bila indeks tidak sah — perbedaan keduanya penting dan dibahas kembali di bawah. - Baris 26 (
pop_back()): elemen terakhir (60) dibuang, sehingga isi berubah dari70 85 60menjadi75 85setelah elemen pertama diperbarui. - 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 isidaftar. - 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.
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 <>.
#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:
1. Budi
2. Siti
3. Dewi
4. RinaMari 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.