Golang 面試題解析 1

1.寫出以下代碼輸出內容

defer 先進後出 (stack) , 然後再執行 panic() https://go.dev/play/p/6p9PVulq0El

package main

import (
    "fmt"
)

func main() {
    defer_call()
}

func defer_call() {
    defer func() { fmt.Println("打印前") }()
    defer func() { fmt.Println("打印中") }()
    defer func() { fmt.Println("打印後") }()

    panic("觸發異常")
}

/* 
打印後
打印中
打印前
panic: 觸發異常
*/

延遲的英文後進先出。協程遇到死機時,遍歷本協程的延遲鍊錶,並執行延遲在執行延遲過程中,遇到恢復則停止恐慌,返回回收處繼續往如果沒有遇到恢復,遍歷完本協程的defer鍊錶後,向stderr引發panic信息。從執行順序上來看,實際上是按照先進後出的順序執行defer


2.以下代碼有什麼問題,說明原因

package main

import "fmt"

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
        stu.Age = stu.Age + 10
    }

    for _, v := range m {
        fmt.Printf("%+v", v)
    }

}

func main() {
    pase_student()
}


/*
遇到寫進m的資料都是stus的最後一筆
&{Name:wang Age:32}&{Name:wang Age:32}&{Name:wang Age:32}
*/
// https://play.golang.org/p/_wGc12urMV1

與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實際上一致指向同一個指針, 最終該指針的值為遍歷的最後一個struct的值拷貝。就像想修改切片元素的屬性:

解答
for i:=0;i<len(stus);i++  {
    m[stus[i].Name] = &stus[i]
}
for k,v:=range m{
    println(k,"=>",v.Name)
}

3.下面的代碼會輸出什麼,並說明原因: go func中i是外部的一個變量,地址不變化,但是值都在改變

package main

import (
    "fmt"
    "runtime"
    "sync"
)

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()
}

https://play.golang.org/p/uTn9OnK0B5E

A:輸出完全隨機,因此goroutine執行時i的值是多少;\ 而B:一定輸出為0〜9,但順序不定。

第一個go func中i是外部的一個變量,地址不變化,但是值都在改變。

第二個go func中i是函數參數,與外部對於中的i完全是兩個變量。\ 尾部(i)將發生值拷貝,go func內部指向值拷貝地址。

所以在使用goroutine在處理閉包的時候,避免發生類似第一個go func中的問題。

4.下面代碼會輸出什麼? go的組合繼承

t.showA() -> showA showB 如果是 t.ShowB() -> teacher showB

package main

import (
    "fmt"
)

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
*/

https://play.golang.org/p/eSGvASIBn5K >

go的組合繼承 解答: 這是Golang的組合模式,可以實現OOP的繼承。被組合的類型People所包含的方法雖然升級成了外部類型Teacher這個組合類型的方法(一定要是匿名字段),但它們的方法(ShowA())調用時接受者並沒有發生變化。此時People類型並不知道自己會被什麼類型組合,當然也就無法調用方法時去使用未知的組合者Teacher類型的功能

5.下面代碼會觸發異常嗎?請詳細說明 : select隨機性

package main

import (
    "fmt"
    "runtime"
)

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:
        fmt.Println(value)
        panic(value)
    }

}

https://play.golang.org/p/MX7B99Rcsu5

解答
  • select隨機性 解答: select會隨機選擇一個可用通用做收發操作。所以代碼是有可能觸發異常,也有可能不會單個chan如果無緩衝時,將會阻塞。但結合 select可以在多個chan間等待執行。
  • 有三點原則:
    1. select 中只要有一個case能return,則立刻執行
    2. 當如果同一時間有多個case均能return則偽隨機方式抽取任意一個執行
    3. 如果沒有一個case能return則可以執行default

6.下面代碼輸出什麼?

package main

import (
    "fmt"
)

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
}

/*
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
*/

https://play.golang.org/p/HX-2hHy5kzB

calc("10",a=1,b=2) -> calc("20",a=0,b=2) -> calc("2",a=0,b=0+2) -> calc("1",a=1,b=1+2)

這道題類似第1題需要注意到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

7.請寫出以下輸入內容: make初始化是由默認值的哦,此處默認值為0

package main

import (
    "fmt"
)

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

https://play.golang.org/p/3-sPBVD2L3u

make初始化是由默認值的哦,此處默認值為0


8.下面的代碼有什麼問題? : map線程安全

要用 sync.Mapsync.RWMutex

package main

import (
    "fmt"
    "sync"
)

type UserAges struct {
    ages map[string]int
    sync.Mutex
}

func (ua *UserAges) Add(name string, age int) {
    ua.Lock()
    defer ua.Unlock()
    ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
    if age, ok := ua.ages[name]; ok {
        return age
    }
    return -1
}

func main() {
    t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
    t.Add("k10", 10)
    fmt.Println(t.Get("k10"))
    // fmt.Printf("%+v \n",t)
}

https://play.golang.org/p/Yfg68Q9FHO7

map線程安全 解答: 可能會出現fatal error: concurrent map read and map write. 修改一下看看效果

解答1 : 用sync.Map
package main

import (
    "fmt"
    "sync"
)

type UserAges struct {
    ages map[string]int
    sync.Map
}

func (ua *UserAges) Add(name string, age int) {
    ua.Store(name, age)
}

func (ua *UserAges) Get(name string) int {
    if age, ok := ua.Load(name); ok {
        return age.(int)
    }
    return -1
}

func main() {
    t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
    go func() {
        for {
            t.Add("k10", 10)
        }
    }()

    go func() {
        for {
            fmt.Println(t.Get("k10"))
        }
    }()

    for {
    }
}
解答2 : 用sync.RWMutex
package main

import (
    "fmt"
    "sync"
)

type UserAges struct {
    ages map[string]int
    sync.RWMutex
}

func (ua *UserAges) Add(name string, age int) {
    ua.Lock()
    defer ua.Unlock()
    ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
    ua.RLock()
    defer ua.RUnlock()
    if age, ok := ua.ages[name]; ok {
        return age
    }
    return -1
}

func main() {
    t := &UserAges{ages: map[string]int{"k": 1, "y": 2}}
    go func() {
        for {
            t.Add("k10", 10)
        }
    }()

    go func() {
        for {
            fmt.Println(t.Get("k10"))
        }
    }()

    for {
    }
}

9.下面的迭代會有什麼問題?

https://go.dev/play/p/LiCp_gt7dVZ

package main

import (
    "fmt"
    "sync"
)

type threadSafeSet struct {
    sync.RWMutex
    s []interface{}
}

func (set *threadSafeSet) Iter() <-chan interface{} {
    ch := make(chan interface{})
    go func() {
        set.RLock()

        for elem, value := range set.s {
            ch <- elem
            println("Iter:", elem, value)
        }

        close(ch)
        set.RUnlock()

    }()
    return ch
}

func main() {

    th := threadSafeSet{
        s: []interface{}{"1", "2"},
    }
    v := <-th.Iter()
    fmt.Printf("---%s%v", "ch", v)
}

// Iter: 0---ch0

chan緩存池 https://studygolang.com/articles/12512 解答: 看到這道題,我也在猜想出題者的意圖在哪裡。 chan?sync.RWMutex?go?chan緩存池?迭代? 所以只能再讀一次題目,就從迭代入手看看。既然是迭代就會要求set.s全部可以遍歷一次。但是chan是為緩存的,那就代表這寫入一次就會阻塞。我們把代碼恢復為可以運行的方式,看看效果

package main

import (
    "fmt"
    "sync"
)

type threadSafeSet struct {
    sync.RWMutex
    s []interface{}
}

func (set *threadSafeSet) Iter() <-chan interface{} {
    // ch := make(chan interface{}) // 解開註解看看
    ch := make(chan interface{}, len(set.s))
    go func() {
        set.RLock()

        for elem, value := range set.s {
            ch <- elem
            println("Iter:", elem, value)
        }

        close(ch)
        set.RUnlock()

    }()
    return ch
}

func main() {

    th := threadSafeSet{
        s: []interface{}{"1", "2"},
    }
    v := <-th.Iter()
    fmt.Printf("---%s%v", "ch", v)
}

