Go 面試題

wacho發表於2020-02-13

下面面試題均收集來自go語言中文網

1.下面是否可以編譯透過?不能透過是為什麼

func main() {
    list := new([]int)
    list = append(list, 1)
    fmt.Println(list)
}

解析:不能透過,因為 new 函式返回到 是一個指標,而 append函式是對切片的操作,不是指標,可以修改成*list= append(*list,1).
channel 、slice、map 必須透過 make 初始化,這題也可以使用make 函式,make([]int,0),0表示初始化有 0 個元素,這樣結果才會是[1],如果寫成 make([]int,1),表示有一個元素了,執行結果就為[0,1],預設有一個 0 元素。

2.是否可以編譯透過?如果透過,輸出什麼?

package main

import "fmt"

func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{4, 5}
    s1 = append(s1, s2)
    fmt.Println(s1)
}

解析:不能透過,append切片時,要後面追加…,正確應該:s1 = append(s1, s2...),此時結果才會是 []int{1, 2, 3,4,5}

3.已知字串 s=’hello’,把第一個字母變成 c

    s := "hello"
    c := []byte(s)  // 將字串 s 轉換為 []byte 型別
    c[0] = 'c'
    s = string(c)  // 再轉換回 string 型別
    fmt.Printf("%s", s)// cello

4、以下程式碼有什麼問題,說明原因

type student struct {
    Name string
    Age  int
}
func pase_student() {
    m := make(map[string]*student)
    stus := []student{
        {Name: "zhou",Age: 24},
        {Name: "li",Age: 23},
        {Name: "wang",Age: 22},
    }
    for _,stu := range stus {
        m[stu.Name] =&stu
    }
}

解答:考點 for, 與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實際上一致指向同一個指標, 最終該指標的值為遍歷的最後一個struct的值複製,正確寫法因該是:

 for i:=0;i<len(stus);i++ {
       m[stus[i].Name] = &stus[i]
    }

5.下面的程式碼會輸出什麼,並說明原因

func main() {
    runtime.GOMAXPROCS(1)
    wg := sync.WaitGroup{}
    wg.Add(20)
    for i := 0; i < 10; i++ {
         go func() {
             fmt.Println("A: ", i)
             wg.Done()
        }()
    }
    for i:= 0; i < 10; i++ {
        go func(i int) {
            fmt.Println("B: ", i)
            wg.Done()
        }(i)
    }
    wg.Wait()
}

解答:考察 go執行的隨機性和閉包。誰也不知道執行後列印的順序是什麼樣的,所以只能說是隨機數字。 但是A:均為輸出10,B:從0~9輸出(順序不定)。 第一個go func中i是外部for的一個變數,地址不變化。遍歷完成後,最終i=10。 故go func執行時,i的值始終是10。

第二個go func中i是函式引數,與外部for中的i完全是兩個變數。 尾部(i)將發生值複製,go func內部指向值複製地址。

6、下面程式碼會觸發異常嗎

func main() {
    runtime.GOMAXPROCS(1)
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    int_chan <- 1
    string_chan <- "hello"
    select {
          case value := <-int_chan:
              fmt.Println(value)
          case value := <-string_chan:
              panic(value)
    }
}

解答:select會隨機選擇一個可用通用做收發操作。 所以程式碼是有肯觸發異常,也有可能不會。 單個chan如果無緩衝時,將會阻塞。但結合 select可以在多個chan間等待執行。有三點原則:
1.select 中只要有一個case能return,則立刻執行。
2.當如果同一時間有多個case均能return則偽隨機方式抽取任意一個執行。
3.如果沒有一個case能return則可以執行”default”塊。
上面語句可能會觸發panic,case到下面的時候就觸犯了,case到上面的就列印1.

7、下面程式碼輸出什麼?

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
}

解答:這道題考察 defer ,需要注意到defer執行順序和值傳遞. index:1肯定是最後執行的,但是index:1的第三個引數是一個函式,所以最先被呼叫 calc("10",1,2)==>10,1,2,3,執行index:2時,與之前一樣,需要先呼叫 calc("20",0,2)==>20,0,2,2 ,執行到b=1時候開始呼叫,index:2==>calc("2",0,2)==>2,0,2,2,最後執行 index:1==>calc("1",1,3)==>1,1,3,4.

8、select可以用於什麼,常用語gorotine的完美退出

