Go面试:面试题笔记整理(一)

 对《Golang精编100题-搞定golang面试》里面的部分题目做一些笔记备查。


33. 【中级】 golang中的引用类型包括()
A. 数组切片
B. map
C. channel
D. interface

参考答案:ABCD
这道题已经过时了,在2013年4月3日的github提交中已经明确说了“Go has no 'reference types'”。而slice源码的说明也由“引用”改成了“描述符”。Go只有值传递,没有所谓的引用传递。上题应该改为哪些类型包含有指针。

Go面试:面试题笔记整理(一)_第1张图片


34. 【中级】 golang中的指针运算包括()
A. 可以对指针进行自增或自减运算
B. 可以通过“&”取指针的地址
C. 可以通过“*”取指针指向的数据
D. 可以对指针进行下标运算

参考答案:BC

D选项应该也是对的,在Go13.8里指向数组的指针的下标运算是可以支持的:


package main
 
import "fmt"
 
func main() {
	var a = [3]int{998, 999, 1000}
	var ip *[3]int = &a
 
	for i := 0; i < len(a); i++ {
		fmt.Printf("ip[%d]=%d\n", i, ip[i])
	}
}

输出:

ip[0]=998
ip[1]=999
ip[2]=1000

 


36. 【中级】下面赋值正确的是()
A. var x = nil
B. var x interface{} = nil
C. var x string = nil
D. var x error = nil

参考答案:BD

可以赋值成nil的变量类型有7种:1)任意类型的指针变量,2)函数变量,3)接口,4)error,5)map, 6)切片 7)通道。这些变量默认值就是nil,实际上都不用赋值成nil。

package main

import "fmt"

func main() {
	var ptrI *int
	var f func()
	var iface interface{}
	var err error
	var m map[string]string
	var sl []string
	var ch chan int

	if ptrI == nil {fmt.Printf("1) pointer is nil\n")}
	if f == nil {fmt.Printf("2) func is nil\n")}
	if iface == nil {fmt.Printf("3) interface is nil\n")}
	if err == nil {fmt.Printf("4) error is nil\n")}
	if m == nil {fmt.Printf("5) map is nil\n")}
	if sl == nil {fmt.Printf("6) slice is nil\n")}
	if ch == nil {fmt.Printf("7) ch is nil\n")}
}

输出: 

1) pointer is nil
2) func is nil
3) interface is nil
4) error is nil
5) map is nil
6) slice is nil
7) ch is nil

 不能赋值成nil的变量类型,会在编译时报错:

package main

import "fmt"

type Person struct {
	name string
}

func main() {
	var str string = nil
	var person Person = nil
	var i int = nil
	var b bool = nil
	var x = nil

	fmt.Printf("str=%v person=%v i=%v b=%v x=%v", str, person, i, b, x)
}

 编译报错:

# command-line-arguments
./tryinit.go:10:6: cannot use nil as type string in assignment
./tryinit.go:11:6: cannot use nil as type Person in assignment
./tryinit.go:12:6: cannot use nil as type int in assignment
./tryinit.go:13:6: use of untyped nil

38. 【中级】从切片中删除一个元素,下面的算法实现正确的是()
A.
func (s *Slice)Remove(value interface{})error {
    for i, v := range *s {
       if isEqual(value, v) {
           if i== len(*s) - 1 {
               *s = (*s)[:i]
           }else {
               *s = append((*s)[:i],(*s)[i + 2:]...)
           }
           return nil
       }
    }
    return ERR_ELEM_NT_EXIST
}

B.
func (s*Slice)Remove(value interface{}) error {
    for i, v:= range *s {
        if isEqual(value, v) {
            *s =append((*s)[:i],(*s)[i + 1:])
            return nil
        }
    }
    returnERR_ELEM_NT_EXIST
}

C.
func (s*Slice)Remove(value interface{}) error {
     for i, v:= range *s {
         if isEqual(value, v) {
             delete(*s, v)
             return nil
         }
     }
     returnERR_ELEM_NT_EXIST
}

D.
func (s*Slice)Remove(value interface{}) error {
    for i, v:= range *s {
        if isEqual(value, v) {
            *s =append((*s)[:i],(*s)[i + 1:]...)
            return nil
        }
    }
    returnERR_ELEM_NT_EXIST
}

参考答案:D
A,切片截取是前闭后开;B,append追加另一个切片时需要加"...";C,delete是map的删除方式


51. 【初级】对于局部变量整型切片x的赋值,下面定义正确的是()

A. x := []int{
                 1, 2, 3,
                 4, 5, 6,
                 }
B. x :=[]int{
           1, 2, 3,
           4, 5, 6
          }
C. x :=[]int{
                1, 2, 3,
                4, 5, 6}
