17-bo‘lim

Generiklar

Tur parametrlari, cheklovlar, comparable va any, generik turlar, tur xulosasi hamda generiklar qachon kerak emasligi.

🕑 12 daqiqa o‘qish 📄 708 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo
  2. Birinchi generik funksiya
  3. Tayyor cheklovlar
  4. O'z cheklovingiz
  5. Metodli cheklov
  6. Generik turlar
  7. Bo'sh qiymat muammosi
  8. Tur xulosasi chegarasi
  9. Qachon generik kerak emas
  10. Xulosa

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:

Go
func EngKattaInt(sonlar []int) int { ... }
func EngKattaFloat(sonlar []float64) float64 { ... }
func EngKattaString(sonlar []string) string { ... }

Uchta funksiya, bir xil mantiq.

Birinchi generik funksiya #

Go
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"}))
}
Natija
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.

Nega cheklov kerak

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 #

Go
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"))
}
Natija
3
anor
true
false
CheklovNimani ruxsat beradi
anyHamma 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 #

Go
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))
}
Natija
6
4
60

Cheklov - bu oddiy interfeys, faqat metodlar o'rniga turlar ro'yxati bilan.

Tilda nima uchun ~ belgisi bor

~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 #

Go
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"}})
}
Natija
talaba: Malika
talaba: Aziza
kitob: O'tkan kunlar
Bu yerda generik shart emas edi

Xuddi shuni oddiy interfeys bilan ham yozish mumkin:

Go
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 #

Go
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)
}
Natija
ikkinchi true
birinchi true
"" false

Strukturalar ham generik bo'lishi mumkin. Stek[string] - satrlar uchun stek.

Metodlar yangi tur parametri qo'sha olmaydi

Bu kod ishlamaydi:

Go
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:

Go
func Ozgartir[T, U any](s *Stek[T], f func(T) U) *Stek[U]

Bo'sh qiymat muammosi #

Go
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{}))
}
Natija
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 #

Go
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)
}
Natija
[#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 #

Generiklar - oxirgi chora

Go jamoasining rasmiy maslahati: generiklarni kerak bo'lgandagina ishlating.

Uchta belgi generik kerak emasligini bildiradi:

  1. Faqat bitta tur bilan ishlatilmoqda. Unda oddiy funksiya yozing.
  2. Metod tanasi har bir tur uchun boshqacha. Unda interfeys kerak, generik emas.
  3. Faqat qiymatni uzatish kerak. any yetarli.

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.

Amaliy topshiriq
  1. Ikki turni qabul qiluvchi generik funksiya yozing.
  2. comparable cheklovi bilan qidiruv funksiyasi yozing.
  3. cmp.Ordered bilan eng kichigini toping.
  4. O'z cheklovingizni interfeys sifatida e'lon qiling.
  5. ~int bilan o'z turingizni qamrab oling.
  6. ~ siz yozib, xatoni ko'ring.
  7. Generik Stek turini yozing.
  8. Metodga tur parametri qo'shishga urinib, xatoni o'qing.
  9. var bosh T bilan nol qiymat qaytaring.
  10. 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; cheklov T bilan nima qilish mumkinligini aytadi.
  • Tayyor cheklovlar: any, comparable (==) va cmp.Ordered (<).
  • Cheklov - oddiy interfeys, ichida turlar ro'yxati bo'lishi mumkin.
  • ~int "asosi int bo'lgan turlar" degani - o'z turlaringizni ham qamrab oladi.
  • Strukturalar ham generik bo'ladi, lekin metodlar yangi tur parametri qo'sha olmaydi.
  • var bosh T har 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.

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.