Go學習筆記(10)Go函數

Go函數

    函數是基本的代碼塊,它的作用是可以將實現某個功能的多行代碼封裝到一段代碼塊中(即函數),在需要的時候去調用。同個函數可以被多次調用,實現代碼重用。
     函數一般會有參數和返回值(也可以沒有),函數名稱、函數參數、函數返回值以及它們的類型被統稱爲函數簽名
     在Go語言中,函數有以下一些特點:

  • 支持不定長變參、多返回值、命名返回值參數
  • 支持匿名函數、閉包
  • 函數可以作爲一種類型使用
  • Go函數不支持嵌套、重載

Go函數定義

    Go函數定義格式如下:

func function_name ([parameter list]) [return_type]{
	函數體
}
  • 由關鍵字func開始聲明定義函數
  • parameter list:參數列表,一般格式爲 param1 type1, param2 type2...
  • return_type:返回值類型,這裏可以只寫返回值類型,也可以爲返回值命名(即支持命名返回值參數的寫法:ret1 type1

函數值傳遞與引用傳遞

    Go函數被調用的時候,傳入的參數會被複制然後傳遞到函數內部使用,即函數體中使用的是參數副本。這種方式也稱之爲值傳遞

  • 值傳遞:即傳遞參數副本。函數接收參數副本之後,在使用變量的過程中可能對副本的值進行更改,但不會影響到原來的變量
  • 引用傳遞:如果希望在函數內對原始的參數進行直接修改,需要將參數的地址(變量名前加&符號)作爲輸入參數傳遞給函數(即引用傳遞)。此時傳遞的是地址的副本,但地址副本指向位置還是原來變量的位置,因此可以通過該指針來修改原來的變量。如果傳遞的是slicemap這類引用類型,它們默認都是採用引用傳遞

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。程序輸出的結果爲1112

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調用的函數中才有效
  • 包含recoverdefer函數需要定義在發生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")
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章