Fungsi Go
Di Go fungsi adalah warga negara kelas satu fungsi adalah komponen paling dasar dari Go juga merupakan inti Go.
Deklarasi
Format deklarasi fungsi adalah sebagai berikut
func namaFungsi([daftarParameter]) [nilaiPengembalian] {
badanFungsi
}Ada dua cara untuk mendeklarasikan fungsi yang pertama adalah melalui kata kunci func langsung mendeklarasikan yang kedua adalah melalui kata kunci var untuk mendeklarasikan sebagai berikut
func sum(a int, b int) int {
return a + b
}
var sum = func(a int, b int) int {
return a + b
}Tanda tangan fungsi terdiri dari nama fungsi daftar parameter nilai pengembalian di bawah ini adalah contoh lengkap nama fungsi adalah Sum memiliki dua parameter tipe int a b tipe nilai pengembalian adalah int.
func Sum(a int, b int) int {
return a + b
}Ada satu poin yang sangat penting yaitu fungsi di Go tidak mendukung overload kode seperti di bawah ini tidak dapat dikompilasi
type Person struct {
Name string
Age int
Address string
Salary float64
}
func NewPerson(name string, age int, address string, salary float64) *Person {
return &Person{Name: name, Age: age, Address: address, Salary: salary}
}
func NewPerson(name string) *Person {
return &Person{Name: name}
}Filosofi Go adalah jika tanda tangan berbeda maka itu adalah dua fungsi yang sepenuhnya berbeda maka tidak boleh mengambil nama yang sama overload fungsi akan membuat kode menjadi membingungkan dan sulit dipahami. Apakah filosofi ini benar atau tidak terserah pendapat masing-masing setidaknya di Go Anda hanya melalui nama fungsi dapat mengetahui apa fungsinya tanpa perlu mencari itu adalah overload yang mana.
Parameter
Nama parameter di Go dapat tanpa nama umumnya ini digunakan saat deklarasi interface atau tipe fungsi tetapi untuk keterbacaan umumnya tetap disarankan untuk menambahkan nama parameter
type ExWriter func(io.Writer) error
type Writer interface {
ExWrite([]byte) (int, error)
}Untuk parameter dengan tipe yang sama hanya perlu mendeklarasikan tipe sekali tetapi kondisinya adalah mereka harus berdekatan
func Log(format string, a1, a2 any) {
...
}Parameter variadic dapat menerima 0 atau lebih nilai harus dideklarasikan di akhir daftar parameter contoh paling khas adalah fungsi fmt.Printf.
func Printf(format string, a ...any) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}Yang layak disebutkan adalah parameter fungsi di Go adalah passing by value yaitu saat passing parameter akan menyalin nilai argumen aktual. Jika Anda merasa saat passing slice atau map akan menyalin memori dalam jumlah besar saya dapat memberitahu Anda tidak perlu khawatir karena kedua struktur data ini pada dasarnya adalah pointer.
Nilai Pengembalian
Di bawah ini adalah contoh sederhana nilai pengembalian fungsi fungsi Sum mengembalikan nilai tipe int.
func Sum(a, b int) int {
return a + b
}Saat fungsi tidak memiliki nilai pengembalian tidak perlu void tanpa nilai pengembalian即可.
func ErrPrintf(format string, a ...any) {
_, _ = fmt.Fprintf(os.Stderr, format, a...)
}Go mengizinkan fungsi memiliki beberapa nilai pengembalian saat ini perlu menggunakan tanda kurung untuk mengelilingi nilai pengembalian.
func Div(a, b float64) (float64, error) {
if a == 0 {
return math.NaN(), errors.New("0 tidak dapat sebagai pembagi")
}
return a / b, nil
}Go juga mendukung nilai pengembalian bernama tidak boleh duplikat dengan nama parameter saat menggunakan nilai pengembalian bernama kata kunci return dapat tidak perlu menentukan nilai mana yang dikembalikan.
func Sum(a, b int) (ans int) {
ans = a + b
return
}Sama seperti parameter saat ada beberapa nilai pengembalian bernama dengan tipe yang sama dapat menghilangkan deklarasi tipe yang duplikat
func SumAndMul(a, b int) (c, d int) {
c = a + b
d = a * b
return
}Tidak peduli bagaimana nilai pengembalian bernama dideklarasikan selalu prioritas tertinggi adalah nilai setelah kata kunci return.
func SumAndMul(a, b int) (c, d int) {
c = a + b
d = a * b
// c, d tidak akan dikembalikan
return a + b, a * b
}Fungsi Anonim
Fungsi anonim adalah fungsi tanpa tanda tangan misalnya fungsi di bawah ini func(a, b int) int tidak memiliki nama jadi kita hanya dapat mengikuti tanda kurung setelah badan fungsinya untuk memanggil.
func main() {
func(a, b int) int {
return a + b
}(1, 2)
}Saat memanggil fungsi saat parameternya adalah tipe fungsi saat ini nama tidak lagi penting dapat langsung meneruskan fungsi anonim sebagai berikut
type Person struct {
Name string
Age int
Salary float64
}
func main() {
people := []Person{
{Name: "Alice", Age: 25, Salary: 5000.0},
{Name: "Bob", Age: 30, Salary: 6000.0},
{Name: "Charlie", Age: 28, Salary: 5500.0},
}
slices.SortFunc(people, func(p1 Person, p2 Person) int {
if p1.Name > p2.Name {
return 1
} else if p1.Name < p2.Name {
return -1
}
return 0
})
}Ini adalah contoh aturan pengurutan kustom slices.SortFunc menerima dua parameter satu adalah slice yang lain adalah fungsi perbandingan tidak mempertimbangkan reuse kita dapat langsung meneruskan fungsi anonim.
Closure
Closure konsep ini di beberapa bahasa juga disebut ekspresi Lambda digunakan bersama dengan fungsi anonim closure = fungsi + referensi lingkungan lihat contoh di bawah ini
func main() {
grow := Exp(2)
for i := range 10 {
fmt.Printf("2^%d=%d\n", i, grow())
}
}
func Exp(n int) func() int {
e := 1
return func() int {
temp := e
e *= n
return temp
}
}Output
2^0=1
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128
2^8=256
2^9=512Nilai pengembalian fungsi Exp adalah fungsi di sini akan disebut fungsi grow setiap kali dipanggil variabel e akan bertambah secara eksponensial. Fungsi grow mereferensikan dua variabel fungsi Exp yaitu e dan n mereka lahir dalam ruang lingkup fungsi Exp dalam situasi normal seiring dengan berakhirnya pemanggilan fungsi Exp memori variabel ini akan dikembalikan seiring dengan keluar dari stack. Tetapi karena fungsi grow mereferensikan mereka jadi mereka tidak dapat dikembalikan melainkan melarikan diri ke heap bahkan siklus hidup fungsi Exp telah berakhir tetapi siklus hidup variabel e dan n belum berakhir dalam fungsi grow masih dapat langsung memodifikasi kedua variabel ini fungsi grow adalah fungsi closure.
Menggunakan closure dapat dengan sangat sederhana mengimplementasikan fungsi untuk menghitung deret Fibonacci kodenya adalah sebagai berikut
func main() {
// 10 angka Fibonacci
fib := Fib(10)
for n, next := fib(); next; n, next = fib() {
fmt.Println(n)
}
}
func Fib(n int) func() (int, bool) {
a, b, c := 1, 1, 2
i := 0
return func() (int, bool) {
if i >= n {
return 0, false
} else if i < 2 {
f := i
i++
return f, true
}
a, b = b, c
c = a + b
i++
return a, true
}
}Output adalah
0
1
1
2
3
5
8
13
21
34Pemanggilan Tertunda
Kata kunci defer dapat membuat fungsi dipanggil setelah menunda一段时间 sebelum fungsi kembali fungsi-fungsi yang dijelaskan defer ini akhirnya akan dieksekusi satu per satu lihat contoh di bawah ini
func main() {
Do()
}
func Do() {
defer func() {
fmt.Println("1")
}()
fmt.Println("2")
}Output
2
1Karena defer dieksekusi sebelum fungsi kembali Anda juga dapat memodifikasi nilai pengembalian fungsi dalam defer
func main() {
fmt.Println(sum(3, 5))
}
func sum(a, b int) (s int) {
defer func() {
s -= 10
}()
s = a + b
return
}Saat ada beberapa fungsi yang dijelaskan defer akan seperti stack urutan masuk pertama keluar terakhir dieksekusi.
func main() {
fmt.Println(0)
Do()
}
func Do() {
defer fmt.Println(1)
fmt.Println(2)
defer fmt.Println(3)
defer fmt.Println(4)
fmt.Println(5)
}0
2
5
4
3
1Pemanggilan tertunda biasanya digunakan untuk melepaskan sumber daya file menutup koneksi jaringan dll ada satu penggunaan adalah menangkap panic tetapi ini adalah hal yang akan dibahas dalam bagian penanganan error.
Loop
Meskipun tidak dilarang secara eksplisit umumnya disarankan untuk tidak menggunakan defer dalam loop for sebagai berikut
func main() {
n := 5
for i := range n {
defer fmt.Println(i)
}
}Output adalah sebagai berikut
4
3
2
1
0Hasil kode ini benar tetapi prosesnya mungkin tidak benar. Di Go setiap membuat defer perlu mengajukan ruang memori di goroutine saat ini. Misalkan dalam contoh di atas bukan loop for n sederhana tetapi proses pengolahan data yang kompleks saat permintaan eksternal tiba-tiba meningkat tajam maka dalam waktu singkat akan membuat大量 defer saat jumlah loop sangat besar atau jumlah loop tidak pasti maka dapat menyebabkan penggunaan memori tiba-tiba melonjak ini umumnya kita sebut kebocoran memori.
Prakalkulasi Parameter
Untuk pemanggilan tertunda ada beberapa detail yang tidak intuitif misalnya contoh di bawah ini
func main() {
defer fmt.Println(Fn1())
fmt.Println("3")
}
func Fn1() int {
fmt.Println("2")
return 1
}Jebakan ini masih sangat tersembunyi penulis sebelumnya karena jebakan ini setengah hari tidak dapat menemukan apa penyebabnya dapat menebak output apa jawabannya adalah sebagai berikut
2
3
1Mungkin banyak orang认为是 output di bawah ini
3
2
1Menurut niat pengguna fmt.Println(Fn1()) bagian ini seharusnya berharap mereka dieksekusi setelah badan fungsi selesai dieksekusi fmt.Println memang dieksekusi terakhir tetapi Fn1() di luar dugaan di bawah ini contoh situasinya lebih jelas.
func main() {
var a, b int
a = 1
b = 2
defer fmt.Println(sum(a, b))
a = 3
b = 4
}
func sum(a, b int) int {
return a + b
}Outputnya pasti 3 bukan 7 jika menggunakan closure bukan pemanggilan tertunda hasilnya akan berbeda
func main() {
var a, b int
a = 1
b = 2
f := func() {
fmt.Println(sum(a, b))
}
a = 3
b = 4
f()
}Output closure adalah 7 lalu jika menggabungkan pemanggilan tertunda dan closure bagaimana
func main() {
var a, b int
a = 1
b = 2
defer func() {
fmt.Println(sum(a, b))
}()
a = 3
b = 4
}Kali ini normal output adalah 7. Di bawah ini diubah lagi tidak ada closure lagi
func main() {
var a, b int
a = 1
b = 2
defer func(num int) {
fmt.Println(num)
}(sum(a, b))
a = 3
b = 4
}Output kembali menjadi 3. Melalui perbandingan beberapa contoh di atas dapat ditemukan kode ini
defer fmt.Println(sum(a,b))Sebenarnya setara dengan
defer fmt.Println(3)Go tidak akan menunggu sampai terakhir untuk memanggil fungsi sum fungsi sum lebih awal sebelum pemanggilan tertunda dieksekusi telah dipanggil dan sebagai parameter diteruskan fmt.Println. Kesimpulan adalah untuk fungsi yang langsung dipengaruhi oleh defer parameternya akan diprakalkulasi ini juga menyebabkan fenomena aneh dalam contoh pertama untuk situasi ini terutama saat mengembalikan nilai fungsi sebagai parameter dalam pemanggilan tertunda perlu diperhatikan.
