Go基礎編程——函數function

函 數 function
1、 Go 函 數 不 支 持 嵌套 、 重 載 和 默 認 參 數
2、但 支 持 以 下 特 性
無 需 聲 明 原 型 、 不 定 長 度 變 參 、 多 返 回 值 、 命 名 返 回 值 參 數
匿 名 函 數 、 閉 包
3、定 義 函 數 使 用 關 鍵 字 func, 並且 左 大 括 號 不 能 另 起 一 行
4、函 數 也 可 以 作 爲 一 種 類 型 使 用
對於放回值有兩種情況要注意
(1)、只說明了返回值的類型,在return的時候要帶上返回值
如下:

func testFunc()(int,int,int){
   a,b,c:=1,2,3
   return a,b,c
}

(2)在聲明的時候就已經說明返回值類型和返回值,return後面就不需要跟上返回值,Go語言底層機制會自動帶上的。

func testFunc()(a int,b int,c int){
   a,b,c=1,2,3
   return
}

這兩種方法還有一個地方要注意,就是聲明地方指出了返回值的變量名稱的話在函數中就不需要再次使用:(冒號來代替var)
上邊的第一種方法初始化並賦值使用

  a,b,c:=1,2,3

第二種方法只需要複製就好了,上邊已經聲明。

  a,b,c=1,2,3

爲了代碼的可讀性,第二種方法也建議帶上返回值的名稱

return a,b,c

不定長度變參的表示如下:

func testFunc(m...int)(a int,b int,c int){
   a,b,c=1,2,3
   return a,b,c
}

其中第一個圓括號中m…int就表示接受變長的參數。
其實這裏很清楚可以理解,m…的底層其實就是一個Slice
用程序說話:

func main(){
testFunc(1,2,3,4,5)

   }
   func testFunc(m...int){
      fmt.Println(m)
   }

運行結果:

[1 2 3 4 5]

還有一個規定-使用不定長參數的後面不可以再跟其他類型的參數,也不可以同時傳遞兩個不定長參數的聲明:
比如以下幾種錯誤類型

func testFunc(m...int,n...string){
   fmt.Println(m)
}

這樣就是報錯的,報錯如下

Can only use '...' as final argument in list less... (Ctrl+F1)
Reports possible issues in functions with variadic parameters
func testFunc(m...int,n string){
   fmt.Println(m)
}

報錯同上。
正確方式如下:

func main(){
testFunc("Hello",1,2,3,4,5)

   }
   func testFunc(n string,m...int){
      fmt.Println(m)
   }

也就是說要放在變長參數的類型前邊纔不會報錯。
下邊補充一點關於Slice引用類型的深入理解:

func main(){
      a,b:=5,6
testFunc(a,b)
fmt.Println(a,b)

   }
   func testFunc(s...int){
      s[0]=1
      s[1]=2
      fmt.Println(s)
   }

運行結果

[1 2]
5 6

這裏看到原始的ab並沒有被Slice改變,因爲Slice是一個引用類型,它只是進行的是值拷貝
而下一種情況進行地址拷貝就不一樣,
看程序:

func main(){
      s1:=[]int{8,9,3,4,5}
      //a,b:=5,6
testFunc(s1)
fmt.Println(s1)

   }
   func testFunc(s []int){
      s[0]=1
      s[1]=2
      fmt.Println(s)
   }

運行結果,

[1 2 3 4 5]
[1 2 3 4 5]

這樣把變長參數換成直接的底層的Slice傳入的話就是一個內存地址的拷貝給它,這時候函數中就能夠修改原內存地址的變量值。這也是值類型和引用類型傳遞的不同之處。同理傳遞指針也能改變元內存地址對應的值。看程序

func main(){
      a:=99

testFunc(&a)
fmt.Println(a)

   }
   func testFunc(a *int){
      *a=100

      fmt.Println(*a)
   }

運行結果

100
100

接下來說說函數function是如何作爲一種類型的

func main(){
   F:=testFunc
   F()
}
func testFunc(){
   fmt.Println("testFunc")
}

運行結果

testFunc

這裏如果某一個函數的使用斌率比較少,建議使用匿名函數,把上邊程序改造如下:
**func main(){
F:=func(){
fmt.Println(“testFunc”)
}
F()
}**
運行結果同上。這就是匿名函數的使用。

