函數 (function)
函數可以沒有參數或者接受多個參數。
當連續兩個或多個函數的已命名形參類型相同時,除最后一個類型以外,其它都可以省略。
func add(x, y int) int {
return x + y
}
函數(或者變量)的名稱以大寫字母開頭時,它就是已導出的。
函數可以返回任意數量的字符串。
func swap(x, y string) (string, string) {
return y, x
}
函數的返回值可以被命名,它們會被視作在函數頂部定義的變量,沒有參數的 return 返回已經被命名的返回值。
func division(dividend, divisor int) (quotient, remainder int) {
quotient = dividend / divisior
remainder = dividend - quotient * divisor
return
}
函數也是值,也可以用作函數的參數和返回值。
// conpute 接受一個函數作為參數
// 調用 conpute 時傳入不同的函數,返回對3和4作不同的操作的結果
func conpute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
函數的閉包 (closure)
A closure is a record storing a function together with an environment.
閉包是由函數和環境組合而成的。閉包保存和記錄了它產生時的外部環境——它的函數體之外的變量,并且可以訪問和修改這些變量。
在閉包實際實現的時候,往往通過調用一個外部函數返回其內部函數來實現的。用戶得到一個閉包,也等同于得到了這個內部函數,每次執行這個閉包就等同于執行內部函數。
如果外部函數的變量可見性是 local 的,即生命周期在外部函數結束時也結束的,那么閉包的環境就是封閉的。反之,那么閉包其實不再封閉,全局可見的變量的修改,也會對閉包內的這個變量造成影響。
package main
import “fmt”
func test_1(x int) func() {
return func() {
x++
fmt.Println(x)
}
}
func test_2(x int) func() {
sum := 0
return func() {
sum += x
fmt.Println(x, sum)
}
}
func test_3(x int) func(int) int {
sum := 0
return func(y int) int {
sum += x * y
return sum
}
}
func main() {
test_1(1)()
test_2(1)()
// 每個閉包事實上有著不同的外部環境
// 即:對每個 for 循環,都會新建一個 test_3()
// 所以每個閉包綁定的是不同的 sum 變量
for i := 0; i 《 5; i++ {
fmt.Printf(“%d ”, test_3(1)(i))
}
fmt.Println()
// 每個閉包的外部環境相同(tmp)
// 即 for 循環中的閉包綁定的是同一個 sum 變量
tmp := test_3(1)
for i := 0; i 《 5; i++ {
fmt.Printf(“%d ”, tmp(i))
}
fmt.Println()
}
上面的程序輸出結果是:
2
1 1
1 1
0 1 2 3 4
0 1 3 6 10
方法 (method)
Go 沒有類,不過可以為結構體類型定義方法。方法就是一類帶特殊的接收者參數的函數。方法接收者在它自己的參數列表內,位于 func 關鍵字和方法名之間。(非結構體類型也可以定義方法)
type Vertex struct {
X, Y float64
}
func (v Vertex) distance() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
方法并不能修改指針接收者的值。只有指針接收者的方法能夠修改接收者指向的值。(在這種情況下,方法也沒有修改接收者的值——指針的內容,只是修改了指針指向的值,和用指針作為參數是一樣的)
在很多意義上,方法的接收者和普通的參數是一樣的。如果不使用指針。
不過,帶指針參數的函數必須接受一個指針,而以指針為接受者的方法被調用時,接受者接收者既能為值又能為指針。
package main
import “fmt”
type Vertex struct {
X, Y float64
}
func (v *Vertex) Move_1(dx, dy float64) {
v.X += dx
v.Y += dy
}
func (v Vertex) Move_2(dx, dy float64) {
v.X += dx
v.Y += dy
}
func Move_3(v *Vertex, dx, dy float64) {
v.X += dx
v.Y += dy
}
func Move_4(v Vertex, dx, dy float64) {
v.X += dx
v.Y += dy
}
func main() {
var v Vertex
v.X = 0
v.Y = 0.
v.Move_1(1, 1)
fmt.Println(v.X, v.Y)
p := &v
p.Move_1(1, 1)
fmt.Println(v.X, v.Y)
v.Move_2(1, 1)
fmt.Println(v.X, v.Y)
Move_3(&v, 1, 1)
fmt.Println(v.X, v.Y)
Move_4(v, 1, 1)
fmt.Println(v.X, v.Y)
}
上面的程序輸出結果是:
1 1
2 2
2 2
3 3
3 3
接口 (interface)
接口是一組方法簽名的集合,接口類型的變量可以保存任何實現了這些方法的值。
Go 語言中的接口是隱式實現的,也就是說,如果一個類型實現了一個接口定義的所有方法,那么它就自動地實現了該接口。沒有 implements 關鍵字。
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f 《 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // MyFloat 實現了 Abs()
a = &v // *Vertex 實現了 Abs()
}
指定了零個方法的接口值被稱為空接口,可以保存任何類型的值(因為每個類型都至少實現了零個方法)。空接口被用來處理未知類型的值。
在內部,接口值可以看做包含值和具體類型的元組,類型斷言提供了訪問接口值底層具體值的方式。
package main
import “fmt”
func main() {
var i interface{} = “hello”
// 該語句斷言接口值 i 保存了具體類型 string,
// 并將其底層類型為 string 的值賦予變量 s。
// 若 i 并未保存 string 類型的值,該語句就會觸發 panic。
s := i.(string)
fmt.Println(s)
// 為了判斷一個接口值是否保存了一個特定的類型,
// 類型斷言可返回兩個值:其底層值以及一個報告斷言是否成功的布爾值。
// 若 i 保存了一個 string,那么 s 將會是其底層值,而 ok 為 true。
// 否則,ok 將為 false 而 s 將為 T 類型的零值,程序并不會產生 panic。
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // 報錯 (panic)
fmt.Println(f)
}
上面的程序輸出結果是:
hello
hello true
0 false
panic: interface conversion: interface {} is string, not float64
。..。..
審核編輯:黃飛
-
字符串
+關注
關注
1文章
585瀏覽量
20601 -
函數
+關注
關注
3文章
4346瀏覽量
62968 -
變量
+關注
關注
0文章
613瀏覽量
28465 -
go語言
+關注
關注
1文章
158瀏覽量
9089
原文標題:Go 的函數,方法和接口
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論