Golang精编问答题

因最近要面试,看到网上的一些面试题,大部分的排版都很糟糕,故从中选了一篇,抽取一些需要注意的,整理内容,做好排版,并亲自测试,原文是《Golang精编100题》,本文只收录了问答题。

1、写出下面代码输出内容。

package main

import (
	"fmt"
)

func defer_fun() {
	defer func() {fmt.Println("before print")}()
	defer func() {fmt.Println("printing")}()
	defer func() {fmt.Println("after print")}()
	panic("panic")
}

func main() {
	defer_fun()
}

考点:defer执行顺序
解答:
defer 是后进先出。
panic 需要等defer 结束后才会向上传递。 出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。

after print
printing
before print
panic: panic

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

package main

import (
	"fmt"
)

type student struct {
	Name	string
	Age 	int
}


func main() {
	m := make(map[string]*student)
	stus := []student{
		{"zhang3", 18},
		{"li4", 19},
		{"wang5", 20},
	}

	for _, stu := range stus {
		m[stu.Name] = &stu
	}

	for _, stu := range m {
		fmt.Println(stu.Name, stu.Age)
	}
}

考点:foreach
解答:
这样的写法初学者经常会遇到的,很危险! 与Java的foreach一样,都是使用副本的方式。所以m[stu.Name]=&stu实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个struct的值拷贝。
稍微修改下,即可正常运行

package main

import (
	"fmt"
)

type student struct {
	Name	string
	Age 	int
}


func main() {
	m := make(map[string]*student)
	stus := []student{
		{"zhang3", 18},
		{"li4", 19},
		{"wang5", 20},
	}

	for i, stu := range stus {
		m[stu.Name] = &stus[i]
	}

	for _, stu := range m {
		fmt.Println(stu.Name, stu.Age)
	}
}

3、下面的代码会输出什么,并说明原因

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

考点: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、下面代码会输出什么?

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 (p *Teacher)ShowB() {
	fmt.Println("Teacher showB")
}

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

考点:go的组合继承
解答:
这是Golang的组合模式,可以实现OOP的继承。 被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。 此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。

showA
showB

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

考点:select随机性
解答:
select会随机选择一个可用通用做收发操作。 所以代码是有可能触发异常,也有可能不会。 单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则:

select 中只要有一个case能return,则立刻执行。
当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
如果没有一个case能return则可以执行”default”块。

5、下面代码输出什么?

package main

import "fmt"

func calc(index, 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

10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

6、下面的迭代会有问题吗?

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缓冲池
解答:ch是无缓冲的,第一次写ch都会阻塞
给ch增加缓冲,即可正常运行

package main

import (
	"fmt"
	"sync"
)

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

func (set *threadSafeSet)Iter() <-chan interface{} {
	ch := make(chan interface{}, len(set.s))
	go func() {
		set.RLock()
		for elem, value := range set.s {
			ch <- elem
			fmt.Println("Iter:", elem, value)
		}
		close(ch)
		set.RUnlock()
	}()

	return ch
}

func main() {
	th := threadSafeSet{s:[]interface{}{"1", "2"},}

	v := <-th.Iter()
	ret := fmt.Sprintf("%s%v", "ch", v)
	fmt.Println(ret)
}

7、以下代码能够编译成功吗?为什么?

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的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。

var peo People = Stduent{}

改为

var peo People = &Stduent{}

即可编译

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

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

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

package main

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
稍微修改接口即可:

func GetValue() interface{} {
	return 1
}

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

package main

import "fmt"

func main() {
	fmt.Println(DeferFunc1(1))
	fmt.Println(DeferFunc2(1))
	fmt.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需要在函数结束前(return之后)执行。 函数返回值变量会在函数起始处被初始化为对应类型的零值并且作用域为整个函数 。
DeferFunc1函数return之后,defer依然可以修改t,返回4;
DeferFunc2函数t是局部变量,defer执行之前已经返回1(因为局部变量要释放,返回值入栈压的是值?);
DeferFunc3函数return之后,把2赋值给t,defer再加1,返回3

可由以下代码验证:

package main

import "fmt"

func main() {
	fmt.Println(DeferFunc1(1))
	fmt.Println(DeferFunc2(1))
	fmt.Println(DeferFunc3(1))
}

func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		t += 3
		fmt.Println("DeferFunc1 defer, t =  ", t)
	}()

	fmt.Println("DeferFunc1")
	return t
}

func DeferFunc2(i int) int {
	t := i
	defer func() {
		t += 3
		fmt.Println("DeferFunc2 defer, t =  ", t)
	}()

	fmt.Println("DeferFunc2")
	return t
}

func DeferFunc3(i int)(t int) {
	defer func() {
		fmt.Println("Before DeferFunc3 defer, t =  ", t)
		t += i
		fmt.Println("DeferFunc3 defer, t =  ", t)
	}()

	fmt.Println("DeferFunc3")
	return 2
}

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

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

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

考点:结构体比较
原因:sm1和sm2结构体中的map不能比较
解答:进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

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

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

可以使用reflect.DeepEqual进行比较

	if reflect.DeepEqual(sn1, sn2) {
		fmt.Println("reflect sn1 == sn2")
	} else {
		fmt.Println("reflect sn1 != sn2")
	}

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

package main

import "fmt"

func getValue(m map[int]string, id int) (string, bool) {
	if _, exist := m[id]; exist {
		return "data is existed", true
	}

	return nil, false
}

func main() {
	m := map[int]string{
		1:	"a",
		2:	"b",
		3:	"c",
	}

	str, err := getValue(m, 3)
	fmt.Println(str, err)
}

考点:函数返回值类型
解答:nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。

cannot use nil as type string in return argument

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

package main

const cl = 100
var bl = 123

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

考点:常量
解答:常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用
编译失败信息:

cannot take the address of cl

14、编译执行下面代码会出现什么?

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,每一次放入匿名函数的应用都是同一个变量。 结果:

0xc4200140c8 2
0xc4200140c8 2

如果想不一样可以改为:

package main

func test() []func() {
	var funs []func()
	for i := 0; i < 2; i++ {
		x := i
		funs = append(funs, func() {
			println(&x, x)
		})
	}

	return funs
}

15、编译执行下面代码会出现什么?

package main

func test(x int) (func(), func()) {
	return func() {
		println(x)
		x += 100
	}, func() {
		println(x)
	}
}

func main() {
	a, b := test(100)
	a()
	b()
}

考点:闭包引用相同变量
结果:
100
200

16、编译执行下面代码会出现什么?

package main

import (
	"fmt"
	"reflect"
)

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println("fatal")
		}
	}()

	defer func() {
		panic("defer panic")
	}()

	panic("panic")
}

考点:panic仅有最后一个可以被revover捕获
触发panic(“panic”)后顺序执行defer,但是defer中还有一个panic,所以覆盖了之前的panic(“panic”)

没有更多推荐了,返回首页