Go 面試題二(記憶體、並發、OOP)
來源:Durant Thorvalds
01 new 和 make 的區別?
new:只負責分配記憶體,回傳指向該位址的指標。會為新類型分配一片記憶體、初始化為零值,並回傳型別為*T的位址,相當於&T{}。make:只用於 slice、map、channel 的初始化,回傳的是「引用」(即 slice / map / channel 本身),不是指標。
// new: 分配記憶體,回傳 *T
p := new(int) // *int,指向零值
s := new([]int) // *[]int,指向 nil slice(不常用)
// make: 初始化 slice / map / channel,回傳 T
sl := make([]int, 0, 10) // []int
m := make(map[string]int) // map[string]int
ch := make(chan int) // chan int
02 Go 的面向對象是怎麼實現的?
Go 實現面向對象的兩 個關鍵是 struct 和 interface。
封裝
- 同一個 package 內,物件對包內檔案可見。
- 不同 package 時,名稱需大寫開頭才會對外可見(導出)。
繼承
繼承是編譯期特徵,在 struct 內嵌入要「繼承」的類型即可:
type A struct{}
type B struct {
A // 嵌入 A,B 擁有 A 的欄位與方法
}
多態
多態是執行期特徵,透過 interface 實現。類型與介面是鬆耦合的:某個類型的實例可以賦給它實現的任意介面型變數。
type Writer interface {
Write([]byte) (int, error)
}
// 只要類型實現了 Write,就可以賦給 Writer 變數
var w Writer = myType{}
Go 支援多重繼承,即在類型中嵌入多個父類型即可。
03 uint 相減結果會怎樣?
var a uint = 1
var b uint = 2
fmt.Println(a - b)
答案:會發生無符號整數溢出。
- 32 位系統:結果為
2^32 - 1 - 64 位系統:結果為
2^64 - 1
為什麼會變成很大的數?
uint 只能存 0 和正整數,沒有「負數」這個概念。在硬體上減法仍是用二進位運算,1 - 2 的位元結果其實等同於「負一」的二補數表示;但 Go 把這串位元一律當成無符號數解讀,所以不會變成 -1,而是被解讀成「該位元寬度下最大的正整數」(模數運算 wrap around),例如 64 位就是 2^64 - 1。
若需要負數怎麼做?
-
改用有符號型別(最直接):需要可能為負的差值時,用
int/int32/int64。var a, b int = 1, 2fmt.Println(a - b) // -1 -
先轉成有符號再相減:資料本身是
uint時,先轉成夠大的有符號型別再算,並注意溢出(例如int裝不下uint最大值時用int64或先檢查範圍)。var a, b uint = 1, 2diff := int(a) - int(b)fmt.Println(diff) // -1 -
需要「帶正負的差值」且要防溢出:先判斷誰大再決定正負,或使用
int64並在轉換前檢查是否在安全範圍內。var a, b uint64 = 1, 2var diff int64if a >= b {diff = int64(a - b)} else {diff = -int64(b - a)}fmt.Println(diff) // -1
04 Go 有沒有在 main 之前執行的函數?怎麼用?
有,init 函數會在 main 之前執行。
func init() {
// ...
}