Membuat tipe data baru adalah bagian penting dari pekerjaan setiap programmer. Dalam kebanyakan bahasa, definisi tipe terdiri dari deskripsi bidang dan metodenya. Di Golang, selain ini, Anda perlu memutuskan semantik penerima mana untuk metode tipe baru yang akan digunakan: nilai (nilai) atau penunjuk (pointer). Pada pandangan pertama, keputusan ini mungkin tampak sekunder, karena dalam kebanyakan kasus program akan bekerja dengan semantik penerima. Oleh karena itu, banyak orang melewati titik ini dan menulis kode, tanpa sepenuhnya memahami apa yang semantik dari penerima metode. Dan untuk mengetahuinya, Anda perlu sedikit lebih dalam tentang bagaimana Golang bekerja.
Pertimbangkan sebuah contoh kecil. Tentukan struktur
kucing dengan satu bidang
Nama dan metode
sayHello (string orang) . Selanjutnya,
dengan metode saya akan merujuk ke fungsi yang terkait dengan tipe tertentu,
objek ke variabel yang memiliki metode, dan
penerima metode akan menjadi variabel yang ditunjukkan dalam tanda kurung setelah kata
func dalam deskripsi metode.
type cat struct { Name string } func (c *cat) sayHello(person string) { fmt.Println(fmt.Sprintf("Meow, meow, %s!", person) }
Jika kita mendefinisikan pointer ke
cat dan meminta bidang
Nama darinya, maka, jelas, kita akan mendapatkan kesalahan, karena bidang tersebut dipanggil dari
nil :
var c *cat
https://play.golang.org/p/L3FnRJXKqs0Namun, ketika metode
sayHello () dipanggil pada variabel yang sama, tidak akan ada kesalahan:
var c *cat
https://play.golang.org/p/EMoFgKL1HEiMengapa
nil dapat memanggil metode dalam contoh ini, dan bagaimana ini dijelaskan dalam hal arsitektur bahasa itu sendiri? Ini menjadi mungkin karena metode dalam Go adalah sintaksis gula, atau, dengan kata lain, pembungkus di sekitar fungsi yang memiliki salah satu argumen penerima. Ketika metode
c.sayHello ("Manusia") dipanggil,
(* cat) .sayHello (c, s) construct (
https://play.golang.org/p/X9leJeIvxcA ) sebenarnya akan dipanggil. Dengan memanggil metode
nil dari contoh di atas, kita secara praktis memanggil fungsi dengan
nil dalam argumen, dan ini sudah merupakan situasi yang cukup normal. Oleh karena itu, di Go
nil, itu adalah penerima yang tepat untuk metode.
Karena penerima metode sebenarnya adalah argumen, rekomendasi untuk menggunakan semantik "nilai" atau "pointer" untuk penerima metode mirip dengan rekomendasi untuk argumen fungsi. Mereka, pada gilirannya, disimpulkan dari aturan dasar Go:
argumen selalu diteruskan ke fungsi dengan nilai . Ini berarti bahwa transfer argumen apa pun ke fungsi terjadi melalui penyalinannya: jika fungsi menerima suatu struktur sebagai input, maka salinan lengkap dari struktur ini akan masuk ke dalamnya; jika dibutuhkan pointer ke objek, maka variabel baru akan datang dengan pointer ke objek yang sama. Ini dapat dilihat dengan membandingkan alamat variabel sebelum meneruskannya ke fungsi dengan alamat argumen di dalam fungsi (
https://play.golang.org/p/oc2ssC_Irs8 ,
https://play.golang.org/p/FeQa2HUdX0a ).
Saat lewat tautan digunakan:
- Untuk struktur besar. Pointer hanya menempati satu kata mesin (32, 64 bit tergantung pada sistem). Oleh karena itu, ketika memanggil metode dengan pointer di penerima, menyalin pointer lebih murah daripada menyalin seluruh objek, seperti halnya dengan melewatkan nilai.
- Jika metode yang dipanggil memodifikasi data objek itu sendiri. Ketika penerima ditransfer dengan referensi, metode ini dapat mempengaruhi keadaan objek panggilan dengan secara tidak langsung membuat perubahan. Yang tidak mungkin ketika melewati nilai.
Saat menggunakan transfer nilai:
- Untuk tipe bawaan yang sederhana seperti angka, string, bool. Saat menggunakan pointer, jumlah memori yang hampir sama digunakan dengan objek jenis ini, dan biaya pemeliharaannya oleh pengumpul sampah meningkat, seperti yang akan dijelaskan di bawah ini.
- Untuk irisan, serta jenis referensi lainnya: peta dan saluran - tidak masuk akal untuk mengambil pointer. Mereka sendiri sudah menjadi pointer.
- Dengan multi-threading, melewati nilai aman, tidak seperti lewat referensi.
- Untuk struktur kecil. Dalam kasus seperti itu, transmisi berdasarkan nilai lebih efisien. Ini karena data internal metode ditempatkan dalam bingkai stack yang terpisah. Setelah keluar dari suatu fungsi, bingkainya dihapus. Ketika kami menggeledah sesuatu di sepanjang pointer, kami mentransfer data ini dari tumpukan ke tumpukan, dari mana data ini mungkin tersedia untuk fungsi lainnya. Meningkatkan tumpukan menciptakan beban tambahan pada pengumpul sampah, yang operasinya mengurangi kecepatan program rata-rata 25%. Saat menggunakan transfer nilai demi nilai, data tetap berada di tumpukan dan tidak ada pekerjaan pengumpul sampah tambahan yang diperlukan.
Saat Anda perlu memikirkan semantik penerima:
- Jenis penerima dapat bervariasi berdasarkan bidang subjek. Dalam salah satu pidatonya, Bill Kennedy memberikan contoh yang baik dengan tipe pengguna yang menggambarkan pengguna. Ketika diteruskan oleh nilai, salinan akan dibuat untuk pengguna. Ini akan mengarah pada fakta bahwa beberapa salinan dari pengguna yang sama dapat hidup berdampingan dalam program pada saat yang sama, yang kemudian dapat diubah secara independen, yang tidak sesuai dengan bidang subjek, karena pengguna sebenarnya selalu satu, dan ia tidak dapat dijelaskan pada waktu yang berbeda dengan set yang berbeda. data.
- Cara pasti lainnya untuk menentukan jenis penerima untuk suatu metode adalah dengan menggunakan metode konstruktor untuk jenisnya. Jika konstruktor mengembalikan nilai / penunjuk, maka saat membuat entitas, diasumsikan bahwa mereka akan terus bekerja dengannya sebagai nilai / penunjuk. Oleh karena itu, lebih baik menggunakan semantik yang sama dalam metode penerima.
- Ada aturan tidak tertulis, yang melanggar kompiler tidak akan bersumpah, tetapi kode Anda pasti tidak akan lebih baik dari ini. Jika salah satu metode tipe menggunakan pointer / nilai sebagai penerima, maka untuk mempertahankan konsistensi, metode yang tersisa harus menggunakan pointer / nilai. Metode tipe seharusnya tidak memiliki hash nilai-dan pointer-penerima.
Apa hasilnya
Dalam Nilai Go, semantik berarti menyalin nilai; semantik pointer berarti memberikan akses ke nilai. Ini berlaku untuk argumen metode dan penerima. Untuk tipe bawaan, seperti angka, garis, irisan, peta, saluran, dan struktur kecil, Anda hampir selalu perlu menggunakan transfer berbasis nilai. Untuk struktur yang menempati banyak memori, dan struktur yang kondisinya dapat secara tidak langsung diubah dengan metode mereka, Anda harus menggunakan transfer dengan referensi. Juga, semantik penerima mungkin bergantung pada domain yang jenisnya jelaskan, semantik yang dikembalikan di pabriknya, dan semantik penerima telah digunakan dalam metode lain dari jenis ini.