Setiap variabel yang kita deklarasikan — baik satu angka kecil maupun struct berisi puluhan field — menempati satu lokasi di memori komputer. Pointer adalah variabel yang menyimpan alamat lokasi tersebut, bukan isi nilainya. Dengan pointer, sebuah fungsi dapat membaca dan menulis nilai yang sama persis dengan variabel milik pemanggilnya tanpa menyalin data berulang kali. Chapter ini membahas dua operator inti pointer, membuktikan bahwa sebuah pointer menunjuk objek yang sama, lalu menutup dengan jaminan keselamatan yang membuat pointer Go jauh lebih aman daripada kerabatnya di C.

Variabel Menempati Alamat Memori #

Compiler Go menempatkan setiap variabel pada satu alamat memori berukuran tetap sesuai tipenya. Format verb %p pada fmt.Printf menampilkan alamat tersebut sebagai deretan heksadesimal, misalnya hasil fmt.Printf("%p\n", &x) berbentuk 0xc0000a0018. Nilai alamat berbeda pada setiap eksekusi program karena sistem operasi menempatkan memori secara dinamis. Karena alamat selalu berubah, contoh-contoh pada chapter ini membuktikan kesamaan objek secara logis dengan operator ==, bukan dengan mencetak alamat mentahnya.

Operator & dan * #

Operator & mengambil alamat sebuah variabel. Operator * memiliki dua peran: pada konteks tipe, *int mendeklarasikan pointer ke int; pada konteks ekspresi, *p melakukan dereference — membaca atau menulis nilai pada alamat yang ditunjuk. Tabel berikut merangkum tiga bentuk pemakaian yang paling sering kita jumpai:

BentukKeterangan
p := &xp menyimpan alamat x; tipenya *int bila x bertipe int
var p *intdeklarasi pointer tanpa nilai awal; nilai bawaannya nil
*pdereference: baca atau tulis nilai pada alamat yang ditunjuk

Tiga bentuk tersebut membentuk satu pola kerja: simpan alamat dengan &, lalu akses nilainya dengan *. Dereference juga dapat menjadi tujuan penugasan, sehingga penulisan lewat pointer mengubah variabel asli secara langsung.

Contoh Utuh: Membaca dan Menulis Lewat Pointer #

Program berikut mempraktikkan seluruh pola dasar pointer dalam satu berkas konsep-pointer-demo.go:

go
package main

import "fmt"

type Titik struct {
	X, Y int
}

func main() {
	x := 42
	p := &x // p bertipe *int, menyimpan alamat x

	fmt.Println("nilai x      :", x)
	fmt.Println("nilai *p     :", *p) // membaca nilai lewat pointer

	*p = 100 // menulis nilai lewat pointer
	fmt.Println("x setelahnya :", x)

	var q *int                              // deklarasi tanpa nilai awal
	fmt.Println("q == nil     :", q == nil) // zero value pointer adalah nil

	fmt.Println("p == &x      :", p == &x) // bukti p menunjuk x

	t := Titik{X: 3, Y: 4}
	pt := &t
	pt.X = 10 // gula sintaksis dari (*pt).X
	fmt.Println("t.X          :", t.X, "t.Y:", t.Y)
}

Jalankan berkas tersebut dari direktori chapter:

bash
go run konsep-pointer-demo.go

Output yang Dihasilkan:

text
nilai x      : 42
nilai *p     : 42
x setelahnya : 100
q == nil     : true
p == &x      : true
t.X          : 10 t.Y: 4

