天天看點

Go學習筆記(10)Go函數Go函數Go函數定義函數值傳遞與引用傳遞Go函數的各種使用形式匿名函數defer 函數

文章目錄

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

繼續閱讀