头图

面试题

这是Go Quiz系列的第3篇,关于Go语言的分号规则和switch的特性。

这道题比较tricky,通过这道题可以加深我们对Go语言里的分号:规则和switch特性的理解。

package main

func f() bool {
    return false
}

func main() {
    switch f() 
  {
    case true:
        println(1)
    case false:
        println(0)
    default:
        println(-1)
    }
}
  • A: 1
  • B: 0
  • C: -1

这道题主要考察以下知识点:

  • Go语言里的分号:规则
  • switch后面的{换行后编译器会在背后做什么?

解析

Go语言和C++一样,在每行语句(statement)的末尾是以分号结尾的。

看到这里,你可能会有点懵,是不是在想:我写Go代码的时候也没有在语句末尾加分号啊。。。

那是因为Go编译器的词法解析程序自动帮你做了这个事情,在需要加分号的地方给你加上了分号。

如果你在代码里显示地加上分号,编译器是不会报错的,只是Go不需要也不建议显示加分号,一切交给编译器去自动完成。

那编译器是怎么往我们代码里插入分号:的呢?规则是什么?我们看看官方文档的说法:

  1. When the input is broken into tokens, a semicolon is automatically inserted into the token stream immediately after a line's final token if that token is

  2. To allow complex statements to occupy a single line, a semicolon may be omitted before a closing ")" or "}".

根据这2个规则,我们来分析下本文最开始的题目,switch代码如下所示:

// 示例1
switch f() 
{
    case true:
        println(1)
    case false:
        println(0)
    default:
        println(-1)
}

上面的代码对于switch f()满足规则1,会在)后面自动加上分号,等价于示例2

// 示例2
switch f();
{
    case true:
        println(1)
    case false:
        println(0)
    default:
        println(-1)
}

示例2的代码等价于示例3,程序运行会进入到case true这个分支

// 示例3
switch f();true {
    case true:
        println(1)
    case false:
        println(0)
    default:
        println(-1)
}

所以本题的答案是1,选择A

总结

  1. Effective Go官方建议:除了for循环之外,不要在代码里显示地加上分号

    你可能在ifswitch关键字后面看到过分号:,比如下面的例子。

    if i := 0; i < 1 {
            println(i)
    }
    
    switch os := runtime.GOOS; os {
        case "darwin":
            fmt.Println("OS X.")
        case "linux":
            fmt.Println("Linux.")
        default:
            // freebsd, openbsd,
            // plan9, windows...
            fmt.Printf("%s.\n", os)
    }

    虽然代码可以正常工作,但是官方不建议这样做。我们可以把分号前面的变量赋值提前,比如修改为下面的版本:

    i := 0
    if i < 1 {
            println(i)
    }
    
    os := runtime.GOOS
    switch os {
        case "darwin":
            fmt.Println("OS X.")
        case "linux":
            fmt.Println("Linux.")
        default:
            // freebsd, openbsd,
            // plan9, windows...
            fmt.Printf("%s.\n", os)
    }
  2. {不要换行,如果对于if, forswitchselect{换行了,Go编译器会自动添加分号:,会导致预期之外的结果,比如本文最开头的题目和下面的例子。

    // good
    if i < f() {
        g()
    }
    
    // wrong: compile error
    if i < f()  // wrong!
    {           // wrong!
        g()
    }
  3. 养成使用go fmt, go vet做代码检查的习惯,可以帮我们提前发现和规避潜在隐患。

思考题

思考下面这2道题的运行结果是什么?大家可以在评论区留下你们的答案。

题目1:

// Foo prints and returns n.
func Foo(n int) int {
    fmt.Println(n)
    return n
}

func main() {
    switch Foo(2) {
    case Foo(1), Foo(2), Foo(3):
        fmt.Println("First case")
        fallthrough
    case Foo(4):
        fmt.Println("Second case")
    }
}

题目2:

a := 1
fmt.println(a++)

开源地址

文章和示例代码开源地址在GitHub: https://github.com/jincheng9/...

公众号:coding进阶

个人网站:https://jincheng9.github.io/

知乎:https://www.zhihu.com/people/...

好文推荐

  1. 被defer的函数一定会执行么?
  2. Go有引用变量和引用传递么?map,channel和slice作为函数参数是引用传递么?
  3. new和make的使用区别是什么?
  4. 一文读懂Go匿名结构体的使用场景
  5. 官方教程:Go泛型入门
  6. 一文读懂Go泛型设计和使用场景
  7. Go Quiz: 从Go面试题看slice的底层原理和注意事项
  8. Go Quiz: 从Go面试题看channel的注意事项

References


coding进阶
116 声望18 粉丝