解答:golang 的 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作
每個case語句裡必須是一個IO操作,確切的說,應該是一個面向channel的IO操作

9、map如何順序讀取

map不能順序讀取,是因為他是無序的,想要有序讀取,首先的解決的問題就是,把key變為有序,所以可以把key放入切片,對切片進行排序,遍歷切片,透過key取值。

10、交替列印數字和字母,使用兩個 goroutine 交替列印序列,一個 goroutinue 列印數字, 另外一個goroutine列印字母, 最終效果如下 12AB34CD56EF78GH910IJ

解答:問題很簡單,使用 channel 來控制列印的進度。使用兩個 channel,來分別控制數字和字母的列印序列, 數字列印完成後透過 channel 通知字母列印, 字母列印完成後通知數字列印,然後週而復始的工作。

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    chan_n := make(chan bool)
    chan_c := make(chan bool, 1)
    done := make(chan struct{})

    go func() {
        for i := 1; i < 11; i += 2 {
            <-chan_c
            fmt.Print(i)
            fmt.Print(i + 1)
            chan_n <- true
        }
    }()

    go func() {
        char_seq := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K"}
        for i := 0; i < 10; i += 2 {
            <-chan_n
            fmt.Print(char_seq[i])
            fmt.Print(char_seq[i+1])
            chan_c <- true
        }
        done <- struct{}{}
    }()

    chan_c <- true
    <-done
}

看完上面的程式碼,是不是會有些疑惑,為什麼 chan_c 需要快取,而 chan_n 不需要呢?
當兩個列印 goroutine 無限交替執行時,沒有快取是OK的,但很明顯上面的示例不是,列印數字的 goroutine 先退出,也就沒有了 goroutine 來讀取 chan_c 中的內容了, 而列印字母的goroutine就會阻塞在 chan_c <- true ,這樣就導致了死鎖。

11.下面兩段程式碼輸出什麼。

// 1.
 func main() {
     s := make([]int, 5)
     s = append(s, 1, 2, 3)
     fmt.Println(s)
 }

// 2.
 func main() {
    s := make([]int,0)
    s = append(s,1,2,3,4)
    fmt.Println(s)
}

兩段程式碼分別輸出:

    [0 0 0 0 0 1 2 3]
    [1 2 3 4]

參考解析:這道題考的是使用 append 向 slice 新增元素,第一段程式碼常見的錯誤是 [1 2 3],需要注意。

12.下面這段程式碼有什麼缺陷

    func funcMui(x,y int)(sum int,error){
        return x+y,nil
    }

參考答案:第二個返回值沒有命名。

參考解析:

在函式有多個返回值時,只要有一個返回值有命名,其他的也必須命名。如果有多個返回值必須加上括號();如果只有一個返回值且命名也必須加上括號()。這裡的第一個返回值有命名 sum,第二個沒有命名,所以錯誤。

13.new() 與 make() 的區別

參考答案:

new(T) 和 make(T,args) 是 Go 語言內建函式,用來分配記憶體,但適用的型別不同。

new(T) 會為 T 型別的新值分配已置零的記憶體空間,並返回地址(指標),即型別為 *T的值。換句話說就是,返回一個指標,該指標指向新分配的、型別為 T 的零值。適用於值型別,如陣列、結構體等。

make(T,args) 返回初始化之後的 T 型別的值,這個值並不是 T 型別的零值,也不是指標 *T,是經過初始化之後的 T 的引用。make() 只適用於 slice、map 和 channel.

14 下面這段程式碼能否透過編譯?不能的話,原因是什麼?如果透過,輸出什麼?

func main() {
    sn1 := struct {
        age  int
        name string
    }{age: 11, name: "qq"}
    sn2 := struct {
        age  int
        name string
    }{age: 11, name: "qq"}

    if sn1 == sn2 {
        fmt.Println("sn1 == sn2")
    }

    sm1 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}
    sm2 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}

    if sm1 == sm2 {
        fmt.Println("sm1 == sm2")
    }
}

參考答案及解析:編譯不透過 invalid operation: sm1 == sm2

這道題目考的是結構體的比較,有幾個需要注意的地方:

  • 結構體只能比較是否相等,但是不能比較大小。
  • 相同型別的結構體才能夠進行比較,結構體是否相同不但與屬性型別有關,還與屬性順序相關,sn3 與 sn1 就是不同的結構體;
     sn3:= struct {
            name string
            age  int
        }{age:11,name:"qq"}
  • 如果 struct 的所有成員都可以比較,則該 struct 就可以透過 == 或 != 進行比較是否相等,比較時逐個項進行比較,如果每一項都相等,則兩個結構體才相等,否則不相等;

