天天看點

Golang入門學習(三):函數

Golang入門學習(三):函數

文章目錄

    • 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

  • 閉包的說明:
    var n int = 10
    
    	return func(x int) int {
    		n += x
    		return n
    	}
               
    傳回一個匿名函數。但是這個匿名函數引用到函數外的n, 是以這個匿名函數就和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,則此變量的作用域僅限于此代碼塊