Mari kita telaah baris demi baris program di atas:

  1. Baris 10 (x := 42): variabel x dibuat dan diisi 42 pada sebuah alamat memori.
  2. Baris 11 (p := &x): operator & mengambil alamat x dan menyimpannya pada p bertipe *int.
  3. Baris 14 (*p): dereference membaca nilai di alamat tersebut — hasilnya 42, sama dengan isi x.
  4. Baris 16 (*p = 100): dereference sebagai tujuan penugasan menulis 100 ke alamat x, dibuktikan baris 17.
  5. Baris 19-20 (var q *int): pointer tanpa nilai awal bernilai nil, zero value seluruh tipe pointer.
  6. Baris 22 (p == &x): hasil true membuktikan keduanya menunjuk objek yang sama tanpa mencetak alamat.
  7. Baris 25-26 (pt.X = 10): penulisan pt.X diterjemahkan menjadi (*pt).X, sehingga field X pada struct t berubah.

Zero Value Pointer adalah nil #

Pointer tanpa nilai awal bernilai nil, bukan alamat acak; nil menandakan pointer belum menunjuk objek mana pun. Compiler tidak selalu dapat membuktikan sebuah pointer bernilai nil saat kompilasi, sehingga dereference pointer nil lolos kompilasi dan baru meledak saat runtime sebagai panic bertuliskan runtime error: invalid memory address or nil pointer dereference. Kode yang menerima pointer dari sumber yang tidak terjamin sebaiknya memeriksa p != nil sebelum dereference.

Pointer Tidak Memiliki Aritmetika #

Di bahasa C, program dapat menghitung p + 1 untuk bergeser ke alamat berikutnya dan menjelajahi array lewat alamat mentah. Go sengaja menghapus fitur tersebut: sebuah pointer hanya boleh menunjuk awal objek yang sah, dan compiler menolak operasi hitung apa pun terhadap pointer. Larangan ini mencegah program membentuk alamat sembarangan — sumber bug klasik di bahasa level rendah. Simpan program kecil berikut sebagai aritmetika.go, lalu jalankan:

go
package main

func main() {
	x := 5
	p := &x
	p++
	_ = p
}
bash
go run aritmetika.go

Output yang Dihasilkan:

text
# command-line-arguments
./aritmetika.go:6:2: invalid operation: p++ (non-numeric type *int)

Pesan galat tersebut muncul karena operator ++ hanya berlaku untuk tipe numerik, sedangkan *int adalah tipe pointer. Nama berkas pada pesan mengikuti lokasi penyimpanan program di komputer Anda.

Pointer ke Struct dan Gula Sintaksis #

Pointer ke struct adalah bentuk pointer yang paling sering dipakai program nyata. Penulisan pt.X = 10 sebenarnya diterjemahkan compiler menjadi (*pt).X = 10: tanda dereference ditambahkan otomatis agar kode tetap ringkas. Pembacaan field mengikuti aturan yang sama. Gula sintaksis ini tidak mengubah perilaku memori — program tetap menulis ke struct asli melalui alamatnya.

Referensi #

Dengan menguasai operator & dan *, kita sudah dapat membuat satu fungsi membaca sekaligus mengubah variabel milik pemanggilnya. Pertanyaan berikutnya adalah kapan pointer sebaiknya dipakai dan kapan nilai biasa sudah cukup — perbandingan yang kita bahas tuntas pada chapter berikutnya.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

konsep-pointer-demo.go

Unduh berkas
go
package main

import "fmt"

type Titik struct {
	X, Y int
}

func main() {
	x := 42
	p := &x // p bertipe *int, menyimpan alamat x

	fmt.Println("nilai x      :", x)
	fmt.Println("nilai *p     :", *p) // membaca nilai lewat pointer

	*p = 100 // menulis nilai lewat pointer
	fmt.Println("x setelahnya :", x)

	var q *int                              // deklarasi tanpa nilai awal
	fmt.Println("q == nil     :", q == nil) // zero value pointer adalah nil

	fmt.Println("p == &x      :", p == &x) // bukti p menunjuk x

	t := Titik{X: 3, Y: 4}
	pt := &t
	pt.X = 10 // gula sintaksis dari (*pt).X
	fmt.Println("t.X          :", t.X, "t.Y:", t.Y)
}