D. x :=[]int{1, 2, 3, 4, 5, 6,}

参考答案:ACD

切片赋值时,最后一个元素后面如果有逗号则“}”可以换行或者不换行,否则”}“必须和最后一个元素保持在同一行。


55. 【初级】关于变量的自增和自减操作,下面语句正确的是()

A. i := 1;i++
B. i := 1;j = i++
C. i := 1;++i
D. i := 1;i--

参考答案:AD

Go目前只支持后置自增自减运算符,而且不能用于赋值或者作为参数传递给函数。

package main

import "fmt"

func main() {
	i:=1;i--
	j := i++  //错误
	if i++ > 0 { //错误
		fmt.Printf("i=%v\n", i++) //错误
	}
	++i //错误
	fmt.Printf("i=%v j=%v\n", i, j)
}

编译报错:

# command-line-arguments
./example.go:7:8: syntax error: unexpected ++ at end of statement
./example.go:8:9: syntax error: unexpected >, expecting {
./example.go:9:25: syntax error: unexpected ++, expecting comma or )
./example.go:11:2: syntax error: unexpected ++, expecting }

57. 【中级】关于函数声明,下面语法错误的是()
A. func f(a, b int) (value int, err error)
B. func f(a int, b int) (value int, err error)
C. func f(a, b int) (value int, error)
D. func f(a int, b int) (int, int, error)

参考答案:C
Go语言的函数返回值,要么全是非命名的返回值,要么全是命名的返回值,不能即有非命名的又有命名的返回值,否则编译会报错:

syntax error: mixed named and unnamed function parameters

58. 【中级】如果Add函数的调用代码为:

func main() {
var a Integer = 1
var b Integer = 2
var i interface{} = &a
sum := i.(*Integer).Add(b)
fmt.Println(sum)
}
则Add函数定义正确的是()
A.
type Integer int
func (a Integer) Add(b Integer) Integer {
 return a + b
}
B
type Integer int
func (a Integer) Add(b *Integer) Integer {
 return a + *b
}
C
type Integer int
func (a *Integer) Add(b Integer) Integer {
 return *a + b
}
D
type Integer int
func (a *Integer) Add(b *Integer) Integer {
 return *a + *b
}
参考答案:AC
对于方法的参数而言,指针类型的参数不能接受值类型的参数,反之亦然,所以BD是错的。而对于结构体的方法而言,不论定义成值方法或者指针方法,都可以同时支持用指针或者非指针实例来调用。(可能是在编译期完成的转换,后续还需要确认)有两种特殊情况只能定义成值方法:

一是接口断言成非指针实例,例如把main函数改为:

func main() {
	var a Integer = 1
	var b Integer = 2
	var c interface{} = a
	sum := c.(Integer).Add(b)
	fmt.Println(sum)
}

则答案是 A
运行C则编译报错:

./example.go:9:20: cannot call pointer method on c.(Integer)
./example.go:9:20: cannot take the address of c.(Integer)

二是类型强制转换,例如把main函数改为:

func main() {
	var a int = 1
	var b Integer = 2
	sum := Integer(a).Add(b)
	fmt.Println(sum)
}

则答案是 A
运行C则同样是编译报错:

./example.go:8:19: cannot call pointer method on Integer(a)
./example.go:8:19: cannot take the address of Integer(a)

72. 【中级】关于GetPodAction定义,下面赋值正确的是()
type Fragment interface {
    Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
    return nil
}
A. var fragment Fragment =new(GetPodAction)
B. var fragment Fragment = GetPodAction
C. var fragment Fragment = &GetPodAction{}
D. var fragment Fragment = GetPodAction{}
参考答案:ACD
Fragment是接口所以赋值成指针或者非指针,AC是指针,D是非指针。B编译报错“type GetPodAction is not an expression”
结构体创建实例有两种等价的形式:
1) var g GetPodAction; 2) g :=  GetPodAction{}


82. 【中级】关于接口,下面说法正确的是()
A. 只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等价的,可以相互赋值
B. 如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A
C. 接口查询是否成功,要在运行期才能够确定
D. 接口赋值是否可行,要在运行期才能够确定

参考答案:ABC
接口赋值时编译期就可以报错。
验证代码:

package main

import "fmt"

type A interface {
	f1()
	f2()
	f3()
}

type B interface {
	f2()
	f1()
}

type AImpl struct {
}

func (a AImpl) f1() {
	fmt.Printf("call AImpl f1()\n")
}
func (a AImpl) f2() {
	fmt.Printf("call AImpl f2()\n")
}
func (a AImpl) f3() {
	fmt.Printf("call AImpl f3()\n")
}

func main() {
	var a A = AImpl{}
	var b B = a
	b.f1()

	//接口查询,判断b是否实现了接口A
	if c, ok := b.(A); ok {
		c.f3()
	}
}

输出:

call AImpl f1()
call AImpl f3()

83. 【初级】关于channel,下面语法正确的是()
A. var ch chan int
B. ch := make(chan int)
C. <- ch
D. ch <-

参考答案:ABC


85. 【中级】 golang中大多数数据类型都可以转化为有效的JSON文本,下面几种类型除外()
A. 指针
B. channel
C. complex
D. 函数

参考答案:BCD


87. 【初级】 flag是bool型变量,下面if表达式符合编码规范的是()
A. if flag == 1
B. if flag
C. if flag == false
D. if !flag

参考答案:BD
这道题C也是符合规范的,Go的源代码有很多这样的写法,例如:runtime/pprof/internal/profile:

func encodeBoolOpt(b *buffer, tag int, x bool) {
	if x == false {
		return
	}
	encodeBool(b, tag, x)
}

88. 【初级】 value是整型变量,下面if表达式符合编码规范的是()
A. if value == 0
B. if value
C. if value != 0
D. if !value

参考答案:AC
BD两项只能是bool变量,否则编译报错:“non-bool value (type int) used as if condition”,“invalid operation: ! int”


91. 【中级】关于slice或map操作,下面正确的是()
A.
var s []int
s = append(s,1)
B.
var m map[string]int
m["one"] = 1
C.
var s []int
s = make([]int, 0)
s = append(s,1)

D.
var m map[string]int
m = make(map[string]int)
m["one"] = 1

参考答案:ACD
slice和map在插入数据之前都需要分配内存,而append函数内置有内存分配操作,所以在调用append之前可以不用make切片。
 


93. 【中级】关于channel的特性,下面说法正确的是()
A. 给一个 nil channel 发送数据,造成永远阻塞
B. 从一个 nil channel 接收数据,造成永远阻塞
C. 给一个已经关闭的 channel 发送数据,引起 panic
D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

参考答案:ABCD
这道题AB两个选项应该是适用于Go的早期版本,在Go1.13.8里面,AB直接报fatal error然后退出。C,D是正确的。
下面是一段能正常运行的代码:

func main() {
	ch := make(chan bool)
	go func(ch chan bool) {
		time.Sleep(1 * time.Second)
		b := <-ch
		fmt.Printf("b=%v\n", b)
	}(ch)
	ch <- true
	fmt.Printf("exit\n")
}
b=true
exit

按照AB说法,把"ch := make(chan bool)"改成:

func main() {
	var ch chan bool
	go func(ch chan bool) {
		time.Sleep(1 * time.Second)
		b := <-ch
		fmt.Printf("b=%v\n", b)
	}(ch)
	ch <- true
	fmt.Printf("exit\n")
}

报错退出:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send (nil chan)]:
main.main()
        /home/zhoupeng/go-test/src/tutorial/example.go:15 +0x5b

goroutine 6 [chan receive (nil chan)]:
main.main.func1(0x0)
        /home/zhoupeng/go-test/src/tutorial/example.go:12 +0x4b
created by main.main
        /home/zhoupeng/go-test/src/tutorial/example.go:10 +0x42
exit status 2

验证C,在“ch <- true”之前加入“close(ch)”,则报错:“panic: send on closed channel”

func main() {
	ch := make(chan bool)
	go func(ch chan bool) {
		time.Sleep(1 * time.Second)
		b := <-ch
		fmt.Printf("b=%v\n", b)
	}(ch)
	close(ch)
	ch <- true
	fmt.Printf("exit\n")
}

验证D,通道写入true之后关闭通道,等待3秒,协程等待1秒后连读2次通道,第一次读到缓冲区里的“true”,第二次缓冲区为空则读取到布尔变量的零值false。注,数值型变量的零值是0,字符串的零值是“”。

func main() {
	ch := make(chan bool, 1)
	go func(ch chan bool) {
		time.Sleep(1 * time.Second)
		b1 := <-ch
		fmt.Printf("b1=%v\n", b1)
		b2 := <-ch
		fmt.Printf("b2=%v\n", b2)
	}(ch)
	ch <- true
	close(ch)
	time.Sleep(3 * time.Second)
	fmt.Printf("exit\n")
}
b1=true
b2=false
exit

94. 【中级】关于无缓冲和有冲突的channel,下面说法正确的是()
A. 无缓冲的channel是默认的缓冲为1的channel
B. 无缓冲的channel和有缓冲的channel都是同步的
C. 无缓冲的channel和有缓冲的channel都是非同步的
D. 无缓冲的channel是同步的,而有缓冲的channel是非同步的

参考答案:D
无缓冲的channel是默认的缓冲为0的channel,对应代码如“make(chan int, 0)”,0是默认值可以省略。有缓冲的channel是非同步的指的是发送/接收操作只在缓冲区满了才阻塞,否则继续执行后面的代码。
缓存为1的时序验证:

func main() {
	ch := make(chan int, 1)
	go func(ch chan int) {
		time.Sleep(2 * time.Second)
		i1 := <-ch
		log.Printf("i1=%v\n", i1)
		time.Sleep(2 * time.Second)
		i2 := <-ch
		log.Printf("i2=%v\n", i2)
	}(ch)
	log.Printf("send 1\n")
	ch <- 1
	log.Printf("send 2\n")
	ch <- 2
	log.Printf("main sleep\n")
	time.Sleep(3 * time.Second)
	log.Printf("exit\n")
}
2020/07/13 14:19:26 send 1
2020/07/13 14:19:26 send 2
2020/07/13 14:19:28 i1=1
2020/07/13 14:19:28 main sleep
2020/07/13 14:19:30 i2=2
2020/07/13 14:19:31 exit

从输出可以看出,缓冲区是一个先进先出的队列,发送的顺序是1,2,接收的顺序也是1,2。运行到send2时缓冲区已满,main函数所在协程阻塞,2秒之后另一个协程从通道读取到第一个值1,缓冲区不再是已满的状态,main函数停止阻塞继续执行后面的代码打印“main sleep”,再过2秒之后另一个协程从通道读取到第二个值2。
如果把上述代码改为非缓冲通道,则输出时序为:

2020/07/13 14:37:15 send 1
2020/07/13 14:37:17 i1=1
2020/07/13 14:37:17 send 2
2020/07/13 14:37:19 i2=2
2020/07/13 14:37:19 main sleep
2020/07/13 14:37:22 exit

send1之后就会阻塞,2秒之后等另一个协程读取到第一个值1之后,main再发送send 2,发了之后再阻塞 ,2秒之后等另一个协程读取到第二个值2之后,main解除阻塞。


95. 【中级】关于异常的触发,下面说法正确的是()
A. 空指针解析
B. 下标越界
C. 除数为0
D. 调用panic函数

参考答案:ABCD


96. 【中级】关于cap函数的适用类型,下面说法正确的是()
A. array
B. slice
C. map
D. channel

参考答案:ABD

func main(){
	var a [3]int
	var sl = make([]int, 1, 5)
	var m = make(map[int]int, 100)
	var ch = make(chan int, 10)
	log.Printf("%d\n",cap(a))
	log.Printf("%d\n",cap(sl))
	log.Printf("%d\n",cap(m))
	log.Printf("%d\n",cap(ch))
}

编译报错:

invalid argument m (type map[int]int) for cap

 


100.             【中级】关于map,下面说法正确的是()
A. map反序列化时json.unmarshal的入参必须为map的地址
B. 在函数调用中传递map,则子函数中对map元素的增加不会导致父函数中map的修改
C. 在函数调用中传递map,则子函数中对map元素的修改不会导致父函数中map的修改
D. 不能使用内置函数delete删除map的元素

参考答案:A
A:func Unmarshal(data []byte, v interface{}) error 其中“If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError.”所以v只能是非空指针。
B,C:子函数对map的"增,删,改"都会导致父函数中map的改变,验证代码如下:

func test(mp map[int]int){
	mp[23] = 321
	mp[11] = 100
	delete(mp, 12)
}

func main(){
	var m = make(map[int]int)
	m[11] = 108
	m[12] = 110
	log.Printf("%v\n",m)
	test(m)
	log.Printf("%v\n",m)
}
[root@dev tutorial]# go run example.go
2020/07/14 10:18:49 map[11:108 12:110]
2020/07/14 10:18:49 map[11:100 23:321]

102. 【初级】关于select机制,下面说法正确的是()
A. select机制用来处理异步IO问题
B. select机制最大的一条限制就是每个case语句里必须是一个IO操作
C. golang在语言级别支持select关键字
D. select关键字的用法与switch语句非常类似,后面要带判断条件

参考答案:ABC
A 不准确,可以用来处理异步IO问题,也可以处理同步IO; B不对,每个case语句里必须是接收通道的操作,不一定非得是IO操作,golang并没有这样的硬性限制。C. 是对的,golang ”语言级别“ 支持select关键字,而不需要引用其它包。D.后面跟的是对通道的接收操作。


103.             【初级】关于内存泄露,下面说法正确的是()
A. golang有自动垃圾回收,不存在内存泄露
B. golang中检测内存泄露主要依靠的是pprof包
C. 内存泄露可以在编译阶段发现
D. 应定期使用浏览器来查看系统的实时内存信息,及时发现内存泄露问题

参考答案:BD

你可能感兴趣的:(go,golang,go,面试,笔记)