最近在很多地方看到了golang的面試題,看到了很多人對Golang的面試題心存恐懼,也是為了復習基礎,我把解題的過程總結下來。
面試題
1. 寫出下面代碼輸出內容。
~~~
package main
import (
"fmt"
)
func main() {
defer_call()
}
func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()
panic("觸發異常")
}
~~~
考點:defer執行順序
解答:
defer 是后進先出。
panic 需要等defer 結束后才會向上傳遞。 出現panic恐慌時候,會先按照defer的后入先出的順序執行,最后才會執行panic。
打印后
打印中
打印前
panic: 觸發異常
2. 以下代碼有什么問題,說明原因。
~~~
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
}
}
~~~
考點:foreach
解答:
這樣的寫法初學者經常會遇到的,很危險! 與Java的foreach一樣,都是使用副本的方式。所以m[stu.Name]=&stu實際上一致指向同一個指針, 最終該指針的值為遍歷的最后一個struct的值拷貝。 就像想修改切片元素的屬性:
~~~
for _, stu := range stus {
stu.Age = stu.Age+10
}
~~~
也是不可行的。 大家可以試試打印出來:
~~~
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 k,v:=range m{
println(k,"=>",v.Name)
}
// 正確
for i:=0;i<len(stus);i++ {
m[stus[i].Name] = &stus[i]
}
for k,v:=range m{
println(k,"=>",v.Name)
}
}
~~~
3. 下面的代碼會輸出什么,并說明原因
~~~
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內部指向值拷貝地址。
4. 下面代碼會輸出什么?
~~~
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()
}
~~~
考點:go的組合繼承
解答:
這是Golang的組合模式,可以實現OOP的繼承。 被組合的類型People所包含的方法雖然升級成了外部類型Teacher這個組合類型的方法(一定要是匿名字段),但它們的方法(ShowA())調用時接受者并沒有發生變化。 此時People類型并不知道自己會被什么類型組合,當然也就無法調用方法時去使用未知的組合者Teacher類型的功能。
~~~
showA
showB
~~~
5. 下面代碼會觸發異常嗎?請詳細說明
~~~
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隨機性
解答:
select會隨機選擇一個可用通用做收發操作。 所以代碼是有肯觸發異常,也有可能不會。 單個chan如果無緩沖時,將會阻塞。但結合 select可以在多個chan間等待執行。有三點原則:
select 中只要有一個case能return,則立刻執行。
當如果同一時間有多個case均能return則偽隨機方式抽取任意一個執行。
如果沒有一個case能return則可以執行”default”塊。
6. 下面代碼輸出什么?
~~~
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執行順序
解答:
這道題類似第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
~~~
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4
~~~
7. 請寫出以下輸入內容
~~~
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
~~~
考點:make默認值和append
解答:
make初始化是由默認值的哦,此處默認值為0
[0 0 0 0 0 1 2 3]
大家試試改為:
~~~
s := make([]int, 0)
s = append(s, 1, 2, 3)
fmt.Println(s)//[1 2 3]
~~~
8. 下面的代碼有什么問題?
~~~
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
}
~~~
考點:map線程安全
解答:
可能會出現fatal error: concurrent map read and map write. 修改一下看看效果
~~~
func (ua *UserAges) Get(name string) int {
ua.Lock()
defer ua.Unlock()
if age, ok := ua.ages[name]; ok {
return age
}
return -1
}
9. 下面的迭代會有什么問題?
func (set *threadSafeSet) Iter() <-chan interface{} {
ch := make(chan interface{})
go func() {
set.RLock()
for elem := range set.s {
ch <- elem
}
close(ch)
set.RUnlock()
}()
return ch
}
~~~
考點:chan緩存池
解答:
看到這道題,我也在猜想出題者的意圖在哪里。 chan?sync.RWMutex?go?chan緩存池?迭代? 所以只能再讀一次題目,就從迭代入手看看。 既然是迭代就會要求set.s全部可以遍歷一次。但是chan是為緩存的,那就代表這寫入一次就會阻塞。 我們把代碼恢復為可以運行的方式,看看效果
~~~
package main
import (
"sync"
"fmt"
)
//下面的迭代會有什么問題?
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.Sprintf("%s%v","ch",v)
}
~~~
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))
}
~~~
考點:golang的方法集
解答:
編譯不通過! 做錯了!?說明你對golang的方法集還有一些疑問。 一句話:golang的方法集僅僅影響接口實現和方法表達式轉化,與通過實例或者指針調用方法無關。
11. 以下代碼打印出來什么內容,說出為什么。
~~~
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")
}
}
~~~
考點:interface內部結構
解答:
很經典的題! 這個考點是很多人忽略的interface內部結構。 go中的接口分為兩種一種是空的接口類似這樣:
`var in interface{}`
另一種如題目:
~~~
type People interface {
Show()
}
~~~
他們的底層結構如下:
~~~
type eface struct { //空接口
_type *_type //類型信息
data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)
}
type iface struct { //帶有方法的接口
tab *itab //存儲type信息還有結構實現方法的集合
data unsafe.Pointer //指向數據的指針(go語言中特殊的指針類型unsafe.Pointer類似于c語言中的void*)
}
type _type struct {
size uintptr //類型大小
ptrdata uintptr //前綴持有所有指針的內存大小
hash uint32 //數據hash值
tflag tflag
align uint8 //對齊
fieldalign uint8 //嵌入結構體時的對齊
kind uint8 //kind 有些枚舉值kind等于0是無效的
alg *typeAlg //函數指針數組,類型實現的所有方法
gcdata *byte
str nameOff
ptrToThis typeOff
}
type itab struct {
inter *interfacetype //接口類型
_type *_type //結構類型
link *itab
bad int32
inhash int32
fun [1]uintptr //可變大小 方法集合
}
~~~
可以看出iface比eface 中間多了一層itab結構。 itab 存儲_type信息和[]fun方法集,從上面的結構我們就可得出,因為data指向了nil 并不代表interface 是nil, 所以返回值并不為空,這里的fun(方法集)定義了接口的接收規則,在編譯的過程中需要驗證是否實現接口 結果:
BBBBBBB
12.是否可以編譯通過?如果通過,輸出什么?
~~~
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
func GetValue() int {
return 1
}
~~~
解析
考點:type
編譯失敗,因為type只能使用在interface
13.下面函數有什么問題?
~~~
func funcMui(x,y int)(sum int,error){
return x+y,nil
}
~~~
解析
考點:函數返回值命名
在函數有多個返回值時,只要有一個返回值有指定命名,其他的也必須有命名。 如果返回值有有多個返回值必須加上括號; 如果只有一個返回值并且有命名也需要加上括號; 此處函數第一個返回值有sum名稱,第二個未命名,所以錯誤。
14.是否可以編譯通過?如果通過,輸出什么?
~~~
package main
func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
}
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
~~~
解析
考點:defer和函數返回值
需要明確一點是defer需要在函數結束前執行。 函數返回值名字會在函數起始處被初始化為對應類型的零值并且作用域為整個函數 DeferFunc1有函數返回值t作用域為整個函數,在return之前defer會被執行,所以t會被修改,返回4; DeferFunc2函數中t的作用域為函數,返回1; DeferFunc3返回3
15.是否可以編譯通過?如果通過,輸出什么?
~~~
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
~~~
解析
考點:new
`list:=make([]int,0)`
16.是否可以編譯通過?如果通過,輸出什么?
~~~
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
~~~
解析
考點:append
append切片時候別漏了'...'
17.是否可以編譯通過?如果通過,輸出什么?
~~~
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")
}
}
~~~
解析
考點:結構體比較
進行結構體比較時候,只有相同類型的結構體才可以比較,結構體是否相同不但與屬性類型個數有關,還與屬性順序相關。
~~~
sn3:= struct {
name string
age int
}{age:11,name:"qq"}
~~~
sn3與sn1就不是相同的結構體了,不能比較。 還有一點需要注意的是結構體是相同的,但是結構體屬性中有不可以比較的類型,如map,slice。 如果該結構屬性都是可以比較的,那么就可以使用“==”進行比較操作。
可以使用reflect.DeepEqual進行比較
~~~
if reflect.DeepEqual(sn1, sm) {
fmt.Println("sn1 ==sm")
}else {
fmt.Println("sn1 !=sm")
}
~~~
所以編譯不通過: `invalid operation: sm1 == sm2`
18.是否可以編譯通過?如果通過,輸出什么?
~~~
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
~~~
解析
考點:interface內部結構
`non-empty interface`
19.是否可以編譯通過?如果通過,輸出什么?
~~~
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "存在數據", true
}
return nil, false
}
func main() {
intmap:=map[int]string{
1:"a",
2:"bb",
3:"ccc",
}
v,err:=GetValue(intmap,3)
fmt.Println(v,err)
}
~~~
解析
考點:函數返回值類型
nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特別指定的話,Go 語言不能識別類型,所以會報錯。報:cannot use nil as type string in return argument.
20.是否可以編譯通過?如果通過,輸出什么?
~~~
const (
x = iota
y
z = "zz"
k
p = iota
)
func main() {
fmt.Println(x,y,z,k,p)
}
~~~
解析
考點:iota
結果:
0 1 zz zz 4
21.編譯執行下面代碼會出現什么?
~~~
package main
var(
size :=1024
max_size = size*2
)
func main() {
println(size,max_size)
}
~~~
解析
考點:變量簡短模式
變量簡短模式限制:
定義變量同時顯式初始化
不能提供數據類型
只能在函數內部使用
結果:
syntax error: unexpected :=
22.下面函數有什么問題?
~~~
package main
const cl = 100
var bl = 123
func main() {
println(&bl,bl)
println(&cl,cl)
}
~~~
解析
考點:常量
常量不同于變量的在運行期分配內存,常量通常會被編譯器在預處理階段直接展開,作為指令數據使用,
`cannot take the address of cl`
23.編譯執行下面代碼會出現什么?
~~~
package main
func main() {
for i:=0;i<10 ;i++ {
loop:
println(i)
}
goto loop
}
~~~
解析
考點:goto
goto不能跳轉到其他函數或者內層代碼
`goto loop jumps into block starting at`
24.編譯執行下面代碼會出現什么?
~~~
package main
import "fmt"
func main() {
type MyInt1 int
type MyInt2 = int
var i int =9
var i1 MyInt1 = i
var i2 MyInt2 = i
fmt.Println(i1,i2)
}
~~~
解析
考點:**Go 1.9 新特性 Type Alias **
基于一個類型創建一個新類型,稱之為defintion;基于一個類型創建一個別名,稱之為alias。 MyInt1為稱之為defintion,雖然底層類型為int類型,但是不能直接賦值,需要強轉; MyInt2稱之為alias,可以直接賦值。
結果:
`cannot use i (type int) as type MyInt1 in assignment`
25.編譯執行下面代碼會出現什么?
~~~
package main
import "fmt"
type User struct {
}
type MyUser1 User
type MyUser2 = User
func (i MyUser1) m1(){
fmt.Println("MyUser1.m1")
}
func (i User) m2(){
fmt.Println("User.m2")
}
func main() {
var i1 MyUser1
var i2 MyUser2
i1.m1()
i2.m2()
}
~~~
解析
考點:**Go 1.9 新特性 Type Alias **
因為MyUser2完全等價于User,所以具有其所有的方法,并且其中一個新增了方法,另外一個也會有。 但是
`i1.m2()`
是不能執行的,因為MyUser1沒有定義該方法。 結果:
~~~
MyUser1.m1
User.m2
~~~
26.編譯執行下面代碼會出現什么?
~~~
package main
import "fmt"
type T1 struct {
}
func (t T1) m1(){
fmt.Println("T1.m1")
}
type T2 = T1
type MyStruct struct {
T1
T2
}
func main() {
my:=MyStruct{}
my.m1()
}
~~~
解析
考點:**Go 1.9 新特性 Type Alias **
是不能正常編譯的,異常:
`ambiguous selector my.m1`
結果不限于方法,字段也也一樣;也不限于type alias,type defintion也是一樣的,只要有重復的方法、字段,就會有這種提示,因為不知道該選擇哪個。 改為:
~~~
my.T1.m1()
my.T2.m1()
~~~
type alias的定義,本質上是一樣的類型,只是起了一個別名,源類型怎么用,別名類型也怎么用,保留源類型的所有方法、字段等。
27.編譯執行下面代碼會出現什么?
~~~
package main
import (
"errors"
"fmt"
)
var ErrDidNotWork = errors.New("did not work")
func DoTheThing(reallyDoIt bool) (err error) {
if reallyDoIt {
result, err := tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
func tryTheThing() (string,error) {
return "",ErrDidNotWork
}
func main() {
fmt.Println(DoTheThing(true))
fmt.Println(DoTheThing(false))
}
~~~
解析
考點:變量作用域
因為 if 語句塊內的 err 變量會遮罩函數作用域內的 err 變量,結果:
<nil>
<nil>
改為:
~~~
func DoTheThing(reallyDoIt bool) (err error) {
var result string
if reallyDoIt {
result, err = tryTheThing()
if err != nil || result != "it worked" {
err = ErrDidNotWork
}
}
return err
}
28.編譯執行下面代碼會出現什么?
package main
func test() []func() {
var funs []func()
for i:=0;i<2 ;i++ {
funs = append(funs, func() {
println(&i,i)
})
}
return funs
}
func main(){
funs:=test()
for _,f:=range funs{
f()
}
}
~~~
解析
考點:閉包延遲求值
for循環復用局部變量i,每一次放入匿名函數的應用都是想一個變量。 結果:
0xc042046000 2
0xc042046000 2
如果想不一樣可以改為:
~~~
func test() []func() {
var funs []func()
for i:=0;i<2 ;i++ {
x:=i
funs = append(funs, func() {
println(&x,x)
})
}
return funs
}
29.編譯執行下面代碼會出現什么?
package main
func test(x int) (func(),func()) {
return func() {
println(x)
x+=10
}, func() {
println(x)
}
}
func main() {
a,b:=test(100)
a()
b()
}
~~~
解析
考點:閉包引用相同變量*
結果:
100
110
30.編譯執行下面代碼會出現什么?
~~~
package main
import (
"fmt"
"reflect"
)
func main1() {
defer func() {
if err:=recover();err!=nil{
fmt.Println(err)
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic("defer panic")
}()
panic("panic")
}
func main() {
defer func() {
if err:=recover();err!=nil{
fmt.Println("++++")
f:=err.(func()string)
fmt.Println(err,f(),reflect.TypeOf(err).Kind().String())
}else {
fmt.Println("fatal")
}
}()
defer func() {
panic(func() string {
return "defer panic"
})
}()
panic("panic")
}
~~~
解析
考點:panic僅有最后一個可以被revover捕獲
觸發panic("panic")后順序執行defer,但是defer中還有一個panic,所以覆蓋了之前的panic("panic")
defer panic

- Go語言基礎篇
- Go語言簡介
- Go語言教程
- Go語言環境安裝
- Go語言結構
- Go語言基礎語法
- Go語言數據類型
- Go語言變量
- Go語言提高篇
- Go語言實現貪吃蛇
- Go 諺語
- 解決連通性問題的四種算法
- golang 幾種字符串的連接方式
- Go JSON 技巧
- Go += 包版本
- Golang 編譯成 DLL 文件
- Go指南:牛頓法開方
- Go語言異步服務器框架原理和實現
- Golang適合高并發場景的原因分析
- 如何設計并實現一個線程安全的 Map ?(上篇)
- go語言執行cmd命令關機、重啟等
- IT雜項
- IT 工程師的自我管理
- IT界不為人知的14個狗血故事
- Go語言版本說明
- Go 1.10中值得關注的幾個變化
- Golang面試題解析
- Golang面試題
- Golang語言web開發
- golang 模板(template)的常用基本語法
- go語言快速入門:template模板
- Go Template學習筆記
- LollipopGo框架
- 框架簡介
- Golang語言版本設計模式
- 設計模式-單例模式
- Golang語言資源下載
- 公眾賬號
- leaf
- 合作講師
- 公開課目錄