文章目錄
- Go函數
- Go函數定義
- 函數值傳遞與引用傳遞
- Go函數的各種使用形式
- 匿名函數
- defer 函數
Go函數
函數是基本的代碼塊,它的作用是可以将實作某個功能的多行代碼封裝到一段代碼塊中(即函數),在需要的時候去調用。同個函數可以被多次調用,實作代碼重用。
函數一般會有參數和傳回值(也可以沒有),函數名稱、函數參數、函數傳回值以及它們的類型被統稱為函數簽名
在Go語言中,函數有以下一些特點:
- 支援不定長變參、多傳回值、命名傳回值參數
- 支援匿名函數、閉包
- 函數可以作為一種類型使用
- Go函數不支援嵌套、重載
Go函數定義
Go函數定義格式如下:
func function_name ([parameter list]) [return_type]{
函數體
}
- 由關鍵字
開始聲明定義函數func
-
:參數清單,一般格式為parameter list
param1 type1, param2 type2...
-
:傳回值類型,這裡可以隻寫傳回值類型,也可以為傳回值命名(即支援命名傳回值參數的寫法:return_type
)ret1 type1
函數值傳遞與引用傳遞
Go函數被調用的時候,傳入的參數會被複制然後傳遞到函數内部使用,即函數體中使用的是參數副本。這種方式也稱之為值傳遞
- 值傳遞:即傳遞參數副本。函數接收參數副本之後,在使用變量的過程中可能對副本的值進行更改,但不會影響到原來的變量
- 引用傳遞:如果希望在函數内對原始的參數進行直接修改,需要将參數的位址(變量名前加
符号)作為輸入參數傳遞給函數(即引用傳遞)。此時傳遞的是位址的副本,但位址副本指向位置還是原來變量的位置,是以可以通過該指針來修改原來的變量。如果傳遞的是&
、slice
這類引用類型,它們預設都是采用引用傳遞map
Go函數的各種使用形式
- 函數傳回多個值,在聲明中在輸入參數後面指定傳回值的類型(用括号括起來),函數結束時
多個傳回值return
func main(){
r1, r2 := A(1, "A")
}
func A(a int, b string) (int, string) {
c := a + 1
d := b + "aa"
return c, d
}
- 多個參數如果類型相同,可以合并寫法,單個傳回值可以不寫括号
func main(){
r3 := B(1, 2, 3)
}
func B(a, b, c int) int {
return a + b + c
}
- 命名傳回值寫法,使用這種形式,傳回值的名稱已經在聲明中定義,不需要在函數體内定義,
後面也可以不寫上傳回值的名稱,預設會傳回函數聲明中那幾個命名的傳回值return
func main(){
r4, r5, r6 := C()
}
func C() (a, b, c int) {
a, b, c = 1, 2, 3
return
}
- 不定長變參,通過傳入
這樣的形式來表示不定長變參,注意不定長變參隻能作為最後一個參數。函數會接收一個某個指定類型的類似...type
的參數slice
func main(){
r7 ;= D(0,1,2,3)
}
func D(base int , s ...int) int {
for _, v := range s {
base += v
}
}
這裡小朋友是否有個問号:這種方式和你直接把參數放到一個
slice
中,再直接傳遞這個
slice
的方式有何差別?差別在于不定長變參是對原來的參數進行拷貝再放到slice中,是以函數内改變它們并不會改變原來的值,而直接傳遞
slice
是引用傳遞,傳遞的是slice的指針,函數内改變會改變原來的值
- 傳遞指針參數(引用傳遞)
func main(){
p := 1
G(&p)
fmt.Println(p)
}
func G(p *int) {
*p = 2
fmt.Println(*p)
}
- 傳遞引用類型參數(引用傳遞)
func main(){
s := []int{1, 2, 3, 4}
F(s)
fmt.Println(s)
}
func F(s []int) {
s[0] = 5
s[1] = 6
s[2] = 7
s[3] = 8
fmt.Println(s)
}
- 函數作為一種類型來使用
func main(){
h := H
h()
}
func H() {
fmt.Println("H")
}
- 将函數調用的結果作為其它該函數的參數。隻要隻要這個被調用函數的傳回值個數、傳回值類型和傳回值的順序與調用函數所需求的實參是一緻的
func main(){
r := f1(f2(5))
}
func f1(a,b,c int) int {
return a+b+c
}
func f2(a int) (b,c,d int){
b = a
c = a + 1
d = a - 1
return
}
- 将函數作為一種參數類型,即函數可以作為其它函數的參數進行傳遞,然後在其它函數内調用執行,一般稱之為回調。注意和上一點進行區分
type cb func(int) int
func main() {
testCallBack(1, callBack)
testCallBack(2, func(x int) int {
fmt.Printf("我是回調,x:%d\n", x)
return x
})
}
func testCallBack(x int, f cb) {
f(x)
}
func callBack(x int) int {
fmt.Printf("我是回調,x:%d\n", x)
return x
}
匿名函數
匿名函數,顧名思義即沒有名字的函數。匿名函數不能獨立地定義,它需要指派給某個變量,即儲存函數的位址到變量中,然後通過變量對函數進行調用。或者定義的同時直接調用
func main(){
i := func(){
fmt.Println("匿名函數指派給變量再調用")
}
i()
func(){
fmt.Println("匿名函數直接調用")
}()
}
Go的匿名函數支援閉包。關于閉包的概念:
閉包函數:聲明在一個函數中的函數,叫做閉包函數
閉包:閉包函數總是可以通路其所在的外部函數中聲明的參數和變量,即使在其外部函數被傳回之後
有關閉包更加深入的了解後面會考慮再寫一篇文章來介紹。下面是一段展示閉包的代碼,
closure
函數中的匿名函數(閉包函數)可以通路到外部函數中的變量
x
。程式輸出的結果為
11
和
12
func main(){
f := closure(10)
fmt.Println(f(1))
fmt.Println(f(2))
}
func closure(x int) func(int) int {
return func(y int) int {
return x + y
}
}
defer 函數
defer
函數的作用類似于其它語言中的析構函數,它會在函數體執行結束後再執行,如果有多個
defer
函數,它們按照調用順序的相反順序逐個執行
- 即使函數發送錯誤也會執行,不過在代碼中需要在發送錯誤之前先調用
-
函數允許将某些語句推遲到函數傳回之前才執行defer
-
defer
函數一般常用于釋放某些已配置設定的資源,檔案關閉,解鎖,計時等操作
函數的使用:直接在函數前加上defer
關鍵字即可defer
func main(){
for i := 0; i < 3; i++ {
defer fmt.Println(i) //将逆序輸出 2 1 0
}
}
defer
經常可以用在異常恢複的代碼塊中,在Go中沒有Java的
try-catch
機制,它是使用
panic-recover
模式來處理程式中發生的錯誤
-
可以在任意地方引發,但panic
隻有在recover
調用的函數中才有效defer
- 包含
的recover
函數需要定義在發生defer
之前panic
func main(){
beforePanic()
panicRecover()
afterPanic()
}
func beforePanic() {
fmt.Println("before panic")
}
//panic-recover
func panicRecover() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recover in this")
}
}()
panic("Panic !!!")
}
func afterPanic() {
fmt.Println("after panic")
}