Go-常見的面試題(一)


文章轉載地址:https://juejin.im/entry/5971bed66fb9a06bb21adf15

1、寫出下面代碼的輸出

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 命令之前,

多個 defer 之前按照先進后出的順序執行,所以,這道題中,在 panic 觸發時結束函數運行,在 return 之前依次打

印:打印后、打印中、打印前。最后 runtime 運行時拋出打印 panic 異常信息,panic 需要 defer 結束后才會向上傳

        需要注意的是,函數的 return value 不是原子操作,而是在編譯器中被分解成兩部分:返回值和return,而我們

知道 defer 是在 return 之前執行的,所以可以在 defer 函數中修改返回值,如下示例:

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
}

 解析:

   程序在執行到第三行的時候,會先執行 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,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/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM