勤學如春起之苗,不見其增日有所長;輟學如磨刀之石,不見其損日有所虧。 本文的重點:逃逸分析、延遲語句、散列表、通道、介面。 1.逃逸分析 逃逸分析是Go語言中的一項重要優化技術,可以幫助程式減少記憶體分配和垃圾回收的開銷,從而提高程式的性能。下麵是一道涉及逃逸分析的面試題及其詳解。 問題描述: 有如下 ...
勤學如春起之苗,不見其增日有所長;輟學如磨刀之石,不見其損日有所虧。
本文的重點:逃逸分析、延遲語句、散列表、通道、介面。
1.逃逸分析
逃逸分析是Go語言中的一項重要優化技術,可以幫助程式減少記憶體分配和垃圾回收的開銷,從而提高程式的性能。下麵是一道涉及逃逸分析的面試題及其詳解。
問題描述:
有如下Go代碼:
func foo() *int {
x := 1
return &x
}
func main() {
p := foo()
fmt.Println(*p)
}
請問上面的代碼中,變數x是否會發生逃逸?
答案解析:
在上面的代碼中,變數x只在函數foo()中被定義和初始化,然後其地址被返回給了主函數main()。因為返回值是指針類型,需要在堆上分配記憶體,所以變數x會發生逃逸。所謂逃逸,就是指變數的生命周期不僅限於函數棧幀,而是超出了函數的範圍,需要在堆上分配記憶體。
如果變數x沒有發生逃逸,那麼它會被分配在函數棧幀中,隨著函數的返回而被自動銷毀。而如果發生了逃逸,變數x就需要在堆上分配記憶體,並由垃圾回收器負責回收。在實際的程式中,大量的逃逸會導致記憶體分配和垃圾回收的開銷增加,從而影響程式的性能。
逃逸分析是Go語言的一項優化技術,可以在編譯期間分析代碼,確定變數的生命周期和分配位置,從而避免不必要的記憶體分配和垃圾回收。通過逃逸分析的優化,可以有效地提高程式的性能和可靠性。
更多逃逸分析的內容,可以閱讀我之前分享的文章:記憶體分配和逃逸分析詳解
2.延遲語句
defer語句是Go語言中的一項重要特性,可以用於在函數返回前執行一些清理或收尾工作,例如釋放資源、關閉連接等。下麵是一道涉及defer語句的面試題及其詳解。
問題描述:
有如下Go代碼:
func main() {
defer func() {
fmt.Println("defer 1")
}()
defer func() {
fmt.Println("defer 2")
}()
fmt.Println("main")
}
請問上面的代碼中,輸出的順序是什麼?
答案解析:
在上面的代碼中,我們定義了兩個defer語句,它們分別輸出"defer 1"和"defer 2"。這兩個defer語句的執行順序是先進後出的,也就是說後定義的defer語句先執行,先定義的defer語句後執行。因此,輸出的順序應該是"main"、"defer 2"、"defer 1"。
這個例子也展示了defer語句的另一個特性,即在函數返回前執行。在main函數返回前,兩個defer語句分別執行了它們的函數體,輸出了相應的內容。這種特性可以用於釋放資源、關閉連接等操作,在函數返回前保證它們被執行。
需要註意的是,defer語句並不是一種非同步操作,它只是將被延遲執行的函數加入到一個棧中,在函數返回前按照後進先出的順序執行。因此,在defer語句中的函數應該是輕量級的,避免影響程式的性能。同時,也需要註意defer語句的執行順序和函數返回時的狀態,避免出現不符合預期的結果。
3.散列表Map
Go語言中的map是一種非常有用的數據結構,可以用於存儲鍵值對。下麵是一道涉及map的面試題及其詳解。
問題描述:
有如下Go代碼:
func main() {
m := make(map[int]string)
m[1] = "a"
m[2] = "b"
fmt.Println(m[1], m[2])
delete(m, 2)
fmt.Println(m[2])
}
請問上面的代碼中,輸出的結果是什麼?
答案解析:
在上面的代碼中,我們使用make函數創建了一個map,然後向其中添加了兩個鍵值對,分別是1:"a"和2:"b"。接著,我們輸出了這兩個鍵對應的值,分別是"a"和"b"。
接下來,我們使用delete函數從map中刪除了鍵為2的元素。然後,我們嘗試輸出鍵為2的值,但是輸出為空。這是因為我們已經從map中刪除了鍵為2的元素,所以它對應的值已經不存在了。
需要註意的是,當我們從map中訪問一個不存在的鍵時,它會返回該值類型的零值。在本例中,值的類型是string,它的零值是""。所以,當我們嘗試輸出鍵為2的值時,它返回的是空字元串。
需要提醒的是,map是一種引用類型的數據結構,它的底層實現是一個哈希表。在使用map時,需要註意以下幾點:
- map是無序的,即元素的順序不固定。
- map的鍵必須是可以進行相等性比較的類型,如int、string、指針等。(通俗來說就是可以用==和!=來比較的,除了slice、map、function這幾個類型都可以)
- map的值可以是任意類型,包括函數、結構體等。
- 在多個goroutine之間使用map時需要進行加鎖,避免併發訪問導致的競態問題。
4.通道Channel
Go語言中的通道(channel)是一種非常有用的特性,用於在不同的goroutine之間傳遞數據。下麵是一道涉及通道的面試題及其詳解。
問題描述:
有如下Go代碼:
func main() {
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
ch <- 3
close(ch)
}()
for {
n, ok := <-ch
if !ok {
break
}
fmt.Println(n)
}
fmt.Println("done")
}
請問上面的代碼中,輸出的結果是什麼?
答案解析:
在上面的代碼中,我們使用make函數創建了一個整型通道ch。然後,我們啟動了一個goroutine,向通道中寫入了三個整數1、2和3,併在最後使用close函數關閉了通道。
接著,在主函數中,我們使用for迴圈不斷從通道中讀取數據,直到通道被關閉。每次從通道中讀取到一個整數後,我們將它輸出。最後輸出"done",表示所有的數據已經讀取完畢。
因為通道是一種同步的數據傳輸方式,寫入和讀取會阻塞直到對方準備好,所以輸出的結果應該是:
需要註意的是:在通道被關閉後,讀取操作仍然可以從通道中讀取到之前寫入的數據。這是因為通道中的數據並沒有立即消失,而是在讀取完畢後被垃圾回收器回收。因此,在使用通道時,需要根據實際情況判斷何時關閉通道,以避免出現不必要的競態和記憶體泄漏。
5.介面
Go語言中的介面(interface)是一種非常重要的特性,用於定義一組方法。下麵是一道涉及介面的面試題及其詳解。
問題描述:
有如下Go代碼:
type Animal interface {
Speak() string
}
type Dog struct{}
func (d *Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c *Cat) Speak() string {
return "Meow!"
}
func main() {
animals := []Animal{&Dog{}, &Cat{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
請問上面的代碼中,輸出的結果是什麼?
答案解析:
在上面的代碼中,我們定義了一個Animal介面,它有一個Speak方法。然後,我們定義了Dog和Cat兩個結構體,分別實現了Animal介面的Speak方法。
接著,在main函數中,我們創建了一個Animal類型的切片,其中包含了一個Dog對象和一個Cat對象。然後,我們使用for迴圈遍歷這個切片,調用每個對象的Speak方法,並輸出它們返回的字元串。
因為Dog和Cat都實現了Animal介面的Speak方法,所以它們都是Animal類型的對象,可以被放入Animal類型的切片中。在遍歷切片時,我們調用每個對象的Speak方法,它們分別返回"Woof!"和"Meow!",然後被輸出。
因此,輸出的結果應該是:
需要註意的是,介面是一種動態類型,它可以包含任何實現了它所定義的方法集的類型。在使用介面時,需要註意以下幾點:
- 介面是一種引用類型的數據結構,它的值可以為nil。
- 實現介面的類型必須實現介面中所有的方法,否則會編譯錯誤。
- 介面的值可以賦給實現介面的類型的變數,反之亦然。
- 在實現介面的類型的方法中,可以通過類型斷言來判斷介面值的實際類型和值。
總結
這篇文章總結了5個知識點的面試題:逃逸分析、延遲語句defer、散列表map、通道Channel、介面interface
下一篇文章計劃分享的5個知識點是:unsafe、context、錯誤處理、計時器、反射。
歡迎大家三連支持一波,你的點贊、分享,是我更文的最大動力。
公眾號:程式員升職加薪之旅