informasi kontak saya
Surat[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Jika kita ingin menulis fungsi untuk membandingkan ukuran dua bilangan bulat dan bilangan floating point, kita harus menulis dua fungsi. sebagai berikut:
func Min(x, y float64) float64 {
if x < y {
return x
}
return y
}
func MinInt(x, y int) int {
if x < y {
return x
}
return y
}
Kedua fungsi tersebut memiliki logika pemrosesan yang persis sama kecuali untuk tipe data yang berbeda. Apakah ada cara untuk menyelesaikan fungsi di atas dengan satu fungsi? Ya, itu umum.
func min[T int | float64](x, y T) T {
if x < y {
return x
}
return y
}
Dokumen situs web resmi:https://go.dev/blog/intro-generik
Generik menambahkan tiga fitur baru yang penting ke bahasa ini:
Fungsi dan tipe sekarang diperbolehkan memiliki parameter tipe. Daftar parameter tipe terlihat mirip dengan daftar parameter normal, hanya saja daftar tersebut menggunakan tanda kurung siku, bukan tanda kurung bulat.
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
x := GMin[int](2, 3)
fmt.Println(x) // 输出结果为2
}
Diantaranya, kendala. Dipesan adalah tipe khusus (kode sumber tidak ditampilkan di sini).
Jika Anda tidak mengerti, Anda dapat mengganti kendala untuk sementara. Dipesan dengan ·int | float64
。
Memberikan parameter tipe (dalam hal ini int) ke GMin disebut instantiasi. Instansiasi terjadi dalam dua langkah.
Setelah instantiasi berhasil, kita memiliki fungsi non-generik yang dapat dipanggil seperti fungsi lainnya.Misalnya dalam kode seperti
fmin := GMin[float64]
m := fmin(2.71, 3.14)
Semua kode adalah
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func GMin[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
fmin := GMin[float64] // 相当于func GMin(x, y float64) float64{...}
m := fmin(2.71, 3.14)
fmt.Println(m) // 输出结果为2.71
}
Membuat instance GMin[float64] sebenarnya menghasilkan fungsi Min floating point asli kita, yang dapat kita gunakan dalam pemanggilan fungsi.
Parameter tipe juga dapat digunakan dengan tipe.
type Tree[T interface{}] struct {
left, right *Tree[T]
value T
}
func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }
var stringTree Tree[string]
Di sini tipe generik Pohon menyimpan nilai parameter tipe T. Tipe generik dapat memiliki metode, seperti Pencarian dalam contoh ini. Untuk menggunakan tipe generik,Itu harus dipakai; Tree[string] adalah contoh pembuatan instance Tree menggunakan parameter tipe string.
Setiap parameter tipe dalam daftar parameter tipe memiliki tipe. Karena parameter tipe itu sendiri adalah sebuah tipe, tipe dari parameter tipe mendefinisikan kumpulan tipe.Metatipe ini disebutbatasan tipe。
Dalam metode generik GMin, batasan tipe diimpor dari paket batasan. Batasan terurut menggambarkan semua jenis koleksi yang memiliki nilai yang dapat diurutkan, atau dengan kata lain dibandingkan dengan operator < (atau <=, >, dll.). Batasan ini memastikan bahwa hanya tipe dengan nilai yang dapat diurutkan yang dapat diteruskan ke GMin. Ini juga berarti bahwa di isi fungsi GMin, nilai parameter tipe ini dapat digunakan untuk perbandingan dengan operator <.
Di Go, batasan tipe harus berupa antarmuka . Artinya, tipe antarmuka dapat digunakan sebagai tipe nilai atau tipe meta. Antarmuka mendefinisikan metode, jadi jelas kita dapat menyatakan batasan tipe yang memerlukan kehadiran metode tertentu. Namun kendala.Ordered juga merupakan tipe antarmuka, dan operator < bukanlah sebuah metode.
Tujuan ganda dari tipe antarmuka memang merupakan konsep penting dalam bahasa Go. Mari kita pahami secara mendalam dan ilustrasikan dengan contoh pernyataan “tipe antarmuka dapat digunakan sebagai tipe nilai dan juga sebagai metatipe” [1] [2] [3] [4] [5].
Ketika antarmuka digunakan sebagai tipe nilai, ia mendefinisikan sekumpulan metode sehingga tipe apa pun yang mengimplementasikan metode ini dapat ditetapkan ke variabel antarmuka. Ini adalah penggunaan antarmuka yang paling umum.
Misalnya:
type Stringer interface {
String() string
}
type Person struct {
Name string
}
func (p Person) String() string {
return p.Name
}
var s Stringer = Person{"Alice"} // Person 实现了 Stringer 接口
fmt.Println(s.String()) // 输出: Alice
Dalam contoh ini,Stringer
Antarmuka digunakan sebagai tipe nilai,Person
Tipe mengimplementasikanString()
metode, sehingga dapat ditugaskan keStringer
tipe variabel.
Ketika sebuah antarmuka digunakan sebagai metatipe, ia mendefinisikan sekumpulan batasan tipe untuk digunakan dalam pemrograman generik. Ini adalah penggunaan baru setelah diperkenalkannya obat generik di Go 1.18.
Misalnya:
type Ordered interface {
int | float64 | string
}
func Min[T Ordered](a, b T) T {
if a < b {
return a
}
return b
}
fmt.Println(Min(3, 5)) // 输出: 3
fmt.Println(Min(3.14, 2.71)) // 输出: 2.71
fmt.Println(Min("a", "b")) // 输出: a
Dalam contoh ini,Ordered
Antarmuka digunakan sebagai metatipe, yang menentukan sekumpulan tipe (bilangan bulat, angka floating point, dan string) yang dapat dibandingkan.Min
Fungsi menggunakan antarmuka ini sebagai batasan tipe dan dapat menerima apa punOrdered
Jenis batasan sebagai argumen.
Tujuan ganda ini membuat antarmuka Go sangat kuat dan fleksibel untuk pemrograman generik. Mereka tidak hanya dapat mendefinisikan perilaku objek (sebagai tipe nilai), tetapi juga mendefinisikan kumpulan tipe (sebagai metatipe), sehingga sangat meningkatkan ekspresi dan penggunaan kembali kode sambil menjaga kesederhanaan bahasa.
Sampai saat ini, spesifikasi Go mengatakan bahwa sebuah antarmuka mendefinisikan kumpulan metode, yang secara kasar merupakan kumpulan metode yang disebutkan dalam antarmuka. Tipe apa pun yang mengimplementasikan semua metode ini mengimplementasikan antarmuka ini.
Namun cara lain untuk melihat hal ini adalah dengan mengatakan bahwa antarmuka mendefinisikan sekumpulan tipe, yaitu tipe yang mengimplementasikan metode ini. Dari perspektif ini, tipe apa pun yang merupakan elemen dari kumpulan tipe antarmuka mengimplementasikan antarmuka tersebut.
Kedua tampilan tersebut menghasilkan hasil yang sama: untuk setiap kumpulan metode, kita dapat membayangkan kumpulan tipe terkait yang mengimplementasikan metode ini, yaitu kumpulan tipe yang ditentukan oleh antarmuka.
Namun, untuk tujuan kita, tampilan kumpulan tipe memiliki satu keunggulan dibandingkan tampilan kumpulan metode: kita dapat mengontrol kumpulan tipe dengan cara baru dengan secara eksplisit menambahkan tipe ke koleksi.
Kami memperluas sintaks tipe antarmuka untuk mencapai hal ini. Misalnya, interface{ int|string|bool } mendefinisikan kumpulan tipe yang berisi tipe int, string, dan bool.
Cara lain untuk mengatakan ini adalah bahwa antarmuka hanya dipenuhi oleh int, string, atau bool.
Sekarang mari kita lihat definisi sebenarnya dari batasan. Diurutkan:
type Ordered interface {
Integer|Float|~string
}
Deklarasi ini menunjukkan bahwa antarmuka Ordered adalah kumpulan semua tipe integer, floating point, dan string. Bilah vertikal mewakili gabungan tipe (atau kumpulan tipe dalam kasus ini). Integer dan Float adalah tipe antarmuka yang didefinisikan serupa dalam paket batasan. Perhatikan bahwa antarmuka Ordered tidak mendefinisikan metode apa pun.
Untuk batasan tipe, kami biasanya tidak peduli dengan tipe tertentu, misalnya string; kami tertarik pada semua tipe string.Ini~
Tujuan dari token tersebut.ekspresi~string
Mewakili kumpulan semua tipe yang tipe dasarnya adalah string.Ini termasuk tipe string itu sendiri dan semua tipe yang dideklarasikan dengan definisi, mis.type MyString string
Tentu saja kami masih ingin menentukan metode di antarmuka, dan kami ingin kompatibel ke belakang. Di Go 1.18, sebuah antarmuka dapat berisi metode dan antarmuka yang tertanam seperti sebelumnya, namun juga dapat menyematkan tipe non-antarmuka, gabungan, dan kumpulan tipe yang mendasarinya.
Antarmuka yang digunakan sebagai batasan dapat diberi nama (seperti Diurutkan) atau menjadi antarmuka literal sebaris dalam daftar parameter tipe. Misalnya:
[S interface{~[]E}, E interface{}]
Di sini S harus bertipe irisan, dan tipe elemennya bisa bertipe apa saja.
Karena ini adalah situasi umum, untuk antarmuka yang membatasi posisi, antarmuka tertutup{} bisa dihilangkan, dan kita cukup menulis (sintaks gula generik dalam bahasa Go dan penulisan batasan tipe yang disederhanakan):
[S ~[]E, E interface{}]
Karena antarmuka kosong umum ditemukan dalam daftar parameter tipe dan juga dalam kode Go normal, Go 1.18 memperkenalkan pengidentifikasi any yang telah dideklarasikan sebelumnya sebagai alias untuk tipe antarmuka kosong. Jadi, kita mendapatkan kode idiomatik ini:
[S ~[]E, E any]
Dengan parameter tipe, parameter tipe harus diteruskan, yang dapat menyebabkan kode verbose. Kembali ke fungsi GMin umum kita: