Go 語言筆試面試題
「Q1」 = 和 := 的區別?
答案:
:= 聲明 + 賦值
= 僅賦值
var foo int
foo = 10
// 等價於
foo := 10
「Q2」 指針的作用?
答案:
指針用來保存變量的地址。
例如
var x = 5
var p *int = &x
fmt.Printf("x = %d", *p) // x 可以用 *p 訪問
-
- 運算符,也稱爲解引用運算符,用於訪問地址中的值。
-
&運算符,也稱爲地址運算符,用於返回變量的地址。
「Q3」 Go 允許多個返回值嗎?
答案:
允許
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("A", "B")
fmt.Println(a, b) // B A
}
「Q4」 Go 有異常類型嗎?
答案:
Go 沒有異常類型,只有錯誤類型(Error),通常使用返回值來表示異常狀態。
f, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
「Q5」 什麼是協程(Goroutine)
答案:
Goroutine 是與其他函數或方法同時運行的函數或方法。Goroutines 可以被認爲是輕量級的線程。與線程相比,創建 Goroutine 的開銷很小。Go 應用程序同時運行數千個 Goroutine 是非常常見的做法。
「Q6」 如何高效地拼接字符串
答案:
Go 語言中,字符串是隻讀的,也就意味着每次修改操作都會創建一個新的字符串。如果需要拼接多次,應使用 strings.Builder,最小化內存拷貝次數。
var str strings.Builder
for i := 0; i < 1000; i++ {
str.WriteString("a")
}
fmt.Println(str.String())
「Q7」 什麼是 rune 類型
答案:
ASCII 碼只需要 7 bit 就可以完整地表示,但只能表示英文字母在內的 128 個字符,爲了表示世界上大部分的文字系統,發明了 Unicode, 它是 ASCII 的超集,包含世界上書寫系統中存在的所有字符,併爲每個代碼分配一個標準編號(稱爲 Unicode CodePoint),在 Go 語言中稱之爲 rune,是 int32 類型的別名。
Go 語言中,字符串的底層表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。例如下面的例子中 語 和 言 使用 UTF-8 編碼後各佔 3 個 byte,因此 len("Go 語言") 等於 8,當然我們也可以將字符串轉換爲 rune 序列。
fmt.Println(len("Go語言")) // 8
fmt.Println(len([]rune("Go語言"))) // 4
「Q8」 如何判斷 map 中是否包含某個 key ?
答案:
if val, ok := dict["foo"]; ok {
//do something here
}
dict["foo"] 有 2 個返回值,val 和 ok,如果 ok 等於 true,則說明 dict 包含 key "foo",val 將被賦予 "foo" 對應的值。
「Q9」 Go 支持默認參數或可選參數嗎?
答案:
Go 語言不支持可選參數(python 支持),也不支持方法重載(java 支持)。
「Q10」 defer 的執行順序
答案:
-
多個 defer 語句,遵從後進先出 (Last In First Out,LIFO) 的原則,最後聲明的 defer 語句,最先得到執行。
-
defer 在 return 語句之後執行,但在函數退出之前,defer 可以修改返回值。
例如:
func test() int {
i := 0
defer func() {
fmt.Println("defer1")
}()
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}
func main() {
fmt.Println("return", test())
}
// defer2
// defer1
// return 0
這個例子中,可以看到 defer 的執行順序:後進先出。但是返回值並沒有被修改,這是由於 Go 的返回機制決定的,執行 return 語句後,Go 會創建一個臨時變量保存返回值,因此,defer 語句修改了局部變量 i,並沒有修改返回值。那如果是有名的返回值呢?
func test() (i int) {
i = 0
defer func() {
i += 1
fmt.Println("defer2")
}()
return i
}
func main() {
fmt.Println("return", test())
}
// defer2
// return 1
這個例子中,返回值被修改了。對於有名返回值的函數,執行 return 語句時,並不會再創建臨時變量保存,因此,defer 語句修改了 i,即對返回值產生了影響。
「Q11」 如何交換 2 個變量的值?
答案:
a, b := "A", "B"
a, b = b, a
fmt.Println(a, b) // B A
「Q12」 Go 語言 tag 的用處?
答案:
tag 可以理解爲 struct 字段的註解,可以用來定義字段的一個或多個屬性。框架 / 工具可以通過反射獲取到某個字段定義的屬性,採取相應的處理方式。tag 豐富了代碼的語義,增強了靈活性。
例如:
package main
import "fmt"
import "encoding/json"
type Stu struct {
Name string `json:"stu_name"`
ID string `json:"stu_id"`
Age int `json:"-"`
}
func main() {
buf, _ := json.Marshal(Stu{"Tom", "t001", 18})
fmt.Printf("%s\n", buf)
}
這個例子使用 tag 定義了結構體字段與 json 字段的轉換關係,Name -> stu_name, ID -> stu_id,忽略 Age 字段。很方便地實現了 Go 結構體與不同規範的 json 文本之間的轉換。
「Q13」 如何判斷 2 個字符串切片(slice) 是相等的?
答案:
go 語言中可以使用反射 reflect.DeepEqual(a, b) 判斷 a、b 兩個切片是否相等,但是通常不推薦這麼做,使用反射非常影響性能。
通常採用的方式如下,遍歷比較切片中的每一個元素(注意處理越界的情況)。
func StringSliceEqualBCE(a, b []string) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
b = b[:len(a)]
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
「Q14」 字符串打印時,%v 和 %+v 的區別
答案:
%v 和 %+v 都可以用來打印 struct 的值,區別在於 %v 僅打印各個字段的值,%+v 還會打印各個字段的名稱。
type Stu struct {
Name string
}
func main() {
fmt.Printf("%v\n", Stu{"Tom"}) // {Tom}
fmt.Printf("%+v\n", Stu{"Tom"}) // {Name:Tom}
}
但如果結構體定義了 String() 方法,%v 和 %+v 都會調用 String() 覆蓋默認值。
「Q15」 Go 語言中如何表示枚舉值 (enums)
答案:
通常使用常量 (const) 來表示枚舉值。
type StuType int32
const (
Type1 StuType = iota
Type2
Type3
Type4
)
func main() {
fmt.Println(Type1, Type2, Type3, Type4) // 0, 1, 2, 3
}
參考 What is an idiomatic way of representing enums in Go? - StackOverflow
「Q16」 空 struct{} 的用途
答案:
使用空結構體 struct{} 可以節省內存,一般作爲佔位符使用,表明這裏並不需要一個值。
fmt.Println(unsafe.Sizeof(struct{}{})) // 0
比如使用 map 表示集合時,只關注 key,value 可以使用 struct{} 作爲佔位符。如果使用其他類型作爲佔位符,例如 int,bool,不僅浪費了內存,而且容易引起歧義。
type Set map[string]struct{}
func main() {
set := make(Set)
for _, item := range []string{"A", "A", "B", "C"} {
set[item] = struct{}{}
}
fmt.Println(len(set)) // 3
if _, ok := set["A"]; ok {
fmt.Println("A exists") // A exists
}
}
再比如,使用信道 (channel) 控制併發時,我們只是需要一個信號,但並不需要傳遞值,這個時候,也可以使用 struct{} 代替。
func main() {
ch := make(chan struct{}, 1)
go func() {
<-ch
// do something
}()
ch <- struct{}{}
// ...
}
再比如,聲明只包含方法的結構體。
type Lamp struct{}
func (l Lamp) On() {
println("On")
}
func (l Lamp) Off() {
println("Off")
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/0mqCfsBkgRAopcW-E3IAWA