Saya sedang memikirkan sedikit tentang cara yang berbeza di mana nil berfungsi, dan kadangkala, sesuatu boleh menjadi nil dan bukan nil pada masa yang sama.
Berikut ialah contoh kecil sesuatu yang boleh menjadi penunjuk sifar, tetapi bukan antara muka sifar. Mari kita lihat maksudnya.
Pertama, go mempunyai konsep antara muka, yang serupa, tetapi tidak sama dengan antara muka dalam sesetengah bahasa berorientasikan objek (go bukan OOP mengikut kebanyakan takrifan). Dalam go, antara muka ialah jenis yang mentakrifkan fungsi yang mesti dilaksanakan oleh jenis lain untuk memenuhi antara muka. Ini membolehkan kami mempunyai berbilang jenis konkrit yang boleh memenuhi antara muka dengan cara yang berbeza.
Sebagai contoh, ralat ialah antara muka terbina dalam yang mempunyai satu kaedah. Ia kelihatan seperti ini:
type error interface { Error() string }
Sebarang jenis yang ingin digunakan sebagai ralat mesti mempunyai kaedah yang dipanggil Ralat yang mengembalikan rentetan. Sebagai contoh, kod berikut boleh digunakan:
type ErrorMessage string func (em ErrorMessage) Error() string { return string(em) } func DoSomething() error { // Try to do something, and it fails. if somethingFailed { var err ErrorMessage = "This failed" return err } return nil } func main() { err := DoSomething() if err != nil { panic(err) } }
Perhatikan dalam contoh ini bahawa DoSomething mengembalikan ralat jika berlaku kesilapan. Kami boleh menggunakan jenis ErrorMessage kami, kerana ia mempunyai fungsi Ralat, yang mengembalikan rentetan, dan oleh itu melaksanakan antara muka ralat.
Jika tiada ralat berlaku, kami mengembalikan sifar.
Dalam pergi, penunjuk menunjuk kepada nilai, tetapi ia juga boleh menunjuk kepada tiada nilai, dalam hal ini penunjuk adalah sifar. Contohnya:
var i *int = nil func main() { if i == nil { j := 5 i = &j } fmt.Println("i is", *i) }
Dalam kes ini, pembolehubah i ialah penunjuk kepada int. Ia bermula sebagai penunjuk nol, sehingga kita mencipta int, dan menunjukkannya.
Memandangkan jenis yang ditentukan pengguna boleh mempunyai fungsi (kaedah) yang dilampirkan, kami juga boleh mempunyai fungsi untuk penunjuk kepada jenis. Ini adalah amalan yang sangat biasa dalam perjalanan. Ini juga bermakna penunjuk juga boleh melaksanakan antara muka. Dengan cara ini, kita boleh mempunyai nilai yang merupakan antara muka bukan nol, tetapi masih penunjuk nol. Pertimbangkan kod berikut:
type TruthGetter interface { IsTrue() bool } func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
Mana-mana jenis yang mempunyai kaedah bool IsTrue() boleh dihantar ke PrintIfTrue, tetapi begitu juga nil. Jadi, kita boleh melakukan PrintIfTrue(nil) dan ia akan mencetak "Saya tidak tahu sama ada ia benar".
Kita juga boleh melakukan sesuatu yang mudah seperti ini:
type Truthy bool func (ty Truthy) IsTrue() bool { return bool(ty) } func main() { var ty Truthy = true PrintIfTrue(ty) }
Ini akan mencetak "Memang benar".
Atau, kita boleh melakukan sesuatu yang lebih rumit, seperti:
type TruthyNumber int func (tn TruthyNumber) IsTrue() bool { return tn > 0 } func main() { var tn TruthyNumber = -4 PrintIfTrue(tn) }
Itu akan mencetak "Ia tidak benar". Kedua-dua contoh ini bukan petunjuk, jadi tiada peluang untuk nol dengan salah satu daripada jenis ini, tetapi pertimbangkan ini:
type TruthyPerson struct { FirstName string LastName string } func (tp *TruthyPerson) IsTrue() bool { return tp.FirstName != "" && tp.LastName != "" }
Dalam kes ini TruthyPerson tidak melaksanakan TruthGetter, tetapi *TruthyPerson melakukannya. Jadi, ini sepatutnya berfungsi:
func main() { tp := &TruthyPerson{"Jon", "Grady"} PrintIfTrue(tp) }
Ini berfungsi kerana tp adalah penunjuk kepada TruthyPerson. Walau bagaimanapun, jika penunjuknya tiada, kita akan panik.
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
Ini akan panik. Walau bagaimanapun, panik tidak berlaku dalam PrintIfTrue. Anda akan fikir ia tidak mengapa, kerana PrintIfTrue menyemak sifar. Tetapi, inilah isunya. Ia menyemak nol terhadap TruthGetter. Dalam erti kata lain, ia menyemak antara muka sifar, tetapi bukan penunjuk sifar. Dan dalam func (tp *TruthyPerson) IsTrue() bool, kami tidak menyemak nol. Dalam perjalanan, kita masih boleh memanggil kaedah pada penunjuk sifar, jadi panik berlaku di sana. Pembetulan sebenarnya agak mudah.
func (tp *TruthyPerson) IsTrue() bool { if tp == nil { return false } return tp.FirstName != "" && tp.LastName != "" }
Sekarang, kami sedang menyemak antara muka nil dalam PrintIfTrue dan untuk penunjuk nil dalam func (tp *TruthyPerson) IsTrue() bool. Dan ia kini akan mencetak "Ia tidak benar". Kita boleh melihat semua kod ini berfungsi di sini.
Dengan refleksi, kami boleh membuat sedikit perubahan kepada PrintIfTrue supaya ia boleh menyemak kedua-dua antara muka nil dan penunjuk nil. Ini kodnya:
func PrintIfTrue(tg TruthGetter) { if tg == nil { fmt.Println("I can't tell if it's true") return } val := reflect.ValueOf(tg) k := val.Kind() if (k == reflect.Pointer || k == reflect.Chan || k == reflect.Func || k == reflect.Map || k == reflect.Slice) && val.IsNil() { fmt.Println("I can't tell if it's true") return } if tg.IsTrue() { fmt.Println("It's true") } else { fmt.Println("It's not true") } }
Di sini, kami menyemak antara muka sifar dahulu, seperti sebelum ini. Seterusnya, kami menggunakan refleksi untuk mendapatkan jenis. chan, func, map dan slice juga boleh menjadi nil, sebagai tambahan kepada penunjuk, jadi kami menyemak sama ada nilai itu adalah salah satu daripada jenis tersebut, dan jika ya, semak sama ada ia adalah nil. Dan jika ya, kami juga mengembalikan mesej "Saya tidak tahu sama ada ia benar". Ini mungkin atau mungkin bukan apa yang anda mahukan, tetapi ini adalah pilihan. Dengan perubahan ini, kita boleh melakukan ini:
func main() { var tp *TruthyPerson PrintIfTrue(tp) }
Kadangkala anda mungkin melihat cadangan kepada sesuatu yang lebih mudah, seperti:
// Don't do this if tg == nil && reflect.ValueOf(tg).IsNil() { fmt.Println("I can't tell if it's true") return }
Terdapat dua sebab ini tidak berfungsi dengan baik. Pertama, ialah terdapat overhed prestasi apabila menggunakan refleksi. Jika anda boleh mengelak daripada menggunakan refleksi, anda mungkin perlu. Jika kita menyemak antara muka nil dahulu, kita tidak perlu menggunakan pantulan jika ia adalah antara muka nil.
Sebab kedua ialah reflect.Value.IsNil() akan panik jika jenis nilai itu bukan jenis yang boleh menjadi nol. Itulah sebabnya kami menambah cek untuk jenis itu. Jika kami tidak menyemak Jenis, maka kami akan menjadi panik pada jenis Truthy dan TruthyNumber.
Jadi, selagi kami memastikan kami menyemak jenisnya dahulu, ini kini akan mencetak "Saya tidak dapat memberitahu sama ada ia benar", bukannya "Ia tidak benar". Bergantung pada perspektif anda, ini mungkin penambahbaikan. Berikut ialah kod lengkap dengan perubahan ini.
Ini pada asalnya diterbitkan di Dan's Musings
Atas ialah kandungan terperinci golang: Memahami perbezaan antara penunjuk nil dan antara muka nil. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!