大厂1号面试

一次大厂面试经历,积累经验

如果你觉得到了技术瓶颈,我的建议是投大厂简历然后去面试,在面试过程中你会发现你的不足。

知识点1: panic、defer、recover执行过程

平时用defer 比较少,多个defer连用的情况更少,所以应该自己下来多总结积累这其中的坑

1.0:defer panic recover执行流程

func defer_call2() {
    defer func() {
        fmt.Println("11111")
    }()
    defer func() {
        fmt.Println("22222")
    }()

    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recover from r : ", r)
        }
    }()

    defer func() {
        fmt.Println("33333")
    }()

    fmt.Println("111 Helloworld")

    panic("Panic 1!")

    panic("Panic 2!")

    fmt.Println("222 Helloworld")
}

func main(){
    defer_call2()
}

输出结果:

111 Helloworld
33333
Recover from r :  Panic 1!
22222
11111

1.1: 多个 defer 与 panic 执行顺序

原题重现:

下面代码打印顺序是什么:

func main() {
    defer_call()
}

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

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

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

    panic("触发异常!")
    // defer 先进后出
}

下面代码输出:

打印后
打印中
打印前
panic: 触发异常goroutine 1 [running]:
main.defer_call()

讲解:

defer 的执行是先进后出的,并且它是在执行 panic 之前运行。

1.2: defer 调用函数的嵌套执行顺序?

主要还是 defer 最近的函数 进行先进后出的执行顺序,嵌套内部的没有defer功能。

例一:

func main() {
    a := 1
    b := 2
    defer clac("1", a, clac("10", a, b))
    a = 0
    defer clac("2", a, clac("20", a, b))
    b = 1
}

func clac(index string, a, b int) int {
    ret := a + b
    fmt.Println(index, a, b, ret)
    return ret
}

输出如下:

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

重点理解分析:

defer 只对它最外层的函数进行的先进后出的调用,至于再内部的函数由于并没有继续带有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

例二:

func main() {
    a := 1
    b := 2
    z := 3
    defer calc(a, calc(a, b, z), calc(13, b, z))
    a = 0
    defer calc(a, calc(a, b, z), calc(12, b, z))
    a = 2
    defer calc(a, calc(a, b, z), calc(11, b, z))
}

输出如下:

1 2 6
13 2 18
0 2 5
12 2 17
2 2 7
11 2 16
2 7 25
0 5 22
1 6 25

1.3:defer 与 return 的坑

这篇文章总结的还可以,可以进行参考: defer 使用小结与注意要点

1.3.1:

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

1.3.2:

func f() (r int) {
     t := 5
     defer func() {
       t = t + 5
     }()
     return t
}

1.3.3:

func f() (r int) {
    defer func(r int) {
          r = r + 5
    }(r)
    return 1
}

总结下来就是: - defer是在return之前执行的。这个在 官方文档中是明确说明了的; - return xxx这一条语句并不是一条原子指令! - 函数返回的过程:先给返回值赋值,然后调用defer表达式,最后才是返回到调用函数中。

其实可以拆分下语句规则:

返回值 = xxx
调用defer函数
空的return

接下来就可以改写了:

func f() (result int) {
     result = 0  //return语句不是一条原子调用,return xxx其实是赋值+ret指令
     func() { //defer被插入到return之前执行,也就是赋返回值和ret指令之间
         result++
     }()
     return
     // 返回 1
}

func f() (r int) {
     t := 5
     r = t //赋值指令
     func() {        //defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
         t = t + 5
     }
     return        //空的return指令
     // 这个的结果是5。
}

func f() (r int) {
     r = 1  //给返回值赋值
     func(r int) {        //这里改的r是传值传进去的r,不会改变要返回的那个r值
          r = r + 5
     }(r)
     return        //空的return
     // 这个例子的结果是1。
}

知识点2:关于 * 和 & 取值赋值问题

type student struct {
    Name string
    Age  int
}
 
func pase_student() {
    m := make(map[string]*student)
    stus := []student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }
    for _, stu := range stus {
        m[stu.Name] = &stu
    }
 
}
//  // map[zhou:%!s(*main.student=&{wang 22}) li:%!s(*main.student=&{wang 22}) wang:%!s(*main.student=&{wang 22})]%

分析:

m[stu.Name]=&stu实际上一致指向同一个指针, ==最终该指针的值为遍历的最后一个struct的值拷贝==。 就像想修改切片元素的属性:(※)

for _, stu := range stus {
    stu.Age = stu.Age+10
} // 也是不可行的

修改上面的BUG:

func pase_student() {
    m := make(map[string]*student)
    stus := []student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }
    // 错误写法
    for _, stu := range stus {
        m[stu.Name] = &stu
    }
 
    for k,v:=range m{
        println(k,"=>",v.Name)
    }
 
    // 正确
    for i:=0;i<len(stus);i++  {
        m[stus[i].Name] = &stus[i]
    }
    for k,v:=range m{
        println(k,"=>",v.Name)
    }
}

或者:

func pase_student() {
    m := make(map[string]*student)

    stus := []*student{
        {Name: "zhou", Age: 24},
        {Name: "li", Age: 23},
        {Name: "wang", Age: 22},
    }

    for _, stu := range stus {
        fmt.Println(stu)  // 这里打印出来一切正常
        m[stu.Name] = stu // 这里会每次都只能取到最后一个值 wang 22
    }

    fmt.Printf("%s", m)
    //map[zhou:%!s(*main.student=&{zhou 24}) li:%!s(*main.student=&{li 23}) wang:%!s(*main.student=&{wang 22})]%
}

知识点3:goroutine 与 for 的打印结果和顺序

func main() {
    runtime.GOMAXPROCS(1)
    wg := sync.WaitGroup{}
    wg.Add(20)
    for i := 0; i < 10; i++ {
        go func() {
            fmt.Println("A: ", i) // 全部只能打印出 A 10
            wg.Done()
        }()
    }
    for i := 0; i < 10; i++ {
        go func(i int) {
            fmt.Println("B: ", i)
            wg.Done()
        }(i)
    }
    wg.Wait()  // 两个 for 中的顺序也是交叉乱的
}

分析:

go执行的随机性和闭包:

  1. A:均为输出10
  2. B:从0~9输出(顺序不定)。
  3. 两个for前后顺序不定,如下打印所示:

输出如下:

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

知识点4:strcut 的继承 和 嵌套的调用输出

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
    t.ShowB() // teacher showB
}

输出如下:

showA showB
teacher showB

知识点5:interface 与 nil 只有类型和值都为nil 才==nil

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

输出如下:

BBBBBBB

分析:

很经典的题! 这个考点是很多人忽略的interface内部结构。

在底层,interface作为两个成员来实现,一个类型和一个值

只有当类型和值都为nil 这个接口类型才==nil

而该题中 已经有类型Student 值为nil 所以不满足==nil 。

下面是 interface 的底层结构:

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(方法集)定义了接口的接收规则

知识点6:select 与 case 配合channel 输出的执行顺序具有随机性

下面代码如何输出?

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

输出结果如下:

1
// 或者
panic: hello
// 考点select随机性

分析:

注意: select会随机选择一个可用通用做收发操作。

所以代码是有可能触发异常,也有可能不会。 单个chan如果无缓冲时,将会阻塞。

但结合 select 可以在多个chan间等待执行。有三点原则:

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