Golang面试题

本文绝大多数题目来源于网络,部分题目为原创。

slice相关

以下代码有什么问题,说明原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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变量为值拷贝,stu变量的地址未改变,即&stu未改变,遍历结束后stu指向stus中的最后一个元素。

使用reflect.TypeOf(str)打印出的类型为main.student,如果使用stu.Age += 10这样的语法是不会修改stus中的值的。

可修改为如下形式:

1
2
3
for i, _ := range stus {
m[stus[i].Name] = &stus[i]
}

有一个slice of object, 遍历slice修改name为指定的值

1
2
3
4
5
6
7
8
type foo struct {
name string
value string
}

func mutate(s []foo, name string) {
// TODO
}

意在考察range遍历的时候是值拷贝,以及slice的内部数据结构,slice的数据结构如下:

1
2
3
4
5
6
struct    Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};

执行append函数后会返回一个新的Slice对象,新的Slice对象跟旧Slice对象共用相同的数据存储,但是len的值并不相同。

该题目中,可以通过下面的方式来修改值:

1
2
3
4
5
6
7
8
9
// range方式
for i, _ := range s {
s[i].name = name
}

// for i形式
for i:=0; i<len(s); i++ {
s[i].name = name
}

从slice中找到一个元素匹配name,并将该元素的指针添加到一个新的slice中,返回新slice

1
2
3
4
func find(s []foo, name string) []*foo {
// TODO

}

仍旧是考察range是值拷贝的用法,此处使用for i 循环即可

1
2
3
4
5
6
7
8
9
10
11
func find(s []foo, name string) []*foo {
res := []*foo{}

for i := 0; i < len(s); i++ {
if s[i].name == name {
res = append(res, &(s[i]))
break
}
}
return res
}

下面输出什么内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func m(s []int) {
s[0] = -1
s = append(s, 4)
}

func main() {
s1 := []int{1, 2, 3}
m(s1)
s2 := make([]int, 3, 6)
m(s2)
s2 = append(s2, 7)
s3 := [3]int{1, 2, 3}
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
}

slice的函数传递为值拷贝方式,在函数m中对下标为0的元素的修改会直接修改原slice中的值,因为slice中的指针指向的地址是相同的。

append之后的slice虽然可能是在原数组上增加了元素,但原slice中的len字段并没有变化。

make([]int, 3, 6)虽然指定了slice的cap,但对于append没有影响,还是会在slice中最后一个元素的下一个位置增加新元素。

数组由于是值拷贝,对新数组的修改不会影响到原数组。

输出内容如下:

1
2
3
[-1 2 3]
[-1 0 0 7]
[1 2 3]

下面输出什么内容

该题目为我自己想出来的,非来自于互联网,意在考察对slice和append函数的理解。

1
2
3
4
5
6
7
8
9
func f() {
s1 := make([]int, 2, 8)
fmt.Println(s1)
s2 := append(s1, 4)
fmt.Println(s2)
s3 := append(s1, 5)
fmt.Println(s3)
fmt.Println(s2)
}

输出结果如下,在执行第二个append后,第一个append在内存中增加的元素4会被5覆盖掉。执行结果可以通过fmt.Println(s1, cap(s1), &s1[0])的形式将第一个元素的内存地址打印出来查看。

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

goroutine

以下代码输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
go func() {
fmt.Println(1)
}()
for {
}
fmt.Println(1)
}

不会有任何输出

下面输出的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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()
}

输出

1
2
showA
showB

有点出乎意料,可以举个反例,如果ShowA()方法会调用到Teacher类型的ShowB()方法,假设People和Teacher并不在同一个包中时,编译一定会出现错误。

Go中没有继承机制,只有组合机制。

下面代码会触发异常吗?请详细说明

1
2
3
4
5
6
7
8
9
10
11
12
13
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会随机选择。

以下代码能编译过去吗?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

type People interface {
Speak(string) string
}

type Student struct{}

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

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

不能编译过去,提示Stduent does not implement People (Speak method has pointer receiver),将Speak定义更改为func (stu Stduent) Speak(think string) (talk string)即可编译通过。

main的调用方式更改为如下也可以编译通过var peo People = new(Stduent)

func (stu *Stduent) Speak(think string) (talk string)*Student类型的方法,不是Stduent类型的方法。

下面输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"time"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
arr := [10000]int{}
for i:=0; i<len(arr); i++ {
arr[i] = i
}
for _, a := range arr {
go func() {
fmt.Println(a)
}()
}
for {
time.Sleep(time.Second)
}
}

一直输出9999.涉及到goroutine的切换时机,仅系统调用或者有函数调用的情况下才会切换goroutine,for循环情况下一直没有系统调用或函数切换发生,需要等到for循环结束后才会启动新的goroutine。

以下代码打印出来什么内容,说出为什么。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

byte与rune的关系

  • byte alias for uint8
  • rune alias for uint32,用来表示unicode
1
2
3
4
5
6
7
8
func main() {
// range遍历为rune类型,输出int32
for _, w:=range "123" {
fmt.Printf("%T", w)
}
// 取数组为byte类型,输出uint8
fmt.Printf("%T", "123"[0])
}

写出打印的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type People struct {
name string `json:"name"`
}

func main() {
js := `{
"name":"11"
}`
var p People
p.name = "123"
err := json.Unmarshal([]byte(js), &p)
if err != nil {
fmt.Println("err: ", err)
return
}
fmt.Println("people: ", p)
}

打印结果为people: {123}

下面函数有什么问题?

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

函数返回值命名 在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号; 此处函数第一个返回值有sum名称,第二个为命名,所以错误。

以下函数输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
println(DeferFunc4(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
}

func DeferFunc4(i int) (t int) {
t = 10
return 2
}

输出结果为: 4 1 3 2

return语句不是一个原子指令,分为两个阶段,执行return后面的表达式和返回表达式的结果。defer函数在返回表达式之前执行。

  1. 执行return后的表达式给返回值赋值
  2. 调用defer函数
  3. 空的return

DeferFunc1在第一步执行表达式后t=1,执行defer后t=4,返回值为4

DeferFunc2在第一步执行表达式后t=1,执行defer后t=4,返回值为第一步表达式的结果1

DeferFunc3在第一步表达式为t=2,执行defer后t=3,返回值为t=3

DeferFunc4在第一步执行表达式后t=2,返回值为t=2

是否可以编译通过?如果通过,输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
"fmt"
)

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

sn2 := struct {
age int
name string
}{age: 11, name: "qq"}

sn3 := struct {
name string
age int
}{age: 11, name: "qq"}

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

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


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

结构体比较 进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

还有一点需要注意的是结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice。 如果该结构属性都是可以比较的,那么就可以使用“==”进行比较操作。

是否可以编译通过?如果通过,输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

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

输出“non-empty interface”

交替打印数字和字母

使用两个 goroutine 交替打印序列,一个 goroutine 打印数字, 另外一个 goroutine 打印字母, 最终效果为: 12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"fmt"
"sync"
)


func main() {
number, letter := make(chan bool), make(chan bool)
wait := new(sync.WaitGroup)

go func () {
num := 1
for {
<-number
fmt.Printf("%d%d", num, num+1)
num += 2
letter <- true
if num > 28 {
break
}
}
wait.Done()
}()

go func () {
begin := 'A'
for {
<- letter
if begin < 'Z' {
fmt.Printf("%c%c", begin, begin+1)
begin+=2
number <- true
} else {
break
}
}

wait.Done()
}()

number <- true

wait.Add(2)

wait.Wait()
}

struct类型的方法调用

假设T类型的方法上接收器既有T类型的,又有T指针类型的,那么就不可以在不能寻址的T值上调用T接收器的方法。

请看代码,试问能正常编译通过吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
li := Lili{}
li.fmtPointer()
}

能正常编译通过,并输出”poniter”

请接着看以下的代码,试问能编译通过?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
Lili{}.fmtPointer()
}

不能编译通过。
“cannot call pointer method on Lili literal”
“cannot take the address of Lili literal”

其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是Lili,因此可以调用Lili的方法。

golang context包的用法

  1. goroutine之间的传值
  2. goroutine之间的控制

在单核cpu的情况下,下面输出什么内容?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"sync"
)

func main() {
wg := sync.WaitGroup{}
for _, i:=range []int{1, 2, 3, 4, 5} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
} ()
}
wg.Wait()
}

考察golang的runtime机制,goroutine的切换时机只有在有系统调用或者函数调用时才会发生,本例子中的for循环结束之前不会发生goroutine的切换,所以最终输出结果为5.

下面输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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))
}

编译不通过,仅*Student实现了People接口,更改为var peo People = &Student{}即可编译通过。

下面输出什么

1
2
3
4
5
6
7
8
9
10
package main

const cl = 100

var bl = 123

func main() {
println(&bl, bl)
println(&cl, cl)
}

编译失败,常量cl通常在预处理阶段会直接展开,无法取其地址。

以下代码是否存在问题,请解释你的判断和理由

1
2
3
4
5
6
7
8
import "sync"

func f(m sync.Mutex) {
m.Lock()
defer m.Unlock()

// Do something...
}

Mutex对象不能被值拷贝,后续传递需要使用指针的形式

以下代码输出是什么 解释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func    main()  {
case1()
case2()
}

func case1() {
s1 := make([]string, 1, 20)
s1[0] = "hello"
p1 := &s1[0]
s1 = append(s1, "world")
*p1 = "hello2"
fmt.Printf("value of p1 is %s, value of s1[0] is %s \n", *p1, s1[0])
}

func case2() {
s1 := make([]string)
s1[0] = "hello"
p1 := &s1[0]
s1 = append(s1, "world")
*p1 = "hello2"
fmt.Printf("value of p1 is %s, value of s1[0] is %s \n", *p1, s1[0])
}

本题意在考察string和slice的数据结构,string的数据结构如下:

http://research.swtch.com/godata2.png

case1的内存结构变化情况如下:

case2由于s1默认长度为0,直接使用s1[0]复制会出现panic错误。

ref