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

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

1.   【初级】声明一个整型变量i__________
参考答案:var i int
2.   【初级】声明一个含有10个元素的整型数组a__________
参考答案:var a [10]int
3.   【初级】声明一个整型数组切片s__________
参考答案:var s []int
4.   【初级】声明一个整型指针变量p__________
参考答案:var p *int
5.   【初级】声明一个key为字符串型value为整型的map变量m__________
参考答案:var m map[string]int
6.   【初级】声明一个入参和返回值均为整型的函数变量f__________
参考答案:var f func(a int) int
函数变量的示例:

package main

import (
	"fmt"
)

func add(i int) int {
	i++
	return i
}

func sub(i int) int {
	i--
	return i
}

func print(i int, f func(i int) int) {
	fmt.Printf("print:%d\n", f(i))
}
func main() {
	var f func(i int) int
	f = sub
	fmt.Printf("f(5)=%d\n", f(5))

	print(3, add)
	print(3, sub)
}
f(5)=4
print:4
print:2

7.   【初级】声明一个只用于读取int数据的单向channel变量ch__________
参考答案:var ch <-chan int
单向通道通常作为函数参数使用,然后把双向通道作为参数传递给单向通道,示例如下:

func main() {
	ch := make(chan int)
	go func(c <-chan int){
		time.Sleep(3 * time.Second)
		<-c
	}(ch)
	ch<-0
	fmt.Printf("exit\n")
}


8.   【初级】假设源文件的命名为slice.go,则测试文件的命名为__________
参考答案:slice_test.go
9.   【初级】 go test要求测试函数的前缀必须命名为__________
参考答案:Test

import "testing"

func TestS1(t *testing.T){
    if s1("123456789") != 9{
        t.Error(`s1("123456789") != 9`)
    }
}

如果是go test -bench的话则前缀必须为Benchmark, 如

import "testing"

func BenchmarkInetNtoA1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		InetNtoA1(4284928154)
	}
}

10. 【中级】下面的程序的运行结果是__________

for i := 0; i < 5; i++ {
   defer fmt.Printf("%d ", i)
}
参考答案:4 3 2 1 0
多个defer的执行顺序是先进后出
 

13. 【中级】下面的程序的运行结果是__________
func main() {
    x := 1
    {
        x := 2
        fmt.Print(x)
    }
    fmt.Println(x)
}
参考答案:21
这个可能是if true{}的省略写法,网上找不到确切说法。
 

21. 【中级】下面的程序的运行结果是__________

func main() {
    strs := []string{"one", "two", "three"}
    for _, s := range strs {
        go func() {
            time.Sleep(1 * time.Second)
            fmt.Printf("%s", s)
        }()
    }
    time.Sleep(3 * time.Second)
}

参考答案:threethreethree
这道题s的作用域是for循环,当每个协程等待1秒后再读取s时,s已经循环到切片的最后一个元素“three”,所以3个协程读到的都是"three"。其实这道题协程不用等1秒,循环始终比协程的函数先执行。
func main() {
    strs := []string{"one", "two", "three"}
    for _, s := range strs {
        go func() {
            fmt.Printf("%s", s)
        }()
    }
    time.Sleep(3 * time.Second)
}
去掉 time.Sleep(1 * time.Second)之后,输出仍然是threethreethree
如果要"one", "two", "three"都能输出,则需要把协程中的s改成局部变量
func main() {
    strs := []string{"one", "two", "three"}

    for _, s := range strs {
        go func(s string) {
            fmt.Printf("%s", s)
        }(s)
    }
    time.Sleep(3 * time.Second)
}
这样的话,"one", "two", "three"都能输出但顺序不固定。


32. 【中级】下面的程序的运行结果是__________
func main() {
    x := []string{"a", "b", "c"}
    for v := range x {
        fmt.Print(v)
    }
}
参考答案:012


38. 【中级】下面的程序的运行结果是__________
func main() {
    x := []string{"a", "b", "c"}
    for _, v := range x {
        fmt.Print(v)
    }
}
参考答案:abc


44. 【初级】下面的程序的运行结果是__________
func main() {
    i := 1
    j := 2
    i, j = j, i
    fmt.Printf("%d%d\n", i, j)
}
参考答案:21


50. 【初级】下面的程序的运行结果是__________
func incr(p *int) int {
    *p++
    return *p
}
func main() {
    v := 1
    incr(&v)
    fmt.Println(v)
}
参考答案:2
Go目前还没有支持对指针地址进行自增自减,*p++ 等价于(*p)++,即对指针所存储的地址对应的变量进行自增。


59. 【初级】启动一个goroutine的关键字是__________
参考答案:go


60. 【中级】下面的程序的运行结果是__________
type Slice []int
func NewSlice() Slice {
    return make(Slice, 0)
}
func (s *Slice) Add(elem int) *Slice {
    *s = append(*s, elem)
    fmt.Print(elem)
    return s
}
func main() {
    s := NewSlice()
    defer s.Add(1).Add(2)
    s.Add(3)
}

