日常學習總結golang(day2)---函數、閉包、defer

1.map:

Go語言中,提供映射關係容器的爲map,其內部結構使用hash實現,引用類型必須初始化才能使用

2.函數:
  1. 函數是組織好的,可重複使用的,用於執行指定任務的代碼塊。

  2. 函數存在的意義:
    一段代碼的封裝;
    把一段邏輯抽象出來封裝到一個函數中,給它起一個名字,每次用到它的時候直接調用就可以啦;
    使代碼結構更清晰,更簡介。

  3. 變量作用域
    (1) 全局變量
    (2)函數作用域
    i.先在函數內部找變量,找不到就往外層找;
    ii.函數內部的變量,外部是訪問不到的。
    (3)代碼塊作用域(比如if,for裏面定義的變量,外面無法訪問)

  4. 函數中查找變量的順序如下:
    (1)現在函數內部查找;
    (2)如果找不到就往函數外部查找,一直到全局.

  5. 高階函數
    函數也是一種類型,它可以作爲參數,也可以作爲返回值。

    //函數可以作爲參數的類型
    func f1(x func() int) {
    	ret := x()
    	fmt.Println(ret)//結果:3
    }
    func main() {
    	f1(func() int {
    		return 3
    	})
    }
    
  6. 匿名函數
    函數內部沒有辦法聲明帶名字的函數,所以匿名函數一般用在函數內部。如果只是調用一次的函數,還可以簡寫成立即執行函數。

    func main() {
    	//匿名函數:
    	f1:= func(x,y int) {
    		fmt.Println(x+y)//30
    	}
    	f1(10,20)
    }
    
    func main() {
    	//立即執行函數
    	func(x,y int) {
    		fmt.Println(x+y)//300
    	}(100,200)
    }
    
  7. 內置函數

內置函數 介紹
close 主要用來關閉channel
len 用來求長度,比如:string、array、slice、map、channel
new 用來分配內存,主要用來分配值類型,比如:int、string。返回的是指針。
make 用來分配內存,主要用來分配引用類型,比如:slice、map、channel。
append 用來追加元素到slice
panic和recover 用來做錯誤處理
3.閉包:
  1. 閉包是什麼?
    閉包是一個函數,這個函數包含了它外部作用域的一個變量。

  2. 底層的原理:
    (1)函數可以作爲返回值;
    (2)函數內部查找變量的順序,先在自己內部找,找不到往外層找。

    //閉包
    func add(x int) func(int) int {
    	return func(y int) int {
    		x += y
    		return x
    	}
    }
    func main() {
    	ret := add(100)
    	ret2:=ret(200)
    	fmt.Println(ret2)//300
    }
    
  3. //閉包
    func calc(base int)(func(int)int,func(int) int) {
    	add := func(i int) int {
    		base += i
    		return base
    	}
    	sub := func(i int) int {
    		base -= i
    		return base
    	}
    	return add,sub
    }
    func main() {
    	f1,f2:=calc(10)
    	//需要注意的是:自始自終都是一個base.
    	fmt.Println(f1(1),f2(2))//f(1)返回11,這時base已經發生改變爲11,在調用sub,此時base爲11-2,返回9.
    	fmt.Println(f1(3),f2(4))//同理,上面f(2)返回9,此時base=9,執行add,9+3=12.
    	fmt.Println(f1(5),f2(6))
    }
    

    11 9
    12 8
    13 7

  4. 閉包解決兩個帶不同類型參數的函數合併問題。例子如下:

    //閉包
    func f1(f func()) {
    	fmt.Println("this is f1")
    	f()
    }
    func f2(x, y int) {
    	fmt.Println("this is f2")
    	fmt.Println(x + y)
    }
    
    //要求:f1(f2):因爲f1中帶的參數是無參函數類型,而f2是帶有兩個參數的,所以不能直接將f2傳給f1.
    func f3(f func(int, int), x, y int) func() {
    	return func() {
    		f(x, y)
    	}
    }
    func main() {
    	ret := f3(f2, 10, 20)//把原來需要傳遞兩個int類型的參數包裝成一個不需要傳參的函數(f2)
    	f1(ret)//f3是返回無參函數類型,正好可以傳給f1
    }
    

    this is f1
    this is f2
    30

