Setiap variable menempati sebuah lokasi di memori komputer dan lokasi tersebut punya alamat. Kebanyakan kode Go bekerja dengan nilai variable tanpa pernah peduli alamatnya, tetapi ada kalanya kita butuh alamat tersebut: memodifikasi variable milik pemanggil dari dalam fungsi, atau menghindari penyalinan data yang besar. Go menyediakan dua operator untuk level ini — & mengambil alamat sebuah variable, dan * membaca serta menulis nilai pada alamat yang ditunjuk. Pada chapter ini kita memakai keduanya dalam satu program utuh dan mengenali keadaan khusus pointer nil.

Dua Operator: & dan * #

Tabel berikut merangkum peran kedua operator beserta tempat kemunculannya:

PenulisanNamaPeran
&xOperator alamatMenghasilkan alamat variable x, bertipe pointer *int bila x bertipe int
*pOperator dereferenceMembaca atau menulis nilai yang berada di lokasi yang ditunjuk pointer p
var p *intDeklarasi tipe*int adalah tipe “pointer ke int”; bintang di sini bagian dari nama tipe, bukan operator

Perhatikan bahwa tanda * mempunyai dua peran berbeda. Pada deklarasi var p *int, tanda bintang adalah bagian dari penulisan tipe pointer. Pada ekspresi *p, tanda bintang adalah operator dereference yang “mengikuti” alamat sampai ke nilainya. Program berikut menunjukkan ketiga peran tersebut:

go
package main

import "fmt"

// naikkan menambah satu pada nilai yang ditunjuk pointer p.
func naikkan(p *int) {
	*p = *p + 1
}

func main() {
	x := 10
	p := &x // operator & mengambil alamat variable x
	fmt.Println("nilai x  :", x)
	fmt.Println("nilai *p :", *p)

	*p = 25 // operator * menulis ke lokasi yang ditunjuk p
	fmt.Println("nilai x  :", x)

	naikkan(&x) // alamat x dikirim ke fungsi
	fmt.Println("nilai x  :", x)

	var ptr *int // zero value pointer adalah nil
	fmt.Println("ptr nil? :", ptr == nil)
}

Output yang Dihasilkan:

text
nilai x  : 10
nilai *p : 10
nilai x  : 25
nilai x  : 26
ptr nil? : true

Mari kita telaah baris demi baris program di atas:

  1. Baris 6-8 (func naikkan(p *int)): Parameter p bertipe *int, sehingga fungsi ini menerima alamat sebuah bilangan bulat. Statement *p = *p + 1 membaca nilai di alamat tersebut, menjumlahkan satu, lalu menulis hasilnya kembali ke alamat yang sama.
  2. Baris 11 (x := 10): Variable x dibuat dengan nilai 10; runtime menyimpan nilai ini di sebuah alamat memori.
  3. Baris 12 (p := &x): Operator & menghasilkan alamat milik x, lalu alamat itu disimpan pada variable p yang bertipe *int. Sejak baris ini, p dan x merujuk ke lokasi memori yang sama.
  4. Baris 14 (fmt.Println("nilai *p :", *p)): Dereference *p membaca nilai di alamat yang ditunjuk, yaitu 10. Membaca *p memberikan hasil yang sama dengan membaca x.
  5. Baris 16 (*p = 25): Assignment melalui *p menulis 25 ke lokasi memori milik x. Baris berikutnya membuktikan x ikut berubah menjadi 25 meskipun statement ini tidak menyebut nama x sama sekali.
  6. Baris 19 (naikkan(&x)): Alamat x dikirim sebagai argumen. Go mengopi nilai argumen seperti biasa, tetapi yang dikopi adalah alamatnya, sehingga fungsi tetap menulis ke lokasi asli dan x menjadi 26. Inilah cara standar membuat fungsi mengubah variable milik pemanggil.
  7. Baris 22-23 (var ptr *int): Pointer yang dideklarasikan tanpa nilai awal berada pada zero value nil. Perbandingan ptr == nil menghasilkan true.

Mencetak Alamat dengan %p #

Package fmt menyediakan verb %p untuk mencetak alamat pointer, misalnya fmt.Printf("%p\n", &x). Namun nilai alamat yang tercetak berbeda pada setiap eksekusi program — bahkan pada program yang sama dijalankan dua kali — karena sistem operasi menempatkan memori secara dinamis. Contoh-contoh pada chapter ini karenanya sengaja hanya mencetak nilai hasil dereference *p yang selalu pasti, bukan alamat pointer yang terus berubah. Jika Anda mencoba sendiri dengan %p, angka heksadesimal seperti 0xc000014098 yang muncul memang wajar berbeda dari komputer lain.

Referensi #

Kemampuan mengambil alamat dan dereference membuka topik yang lebih dalam. Section Pointer dan Memory akan membedah perbedaan pointer dan value secara menyeluruh, termasuk kapan memilih salah satunya. Sebelum sampai di sana, kita perlu menuntaskan satu keterampilan dasar terakhir pada ekspresi: membaca urutan evaluasi operator, yang dibahas pada chapter Prioritas Operator .

Berkas Sumber

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

pointer.go

Unduh berkas
go
package main

import "fmt"

// naikkan menambah satu pada nilai yang ditunjuk pointer p.
func naikkan(p *int) {
	*p = *p + 1
}

func main() {
	x := 10
	p := &x // operator & mengambil alamat variable x
	fmt.Println("nilai x  :", x)
	fmt.Println("nilai *p :", *p)

	*p = 25 // operator * menulis ke lokasi yang ditunjuk p
	fmt.Println("nilai x  :", x)

	naikkan(&x) // alamat x dikirim ke fungsi
	fmt.Println("nilai x  :", x)

	var ptr *int // zero value pointer adalah nil
	fmt.Println("ptr nil? :", ptr == nil)
}