Go 基础 - 记录一些 Go 的特殊语法(一)

369 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

2022-07-25-09-23-06-image.png

前言

本系列的文章是写给刚开始学习 Go 这门语言的朋友的(但也并不是入门教学),需要你已经看过了 A Tour of Go 官方教程,或者通过其他网站了解了 Go 的基本语法。本系列文章的目的在于记录、分享一些 Go 语言中较为特殊的语法或用法,起一个裨补缺漏的作用。

这里也把本系列文章引用的文档放在下面:

你也可以在中文网站中找到对应的文档:

Go 的特殊语法

注释

出现在顶级声明之前,且与该声明之间没有空行的注释,将与该声明一起被 godoc 提取出来,作为文档注释。应当注意的是,在程序中每个可导出的名称都应该有文档注释。

命名

Go 约定使用驼峰命名法,并且 Go 中的名称会影响到语义。某个名称在包外是否可见,就取决于其首个字母是否为大写字母。因此,应将导出名的首字母大写,而私有名称的首字母小写。

分号

Go 的词法分析器会按照规则来自动插入分号,规则如下:

若在新行前的最后一个标记为标识符(包括 int 和 float64 这类的单词)、数值或字符串常量之类的基本字面或 break continue fallthrough return ++ -- ) } 之一。

则词法分析将始终在该标记后面插入分号。

所以你不能将 ifforswitch 或 select 的左大括号放到下一行去,否则就会产生错误。像下面这样:

if i < f() // 这里会被加一个分号
{
    g()
}

不过,语法分析器会检测出来该问题并报错。

声明与初始化

常量不能用 := 来声明。

声明时未初始化的变量会被赋以零值 0, false, "", nil

赋值

Go 中多变量可以使用平行赋值,例如用 a, b = b, a 来交换 a 和 b 的值, 或者在循环的条件中使用如下语句 i, j = i+1, j+1

:= 的再次赋值

在Go中,可用 := 来声明并初始化变量。由于同一作用域中不能对同一变量重复声明,一般情况下 := 不能对已声明的变量再次使用。

但是当满足下列条件时,已被声明的变量 v 可重复出现在 := 声明中:

  • 本次声明与已声明的 v 处于同一作用域;
  • 将要赋给 v 的值与 v 已有类型相同;
  • 在此次声明中至少另有一个变量是新声明的。

例如:

f, err := os.Open(name)
d, err := f.Stat()

此外,这个特性也常被用于较长的 if-else 语句链中。

流程控制

if & switch

ifswitch 语句可以在条件语句之前,添加初始化语句来设置局部变量。

if i:=f(); i > 0 {
    fmt.Println(i)
}
switch i:=f(); i {
    case 0:
        fmt.Println(0)
    case 1, 2, 3, 4:
        fmt.Println(i)
    default:
        fmt.Println("value > 4")
}

switch 语句只会运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。

另一点重要的不同在于 case 的条件可以为任意类型量,并且可通过逗号分隔来列举相同的处理条件。

没有条件的 switch 同 switch true 一样,这种形式能将一长串 if-else 链写得更加清晰。

break/continue + label

除了 goto 外, breakcontinue 同样可以通过后面加标签语句来跳出多层循环或继续外层循环,一般会把标签顶头写在循环的上一空行中。

walk:
    for {
        for {
            break walk
        }
    }

相比于在其他语言中可能不推荐使用 goto 以免使程序逻辑混乱;在 Go 中,可能会更建议你无论是否是多层循环,当循环内部代码比较长时(例如大于100行),最好都给循环打上标签,并使用 breakcontinue 加标签的形式来退出或继续循环,这样反而能使程序逻辑更加清晰。

defer

defer 语句会将函数推迟到外层函数返回之后执行。

推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

func main() {
    fmt.Println("counting")
    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("done")
}

for

range 语句可以用来解析 utf-8 字符,这对于遍历获取字符串内容是非常有用的。

另外,Go 没有逗号操作符,而 ++ 和 -- 是语句而不是表达式。 如果你想要在 for 中使用多个变量,应采用平行赋值的方式 (因为它会拒绝 ++ 和 --

for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
	a[i], a[j] = a[j], a[i]
}

结构体

如果我们有一个指向结构体的指针 x,那么可以通过 (*x).f 来访问其字段 f。不过这么写太啰嗦了,所以 Go 也允许我们使用隐式间接引用,直接写 x.f 就可以。