
文章目錄
-
- 2.3 函數
-
- 2.3.1 基本文法
- 2.3.2 入門demo:
- 2.3.3 函數遞歸:
- 2.3.4 函數注意事項
- 2.3.5 init函數
- 2.3.6 匿名函數
- 2.3.7 閉包
- 2.3.8 defer
- 2.3.9 函數參數傳遞方式
- 2.3.10 變量作用域
2.3 函數
2.3.1 基本文法
func 函數名(形參清單) (傳回值清單){
執行語句... ...
return 傳回值清單
}
- Go語言中支援函數傳回多個值
- 如果傳回值隻有一個,則(傳回值清單)的括号可以不寫
- 如果有多個傳回值,可以使用’_'來表示不處理此傳回值
2.3.2 入門demo:
package main
import "fmt"
/*傳回兩個整數的和*/
/*函數隻有一個傳回值*/
func getSum(n1 int, n2 int) int {
return n1 + n2
}
/*傳回有兩個傳回值*/
func getSumSub(n1 int, n2 int) (sum, sub int) {
return n1 + n2, n1 - n2
}
func main() {
fmt.Println(getSum(10, 23))
fmt.Println(getSumSub(10, 23))
sum, _ := getSumSub(123, 543) /*使用下劃線來忽略此兩者之差的傳回值*/
fmt.Println(sum)
}
2.3.3 函數遞歸:
- 示例一:使用斐波那契數數列來說明:
/*1 1 2 3 5 8 13 21 34 55*/
func fabnacci(n int) int {
if n == 1 || n == 2 {
return 1
} else if n > 2 {
return fabnacci(n-1) + fabnacci(n-2)
}
return 0
}
- 示例二:猴子吃桃問題:
/*猴子吃桃問題:有一堆桃子,猴子第一天吃了其中的一半并再多吃一個;以後每天吃一半再多吃一個,當第10天時,隻剩下一個桃子
* 問:最開始有多少桃子?
*/
func monkeyPeach(day int) int {
if day == 10 {
return 1
} else if day < 10 && day > 0 {
return (monkeyPeach(day+1) + 1) * 2
}
fmt.Println("輸入的天數有錯誤!!!")
return -1
}
2.3.4 函數注意事項
- 函數的形參清單和傳回值清單都可以是多個
- 如果有多個傳回值,可以使用’_'來表示不處理此傳回值
- 形參清單和傳回值清單可以是值類型也可以是引用類型
- 函數名應該遵循相應的命名規範
- 如果需要導出供其他包調用,首字母需要大寫…
- 函數傳參是值傳遞,即将原參數拷貝一份傳遞給形參
- 支援傳遞指針(本質還是值傳遞),即傳輸變量位址
- GO不支援函數重載(函數名相同,但是形參不同)
- 在Go中函數也是一種資料類型,可以指派給一個變量,這種情況下此變量便是一個函數類型的變量。可以通過此變量對函數進行調用
- 函數既然是一種函數類型,那麼函數的形參清單和傳回值清單都可以使用函數類型
func getSum(n1 int, n2 int) int { return n1 + n2 } func myFun(funcPtr func(int, int) int, num1 int, num2 int) int { return funcPtr(num1, num2) } func main(){ fmt.Println(myFun(getSum, 10, 20)) }
-
GO中支援自定義資料類型
基本文法:
例子:type 自定義資料類型名稱 資料類型
type age int type getSum func(int,int) int
- GO中支援對傳回值命名
func getSumSub(x,y int)(sum int, sub int){ sum = x + y sub = x - y return }
- GO函數支援可變參數,可變參數必須是形參的最後一個。可變參數實際上是切片類型,可以通過索引通路
func getSum(sum int, args...int) int { for i:=0;i<len(args);i++{/*周遊可變參數args*/ sum+=args[i] fmt.Println(args[i]) } return sum }
2.3.5 init函數
每一個源檔案都可以有一個init函數,它在main函數之前被Go架構調用。可以在init函數中完成初始化工作
注意事項:
- 如果一個檔案同時包含全局變量定義、init函數、main函數,則執行順序為: 自定義變量—> init函數 --> main函數
var age = test() func test() int { fmt.Println("-----test函數初始化-------") return 10 } func init() { fmt.Println("----init函數初始化--------") } func main() { fmt.Println("--------main函數-------") } /*執行結果如下:*/ -----test函數初始化------- ----init函數初始化-------- --------main函數-------
- 如果import的其他包中也有變量定義,init函數,那麼他們的執行順序是:先import其他包的定義、init,然後本包的定義,init
2.3.6 匿名函數
Go支援匿名函數。如果某一個函數我們隻是用一次,則可以考慮使用匿名函數。但是匿名函數可以調用多次
匿名函數用法:
- 定義匿名函數時直接調用,這種匿名函數隻能調用一次
func main() { rest1 := func(n1 int, n2 int) int { return n1 + n2 }(10, 20) fmt.Println(rest1) }
- 将匿名函數指派給變量,通過變量調用匿名函數。此時可以條用多次
func main() { a := func(n1 int, n2 int) int { return n1 + n2 } rest := a(2, 3) fmt.Println(res) }
2.3.7 閉包
1)概念介紹:
- 閉包就是一個函數及其相關的引用環境組成的一個整體
2)示例:
/* 函數閉包 : 累加器函數*/
func addUpper() func(int) int {
var n int = 10
return func(x int) int {
n += x
return n
}
}
func main() {
f := addUpper() //傳回一個函數
fmt.Println(f(1)) //11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
3)上述代碼說明:
- addUpper()是一個函數,它的傳回值是一個函數:
func(int)int
- 閉包的說明:
傳回一個匿名函數。但是這個匿名函數引用到函數外的n, 是以這個匿名函數就和n形成一個整體,構成閉包var n int = 10 return func(x int) int { n += x return n }
- 大家可以這樣了解:閉包是一個類,匿名函數是操作,n是字段。函數和它使用到的n構成閉包
- 當我們反複調用f函數時,n隻初始化一次,是以每次調用一次,n就會累加一次
- 了解的關鍵:傳回的函數與它引用的外部變量組成閉包
4)閉包的最佳實踐:
編寫一個函數makeSuffix(suffix string),可以接受一個檔案的字尾名(如.jpg),并傳回一個閉包
調用閉包,可以傳入一個檔案名,如果檔案名沒有字尾,則傳回檔案名.jpg;如果有檔案名則傳回檔案名
要求使用閉包完成
strings.HasSuffix 可以用來判斷某一個字元串是否包含指定字尾
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
func main(){
f2 := makeSuffix(".jpg")
fmt.Println(f2("123")) //
fmt.Println(f2("456.avi")) //
fmt.Println(f2("789")) //
f3 := makeSuffix(".avi")
fmt.Println(f3("f:\\golang")) //
fmt.Println(f3("f:\\golang\\Go_han")) //
fmt.Println(f3("f:\\golang\\Go_han\\chapter06")) //
}
}
2.3.8 defer
1)為什麼需要defer
在函數中往往需要申請架構各種資源(如檔案句柄、鎖、套接字等),為了在函數執行完畢後,能及時釋放資源,Go語言提供了defer延時處理機制。**
2)快速入門案例
func sum(n1 int, n2 int) int {
defer fmt.Println("n1 =", n1)
defer fmt.Println("n2 =", n2)
n1++
n2++
ret := n1 + n2
fmt.Println("sum = ", ret)
return ret
}
func main() {
sum(1, 2)
}
運作結果如下:
sum = 3
n2 = 2
n1 = 1
3) defer注意事項
- defer在函數的return之後運作
- defer是一個棧,先定義的defer後執行
- defer也會将變量壓入棧中
func sum(n1 int, n2 int) int { defer fmt.Println("n1 =", n1) defer fmt.Println("n2 =", n2) n1++ n2++ ret := n1 + n2 fmt.Println("sum = ", ret) return ret } func main() { sum(1, 2) } 執行結果如下: sum = 5 n2 = 2 n1 = 1
- 在Go中defer通常的用法是:建立資源後,執行defer語句來延時釋放資源
- defer之後可以繼續使用該資源
- 在函數執行完畢後,系統會依次從defer棧中取出語句,并執行之
- defer機制簡潔高效
2.3.9 函數參數傳遞方式
1)基本介紹
函數參數傳遞通常有兩種方式:
- 值傳遞
- 基本資料類型,如int系列、float系列、bool、string、資料、結構體struct
- 引用傳遞
- 指針、切片、map、管道chan、接口interface等都是引用資料類型
個人感覺這兩種是一種傳遞方式,都是值傳遞。不同的是一個傳遞變量的副本,另一個傳遞的是變量位址的副本。
Go嚴格的說與C相同,不支援引用傳遞。但是通常情況下,将位址傳遞稱之為引用傳遞(效果上類似),是以就沿用這種說法吧
2.3.10 變量作用域
- 函數内部聲明/定義的變量,稱之為局部變量,作用域僅限于函數内部
- 函數外部聲明/定義的變量,稱之為全局變量,作用域在整個包都有效,如果其首字母為大寫,則作用域在整個程式都有效
- 如果變量在一個代碼塊中定義,如for/if,則此變量的作用域僅限于此代碼塊