函數(shù)是組織好的、可重復(fù)使用的恨樟、用于執(zhí)行指定任務(wù)的代碼塊半醉。
Go語言中支持函數(shù)、匿名函數(shù)和閉包厌杜,并且函數(shù)在Go語言中屬于“一等公民”奉呛。
一夯尽、函數(shù)的聲明和調(diào)用
1、函數(shù)的聲明
Go語言中定義函數(shù)使用func關(guān)鍵字登馒,具體格式如下:
func funcName(parametername type) (output type) {
//這里是處理邏輯代碼
//返回多個值
return valu
}
- func 函數(shù)由func開始聲明
- funcName 函數(shù)名
- () 函數(shù)的標(biāo)志
- parametername type 函數(shù)的參數(shù)列表匙握,參數(shù)列表指定參數(shù)的類型、順序以及個數(shù)陈轿,參數(shù)是可選圈纺,可有可無秦忿;這里的參數(shù)相當(dāng)于一個占位符,所以也叫形式參數(shù)蛾娶,當(dāng)函數(shù)被調(diào)用時灯谣,傳入的值傳遞給參數(shù),這個值被稱為實際參數(shù)
- ouput type 返回值列表蛔琅,返回值可以由名稱和類型組成胎许,也可以只寫類型不寫名稱;返回值可以有一個或多個罗售,當(dāng)有一個返回值時可以不加
()
辜窑,多個返回值時必須加()
- 函數(shù)體:實現(xiàn)指定功能的代碼塊
{}
里面的內(nèi)容。
2寨躁、函數(shù)的調(diào)用
可以通過funcName(parameter)
的方式調(diào)用函數(shù)穆碎。調(diào)用有返回值的函數(shù)時,可以不接收其返回值职恳。
package main
import "fmt"
func main() {
a := 10
b := 20
res := sum(a, b)
fmt.Printf("%v + %v = %v", a, b, res)
}
func sum(a, b int) int {
return a + b
}
運行結(jié)果
10 + 20 = 30
二所禀、函數(shù)的參數(shù)
1、參數(shù)的使用:
形式參數(shù):定義函數(shù)時放钦,用于接收外部傳入的數(shù)據(jù)北秽,叫做形式參數(shù),簡稱形參最筒。
實際參數(shù):調(diào)用函數(shù)時贺氓,傳給形參的實際的數(shù)據(jù),叫做實際參數(shù)床蜘,簡稱實參辙培。
函數(shù)調(diào)用:
- A:函數(shù)名稱必須匹配
- B:實參與形參必須一一對應(yīng):順序,個數(shù)邢锯,類型
函數(shù)的參數(shù)中如果相鄰變量的類型相同扬蕊,則可以省略類型,如:
package main
import "fmt"
func main() {
a := 10
b := 20
res := add(a, b)
fmt.Printf("%v + %v = %v", a, b, res)
}
func add(a, b int) sum int {
sum = a + b
return
}
2丹擎、可變參數(shù)
可變參數(shù)是指函數(shù)的參數(shù)數(shù)量不固定尾抑。Go語言中的可變參數(shù)通過在參數(shù)名后加...來標(biāo)識。
注意:可變參數(shù)通常要作為函數(shù)的最后一個參數(shù)蒂培。
func funcName(arg ...int) {}
arg ...int
告訴Go這個函數(shù)接受不定數(shù)量的參數(shù)再愈。注意,這些參數(shù)的類型全部是int护戳。在函數(shù)體中翎冲,變量arg是一個int類型的slice
3、參數(shù)的傳遞
go語言函數(shù)的參數(shù)也是存在值傳遞和引用傳遞
數(shù)據(jù)類型:
-
按照數(shù)據(jù)類型來分:
- 基本數(shù)據(jù)類型
int媳荒,float抗悍,string驹饺,bool - 復(fù)合數(shù)據(jù)類型
arry,slice缴渊,map赏壹,struct,interface衔沼,chan...
- 基本數(shù)據(jù)類型
-
按照數(shù)據(jù)的存儲特點來分:
- 值類型的數(shù)據(jù)蝌借,操作的是數(shù)據(jù)本身。
int俐巴,float骨望,string,bool欣舵,arry擎鸠,struct - 引用類型的數(shù)據(jù),操作的數(shù)據(jù)的地址缘圈。
slice劣光,map,chan
- 值類型的數(shù)據(jù)蝌借,操作的是數(shù)據(jù)本身。
值傳遞:值類型的數(shù)據(jù)傳遞為值傳遞糟把,傳遞的是數(shù)據(jù)的副本绢涡。修改數(shù)據(jù),對原始數(shù)據(jù)沒有影響遣疯。
package main
import "fmt"
func main() {
arr1 := [3]int{1, 2, 3}
fmt.Println("函數(shù)調(diào)用前雄可,數(shù)組的數(shù)據(jù):", arr1)
fun(arr1)
fmt.Println("函數(shù)調(diào)用后,數(shù)組的數(shù)據(jù):", arr1)
}
func fun(arr2 [3]int) {
fmt.Println("函數(shù)中缠犀,數(shù)組的數(shù)據(jù):", arr2)
arr2[0] = 100
fmt.Println("函數(shù)中数苫,修改后數(shù)據(jù)的數(shù)據(jù):", arr2)
}
運行結(jié)果
函數(shù)調(diào)用前,數(shù)組的數(shù)據(jù): [1 2 3]
函數(shù)中辨液,數(shù)組的數(shù)據(jù): [1 2 3]
函數(shù)中虐急,修改后數(shù)據(jù)的數(shù)據(jù): [100 2 3]
函數(shù)調(diào)用后,數(shù)組的數(shù)據(jù): [1 2 3]
- 引用傳遞:引用類型的數(shù)據(jù)傳遞為引用傳遞滔迈,傳遞的數(shù)據(jù)的地址止吁,導(dǎo)致多個變量指向同一塊內(nèi)存。
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3}
fmt.Println("函數(shù)調(diào)用前燎悍,切片的數(shù)據(jù):", slice1)
fun(slice1)
fmt.Println("函數(shù)調(diào)用后敬惦,切片的數(shù)據(jù):", slice1)
}
func fun(slice2 []int) {
fmt.Println("函數(shù)中,切片的數(shù)據(jù):", slice2)
slice2[0] = 100
fmt.Println("函數(shù)中间涵,修改后切片的數(shù)據(jù):", slice2)
}
運行結(jié)果
函數(shù)調(diào)用前仁热,切片的數(shù)據(jù): [1 2 3]
函數(shù)中,切片的數(shù)據(jù): [1 2 3]
函數(shù)中勾哩,修改后切片的數(shù)據(jù): [100 2 3]
函數(shù)調(diào)用后抗蠢,切片的數(shù)據(jù): [100 2 3]
- 指針傳遞:傳遞的就是數(shù)據(jù)的內(nèi)存地址。
三思劳、函數(shù)的返回值
函數(shù)的返回值:
一個函數(shù)的執(zhí)行結(jié)果迅矛,返回給函數(shù)調(diào)用處,執(zhí)行結(jié)果就叫函數(shù)的返回值潜叛。
return語句:
一個函數(shù)的定義上有返回值秽褒,那么函數(shù)中必須有return語句,將執(zhí)行結(jié)果返回給函數(shù)的調(diào)用處威兜。
函數(shù)的返回結(jié)果必須和函數(shù)定義的一致销斟,類型、數(shù)量椒舵、順序蚂踊。
- 返回值的命名
函數(shù)定義時可以給返回值命名,并在函數(shù)體中直接使用這些變量笔宿,最后通過return關(guān)鍵字返回犁钟。
func add(x, y int) (sum int) {
sum = x + y
return
}
- 多返回值
一個函數(shù)可以沒有返回值,也可以有一個返回值泼橘,也可以有返回多個值涝动。
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
- 空白標(biāo)識符
-
可用來舍棄某些返回值。
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
_, sub := calc(10, 20) //舍棄sum
四炬灭、函數(shù)的作用域
作用域:變量可以使用的范圍醋粟。
1、全局變量
全局變量是定義在函數(shù)外部的變量重归,它在程序整個運行周期內(nèi)都有效米愿。 所有的函數(shù)都可以使用,而且共享這一份數(shù)據(jù)提前。
package main
import "fmt"
var a = 10
func main() {
fmt.Println("test調(diào)用前吗货,main中訪問a:", a)
test()
fmt.Println("test調(diào)用后,main中訪問a:", a)
}
func test() {
fmt.Println("操作前狈网,test中訪問a: ", a)
a = 20
fmt.Println("操作后宙搬,test中訪問a: ", a)
}
運行結(jié)果
test調(diào)用前,main中訪問a: 10
操作前拓哺,test中訪問a: 10
操作后勇垛,test中訪問a: 20
test調(diào)用后,main中訪問a: 20
2士鸥、局部變量
一個函數(shù)內(nèi)部定義的變量闲孤,就叫做局部變量
局部變量只能在定義的范圍內(nèi)訪問操作
package main
import "fmt"
func main() {
test()
fmt.Println("main中訪問a:", a) //undefined: a
}
func test() {
a := 20
fmt.Println("test中訪問a: ", a)
}
運行結(jié)果
# command-line-arguments
.\main.go:7:35: undefined: a
局部變量和全局變量重名,優(yōu)先訪問局部變量烤礁。
package main
import "fmt"
var a = 100
func main() {
test()
}
func test() {
a := 20
fmt.Println("test中訪問a: ", a)
}
運行結(jié)果
test中訪問a: 20
另外讼积,if
肥照,switch
,for
語句中聲明的變量也屬于局部變量勤众,在代碼塊外無法訪問舆绎。
五、函數(shù)的本質(zhì)
函數(shù)也是Go語言中的一種數(shù)據(jù)類型们颜,可以作為另一個函數(shù)的參數(shù)吕朵,也可以作為另一個函數(shù)的返回值。
1窥突、函數(shù)是一種數(shù)據(jù)類型
package main
import "fmt"
func main() {
fmt.Printf("%T\n", fun1) //fun1的類型是func(int, int)
fmt.Printf("%T\n", fun2) //fun2的類型是func(int, int) int
}
func fun1(a, b int) {
fmt.Println(a, b)
}
func fun2(c, d int) int {
fmt.Println(c, d)
return 0
}
運行結(jié)果
func(int, int)
func(int, int) int
2努溃、定義函數(shù)類型的變量
var f fun(int, int) int
上面語句定義了一個變量f,它是一個函數(shù)類型阻问,這種函數(shù)接收兩個int類型的參數(shù)并且返回一個int類型的返回值梧税。
所有參數(shù)和返回值符合條件的函數(shù)可以賦值給f變量
package main
import "fmt"
func main() {
var f func(int, int) int
f = sum
res := f(20, 10)
fmt.Println("20 + 10 = ", res)
f = sub
res = f(20, 10)
fmt.Println("20 - 10 = ", res)
}
func sum(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
運行結(jié)果
20 + 10 = 30
20 - 10 = 10
六、匿名函數(shù)
匿名函數(shù)就是沒有函數(shù)名的函數(shù)
func (參數(shù)) (返回值) {
函數(shù)體
}
匿名函數(shù)因為沒有函數(shù)名则拷,所以沒辦法像普通函數(shù)那樣調(diào)用贡蓖,所以匿名函數(shù)需要保存到某個變量或者作為立即執(zhí)行函數(shù)。
package main
import "fmt"
func main() {
// 將匿名函數(shù)保存到變量中
sum := func(a, b int) int {
return a + b
}
// 通過變量調(diào)用匿名函數(shù)
res := sum(10, 20)
fmt.Println("10 + 20 =", res)
// 自執(zhí)行函數(shù),匿名函數(shù)定義完直接加()執(zhí)行
func(c, d int) {
fmt.Printf("%v + %v = %v\n", c, d, c+d)
}(10, 20)
}
運行結(jié)果
10 + 20 = 30
10 + 20 = 30
七煌茬、高階函數(shù)
go語言支持函數(shù)式編程:
- 將一個函數(shù)作為另一個函數(shù)的參數(shù)
- 將一個函數(shù)作為另一個函數(shù)的返回值
1斥铺、回調(diào)函數(shù)
一個函數(shù)被作為參數(shù)傳遞給另一個函數(shù),那么這個函數(shù)就叫做回調(diào)函數(shù)坛善。
回調(diào)函數(shù)并不會馬上被調(diào)用執(zhí)行晾蜘,它會在包含它的函數(shù)內(nèi)的某個特定的時間點被“回調(diào)”(就像它的名字一樣)。
package main
import "fmt"
func main() {
res := calc(10, 20, add)
fmt.Println(res)
}
// add是一個func(int, int)int類型的函數(shù)眠屎,可以作為參數(shù)傳遞給calc函數(shù)
func add(a, b int) int {
return a + b
}
// calc 高階函數(shù)剔交,它有兩個int類型的參數(shù)和一個func(int, int)int函數(shù)類型的參數(shù)
// oper 回調(diào)函數(shù),它被作為參數(shù)傳遞給calc函數(shù)
func calc(a, b int, oper func(int, int) int) int {
res := oper(a, b)
return res
}
運行結(jié)果
30
2改衩、函數(shù)作為返回值
package main
import "fmt"
func main() {
fun := calc("+")
res := fun(10, 20)
fmt.Println("10 + 20 =", res)
}
func sum(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
func calc(s string) func(int, int) int {
switch s {
case "+":
return sum
case "-":
return sub
default:
fmt.Println("你傳的是個啥玩意岖常!")
return nil
}
}
運行結(jié)果
10 + 20 = 30
3、閉包
一個外層函數(shù)葫督,有內(nèi)層函數(shù)竭鞍,該內(nèi)層函數(shù)會操作外層函數(shù)的局部變量(外層函數(shù)的參數(shù),或外層函數(shù)定義的變量)橄镜,并且該內(nèi)層函數(shù)作為外層函數(shù)的返回值偎快。
這個內(nèi)層函數(shù)和外層函數(shù)的局部變量,統(tǒng)稱為閉包結(jié)構(gòu)洽胶。
局部變量的生命周期會發(fā)生改變晒夹。正常的局部變量隨著函數(shù)的調(diào)用而創(chuàng)建,隨著函數(shù)的結(jié)束而銷毀。
但是閉包結(jié)構(gòu)的外層函數(shù)的局部變量并不會隨著外層函數(shù)的結(jié)束而銷毀丐怯,因為內(nèi)層函數(shù)還要繼續(xù)使用喷好。
package main
import "fmt"
func main() {
fun := add()
res := fun()
fmt.Println("第一次調(diào)用,res=", res)
res = fun()
fmt.Println("第二次調(diào)用响逢,res=", res)
res = fun()
fmt.Println("第二次調(diào)用绒窑,res=", res)
}
func add() func() int {
i := 0
return func() int {
i++
return i
}
}
運行結(jié)果
第一次調(diào)用棕孙,res= 1
第二次調(diào)用舔亭,res= 2
第二次調(diào)用,res= 3
七蟀俊、defer語句
defer是Go語言中的延遲執(zhí)行語句钦铺,用來添加函數(shù)結(jié)束時執(zhí)行的代碼,常用于釋放某些已分配的資源肢预、關(guān)閉數(shù)據(jù)庫連接矛洞、斷開socket連接、解鎖一個加鎖的資源烫映。
Go語言機(jī)制擔(dān)保一定會執(zhí)行defer語句中的代碼沼本。
1、延遲函數(shù)
- 被延遲的函數(shù)锭沟,在離開所在的函數(shù)或方法時抽兆,執(zhí)行(報錯的時候也會執(zhí)行
- 如果有很多defer語句,遵從“先進(jìn)后出”的模式
package main
import "fmt"
func main() {
a := 1
b := 2
c := 3
d := 4
//defer a++ //a++ 是一個語句族淮,并非函數(shù)或方法辫红,程序報錯
defer fmt.Println("defer", a)
defer fmt.Println("defer", b)
defer fmt.Println("defer", c)
defer fmt.Println("defer", d)
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
運行結(jié)果
1
2
3
4
defer 4
defer 3
defer 2
defer 1
2、延遲方法
延遲并不僅僅局限于函數(shù)祝辣。延遲一個方法調(diào)用也是完全合法的贴妻。
package main
import "fmt"
// Student 學(xué)生結(jié)構(gòu)體
type Student struct {
name string
city string
}
func (s Student) hello() {
fmt.Printf("我叫%v, 我來自%v。\n", s.name, s.city)
}
func main() {
s := Student{
name: "jack",
city: "北京市",
}
defer s.hello()
fmt.Print("大家好蝙斜,")
}
運行結(jié)果
大家好名惩,我叫jack, 我來自北京市
3、延遲參數(shù)
defer聲明時會先計算確定參數(shù)的值孕荠,defer推遲執(zhí)行的僅是其函數(shù)體娩鹉。
package main
import "fmt"
func main() {
a := 1
defer fun(a)
a++
fmt.Println("main中的a =", a)
}
func fun(a int) {
fmt.Println("fun中的a =", a)
}
運行結(jié)果
main中的a = 2
fun中的a = 1
4、defer與return
- 所有函數(shù)在執(zhí)行 RET 返回指令之前岛琼,都會先檢查是否存在 defer 語句底循,若存在則先逆序調(diào)用 defer 語句進(jìn)行收尾工作再退出返回;
- 匿名返回值是在 return 執(zhí)行時被聲明槐瑞,有名返回值則是在函數(shù)聲明的同時被聲明熙涤,因此在 defer 語句中只能訪問有名返回值,而不能直接訪問匿名返回值;
- return 其實應(yīng)該包含前后兩個步驟:第一步是給返回值賦值(若為有名返回值則直接賦值祠挫,若為匿名返回值則先聲明再賦值)那槽;第二步是調(diào)用 RET 返回指令并傳入返回值,而 RET 則會檢查 defer 是否存在等舔,若存在就先逆序插播 defer 語句骚灸,最后 RET 攜帶返回值退出函數(shù);
- 因此慌植,??defer甚牲、return、返回值三者的執(zhí)行順序應(yīng)該是:return最先給返回值賦值蝶柿;接著 defer 開始執(zhí)行一些收尾工作丈钙;最后 RET 指令攜帶返回值退出函數(shù)。
(1)匿名返回值的情況
package main
import "fmt"
func main() {
fmt.Println(fun1())
}
func fun1() int {
var i int
defer func() {
i++
}()
return i
}
運行結(jié)果
0
(2)有名返回值的情況
package main
import "fmt"
func main() {
fmt.Println(fun2())
}
func fun2() (i int) {
defer func() {
i++
}()
return i
}
運行結(jié)果
1
分析:
-
fun1()int
函數(shù)的返回值沒有被提前聲名交汤,其值來自于其他變量的賦值雏赦,而 defer 中修改的也是其他變量(其實該 defer 根本無法直接訪問到返回值),因此函數(shù)退出時返回值并沒有被修改芙扎。 -
fun2()(i int)
函數(shù)的返回值被提前聲名星岗,這使得 defer 可以訪問該返回值,因此在 return 賦值返回值 i 之后戒洼,defer 調(diào)用返回值 i 并進(jìn)行了修改俏橘,最后致使 return 調(diào)用 RET 退出函數(shù)后的返回值才會是 defer 修改過的值。
經(jīng)典案例
package main
import "fmt"
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
func f1() int {
x := 5
defer func() {
x++ // defer 訪問的是變量x施逾,訪問不到返回值
// fmt.Println("f1函數(shù)defer中的x =", x) //6
}()
return x // 返回值 = 5 //返回5
}
func f2() (x int) {
defer func() {
x++ //defer 訪問x, 可以訪問返回值敷矫,在RET之前,將返回值修改為6
// fmt.Println("f2函數(shù)defer中的x =", x) //6
}()
return 5 // 返回值(x) = 5 //返回6
}
func f3() (y int) {
x := 5
defer func() {
x++ // defer 訪問變量x汉额,將變量x修改為6
// fmt.Println("f3函數(shù)defer中的x =", x) //6
}()
return x // 返回值(y) = 5 //返回5
}
func f4() (x int) {
defer func(x int) {
x++ // 這里修改的defer時傳入的x(0),將其修改為1
// fmt.Println("f4函數(shù)defer中的x =", x) //1
}(x) // defer 語句調(diào)用時傳入x的值為int類型的默認(rèn)值0
return 5 // 返回值(x) = 5 //返回5
}
運行結(jié)果
5
6
5
5
5曹仗、defer的作用域
- defer 只對當(dāng)前協(xié)程有效(main 可以看作是主協(xié)程);
- 當(dāng)任意一條(主)協(xié)程發(fā)生 panic 時蠕搜,會執(zhí)行當(dāng)前協(xié)程中 panic 之前已聲明的 defer怎茫;
- 在發(fā)生 panic 的(主)協(xié)程中,如果沒有一個 defer 調(diào)用 recover()進(jìn)行恢復(fù)妓灌,則會在執(zhí)行完最后一個已聲明的 defer 后轨蛤,引發(fā)整個進(jìn)程崩潰;
- 主動調(diào)用 os.Exit(int) 退出進(jìn)程時虫埂,defer 將不再被執(zhí)行祥山。
package main
import (
"fmt"
// "os"
)
func main() {
fmt.Println("start")
// panic("崩潰了") // defer和之后的語句都不再執(zhí)行
// os.Exit(1) // defer和之后的語句都不再執(zhí)行
defer fmt.Println("defer")
// go func() {
// panic("崩潰了")
// }() // defer不被執(zhí)行
// panic("崩潰了") // defer會執(zhí)行,但后面的語句不再執(zhí)行
fmt.Println("over")
// os.Exit(1) // defer不被執(zhí)行
}
八掉伏、內(nèi)置函數(shù)
內(nèi)置函數(shù) | 介紹 |
---|---|
close | 主要用來關(guān)閉channel |
len | 用來求長度缝呕,比如string澳窑、array、slice供常、map摊聋、channel |
new | 用來分配內(nèi)存,主要用來分配值類型栈暇,比如int麻裁、struct。返回的是指針 |
make | 用來分配內(nèi)存源祈,主要用來分配引用類型煎源,比如chan、map新博、slice |
append | 用來追加元素到數(shù)組薪夕、slice中 |
panic和recover | 用來做錯誤處理 |
panic和recover
Go語言中目前是沒有異常機(jī)制,但是使用panic/recover模式來處理錯誤赫悄。 panic可以在任何地方引發(fā),但recover只有在defer調(diào)用的函數(shù)中有效馏慨。
package main
import (
"fmt"
)
func main() {
fmt.Println("start")
defer func() {
err := recover()
if err != nil {
fmt.Println("recover")
fmt.Println("活了")
}
}()
panic("panic")
fmt.Println("over")
}
運行結(jié)果
start
recover
活了