golang后台面试题目

1、go struct 能不能比较

struct是强类型语言,不同类型不能比较,同类型才能比较,实例也不能比较,因为实例是指针类型

2、写出下面代码输出内容

package main

import (
	"fmt"
)

func main() {
	defer_call()
}

func defer_call() {
	defer func() { fmt.Println("打印前") }()
	defer func() { fmt.Println("打印中") }()
	defer func() { fmt.Println("打印后") }()

	panic("触发异常")
}

打印后
打印中
打印前
panic: 触发异常

3、range陷阱

package main

import "fmt"

type AStruct struct {
    bar string
}

func main() {
    list := []AStruct{
        {"1"},
        {"2"},
        {"3"},
    }

    copyedList := make([]*AStruct, len(list))
    for i,v:= range list {
        copyedList[i] = &v
    }

    fmt.Println(list[0], list[1], list[2])
    fmt.Println(copyedList[0], copyedList[1], copyedList[2])
}

运行这段代码,你就会发现

{1} {2} {3}
&{3} &{3} &{3}

怎么都是同一个地址,因为range赋值用的是用的是一个循环变量的地址,Go每次循环都会复用这一”循环变量”,所以一直会输出最后一个数的地址

解决方案
for i := range list {
  copyedList[i] = &list[i]
}

4、变量的作用域陷阱

下面这段程序会有什么输出结果?

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

A都输出10,B先输出9,然后按顺序输出

B:  9
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
A:  10
B:  0
B:  1
B:  2
B:  3
B:  4
B:  5
B:  6
B:  7
B:  8

如果不用runtime.GOMAXPROCS(1)约束,就会随机输出A和B,A和B中数字也随机输出,但是A是10

A:  3
B:  5
A:  10
B:  9
A:  10
A:  10
A:  10
A:  10
B:  1
A:  10
B:  0
B:  2
B:  3
B:  4
A:  3
B:  6
B:  7
B:  8
A:  10
A:  10

为什么A一直会是10,这是一个变量作用域不同引起的

5、继承和组合的区别和优势劣势(还不懂)

继承是一个白盒模型,父类的内部细节对子类是透明的,

组合是黑盒模型,父类的内部细节对子类不透明,对现有的对象进行拼装组合产生更复杂的功能

继承的优点:

易于操作,易于修改和拓展父类

继承的缺点:

1、子类和父类缺乏独立性,父类改变子类不得不改变

2、破坏了封装性

组合的优点

1、降低各个模块之间的耦合性

2、不会破坏封装性,只用一个接口用来连接对象内部的函数

3、减少依存关系

组合的缺点

1、操作困难,对不熟悉的人难以操作

2、比继承要写更多的代码

6、go的组合和继承

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

输出

showA
showB

解答:被组合的类型People虽然被包装成Teacher,执行t.ShowA()但是people并不自己会被什么类型组合,所以t.showB()不会执行

7、select的随机性

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随机执行一个case,有一定概率出现panic(“hello”),一定概率输出1

8、defer执行顺序

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

结果

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

解析;calc(“1”, a, calc(“10”, a, b))会先对这句话里面的a,b赋值,赋值过程中先执行10,同理下一句话先执行20,再是2,最后1,因为defer是像栈一样,最后的先执行

9、看看下面会有什么问题

type UserAges struct {
	ages map[string]int
	sync.Mutex
}

func (ua *UserAges) Add(name string, age int) {
	ua.Lock()
	defer ua.Unlock()
	ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
	if age, ok := ua.ages[name]; ok {
		return age
	}
	return -1
}

解答:在并发处理的时候可能会出现fatal error: concurrent map read and map write.,因为get没上锁,在同一时间读写一个东西会出现竞争

10、printf和Sprintf的区别

printf

  1. 用传入的格式化规则符将传入的变量写入到标准输出里面(即在终端中有显示),
  2. 返回值为 写入标准输出的字节数和写入过程中遇到的问题。

sprintf

  1. 用传入的格式化规则符将传入的变量格式化,(终端中不会有显示)
  2. 返回为 格式化后的字符串。
package main

import (
    "fmt"
)

func main()  {

    testStr := []byte("test str")
    fmt.Println(testStr, "hello world")    //[116 101 115 116 32 115 116 114] hello world
    fmt.Printf("%s", testStr) //test str
    fmt.Println()
    fmt.Sprintf("%s", testStr) //空,无IO输出
    fmt.Println()
    printStr := fmt.Sprintf("%s", testStr)
    fmt.Println(printStr)    //test str
}

sprintf就是把一个[]byte类型的东西格式化成string

make和new的区别

​ 二者都是内存的分配(堆上),但是make只用于slice、map以及channel的初始化(非零值);而new用于类型的内存分配,并且内存置为零。所以在我们编写程序的时候,就可以根据自己的需要很好的选择了。

make返回的还是这三个引用类型本身;而new返回的是指向类型的指针。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!