参考答案:132
只有Add(2)是这个defer要执行的函数在父函数执行结束时执行,前面Add(1)是参数计算是在执行defer语句时执行。详见《Go面试:defer语句里究竟哪一个函数或者方法是在父函数结束时调用?》


1.   【初级】数组是一个值类型()
参考答案:T
2.   【初级】使用map不需要引入任何库()
参考答案:T
3.   【中级】内置函数delete可以删除数组切片内的元素()
参考答案:F
4.   【初级】指针是基础类型()
参考答案:F
5.   【初级】 interface{}是可以指向任意对象的Any类型()
参考答案:T
6.   【中级】下面关于文件操作的代码可能触发异常()
    file, err := os.Open("test.go")
    defer file.Close()
    if err != nil {
        fmt.Println("open file failed:", err)
        return
    }
...
参考答案:T
这道题应该是F。只有正常返回的错误,不会触发panic。file即便是nil Close()也只是会返回错误,不会有异常。
func (f *File) Close() error {
    if f == nil {
        return ErrInvalid
    }
    return f.file.close()
}
详见《Go面试:一道关于错误和异常的有争议题目》


13. 【初级】 Golang不支持自动垃圾回收()
参考答案:F
14. 【初级】 Golang支持反射,反射最常见的使用场景是做对象的序列化()
参考答案:T
15. 【初级】 Golang可以复用C/C++的模块,这个功能叫Cgo()
参考答案:F
CGO是C语言和Go语言之间的桥梁,原则上无法直接支持C++的类。

16. 【初级】下面代码中两个斜点之间的代码,比如json:"x",作用是X字段在从结构体实例编码到JSON数据格式的时候,使用x作为名字,这可以看作是一种重命名的方式()
type Position struct {
X int `json:"x"`
Y int `json:"y"`
Z int `json:"z"`
}
参考答案:T
21. 【初级】通过成员变量或函数首字母的大小写来决定其作用域()
参考答案:T
22. 【初级】对于常量定义zero(const zero = 0.0),zero是浮点型常量()
参考答案:F
除非明确声明常量的类型,否则都是无类型常量。浮点型无类型常量的默认类型是float64,但这不同于明确声明的float64类型常量,因为前者不受强类型约束,见下例:

func main() {
	const zero = 0.0
	var a float32 = zero
	var b float64 = zero
	fmt.Printf("%T\n",zero)
	fmt.Printf("%T\n",a)
	fmt.Printf("%T\n",b)
}

输出:

float64
float32
float64

无类型常量zero的输出类型是float64,但它可以赋值给float32,float64两种类型的变量。如果把zero改成浮点型常量,如下:

const zero float64 = 0.0

则编译报错:

./example.go:9:6: cannot use zero (type float64) as type float32 in assignment

详见《Go语言:深入理解Go的常量》


23. 【初级】对变量x的取反操作是~x()
参考答案:F
取反^x


24. 【初级】下面的程序的运行结果是xello()
func main() {
    str := "hello"
    str[0] = 'x'
    fmt.Println(str)
}
参考答案:F
编译报错”cannot assign to str[0]“,字符串不能被修改。


30. 【初级】下面代码中的指针p为野指针,因为返回的栈内存在函数结束时会被释放()
 type TimesMatcher struct {
 base int
 }
 func NewTimesMatcher(base int) *TimesMatcher{
 return &TimesMatcher{base:base}
 }
 func main() {
 p := NewTimesMatcher(3)
 ...
}

参考答案:F
go语言的自动内存管理机制使得只要还有一个指针引用一个变量,那这个变量就会在内存中得以保留,因此在Go语言函数内部返回指向本地变量的指针是安全的。这题考查的是Go语言的变量逃逸...Go语言会通过判断引用关系,将在栈中初始化的变量,转变为到堆初始化。野指针是指无效的地址空间、即访问不到的变量


40. 【初级】匿名函数可以直接赋值给一个变量或者直接执行()
参考答案:T
 
41. 【初级】如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线“_”来跳过这个返回值,该下划线对应的变量叫匿名变量()
参考答案:T
“匿名变量”叫法不严谨,应该是”忽略返回值“,告诉编译器忽略掉这个返回值不要报错,详见《Go语言并没有匿名变量(anonymous variable)》


42. 【初级】在函数的多返回值中,如果有error或bool类型,则一般放在最后一个()
参考答案:T
 
43. 【初级】错误是业务过程的一部分,而异常不是()
参考答案:T
 
44. 【初级】函数执行时,如果由于panic导致了异常,则延迟函数不会执行()
参考答案:F
在panic之前的defer会执行,在panic之后的defer不会执行。

package main

import(
	"fmt"
)

func main() {
	defer func(){
		fmt.Printf("first defer is called\n")
	}()

	panic("I'm panic")

	defer func(){
		fmt.Printf("second defer is called\n")
	}()

}
[root@dev tutorial]# go run example.go
first defer is called
panic: I'm panic

goroutine 1 [running]:
main.main()
        /home/go-test/src/tutorial/example.go:12 +0x5f
exit status 2

