17-bo‘lim
Generiklar
Tur parametrlari, cheklovlar, comparable va any, generik turlar, tur xulosasi hamda generiklar qachon kerak emasligi.
Ushbu bo‘lim mundarijasi
Go 1.18 gacha tilda generiklar yo'q edi. Bu eng ko'p so'ralgan imkoniyat bo'lib, o'n yildan ortiq muhokama qilingan.
Muammo #
Generiklardan oldin bir xil funksiyani har bir tur uchun qayta yozish kerak edi:
func EngKattaInt(sonlar []int) int { ... }
func EngKattaFloat(sonlar []float64) float64 { ... }
func EngKattaString(sonlar []string) string { ... }
Uchta funksiya, bir xil mantiq.
Birinchi generik funksiya #
package main
import "fmt"
func EngKatta[T int | float64 | string](royxat []T) T {
eng := royxat[0]
for _, q := range royxat[1:] {
if q > eng {
eng = q
}
}
return eng
}
func main() {
fmt.Println(EngKatta([]int{3, 9, 2}))
fmt.Println(EngKatta([]float64{1.5, 0.2}))
fmt.Println(EngKatta([]string{"olma", "anor", "uzum"}))
}
9
1.5
uzum
[T int | float64 | string] - tur parametri. T - turning
nomi, undan keyingisi esa cheklov: T faqat shu uchtadan biri
bo'lishi mumkin.
E'tibor bering: chaqirganda turni yozmadik. Go uni argumentdan o'zi aniqladi.
T istalgan tur bo'lsa, q > eng deb yozib bo'lmaydi - chunki
ikkita struct ni yoki ikkita map ni > bilan taqqoslab
bo'lmaydi.
Cheklov kompilyatorga "bu turlar bilan nima qilish mumkinligini" aytadi. Shuning uchun generik kod ham to'liq tekshiriladi.
Bu Java yoki C# dagi generiklardan farqi emas, lekin C++ shablonlaridan katta farqi: C++ da xato faqat shablon ishlatilganda, ko'pincha o'qib bo'lmaydigan xabar bilan chiqadi.
Tayyor cheklovlar #
package main
import (
"cmp"
"fmt"
)
func Kichigi[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
func Bormi[T comparable](royxat []T, nishon T) bool {
for _, q := range royxat {
if q == nishon {
return true
}
}
return false
}
func main() {
fmt.Println(Kichigi(3, 7))
fmt.Println(Kichigi("olma", "anor"))
fmt.Println(Bormi([]int{1, 2, 3}, 2))
fmt.Println(Bormi([]string{"a", "b"}, "z"))
}
3
anor
true
false
| Cheklov | Nimani ruxsat beradi |
|---|---|
any | Hamma narsa; faqat uzatish mumkin |
comparable | == va != |
cmp.Ordered | <, >, <=, >= |
cmp.Ordered - Go 1.21 da qo'shilgan; u hamma son turlarini
va string ni qamrab oladi.
O'z cheklovingiz #
package main
import "fmt"
type Son interface {
~int | ~int64 | ~float64
}
func Yigindi[T Son](royxat []T) T {
var jami T
for _, q := range royxat {
jami += q
}
return jami
}
type Ball int
func main() {
fmt.Println(Yigindi([]int{1, 2, 3}))
fmt.Println(Yigindi([]float64{1.5, 2.5}))
ballar := []Ball{10, 20, 30}
fmt.Println(Yigindi(ballar))
}
6
4
60
Cheklov - bu oddiy interfeys, faqat metodlar o'rniga turlar ro'yxati bilan.
~int "asosi int bo'lgan har qanday tur" degani.
type Ball int - bu yangi tur, lekin uning asosi int.
Cheklovda oddiy int yozsangiz, Ball unga mos kelmasdi va
oxirgi chaqiruv xato berardi.
~ belgisi esa 12-bo'limda yaratgan Harorat kabi o'z
turlaringizni ham qamrab oladi.
Qoida: cheklov yozayotganda deyarli har doim ~ ishlating.
Metodli cheklov #
package main
import "fmt"
type Tavsiflanuvchi interface {
Tavsif() string
}
func HammasiniChiqar[T Tavsiflanuvchi](royxat []T) {
for _, q := range royxat {
fmt.Println(q.Tavsif())
}
}
type Talaba struct{ Ism string }
func (t Talaba) Tavsif() string { return "talaba: " + t.Ism }
type Kitob struct{ Nom string }
func (k Kitob) Tavsif() string { return "kitob: " + k.Nom }
func main() {
HammasiniChiqar([]Talaba{{Ism: "Malika"}, {Ism: "Aziza"}})
HammasiniChiqar([]Kitob{{Nom: "O'tkan kunlar"}})
}
talaba: Malika
talaba: Aziza
kitob: O'tkan kunlar
Xuddi shuni oddiy interfeys bilan ham yozish mumkin:
func HammasiniChiqar(royxat []Tavsiflanuvchi) { ... }
Farqi bor: interfeysli shaklda []Talaba ni to'g'ridan-to'g'ri
berib bo'lmaydi - avval []Tavsiflanuvchi ga ko'chirish kerak.
Go slicelarni avtomatik o'tkazmaydi.
Generik shaklda esa []Talaba darhol ishlaydi va qo'shimcha
xotira sarflanmaydi.
Generik turlar #
package main
import "fmt"
type Stek[T any] struct {
elementlar []T
}
func (s *Stek[T]) Qosh(q T) {
s.elementlar = append(s.elementlar, q)
}
func (s *Stek[T]) Ol() (T, bool) {
var bosh T
if len(s.elementlar) == 0 {
return bosh, false
}
oxirgi := s.elementlar[len(s.elementlar)-1]
s.elementlar = s.elementlar[:len(s.elementlar)-1]
return oxirgi, true
}
func main() {
var s Stek[string]
s.Qosh("birinchi")
s.Qosh("ikkinchi")
q, bor := s.Ol()
fmt.Println(q, bor)
q2, bor2 := s.Ol()
fmt.Println(q2, bor2)
q3, bor3 := s.Ol()
fmt.Printf("%q %t\n", q3, bor3)
}
ikkinchi true
birinchi true
"" false
Strukturalar ham generik bo'lishi mumkin. Stek[string] -
satrlar uchun stek.
Bu kod ishlamaydi:
func (s *Stek[T]) Ozgartir[U any](f func(T) U) *Stek[U] {
Go da metod o'z tur parametriga ega bo'lolmaydi - faqat turning o'zinikidan foydalanadi.
Sabab interfeyslarda: agar metodlar generik bo'lsa, interfeysni qanoatlantirishni tekshirish cheksiz murakkablashardi.
Yechim - buni oddiy funksiya qilib yozish:
func Ozgartir[T, U any](s *Stek[T], f func(T) U) *Stek[U]
Bo'sh qiymat muammosi #
package main
import "fmt"
func Birinchi[T any](royxat []T) (T, bool) {
var bosh T
if len(royxat) == 0 {
return bosh, false
}
return royxat[0], true
}
func main() {
fmt.Println(Birinchi([]int{5, 6}))
fmt.Println(Birinchi([]int{}))
fmt.Println(Birinchi([]string{}))
}
5 true
0 false
false
Generik funksiyada "hech narsa" qaytarish kerak bo'lsa, muammo
tug'iladi: nil hamma tur uchun to'g'ri kelmaydi.
Yechim - var bosh T deb e'lon qilish. Bu har bir tur uchun
o'z nol qiymatini beradi: int uchun 0, string uchun
bo'sh satr.
Tur xulosasi chegarasi #
package main
import "fmt"
func Aylantir[T, U any](royxat []T, f func(T) U) []U {
natija := make([]U, 0, len(royxat))
for _, q := range royxat {
natija = append(natija, f(q))
}
return natija
}
func main() {
sonlar := []int{1, 2, 3}
matnlar := Aylantir(sonlar, func(n int) string {
return fmt.Sprintf("#%d", n)
})
fmt.Println(matnlar)
kvadratlar := Aylantir[int, int](sonlar, func(n int) int {
return n * n
})
fmt.Println(kvadratlar)
}
[#1 #2 #3]
[1 4 9]
Go tur parametrlarini argumentlardan aniqlaydi. Yuqoridagi
birinchi chaqiruvda T - int, U esa lambdaning natijasidan -
string.
Aniqlab bo'lmagan holatda turlarni o'zingiz yozasiz:
Aylantir[int, int](...).
Qachon generik kerak emas #
Go jamoasining rasmiy maslahati: generiklarni kerak bo'lgandagina ishlating.
Uchta belgi generik kerak emasligini bildiradi:
- Faqat bitta tur bilan ishlatilmoqda. Unda oddiy funksiya yozing.
- Metod tanasi har bir tur uchun boshqacha. Unda interfeys kerak, generik emas.
- Faqat qiymatni uzatish kerak.
anyyetarli.
Generik kod o'qishga qiyinroq va kompilyatsiya sekinroq bo'ladi. Foydasi aniq bo'lgandagina uni oling.
Yaxshi misollar - 9- va 10-bo'limlarda ko'rgan slices va maps
paketlari. Ular aynan generiklar tufayli mumkin bo'ldi.
- Ikki turni qabul qiluvchi generik funksiya yozing.
comparablecheklovi bilan qidiruv funksiyasi yozing.cmp.Orderedbilan eng kichigini toping.- O'z cheklovingizni interfeys sifatida e'lon qiling.
~intbilan o'z turingizni qamrab oling.~siz yozib, xatoni ko'ring.- Generik
Stekturini yozing. - Metodga tur parametri qo'shishga urinib, xatoni o'qing.
var bosh Tbilan nol qiymat qaytaring.- Tur xulosasi ishlamaydigan holatni topib, turlarni ochiq yozing.
Xulosa #
- Generiklar Go 1.18 da qo'shilgan; undan oldin kod har bir tur uchun takrorlanardi.
[T cheklov]- tur parametri; cheklovTbilan nima qilish mumkinligini aytadi.- Tayyor cheklovlar:
any,comparable(==) vacmp.Ordered(<). - Cheklov - oddiy interfeys, ichida turlar ro'yxati bo'lishi mumkin.
~int"asosiintbo'lgan turlar" degani - o'z turlaringizni ham qamrab oladi.- Strukturalar ham generik bo'ladi, lekin metodlar yangi tur parametri qo'sha olmaydi.
var bosh Thar bir tur uchun o'z nol qiymatini beradi.- Go turlarni argumentlardan o'zi aniqlaydi; aniqlab bo'lmasa, ochiq yozasiz.
- Generiklarni kerak bo'lgandagina ishlating.
Keyingi bo'limda gorutinalarni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.