Məzmuna keç
Educora
Başlanğıc15 dəq4 / 10

Funksiyalar, closure-lər və defer

Bir neçə nəticə qaytaran, adlı nəticəli və variadik funksiyalar yaz, closure-ləri və işi funksiyanın sonuna saxlayan `defer`-i öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Bir neçə və adlı nəticə qaytaran funksiyalar yazmaq
  • Variadik funksiyalardan və funksiyaları qiymət kimi ötürməkdən istifadə etmək
  • Closure-in xarici dəyişəni necə tutduğunu izah etmək
  • defer-in icra sırasını və arqumentlərin nə vaxt hesablandığını bilmək

Kalkulyatorda hər dəfə «+» düyməsini basanda eyni toplama qaydası işə düşür. Proqramlaşdırmada belə təkrar istifadə olunan kod parçasına funksiya deyilir. Go funksiyaları bir neçə nəticə qaytara, başqa funksiyaları arqument kimi qəbul edə və öz vəziyyətini «yadda saxlaya» bilir. Bu dərsdə həm də defer ilə tanış olacaqsan — işi funksiyanın sonuna təxirə salan açar sözlə.

Funksiyanın elanı və nəticələri

Funksiya func açar sözü ilə elan olunur: əvvəlcə ad, sonra mötərizədə parametrlər, sonda isə nəticənin tipi. Eyni tipli parametrləri qısaltmaq olar: a int, b int əvəzinə a, b int. Go-nun fərqli cəhəti budur ki, funksiya bir neçə qiymət qaytara bilər: (int, int). Lazım olmayan nəticəni boş identifikator _ ilə atmaq olar.

Go
package main

import "fmt"

func add(a, b int) int {
	return a + b
}

func divmod(a, b int) (int, int) {
	return a / b, a % b
}

func rectangle(w, h float64) (area, perimeter float64) {
	area = w * h
	perimeter = 2 * (w + h)
	return
}

func main() {
	fmt.Println(add(2, 3))
	q, r := divmod(17, 5)
	fmt.Println(q, r)
	_, rest := divmod(10, 4)
	fmt.Println(rest)
	fmt.Println(rectangle(3, 2.5))
}
Gözlənilən nəticə
5
3 2
2
7.5 11

rectangle funksiyasında nəticələrin adı var: (area, perimeter float64). Belə dəyişənlər funksiyanın əvvəlində sıfır qiymətlə yaranır, arqumentsiz return isə onların cari qiymətlərini qaytarır. Son sətirdə gördüyün kimi, çox nəticəli funksiyanın çağırışını birbaşa Println-ə ötürmək də olar.

Variadik funksiyalar və funksiya qiymət kimi

Son parametrin tipindən əvvəl ... yazsan, funksiya istənilən sayda arqument qəbul edər — belə funksiyaya variadik deyilir. fmt.Println da məhz belədir. Funksiyanın içində bu parametr adi dilimdir. Hazır dilimi ötürmək üçün adından sonra ... yazılır: sum(scores...). Go-da funksiyalar həm də adi qiymətdir: onları dəyişənə yazmaq, başqa funksiyaya ötürmək və qaytarmaq olar.

Go
package main

import "fmt"

func sum(nums ...int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func apply(n int, f func(int) int) int {
	return f(n)
}

func main() {
	fmt.Println(sum())
	fmt.Println(sum(1, 2, 3))
	scores := []int{90, 85, 70}
	fmt.Println(sum(scores...))
	square := func(x int) int { return x * x }
	fmt.Println(apply(7, square))
}
Gözlənilən nəticə
0
6
245
49

Closure-lər

Adsız funksiya birbaşa kodun içində yaradılır: func(x int) int { ... }. Əgər o, xaricindəki dəyişəndən istifadə edirsə, həmin dəyişəni «tutur» və yaşadır — belə funksiyaya closure (qapanma) deyilir. Aşağıdakı counter hər çağırışda yeni sayğac yaradır: hər closure öz count dəyişənini saxlayır.

Go
package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
	other := counter()
	fmt.Println(other(), next())
}
Gözlənilən nəticə
1 2 3
1 4

next və other iki müstəqil sayğacdır: other() yenidən 1-dən başlayır, next() isə öz hesabını davam etdirib 4 qaytarır. count dəyişəni counter funksiyası bitəndən sonra da yaşayır, çünki onu closure tutub.

Go 1.21 və köhnə: əl ilə surət
var funcs []func()
for i := 0; i < 3; i++ {
	i := i // needed before Go 1.22
	funcs = append(funcs, func() { fmt.Print(i, " ") })
}
Go 1.22+: surət lazım deyil
var funcs []func()
for i := 0; i < 3; i++ {
	funcs = append(funcs, func() { fmt.Print(i, " ") })
}

defer: işi sona saxlamaq

defer açar sözü funksiya çağırışını cari funksiya bitənə qədər təxirə salır. Funksiya adi qaydada bitsə də, xəta ilə çıxsa da, təxirə salınmış çağırışlar mütləq icra olunur. Bir neçə defer varsa, onlar tərs sırada — sonuncudan birinciyə doğru işləyir (LIFO, üst-üstə qoyulmuş boşqablar kimi). Diqqət: arqumentlər sonra yox, defer yazılan anda hesablanır.

Go
package main

import "fmt"

func main() {
	fmt.Println("start")
	for i := 1; i <= 3; i++ {
		defer fmt.Println("deferred", i)
	}
	x := 10
	defer fmt.Println("x was", x)
	x = 20
	fmt.Println("end, x =", x)
}
Gözlənilən nəticə
start
end, x = 20
x was 10
deferred 3
deferred 2
deferred 1

Nəticəyə bax: x sonradan 20 olsa da, təxirə salınmış çağırış 10 çap etdi, çünki arqument defer sətrində hesablanmışdı. Təxirə salınmış çağırışların hamısı end sətrindən sonra, tərs sırada işlədi.

Əsas fikirlər

  • Funksiya func ad(parametrlər) nəticə şəklində yazılır; eyni tipli parametrlər birləşdirilə bilər: a, b int.
  • Go funksiyası bir neçə qiymət qaytara bilər; lazımsız nəticə _ ilə atılır.
  • ...T parametri istənilən sayda arqument qəbul edir; hazır dilim s... kimi ötürülür.
  • Closure xarici dəyişəni tutub saxlayır; Go 1.22-dən hər dövr addımı öz dəyişənini alır.
  • defer çağırışı funksiyanın sonuna saxlayır, tərs sırada icra edir və arqumentləri dərhal hesablayır.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
func divmod(a, b int) (int, int) funksiyasının düzgün çağırışı hansıdır?