Pointer adalah variabel biasa, sehingga pointer juga menempati alamat memori dan dapat ditunjuk oleh pointer lain. Hasilnya adalah pointer bertingkat seperti **int — pointer yang menunjuk pointer yang menunjuk bilangan. Kebutuhan nyatanya memang sempit: fungsi yang harus mengganti variabel pointer milik pemanggil, misalnya mengalihkan target penunjuk atau mengisi pointer keluaran. Chapter ini membahas cara kerja dereference bertingkat, mendemonstrasikan pengalihan target melalui **int, dan menegaskan batas kewajaran pemakaiannya agar kode tetap mudah dibaca.

Deklarasi dan Makna Pointer Bertingkat #

Setiap operator & menambah satu tingkat pada tipe pointer. Tabel berikut memperlihatkan hubungan tiga nilai yang saling terkait:

EkspresiTipeMakna
xintbilangan asli
p := &x*intpointer yang menunjuk x
pp := &p**intpointer yang menunjuk variabel pointer p

Dereference mengikuti tingkatnya: *pp menghasilkan nilai p bertipe *int, sedangkan **pp menghasilkan bilangan x bertipe int. Pola yang sama berlaku untuk tipe apa pun, termasuk **Titik untuk pointer ke pointer struct.

Contoh Utuh: Mengubah Pointer Milik Pemanggil #

Program berikut mendefinisikan fungsi gantiTarget yang menerima **int. Fungsi tersebut tidak mengubah bilangan yang ditunjuk, melainkan mengganti variabel pointer milik pemanggil agar menunjuk objek lain:

go
package main

import "fmt"

// gantiTarget mengubah variabel pointer milik pemanggil.
// pp bertipe **int: pointer ke pointer ke int.
func gantiTarget(pp **int, baru *int) {
	*pp = baru
}

func main() {
	x, y := 10, 20
	p := &x

	fmt.Printf("tipe p  : %T\n", p)
	fmt.Printf("tipe &p : %T\n", &p)

	fmt.Println("*p sebelum gantiTarget:", *p)

	gantiTarget(&p, &y)
	fmt.Println("*p sesudah gantiTarget:", *p)
	fmt.Println("p sekarang == &x      :", p == &x)
	fmt.Println("p sekarang == &y      :", p == &y)
}

Jalankan program tersebut dari direktori chapter dengan perintah berikut:

bash
go run pointer-ke-pointer-demo.go

Output yang Dihasilkan:

text
tipe p  : *int
tipe &p : **int
*p sebelum gantiTarget: 10
*p sesudah gantiTarget: 20
p sekarang == &x      : false
p sekarang == &y      : true

Mari kita telaah baris demi baris program di atas:

  1. Baris 7-9 (gantiTarget): parameter pp bertipe **int menerima alamat variabel pointer milik pemanggil; penugasan *pp = baru menulis pointer baru ke variabel tersebut.
  2. Baris 13 (p := &x): variabel pointer p dibuat dan menunjuk bilangan x bernilai 10.
  3. Baris 15-16 (%T): format verb %T mencetak tipe nilai — p bertipe *int dan &p bertipe **int.
  4. Baris 18 (*p): sebelum pengalihan, dereference *p membaca 10 dari x.
  5. Baris 20-21 (gantiTarget(&p, &y)): pemanggilan ini mengirim alamat variabel p beserta alamat y; setelah pemanggilan, p tidak lagi menunjuk x.
  6. Baris 22-23 (p == &x): hasil false dan true membuktikan p kini menunjuk y, sehingga *p bernilai 20.

Pola Output Parameter #

Bentuk **T muncul sebagai output parameter: fungsi mengisi variabel pointer milik pemanggil dengan alamat hasil yang baru dibuat. Pemanggil cukup mendeklarasikan var hasil *T tanpa nilai, lalu mengirim &hasil. Kerabat dekatnya adalah fungsi penukar dua variabel pointer milik pemanggil:

go
func tukarPointer(a, b **int) {
	*a, *b = *b, *a
}

Setelah pemanggilan tukarPointer(&p, &q), variabel p dan q milik pemanggil saling bertukar objek yang ditunjuk. Kedua pola ini bekerja karena dereference *a dan *b menulis langsung ke variabel pointer milik pemanggil, bukan ke salinannya.

Kapan Benar-benar Perlu #

Kasus sah **T di program Go umumnya terbatas pada tiga situasi: penghapusan node pada struktur berantai seperti linked list yang menyimpan alamat node berikutnya di dalam node sebelumnya, fungsi utilitas yang mengalihkan pointer milik pemanggil seperti contoh di atas, dan kode pengikat pustaka C lewat cgo yang memang mewarisi tipe char** dari sisi C. Di luar ketiganya, kebutuhan **T hampir selalu dapat digantikan bentuk yang lebih sederhana: fungsi cukup mengembalikan pointer baru sebagai nilai kembalian, dan pemanggil menugaskannya kembali.

Referensi #

Pointer bertingkat menuntaskan perbendaharaan bentuk pointer kita. Pertanyaan berikutnya berpindah ke memori itu sendiri: di mana sebenarnya variabel x, p, dan pp tinggal selama program berjalan? Chapter berikutnya membuka pintu tersebut lewat escape analysis.

Berkas Sumber

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

pointer-ke-pointer-demo.go

Unduh berkas
go
package main

import "fmt"

// gantiTarget mengubah variabel pointer milik pemanggil.
// pp bertipe **int: pointer ke pointer ke int.
func gantiTarget(pp **int, baru *int) {
	*pp = baru
}

func main() {
	x, y := 10, 20
	p := &x

	fmt.Printf("tipe p  : %T\n", p)
	fmt.Printf("tipe &p : %T\n", &p)

	fmt.Println("*p sebelum gantiTarget:", *p)

	gantiTarget(&p, &y)
	fmt.Println("*p sesudah gantiTarget:", *p)
	fmt.Println("p sekarang == &x      :", p == &x)
	fmt.Println("p sekarang == &y      :", p == &y)
}