那什麼是可比較的呢,常見的有 bool、數值型、字元、指標、陣列等,像切片、map、函式等是不能比較的。

15 下面這段程式碼能否透過編譯?如果透過,輸出什麼?

package main

import "fmt"

type MyInt1 int
type MyInt2 = int

func main() {
    var i int =0
    var i1 MyInt1 = i 
    var i2 MyInt2 = i
    fmt.Println(i1,i2)
}

參考答案及解析:編譯不透過,cannot use i (type int) as type MyInt1 in assignment。

這道題考的是型別別名與型別定義的區別。

第 5 行程式碼是基於型別 int 建立了新型別 MyInt1,第 6 行程式碼是建立了 int 的型別別名 MyInt2,注意型別別名的定義時 = 。所以,第 10 行程式碼相當於是將 int 型別的變數賦值給 MyInt1 型別的變數,Go 是強型別語言,編譯當然不透過;而 MyInt2 只是 int 的別名,本質上還是 int,可以賦值。

第 10 行程式碼的賦值可以使用強制型別轉化 var i1 MyInt1 = MyInt1(i).

16 關於字串連線,下面語法正確的是?

  A. str := ‘abc’ +123’
  B. str := “abc” +123”
  C  str :=123+ “abc”
  D. fmt.Sprintf(“abc%d”, 123)

參考答案及解析:BD。知識點:字串連線。除了以上兩種連線方式,還有 strings.Join()、buffer.WriteString()等。

17 下面這段程式碼能否編譯透過?如果可以,輸出什麼?

const (
     x = iota
     _
     y
     z = "zz"
     k 
     p = iota
 )

func main()  {
    fmt.Println(x,y,z,k,p)
}

參考答案及解析:編譯透過,輸出:0 2 zz zz 5。知識點:iota 的使用。iota在const關鍵字出現時將被重置為0(const內部的第一行之前),const中每新增一行常量宣告將使iota計數一次(iota可理解為const語句塊中的行索引)。z=”zz”,下面k 預設就等於了上面z的值,下面的p雖然等於iota,但是沒有const關鍵字,所以還是自增。

18 下面賦值正確的是()

A. var x = nil
B. var x interface{} = nil
C. var x string = nil
D. var x error = nil

參考答案及解析:BD。知識點:nil 值。nil 只能賦值給指標、chan、func、interface、map 或 slice 型別的變數。強調下 D 選項的 error 型別,它是一種內建介面型別,看下方貼出的原始碼就知道,所以 D 是對的。
type error interface {
Error() string
}

19 關於init函式,下面說法正確的是()

A. 一個包中,可以包含多個 init 函式;
B. 程式編譯時,先執行依賴包的 init 函式,再執行 main 包內的 init 函式;
C. main 包中,不能有 init 函式;
D. init 函式可以被其他函式呼叫;

1.參考答案及解析:AB。關於 init() 函式有幾個需要注意的地方:

init() 函式是用於程式執行前做包的初始化的函式,比如初始化包裡的變數等;
一個包可以出線多個 init() 函式,一個原始檔也可以包含多個 init() 函式;
同一個包中多個 init() 函式的執行順序沒有明確定義,但是不同包的init函式是根據包匯入的依賴關係決定的(看下圖);
init() 函式在程式碼中不能被顯示呼叫、不能被引用(賦值給函式變數),否則出現編譯錯誤;
一個包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只會初始化一次;
引入包,不可出現死循壞。即 A import B,B import A,這種情況編譯失敗;

Go 面試題

20 下面這段程式碼輸出什麼以及原因?

func hello() []string {  
     return nil
 }

 func main() {  
     h := hello
     if h == nil {
         fmt.Println("nil")
     } else {
        fmt.Println("not nil")
    }
}

A. nil

B. not nil

C. compilation error

答案及解析:B。這道題目裡面,是將 hello() 賦值給變數 h,而不是函式的返回值,所以輸出 not nil。如果換成h:=hello();那麼h= nil;就列印nil了。

21 下面這段程式碼能否編譯透過?如果可以,輸出什麼?