閉包
作用——返回一個匿名函數

func main(){
   f:=closure(10)
   fmt.Println(f(1))
   fmt.Println(f(5))

}
func closure(x int) func(int) int{
   return func(y int) int {
      return x+y

   }
}

運行結果

11
15

這裏的底層實現是x的內存地址的拷貝。看程序

func main(){
   f:=closure(10)
   fmt.Println(f(1))
   fmt.Println(f(5))

}
func closure(x int) func(int) int{
   fmt.Printf("%p\n",&x)
   return func(y int) int {
      fmt.Printf("%p\n",&x)
      return x+y

   }
}

運行結果

0xc0420080a8
0xc0420080a8
11
0xc0420080a8
15

三次打印地址都是一樣的,證明是地址拷貝。

defer
1、defer 的 執 行 方 式 類 似 其 它 語 言 中 的 析 構 函 數 , 在 函 數 體 執行結束後按照調用順序的相反順序逐 個 執 行
2、 即 使 函 數 發 生 嚴 重錯誤 也 會 執 行
3、支 持 匿 名 函 數 的 調 用
4、常 用 於資源清 理 、 文 件 關 閉 、 解 鎖 以 及 記 錄時間等操作
5、通 過 與 匿 名 函 數 配 合 可 在 return 之 後 修 改 函 數 計算結果
6、如 果 函 數 體 內 某 個 變 量 作爲 defer 時 匿 名 函數的 參 數 , 則 在 定 義 defer
時 即 已 經 獲 得 了 拷 貝 , 否 則 則 是 引 用 某 個 變 量 的 地址
7、Go 沒 有 異 常 機 制 , 但 有 panic/recover 模 式 來 處 理 錯 誤
8、Panic 可 以 在 任 何 地 方 引 發 , 但 recover 只 有 在 defer調用 的 函 數 中 有 效

先看defer知識點的第一條,相反順序執行

fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
defer fmt.Println("d")
defer fmt.Println("e")

運行結果

a
e
d
c
b

這就有些類似於Java中JDBC關閉數據庫資源的順序,反向來操作。
再來看defer支持匿名函數

for i:=0;i<3 ;i++  {
   defer func() {
      fmt.Println(i)
   }()
}

運行結果

3
3
3

首先來解釋最後一個括號的原因,它就類似於將一個匿名函數作爲一個類型賦給一個變量,變量名加上()表示執行這個匿名函數。類似於如下代碼

func main(){
   F:=testFunc
   F()
}
func testFunc(){
   fmt.Println("testFunc")
}

中的F之後的().
這裏還有要重點理解爲什麼打印的三個都是3.
這裏實際上是一個閉包,在閉包中i一直引用局部變量i,這個循環體退出的時候i實際上已經是3,這就導致三次打印出來的都是3.最開始沒有defer作用時候i作爲一個參數傳入,而有defer作用時i作爲一個引用,一直都在引用局部變量。
用程序確保pnic()後面的程序依舊執行的時候就要用到recover()
接下來對比着看代碼
首先是不使用recover的時候

func main(){
              A()
              C()
              D()
}
func A(){
   fmt.Println("func A")
}
func C(){
   panic("panic in C")
}
func D(){
   fmt.Println("func D")
}

運行結果

func A
panic: panic in C

goroutine 1 [running]:
main.C()
    D:/worplace/sorter/src/main/new_file.go:135 +0x40
main.main()
    D:/worplace/sorter/src/main/new_file.go:127 +0x2c

Process finished with exit code 2

第二種是使用recover的情況

func main(){
               A()
               C()
               D()
   }
   func A(){
      fmt.Println("func A")
   }
   func C(){
      defer func() {
         if err:=recover();err!=nil{
fmt.Println("recover in C")
         }
      }()

      panic("panic in C")
   }
   func D(){
      fmt.Println("func D")
   }

運行結果

func A
recover in C
func D

Process finished with exit code 0

這樣就表示正常的運行了panic之後的程序。所以recover是用在不管出現什麼情況下都可以補救,也只有在defer調用的函數中才能起到這種絕對的執行作用,也就這樣纔有他的意義

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章