4. defer:
  1. Go語言中defer語句會將其後面跟隨的語句進行延遲處理。在defer歸屬的函數即將返回時,將延遲處理的語句按defer定義的逆序進行執行。

  2. defer 多用於函數結束之前,釋放資源。(資源句柄、數據庫連接、socket連接等)

  3. defer執行時機:
    在Go語言的函數中return語句在底層並不是原子操作,它會分爲給返回值賦值和RET指令兩步。而defer語句執行的時機就在返回值賦值操作之後,RET指令之前。具體如下圖所示:
    在這裏插入圖片描述

    1. defer的經典案例
    func f1() int {
    	x := 5
    	defer func() {
    		x++ //修改的是x,不是返回值
    	}()
    	return x //返回值=x=5
    }
    func f2() (x int) {
    	defer func() {
    		x++
    	}()
    	return 5 //1.返回值=x
    }
    func f3() (y int) {
    	x := 5
    	defer func() {
    		x++ //修改的是x
    	}()
    	return 5 //1.返回值=y=x=5
    }
    func f4() (x int) {
    	defer func(x int) {
    		x++ //改變的是函數中的副本
    	}(x)
    	return 5 //返回值=x=5
    }
    func f5() (x int) {
    	defer func(x int) int {
    		x++
    		return x //沒有變量接收返回的x,相當於丟棄了
    	}(x)
    	return 5
    }
    
    //傳一個x的指針到匿名函數中
    func f6() (x int) {
    	defer func(x *int) {
    		*x++ //修改直接修改了x的值
    	}(&x)    //將x的內存地址傳進去了,
    	return 5 //返回值=x=5
    }
    func main() {
    	fmt.Println(f1()) //5
    	fmt.Println(f2()) //6
    	fmt.Println(f3()) //5
    	fmt.Println(f4()) //5
    	fmt.Println(f5()) //5
    	fmt.Println(f6()) //6
    }
    

    defer面試題:

    func calc(index string, a, b int) int {
    	ret := a + b
    	fmt.Println(index, a, b, ret)
    	return ret
    }
    func main() {
    	a := 1
    	b := 2
    	defer calc("1", a, calc("10", a, b))
    	a = 0
    	defer calc("2", a, calc("20", a, b))
    	b = 1
    }
    

    解題思路:
    defer延遲調用了一個函數,這個函數裏面的參數又有一個函數,這時候會先執行裏面的函數,把執行結果確切的算出來.而不是最後再計算。
    1.a=1,b=2
    2.defer calc(“1”, 1, calc(“10”, 1, 2))
    3.calc(“10”, 1, 2) //1. (10,1,2,3)
    4.defer calc(“1”, 1,3) //4.(1,1,3,4)
    5.a=0
    6.defer calc(“2”, 0, calc(“20”, 0, 2))
    7.calc(“20”, 0, 2) //2. (20,0,2,2)
    8.defer calc(“2”, 0,2) //3.(2,0,2,2)
    9.b=1 //混淆。沒有函數要執行它了

    執行結果:
    10 1 2 3
    20 0 2 2
    2 0 2 2
    1 1 3 4

5.panic和recover

Go語言中使用panic/recover模式處理錯誤,panic可在任何地方引發,但recover只有在defer調用的函數中有效。
注意:

  1. recover()必須搭配defer使用
  2. defer一定要在可能引發panic的語句之前定義
6. 練習題:

你有50枚金幣,需要分配給以下幾個人:Matthew、Sarah、Augustus、Heidi、Emilie、Peter、Giana、Adriano、Aaron、Elizabeth。
分配規則如下:
a. 名字中每包含1個’e’或’E’分1枚金幣;
b.名字中每包含1個’i’或’I’分2枚金幣;
c. 名字中每包含1個’o’或’O’分3枚金幣;
d. 名字中每包含1個’u’或’U’分4枚金幣;
寫一個程序,計算每個用戶分到多少金幣,以及最後剩下多少金幣?

package main

import (
	"fmt"
)

var (
	coins = 50
	users = []string{"Matthew", "Sarah", "Augustus", "Heidi", "Emilie",
		"Peter", "Giana", "Adriano", "Aaron", "Elizabeth"}
	distribution = make(map[string]int, len(users))
)

func dispatchCoin() (left int) {
	//1.依次拿到每個人的名字
	for _,name:=range users{
		//2.拿到一個人名根據分金幣的規則分金幣
		for _,c:=range name{
			switch c {
			case 'e','E':
				//滿足這個條件分1枚金幣
				distribution[name]++
				coins--
			case 'i','I':
				//滿足這個條件分2枚金幣
				distribution[name]+=2
				coins-=2
			case 'o','O':
				//滿足這個條件分3枚金幣
				distribution[name]+=3
				coins-=3
			case 'u','U':
				//滿足這個條件分4枚金幣
				distribution[name]+=4
				coins-=4
			}
		}

	}
	left=coins
	return
}

func main() {
	left := dispatchCoin()
	fmt.Println("剩下的金幣數:",left)
	for k,v:=range distribution{
		fmt.Printf("%s:%d\n",k,v)
	}
}

執行結果:
剩下的金幣數: 10
Emilie:6
Aaron:3
Giana:2
Adriano:5
Elizabeth:4
Matthew:1
Augustus:12
Heidi:5
Peter:2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章