func GetValue() int {
     return 1
 }

 func main() {
     i := GetValue()
     switch i.(type) {
     case int:
         println("int")
     case string:
        println("string")
     case interface{}:
        println("interface")
     default:
        println("unknown")
    }
}

參考答案及解析:編譯失敗。考點:型別選擇,型別選擇的語法形如:i.(type),其中 i 是介面,type 是固定關鍵字,需要注意的是,只有介面型別才可以使用型別選擇。看下關於介面的文章,這裡要斷言型別,需要用到反射。

22 關於channel,下面語法正確的是()

  • A. var ch chan int

  • B. ch := make(chan int)

  • C. <- ch

  • D. ch <-

參考答案及解析:ABC。A、B都是宣告 channel;C 讀取 channel;寫 channel 是必須帶上值,所以 D 錯誤。

23 下面這段程式碼輸出什麼?

type person struct {  
    name string
}

func main() {  
    var m map[person]int
    p := person{"mike"}
    fmt.Println(m[p])
}
  • A.0

  • B.1

  • C.Compilation error

參考答案及解析:A。列印一個 map 中不存在的值時,返回元素型別的零值。這個例子中,m 的型別是 map[person]int,因為 m 中不存在 p,所以列印 int 型別的零值,即 0。

24 下面這段程式碼輸出什麼?

func main() {  
    a := 5
    b := 8.1
    fmt.Println(a + b)
}
  • A.13.1

  • B.13

  • C.compilation error

參考答案及解析:C。a 的型別是 int,b 的型別是 float,兩個不同型別的數值不能相加,編譯報錯。

25 下面這段程式碼輸出什麼?

package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{1, 2, 3, 4, 5}
    t := a[3:4:4]
    fmt.Println(t[0])
}
  • A.3

  • B.4

  • C.compilation error

參考答案及解析:B。知識點:運算子 [i,j]。基於陣列(切片)可以使用運算子 [i,j] 建立新的切片,從索引 i,到索引 j 結束,擷取已有陣列(切片)的任意部分,返回新的切片,新切片的值包含原陣列(切片)的 i 索引的值,但是不包含 j 索引的值。i、j 都是可選的,i 如果省略,預設是 0,j 如果省略,預設是原陣列(切片)的長度。i、j 都不能超過這個長度值。

假如底層陣列的大小為 k,擷取之後獲得的切片的長度和容量的計算方法:長度:j-i,容量:k-i。

擷取運算子還可以有第三個引數,形如 [i,j,k],第三個引數 k 用來限制新切片的容量,但不能超過原陣列(切片)的底層陣列大小。擷取獲得的切片的長度和容量分別是:j-i、k-i。

所以例子中,切片 t 為 [4],長度和容量都是 1。

26 下面這段程式碼輸出什麼?

func main() {
    a := [2]int{5, 6}
    b := [3]int{5, 6}
    if a == b {
        fmt.Println("equal")
    } else {
        fmt.Println("not equal")
    }
}
  • A. compilation error

  • B. equal

  • C. not equal

參考答案及解析:A。Go 中的陣列是值型別,可比較,另外一方面,陣列的長度也是陣列型別的組成部分,所以 a 和 b 是不同的型別,是不能比較的,所以編譯錯誤。

27 關於 cap() 函式的適用型別,下面說法正確的是()

  • A. array

  • B. slice

  • C. map

  • D. channel

參考答案及解析:ABD。知識點:cap(),cap() 函式不適用 map。

28 下面這段程式碼輸出什麼?

func main() {  
    var i interface{}
    if i == nil {
        fmt.Println("nil")
        return
    }
    fmt.Println("not nil")
}
  • A. nil

  • B. not nil

  • C. compilation error

參考答案及解析:A。當且僅當介面的動態值和動態型別都為 nil 時,介面型別值才為 nil。

29 下面這段程式碼輸出什麼?

func main() {  
    s := make(map[string]int)
    delete(s, "h")
    fmt.Println(s["h"])
}
  • A. runtime panic

  • B. 0

  • C. compilation error

參考答案及解析:B。刪除 map 不存在的鍵值對時,不會報錯,相當於沒有任何作用;獲取不存在的減值對時,返回值型別對應的零值,所以返回 0。

30 下面這段程式碼輸出什麼?

type People struct{}

func (p *People) ShowA() {
    fmt.Println("showA")
    p.ShowB()
}
func (p *People) ShowB() {
    fmt.Println("showB")
}

