对《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后面的最后一个元素是包名” 这道题本来是对的》