文章轉載地址:https://juejin.im/entry/5971bed66fb9a06bb21adf15php
一、寫出下面代碼的輸出git
package main import "fmt" func main() { defer_all() panic("觸發異常") } func defer_all() { defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印後") }() }
解析:這道題主要考察的是對 defer 的理解,defer 主要是延遲函數,延遲到調用者函數執行 return 命令以前,github
多個 defer 以前按照先進後出的順序執行,因此,這道題中,在 panic 觸發時結束函數運行,在 return 以前依次打golang
印:打印後、打印中、打印前。最後 runtime 運行時拋出打印 panic 異常信息,panic 須要 defer 結束後纔會向上傳app
遞函數
須要注意的是,函數的 return value 不是原子操做,而是在編譯器中被分解成兩部分:返回值和return,而咱們oop
知道 defer 是在 return 以前執行的,因此能夠在 defer 函數中修改返回值,以下示例:.net
package main import ( "fmt" ) func main() { fmt.Println(doubleScore(0)) //0 fmt.Println(doubleScore(20.0)) //40 fmt.Println(doubleScore(50.0)) //50 } func doubleScore(source float32) (score float32) { defer func() { if score < 1 || score >= 100 { //將影響返回值 score = source } }() score = source * 2 return //或者 //return source * 2 }
2. 下面的代碼輸出什麼?指針
package main import "fmt" 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 }
解析:blog
程序在執行到第三行的時候,會先執行 calc 函數的 b 參數,即:calc("10",a,b),輸出:10,1,2,3 獲得值 3,而後由於
defer 定義的函數是延遲函數故 calc("1",1,3) 會被延遲執行
程序執行到第五行的時候,一樣先執行 calc("20",a,b) 輸出:20,0,2,2 獲得值 2,一樣將 calc("2",0,2) 延遲執行
程序執行到末尾的時候,按照棧先進後出的方式依次執行:calc("2",0,2),calc("1",1,3),則就依次輸出:2,0,2,二、
1,1,3,4
3.請寫出如下輸出內容
func main() { s := make([]int, 5) s = append(s,1,2,3) fmt.Println(s) }
解析:
使用 make 初始化 slice,第二個參數表明的是 slice 的長度,slice 還有第三個參數表示容量,這裏沒有指定容量表示建立一個
滿容的切片,使用 len()、cap() 函數獲取切片的 長度,初始化後切片的長度和容量都是 5,使用 append 追加三個元素使得切片的
長度大於原有的容量,此時切片的容量擴大一倍,變成 10,所以輸出的結果爲:
[0 0 0 0 0 1 2 3]
3. 下面的代碼能正常編譯嗎?
package main import ( "fmt" ) type People interface { Speak(string) string } type Stduent struct{} func (stu *Stduent) Speak(think string) (talk string) { if think == "bitch" { talk = "You are a good boy" } else { talk = "hi" } return } func main() { var peo People = Stduent{} think := "bitch" fmt.Println(peo.Speak(think)) }
運行打印結果:
# command-line-arguments .\main.go:23:6: cannot use Stduent literal (type Stduent) as type People in assignment: Stduent does not implement People (Speak method has pointer receiver)
從上面的輸出信息能夠看出 Student 沒有實現 People 這個接口
解析:
咱們來看一下語言規範裏面定義的規則,這些規則用來講明一個類型的值或指針是否實現了該接口:
1.類型 *T 的可調用方法集包含接收者爲 *T 或 T 的全部方法集
這條規則說的是若是咱們用來調用接口方法的變量是一個指針類型,那麼方法的接收者能夠是值類型也能夠是指針類型,
如今看一下咱們的例子顯然是不符合規則的,var peo People = Student{} 是一個值類型
2. 類型 T 的可調用方法集包含接收者爲 T 的全部方法集
這條規則說的是若是咱們用來調用接口方法的變量是一個值類型,那麼方法的接收者必需要是值類型才能夠被調用,看一下
咱們的例子,方法的接收者是指針類型
上面的代碼能夠這樣修改:
1.var peo People = &Student{}
2.將方法的接收者改爲值類型
能夠參考這篇文章:https://github.com/Unknwon/gcblog/blob/master/content/26-methods-interfaces-and-embedded-types-in-golang.md
4. 下面的代碼是死循環嗎?
func main() { v := []int{1, 2, 3} for i := range v { v = append(v, i) } }
解析:
直接運行上面的代碼咱們會發現上面的程序不會出現死循環,可以正常結束。
咱們來看一下切片的 for range ,它的底層代碼是:
// for_temp := range // len_temp := len(for_temp) // for index_temp = 0; index_temp < len_temp; index_temp++ { // value_temp = for_temp[index_temp] // index = index_temp // value = value_temp // original body // }
從底層代碼中咱們能夠看到在遍歷 slice 以前會先計算 slice 的長度做爲循環次數,循環體中,每次循環會先獲取
元素值,若是 for-range 中接收 index,value 則會對 index,value 進行一次賦值
因爲循環開始前循環次數已經肯定了,因此循環過程當中新添加的元素沒辦法遍歷到
參考文章:https://my.oschina.net/renhc/blog/2396058
5.下面的代碼有什麼問題嗎?
slice := []int{0, 1, 2, 3} myMap := make(map[int]*int) for index, value := range slice { myMap[index] = &value } fmt.Println("=====new map=====") for k, v := range myMap { fmt.Printf("%d => %d\n", k, *v) }
運行打印輸出結果:
=====new map===== 3 => 3 0 => 3 1 => 3 2 => 3
結果徹底同樣,都是最後一次遍歷的值。經過第 4 道題目對切片 for-range 的底層代碼可知,遍歷後
的值賦值給 value,在咱們的例子中,會把 value 的地址保存到 myMap 中,這裏的 value 是一個全局變量,
&value 取得是這個全局變量的地址,因此最後輸出的結果都是同樣的而且是最後一個值,至關於以下代碼:
// for_temp := range // len_temp := len(for_temp) // for index_temp = 0;index_temp < len_temp;index_temp++ { // value_temp = for_temp[index_temp] // index = index_temp // value = value_temp // myMap[index] = &value // original body // }
注意:這裏必須是保存指針纔會有問題,若是直接保存的是 value,不會有問題
總結:經過 for-range 遍歷切片,首先,計算遍歷的次數(切片的長度);每次遍歷,都會把當前遍歷到的值
存放到一個全局變量中
參考文章:https://juejin.im/entry/5bd7d1ac51882541b558f0b8
go range 內部實現(Dave 大神):https://garbagecollected.org/2017/02/22/go-range-loop-internals/