type Teacher struct {
    People
}

func (t *Teacher) ShowB() {
    fmt.Println("teacher showB")
}

func main() {
    t := Teacher{}
    t.ShowB()
}

參考答案及解析:teacher showB。知識點:結構體巢狀。在巢狀結構體中,People 稱為內部型別,Teacher 稱為外部型別;透過巢狀,內部型別的屬性、方法,可以為外部型別所有,就好像是外部型別自己的一樣。此外,外部型別還可以定義自己的屬性和方法,甚至可以定義與內部相同的方法,這樣內部型別的方法就會被“遮蔽”。這個例子中的 ShowB() 就是同名方法。

31 下面這段程式碼輸出什麼?

func hello(i int) {  
    fmt.Println(i)
}
func main() {  
    i := 5
    defer hello(i)
    i = i + 10
}

參考答案及解析:5。這個例子中,hello() 函式的引數在執行 defer 語句的時候會儲存一份副本,在實際呼叫 hello() 函式時用,所以是 5.

32 下面這段程式碼輸出什麼?

type People struct{}

func (p *People) ShowA() {
    fmt.Println("showA")
    p.ShowB()
}
func (p *People) ShowB() {
    fmt.Println("showB")
}

type Teacher struct {
    People
}

func (t *Teacher) ShowB() {
    fmt.Println("teacher showB")
}

func main() {
    t := Teacher{}
    t.ShowA()
}

參考答案及解析:

showA
showB

知識點:結構體巢狀。這道題可以結合上面 的題一起看,Teacher 沒有自己 ShowA(),所以呼叫內部型別 People 的同名方法,需要注意的是第 5 行程式碼呼叫的是 People 自己的 ShowB 方法。

33 下面程式碼輸出什麼?

func main() {
    str := "hello"
    str[0] = 'x'
    fmt.Println(str)
}
  • A. hello

  • B. xello

  • C. compilation error

參考程式碼及解析:C。知識點:常量,Go 語言中的字串是隻讀的。如果要修改,需要轉換成位元組切片去操作,然後再轉成字串。

34 下面程式碼輸出什麼?

func incr(p *int) int {
    *p++
    return *p
}

func main() {
    p :=1
    incr(&p)
    fmt.Println(p)
}
  • A. 1

  • B. 2

  • C. 3

參考答案及解析:B。知識點:指標,incr() 函式里的 p 是 *int 型別的指標,指向的是 main() 函式的變數 p 的地址。第 2 行程式碼是將該地址的值執行一個自增操作,incr() 返回自增後的結果。

35 對 add() 函式呼叫正確的是()

func add(args ...int) int {

    sum := 0
    for _, arg := range args {
        sum += arg
    }
    return sum
}
  • A. add(1, 2)

  • B. add(1, 3, 7)

  • C. add([]int{1, 2})

  • D. add([]int{1, 3, 7}…)

參考答案及解析:ABD。知識點:可變函式。

36 下面這段程式碼輸出什麼?

type A interface {
    ShowA() int
}

type B interface {
    ShowB() int
}

type Work struct {
    i int
}

func (w Work) ShowA() int {
    return w.i + 10
}

func (w Work) ShowB() int {
    return w.i + 20
}

func main() {
    c := Work{3}
    var a A = c
    var b B = c
    fmt.Println(a.ShowA())
    fmt.Println(b.ShowB())
}

參考答案及解析:13 23。知識點:介面。一種型別實現多個介面,結構體 Work 分別實現了介面 A、B,所以介面變數 a、b 呼叫各自的方法 ShowA() 和 ShowB(),輸出 13、23。

37 .切片 a、b、c 的長度和容量分別是多少?

func main() {

    s := [3]int{1, 2, 3}
    a := s[:0]
    b := s[:2]
    c := s[1:2:cap(s)]
}

參考答案及解析:a、b、c 的長度和容量分別是 0 3、2 3、1 2。知識點:陣列或切片的擷取操作。擷取操作有帶 2 個或者 3 個引數,形如:[i:j] 和 [i:j:k],假設擷取物件的底層陣列長度為 l。在運算子 [i:j] 中,如果 i 省略,預設 0,如果 j 省略,預設底層陣列的長度,擷取得到的切片長度和容量計算方法是 j-i、l-i。運算子 [i:j:k],k 主要是用來限制切片的容量,但是不能大於陣列的長度 l,擷取得到的切片長度和容量計算方法是 j-i、k-i。