// Iter: 0 (0x486fe0,0x4b2998)
// Iter: 1 (0x486fe0,0x4b29a8)
// ---ch0

10.以下代碼能編譯過去嗎?為什麼?

package main

import (
    "fmt"
)

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
    if think == "bitch" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var peo People = Stduent{} // 這邊出錯
    think := "bitch"
    fmt.Println(peo.Speak(think))
}

go:23:6: cannot use Stduent literal (type Stduent) as type People in assignment: Stduent does not implement People (Speak method has pointer receiver)

golang的方法集 解答: 編譯不通過!做錯了! ?說明你對golang的方法集還有一些疑問。一句話:golang的方法集僅僅影響接口實現和方法表達式轉化,與通過實例或者指針調用方法無關。

解法
  1. (t T) -> T
    func (stu *Student)Speak(think string)(talk string){}  // 這邊是Methods Receivers, (t *T)的話, Values 只能是 *T
    func main() {
         var p People = &Student{} // 這邊是Values
    }
    
  1. (t T) -> *T and T

     func (stu Student)Speak(think string)(talk string) // 這邊是Methods Receivers, (t T)的話, Values 可以是 T跟 *T
     func main() {
         var p People = Student{}  // 這邊是Values
         // or
         var p2 People = &Student{} // 這邊是Values
     }
    
  2. new http://www.jeepxie.net/article/227769.html

    var p People = new(Stduent)
    

Methods Receivers Value
(t T) T and *T
(t *T) *T
解法一: Methods Receivers: t T, Value: T

https://play.golang.org/p/8c\_4T6vElcB

package main

import "fmt"

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
    if think == "bitch" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var p People = &Stduent{}
    think := "bitch"
    fmt.Println(p.Speak(think))
}

解法二 : Methods Receivers: t T, Value: *T or T

https://go.dev/play/p/xRjg8K5Ph4E

package main

import "fmt"

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu Stduent) Speak(think string) (talk string) {
    if think == "bitch" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var p People = Stduent{}
    think := "bitch"
    fmt.Println(p.Speak(think))
}

解法三 : new(Student)

https://go.dev/play/p/dShDAGXkvEf

package main

import "fmt"

type People interface {
    Speak(string) string
}

type Stduent struct{}

func (stu Stduent) Speak(think string) (talk string) {
    if think == "bitch" {
        talk = "You are a good boy"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var p People = new(Stduent)
    think := "bitch"
    fmt.Println(p.Speak(think))
}

用法

package main

import (
    "fmt"
)

type Member interface {
    GetName() string
    GetAge() int
}

type Robot struct {
    name  string
    age   int
    power int
}

func (r *Robot) Work() {}

func (r *Robot) GetName() string {
    return r.name
}
func (r *Robot) GetAge() int {
    return r.age
}

type Car struct {
    name     string
    age      int
    odometer int
}

func (r *Car) Run() {
}

func (r *Car) GetName() string {
    return r.name
}
func (r *Car) GetAge() int {
    return r.age
}

func Cleaning(m Member) {
    fmt.Printf("Consumer Name:%s, Age:%d\n", m.GetName(), m.GetAge())
}

func main() {
    r := &Robot{
        name:  "GUMDAM",
        age:   1,
        power: 100,
    }
    c := &Car{
        name:     "BENZ",
        age:      2,
        odometer: 100,
    }
    Cleaning(c)
    Cleaning(r)
}
/*
Consumer Name:BENZ, Age:2
Consumer Name:GUMDAM, Age:1
*/

11.以下代碼打印出來什麼內容,說出為什麼

https://go.dev/play/p/2nBU-dMwW6b

package main

import (
    "fmt"
)

type People interface {
    Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
    var stu *Student
    return stu
}

func main() {
    if live() == nil {
        fmt.Println("AAAAAAA")
    } else {
        fmt.Println("BBBBBBB")
    }
}
// BBBBBBB

interface內部結構 解答: 很經典的題!這個考點是很多人忽略的interface內部結構。 go中的接口分為兩種一種是空的接口類似這樣:

Reference

© Kimi Tsai all right reserved.            Updated : 2023-07-12 09:04:53

results matching ""

    No results matching ""

    results matching ""

      No results matching ""