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")
}