38 下面程式碼中 A B 兩處應該怎麼修改才能順利編譯?

func main() {
    var m map[string]int        //A
    m["a"] = 1
    if v := m["b"]; v != nil {  //B
        fmt.Println(v)
    }
}

參考答案及解析:

func main() {
    m := make(map[string]int)
    m["a"] = 1
    if v,ok := m["b"]; ok {
        fmt.Println(v)
    }
}

在 A 處只宣告瞭map m ,並沒有分配記憶體空間,不能直接賦值,需要使用 make(),都提倡使用 make() 或者字面量的方式直接初始化 map。
B 處,v,k := m[“b”] 當 key 為 b 的元素不存在的時候,v 會返回值型別對應的零值,k 返回 false。

39 下面程式碼輸出什麼?

type A interface {
    ShowA() int
}

type B interface {
    ShowB() int
}

type Work struct {
    i int
}

func (w Work) ShowA() int {
    return w.i + 10
}

func (w Work) ShowB() int {
    return w.i + 20
}

func main() {
    c := Work{3}
    var a A = c
    var b B = c
    fmt.Println(a.ShowB())
    fmt.Println(b.ShowA())
}
  • A. 23 13

  • B. compilation error

參考答案及解析:B。知識點:介面的靜態型別。a、b 具有相同的動態型別和動態值,分別是結構體 work 和 {3};a 的靜態型別是 A,b 的靜態型別是 B,介面 A 不包括方法 ShowB(),介面 B 也不包括方法 ShowA(),編譯報錯。看下編譯錯誤:

a.ShowB undefined (type A has no field or method ShowB)
b.ShowA undefined (type B has no field or method ShowA)

40 下面程式碼輸出什麼?

func increaseA() int {
    var i int
    defer func() {
        i++
    }()
    return i
}

func increaseB() (r int) {
    defer func() {
        r++
    }()
    return r
}

func main() {
    fmt.Println(increaseA())
    fmt.Println(increaseB())
}
  • A. 1 1

  • B. 0 1

  • C. 1 0

  • D. 0 0

參考答案及解析:B。知識點:defer、返回值。注意一下,increaseA() 的返回引數是匿名,increaseB() 是具名。

41 下面程式碼輸出什麼?

type A interface {
    ShowA() int
}

type B interface {
    ShowB() int
}

type Work struct {
    i int
}

func (w Work) ShowA() int {
    return w.i + 10
}

func (w Work) ShowB() int {
    return w.i + 20
}

func main() {
    var a A = Work{3}
    s := a.(Work) //把a 轉換成Work 型別
    fmt.Println(s.ShowA())
    fmt.Println(s.ShowB())
}
  • A. 13 23

  • B. compilation error

參考答案及解析:A。知識點:型別斷言。

42 下面程式碼段輸出什麼?

type Person struct {
    age int
}

func main() {
    person := &Person{28}

    // 1. 
    defer fmt.Println(person.age)

    // 2.
    defer func(p *Person) {
        fmt.Println(p.age)
    }(person)  

    // 3.
    defer func() {
        fmt.Println(person.age)
    }()

    person.age = 29
}

參考答案及解析:29 29 28。變數 person 是一個指標變數 。

1.person.age 此時是將 28 當做 defer 函式的引數,會把 28 快取在棧中,等到最後執行該 defer 語句的時候取出,即輸出 28;

2.defer 快取的是結構體 Person{28} 的地址,最終 Person{28} 的 age 被重新賦值為 29,所以 defer 語句最後執行的時候,依靠快取的地址取出的 age 便是 29,即輸出 29;

3.閉包引用,輸出 29;

又由於 defer 的執行順序為先進後出,即 3 2 1,所以輸出 29 29 28。

43 下面程式碼輸出什麼?

type Person struct {
    age int
}

func main() {
    person := &Person{28}

    // 1.
    defer fmt.Println(person.age)

    // 2.
    defer func(p *Person) {
        fmt.Println(p.age)
    }(person)

    // 3.
    defer func() {
        fmt.Println(person.age)
    }()

    person = &Person{29}
}

參考答案及解析:29 28 28。這道題在上個題目的基礎上做了一點點小改動,前一題最後一行程式碼 person.age = 29 是修改引用物件的成員 age,這題最後一行程式碼 person = &Person{29} 是修改引用物件本身,來看看有什麼區別。