45. 【中级】当程序运行时,如果遇到引用空指针、下标越界或显式调用panic函数等情况,则先触发panic函数的执行,然后调用延迟函数。调用者继续传递panic,因此该过程一直在调用栈中重复发生:函数停止执行,调用延迟执行函数。如果一路在延迟函数中没有recover函数的调用,则会到达该携程的起点,该携程结束,然后终止其他所有携程,其他携程的终止过程也是重复发生:函数停止执行,调用延迟执行函数()

参考答案:F
TODO, panic defer的实现原理

46. 【初级】同级文件的包名不允许有多个()
参考答案:T
不同包名的文件应该放到对应包名的文件夹下。

47. 【中级】可以给任意类型添加相应的方法()
参考答案:F
不能给当前package以外定义的类型添加方法,包括内置类型/第三方包里的类型。如果一定要添加方法,需要在这些类型的基础上定义新的类型。

48. 【初级】 golang虽然没有显式的提供继承语法,但是通过匿名组合实现了继承()
参考答案:T

type Bird struct {
        name string
}

func (b *Bird) Fly() {
        fmt.Printf("%s can fly\n", b.name)
}

type Duck struct {
        *Bird
}

func (d *Duck) Speak() {

        fmt.Printf("%s can speak\n", d.name)
}

func main() {
        d := &Duck{&Bird{"Donald"}}
        d.Fly()
        d.Speak()
}

49. 【初级】使用for range迭代map时每次迭代的顺序可能不一样,因为map的迭代是随机的()
参考答案:T
go 1之前,两次遍历是相同的。go开发组发现有些程序员已经开始依赖遍历顺序稳定(不是有序)这个特性来开发程序,这其实并不好,因为这个“稳定”因平台不同而不同,在 win 上可能是 1342,但是在linux上可能就是4312。这会导致程序不可移植,因此go要求开发者自己实现稳定有序的遍历。
源码在runtime/map.go,通过调用fastrand()生成随机数来决定从哪儿开始遍历

        // decide where to start
        r := uintptr(fastrand())
        if h.B > 31-bucketCntBits {
                r += uintptr(fastrand()) << 31
        }

50. 【初级】 switch后面可以不跟表达式()
参考答案:T
没有表达式,则当成布尔变量true

func main() {
        var i int = 6
        switch {
        case i == 3, i == 6:
                fmt.Printf("is 3 or 6\n")
        case i == 4:
                fmt.Printf("is 4\n")
        case i == 5:
                fmt.Printf("is 5\n")
        default:
                fmt.Printf("is defaul\n")
        }
}

51. 【中级】结构体在序列化时非导出变量(以小写字母开头的变量名)不会被encode,因此在decode时这些非导出变量的值为其类型的零值()
参考答案:T

package main

import (
        "encoding/json"
        "fmt"
)

type Person struct {
        Name string
        age  int
}

func main() {
        p := Person{"Jack", 40}
        bytes, err := json.Marshal(p)
        if err != nil {
                fmt.Printf("%s\n", err.Error())
                return
        }

        fmt.Printf("%s\n", bytes)
        var b Person
        err = json.Unmarshal(bytes, &b)
        if err != nil {
                fmt.Printf("%s\n", err.Error())
                return
        }
        fmt.Printf("%v\n", b)
}
{"Name":"Jack"}
{Jack 0}

52. 【初级】 golang中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以NewXXX来命名()
参考答案:T
53. 【中级】当函数deferDemo返回失败时,并不能destroy已create成功的资源()
func deferDemo() error {
        err := createResource1()
        if err != nil {
                return ERR_CREATE_RESOURCE1_FAILED
        }
        defer func() {
                if err != nil {
                        destroyResource1()
                }
        }()
        err = createResource2()
        if err != nil {
                return ERR_CREATE_RESOURCE2_FAILED
        }
        defer func() {
                if err != nil {
                        destroyResource2()
                }
        }()
        err = createResource3()
        if err != nil {
                return ERR_CREATE_RESOURCE3_FAILED
        }
        return nil
}
参考答案:F
这道题考的是延迟函数的函数体内的变量的估值时间点。err的取值是在延迟函数被调用时进行估值,例如当createResource2报错返回时,第一个defer函数被调用且err等于createResource2()返回的err,destroyResource1()得到调用。如果把题目种延迟函数改成:
        defer func(err error) {
                if err != nil {
                        destroyResource1()
                }
        }(err)
那么只要resource1创建成功则当延迟函数被调用时err == nil,因为延迟函数的参数估值是发生在延迟函数加入队列的时点上,


80. 【中级】 channel本身必然是同时支持读写的,所以不存在单向channel()
参考答案:F
Go支持单向channel包括只读和只写两种,如果给只读channel进行写操作则在编译期会报错。


81. 【初级】 import后面的最后一个元素是包名()
参考答案:F
这个题应该是T,它的原话是:“Go's convention is that the package name is the last element of the import path: the package imported as "crypto/rot13" should be named rot13. ” 题目把import path中的路径省略不翻译,导致有一定的歧义。《Go面试:判断题“import后面的最后一个元素是包名” 这道题本来是对的》

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