Banyak struktur data (linked list, tree, graph) memerlukan kita untuk menunjuk ke memori lain secara eksplisit. Pointer dan reference adalah dua sarana C++ untuk tujuan tersebut — keduanya menerjemahkan ke alamat memori, tetapi dengan sintaks dan jaminan yang berbeda. Pada Chapter ini kita akan mempelajari pointer, reference, pointer arithmetic, dan bagaimana pemilihan mode pass (value vs reference vs pointer) menentukan apakah fungsi bisa memodifikasi data pemanggil.

Pointer: Alamat sebagai Variabel #

Pointer adalah variabel yang menyimpan alamat memori. Kita mendeklarasikannya dengan T*, mengambil alamat variabel biasa dengan &, dan mengakses nilai yang ditunjuk dengan dereference *.

Mekanisme: Alamat dan Dereference #

Ketika kita menulis int a = 10; int* p = &a;, compiler memesan memori untuk a (4 byte), lalu memesan memori terpisah untuk p yang berisi alamat a. Mengakses *p berarti membaca nilai pada alamat yang tersimpan di p — yang sama dengan a.

Kode: Pointer dan Dereference #

cpp
#include <iostream>

int main() {
    int a = 10;
    int* p = &a;        // p menyimpan alamat a

    std::cout << "a  = " << a << '\n';
    std::cout << "*p = " << *p << '\n';
    std::cout << "p  = " << p << '\n';

    *p = 25;            // ubah nilai a lewat pointer
    std::cout << "a  = " << a << '\n';
    return 0;
}

Output yang Dihasilkan:

text
a  = 10
*p = 10
p  = 0x7ffdce2c157c
a  = 25
  1. Baris int* p = &a;: &a mengambil alamat a. p sekarang menunjuk ke a.
  2. Baris *p: operator dereference — akses nilai pada alamat yang tersimpan di p, yaitu nilai a (10).
  3. Baris *p = 25;: menulis 25 ke alamat p, yang sama dengan mengubah nilai a. Setelah baris ini, a menjadi 25.

Reference: Alias untuk Variabel #

Reference (T&) adalah alias — nama lain untuk variabel yang sudah ada. Setelah diinisialisasi, reference tidak bisa di- rebind ke variabel lain, dan tidak bisa null.

Kode: Reference sebagai Alias #

cpp
#include <iostream>

int main() {
    int a = 10;
    int& r = a;         // r adalah alias a

    std::cout << "r  = " << r << '\n';
    r = 50;             // mengubah a
    std::cout << "a  = " << a << '\n';
    return 0;
}

Output yang Dihasilkan:

text
r  = 10
a  = 50
  1. Baris int& r = a;: r menjadi nama lain untuk a. Tidak ada memori tambahan untuk r — keduanya menunjuk slot yang sama.
  2. Baris r = 50;: menulis 50 ke slot yang sama dengan a. Setelah baris ini, a bernilai 50.

Pass by Value vs Reference vs Pointer #

Tiga cara melewatkan parameter memberi tiga jaminan berbeda. Pemilihan memengaruhi apakah fungsi bisa memodifikasi argumen pemanggil, dan apakah pemanggil wajib menangani null.

ModeSintaksModifikasi?Bisa null?
By valueint xTidakTidak berlaku
By referenceint& xYaTidak
By pointerint* pYa (lewat deref)Ya

Kode: Tiga Mode Pass #

cpp
#include <iostream>

void ubah_value(int x) {       // copy; tidak memengaruhi pemanggil
    x = 99;
}

void ubah_ref(int& x) {         // reference; memengaruhi pemanggil
    x = 99;
}

void ubah_ptr(int* p) {         // pointer; memengaruhi pemanggil
    if (p != nullptr) *p = 99;
}

int main() {
    int x = 1, y = 1, z = 1;
    ubah_value(x);
    ubah_ref(y);
    ubah_ptr(&z);
    std::cout << "x,y,z = " << x << ',' << y << ',' << z << '\n';
    return 0;
}

Output yang Dihasilkan:

text
x,y,z = 1,99,99
  1. ubah_value(x): x disalin ke parameter. Modifikasi di dalam fungsi tidak memengaruhi x di main → tetap 1.
  2. ubah_ref(y): parameter adalah alias y. Modifikasi langsung mengubah y → menjadi 99.
  3. ubah_ptr(&z): alamat z dilewatkan. Fungsi dereference untuk mengubah nilai z → menjadi 99. Pengecekan nullptr melindungi dari kasus pemanggilan ubah_ptr(nullptr).

Pointer Arithmetic #

Pointer ke elemen array mendukung operasi aritmetika: p + n maju n elemen, p - n mundur n elemen, p++ ke elemen berikutnya. Operasi ini adalah dasar iterator C++.

Mekanisme: Skala Sesuai Tipe #

p + 1 untuk int* sebenarnya menambah alamat sebanyak sizeof(int) byte (4 byte), bukan 1 byte. Compiler secara otomatis mengalikan offset dengan ukuran tipe yang ditunjuk.

Kode: Pointer Arithmetic pada Array #

cpp
#include <iostream>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* it = arr;                       // menunjuk elemen pertama

    std::cout << "*it       = " << *it << '\n';
    std::cout << "*(it+2)   = " << *(it + 2) << '\n';

    ++it;                                 // maju satu elemen
    std::cout << "*it now   = " << *it << '\n';
    return 0;
}

Output yang Dihasilkan:

text
*it       = 10
*(it+2)   = 30
*it now   = 20
  1. Baris int* it = arr;: nama array arr meluruh menjadi pointer ke elemen pertama. it menunjuk arr[0].
  2. Baris *(it + 2): alamat maju 2 elemen (= 8 byte untuk int), lalu dereference → arr[2] = 30.
  3. Baris ++it: memajukan it satu elemen. Sekarang *it sama dengan arr[1] = 20.

Null Pointer dan Keamanan #

Pointer bisa bernilai nullptr — penanda bahwa pointer tidak menunjuk ke mana-mana. Dereference nullptr adalah undefined behavior (umumnya segmentation fault).

Kode: Cek Null Sebelum Dereference #

cpp
#include <iostream>

int main() {
    int* p = nullptr;
    if (p == nullptr) {
        std::cout << "p null\n";
    } else {
        std::cout << *p << '\n';     // tidak aman!
    }
    return 0;
}

Output yang Dihasilkan:

text
p null
  1. Baris int* p = nullptr;: nullptr adalah literal null modern (sejak C++11) yang menggantikan NULL atau 0.
  2. Baris if (p == nullptr): pengecekan eksplisit sebelum dereference. Tanpa pengecekan, program bisa crash.

Return by Reference #

Fungsi bisa mengembalikan reference, sehingga pemanggil dapat memodifikasi objek asli langsung. Pola ini dipakai oleh operator[] pada std::vector dan std::map.

Kode: Akses Elemen via Return Reference #

cpp
#include <iostream>
#include <vector>

int& akses_elemen(std::vector<int>& v, std::size_t i) {
    return v[i];
}

int main() {
    std::vector<int> data{1, 2, 3};
    akses_elemen(data, 1) = 100;        // modifikasi langsung
    std::cout << "data[1] = " << data[1] << '\n';
    return 0;
}

Output yang Dihasilkan:

text
data[1] = 100
  1. Baris int& akses_elemen(...): return type int& — reference ke elemen, bukan copy.
  2. Baris akses_elemen(data, 1) = 100;: hasil pemanggilan fungsi adalah reference, sehingga = 100 menulis langsung ke data[1]. Tanpa &, assignment hanya mengubah copy lokal yang langsung dihancurkan.

Ringkasan #

  • Pointer menyimpan alamat; reference adalah alias yang tidak bisa null dan tidak bisa di-rebind.
  • Dereference pointer dengan *, ambil alamat dengan &.
  • Pilih mode pass: by value untuk data kecil read-only, by const reference untuk data besar read-only, by reference/pointer untuk modifikasi.
  • Pointer arithmetic otomatis diskalakan dengan sizeof(T).
  • Cek nullptr sebelum dereference; gunakan nullptr literal modern, bukan NULL.
  • Return by reference memungkinkan pola f(x) = value seperti pada operator[] STL.