1處.person.age 這一行程式碼跟之前含義是一樣的,此時是將 28 當做 defer 函式的引數,會把 28 快取在棧中,等到最後執行該 defer 語句的時候取出,即輸出 28;

2處.defer 快取的是結構體 Person{28} 的地址,這個地址指向的結構體沒有被改變,最後 defer 語句後面的函式執行的時候取出仍是 28;

3處.閉包引用,person 的值已經被改變,指向結構體 Person{29},所以輸出 29.

由於 defer 的執行順序為先進後出,即 3 2 1,所以輸出 29 28 28。

44 下面的兩個切片宣告中有什麼區別?哪個更可取?

A. var a []int
B. a := []int{}

參考答案及解析:A 宣告的是 nil 切片;B 宣告的是長度和容量都為 0 的空切片。第一種切片宣告不會分配記憶體,優先選擇。

45 A、B、C、D 哪些選項有語法錯誤?

type S struct {
}

func f(x interface{}) {
}

func g(x *interface{}) {
}

func main() {
    s := S{}
    p := &s
    f(s) //A
    g(s) //B
    f(p) //C
    g(p) //D
}

參考答案及解析:BD。函式引數為 interface{} 時可以接收任何型別的引數,包括使用者自定義型別等,即使是接收指標型別也用 interface{},而不是使用 *interface{}

永遠不要使用一個指標指向一個介面型別,因為它已經是一個指標。

46 下面 A、B 兩處應該填入什麼程式碼,才能確保順利列印出結果?

type S struct {
    m string
}

func f() *S {
    return __  //A
}

func main() {
    p := __    //B
    fmt.Println(p.m) //print "foo"
}

參考答案及解析:

A. &S{"foo"} 
B. *f() 或者 f()

f() 函式返回引數是指標型別,所以可以用 & 取結構體的指標;B 處,如果填 *f(),則 p 是 S 型別;如果填 f(),則 p 是 *S 型別,不過都可以使用 p.m 取得結構體的成員。

47 下面的程式碼有幾處語法問題,各是什麼?

package main
import (
    "fmt"
)
func main() {
    var x string = nil
    if x == nil {
        x = "default"
    }
    fmt.Println(x)
}

參考答案及解析:兩個地方有語法問題。golang 的字串型別是不能賦值 nil 的,也不能跟 nil 比較。

48 return 之後的 defer 語句會執行嗎,下面這段程式碼輸出什麼?

var a bool = true
func main() {
    defer func(){
        fmt.Println("1")
    }()
    if a == true {
        fmt.Println("2")
        return
    }
    defer func(){
        fmt.Println("3")
    }()
}

參考答案及解析:2 1。defer 關鍵字後面的函式或者方法想要執行必須先註冊,return 之後的 defer 是不能註冊的, 也就不能執行後面的函式或方法

48 下面這段程式碼輸出什麼?為什麼?

func main() {

    s1 := []int{1, 2, 3}
    s2 := s1[1:]
    s2[1] = 4
    fmt.Println(s1)
    s2 = append(s2, 5, 6, 7)
    fmt.Println(s1)
}

參考答案及解析:

[1 2 4]

[1 2 4]

我們知道,golang 中切片底層的資料結構是陣列。當使用 s1[1:] 獲得切片 s2,和 s1 共享同一個底層陣列,這會導致 s2[1] = 4 語句影響 s1。

而 append 操作會導致底層陣列擴容,生成新的陣列,因此追加資料後的 s2 不會影響 s1。

但是為什麼對 s2 賦值後影響的卻是 s1 的第三個元素呢?這是因為切片 s2 是從陣列的第二個元素開始,s2 索引為 1 的元素對應的是 s1 索引為 2 的元素。

49 下面選項正確的是?

func main() {
    if a := 1; false {
    } else if b := 2; false {
    } else {
        println(a, b)
    }
}
  • A. 1 2

  • B. compilation error

參考答案及解析:A。知識點:程式碼塊和變數作用域。

50 下面這段程式碼輸出什麼?

func main() {
    m := map[int]string{0:"zero",1:"one"}
    for k,v := range m {
        fmt.Println(k,v)
    }
}

參考答案及解析:

0 zero
1 one
// 或者
1 one
0 zero

map 的輸出是無序的。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章