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 的執行順序

答案:

例如:

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