你应该了解的 Golang 的语法糖

探索 Go 中的语法糖:综合指南
photo by Matteo Catanese on Unsplash

The free man is he who does not fear to go to the end of his thought.
— Léon Blum

Golang 语法糖简介:

解释什么是语法糖以及它们在 Golang 中的重要性。

在计算机科学中,”语法糖”是指一种编程语法,它是为了使程序更易于读或表达,而在内部转化为基础语法。换句话说,这种语法并没有引入新的功能,而是提供了一种更加方便的编程方式。

Golang,作为一个现代语言,包含了大量的语法糖来减轻程序员的负担并使代码更可读。

例如,许多高级编程语言中的 ++ 操作符,它是语法糖,用于增加变量的值 1。因此,我们可以输入 i++ 而不是 i = i + 1,它更短和更快速地输入,并且它表达相同的增量操作。

接下来我们将会遍历Golang中的常见语法糖,分别详细介绍。这将会帮助你更好的理解和使用Golang,写出更紧凑、可读性更高的代码。

当然,学习的目标不仅是学习这些特性,更重要的是了解何时和为什么要使用它们。一个负责的 Go 开发人员不仅知道如何利用这些语法糖,还需要知道何时合适地使用它们。

可变长参数

基本介绍

Go语言允许一个函数把任意数量的值作为参数,Go语言内置了…操作符,在函数的最后一个形参才能使用,使用它必须注意如下事项:

  • 一个函数的最后一个参数可以是一个变长参数;
  • 一个函数可以最多有一个变长参数;
  • 一个变长参数的类型总为一个切片类型。

声明和调用

变长函数声明和普通函数声明类似,只不过最后一个参数必须为变长参数。 一个变长参数在函数体内将被视为一个切片。

1
2
3
4
5
6
7
8
func SumData(values ...int64) (sum int64) {
// type of values is []int64。
sum = 0
for _, v := range values {
sum += v
}
return
}

在变长参数函数调用中,可以使用两种风格的方式将实参传递给类型为[]T的变长形参:

1.传递一个切片做为实参。此切片必须可以被赋值给类型为[]T的值(或者说此切片可以被隐式转换为类型[]T)。 **此实参切片后必须跟随三个点**。
2.传递零个或者多个可以被隐式转换为T的实参(或者说这些实参可以赋值给类型为T的值)。 这些实参将被添加入一个匿名的在运行时刻创建的类型为[]T的切片中,然后此切片将被传递给此函数调用。

注意,这两种风格的方式不可在同一个变长参数函数调用中混用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
a0 := SumData()
a1 := SumData(3)
a3 := SumData(3, 4, 8)
// The top three lines are equivalent to the bottom three lines.
b0 := SumData([]int64{}...)
b1 := SumData([]int64{2}...)
b3 := SumData([]int64{2, 3, 5}...)
fmt.Println(a0, a1, a3)
fmt.Println(b0, b1, b3)
}
// print
// 0 3 15
// 0 3 15

fmt标准库包中的PrintPrintlnPrintf函数均为变长参数函数。 它们的声明大致如下:

1
2
3
func Print(a ...interface{}) (n int, err error)  
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)

忽略相关信息

我们只想初始化包里的init函数,但是不会使用包内的任何方法,这时就可以使用 _ 操作符号重命名导入一个不使用的包:

1
import _ "github.com/tfrain"

方法的返回值我们并不一定都使用,还要绞尽脑汁的给他想一个命名,有没有办法可以不处理不要的返回值呢?当然有,还是 _ 操作符,将不需要的值赋给空标识符,就可以忽略:

1
_, ok := test(a, b int)
  1. 有些时候我们想要json里面的某些字段不参加序列化,- 操作符可以帮我们处理,Go语言的结构体提供标签功能,在结构体标签中使用 -  操作符就可以对不需要序列化的字段做特殊处理:
  2. 我们使用json.Marshal进行序列化时不会忽略struct中的空值,默认输出字段的类型零值(string类型零值是””,对象类型的零值是nil),如果我们想在序列化时忽略掉这些没有值的字段时,可以在结构体标签中中添加omitempty :
1
2
3
4
5
type Person struct{  
  Name string  `json:"-"`
  Age string  `json:"age"`
  Email string   `json:"email,omitempty"`
}

声明相关

短变量声明

每次使用变量时都要先进行函数声明,对于些其他语言的人来说,并不习惯,那么在Go语言是不是也可以不进行变量声明直接使用呢?我们可以使用 name := expression 的语法形式来声明和初始化局部变量,相比于使用var声明的方式可以减少声明的步骤:

1
2
3
var a int = 10  
same as
a := 10

使用短变量声明时有两个注释事项:

  • 短变量声明只能在函数内使用,不能用于初始化全局变量
  • 短变量声明代表引入一个新的变量,不能在同一作用域重复声明变量
  • 多变量声明中如果其中一个变量是新变量,那么可以使用短变量声明,否则不可重复声明变量;

声明不定长数组

数组一般是有固定长度的,所以我们在声明数组时一般要声明长度,因为数组在编译时就要确认,但也可以不写数组长度,使用…操作符声明数组时,你只管填充元素值,其他的交给编译器自己去搞就好了;

a := […]int{1, 3, 5} // same as a := [3]{1, 3, 5}

有时我们想声明一个大数组,但是某些index想设置特别的值也可以使用…操作符搞定:

a := […]int{1: 20, 999: 10} // 数组长度是100, 下标1的元素值是20,下标999的元素值是10,其他元素值都是0

判断相关

判断map的key是否存在

Go语言提供语法 value, ok := m[key]来判断map中的key是否存在,一般都是只利用ok来进行判断。value如果存在就会返回key所对应的值,不存在就会返回空值:

1
2
3
4
5
6
7
8
9
10
import "fmt"  

func main() {
    dict := map[string]int{"tfrain": 1}
    if value, ok := dict["tfrain"]; ok {
        fmt.Printf(value)
    } else {
      fmt.Println("key:tfrain not exist")
    }
}

类型断言

我们通常都会使用interface,一种是带方法的interface,一种是空的interfaceGo1.18之前是没有泛型的,所以我们可以用空的interface{}来作为一种伪泛型使用,当我们使用到空的interface{}作为入参或返回值时,就会使用到类型断言,来获取我们所需要的类型,在Go语言中类型断言的语法格式如下:

1
value, ok := x.(T)

x是interface类型,T是具体的类型。这里类型断言需要区分x的类型,
如果x是空接口类型:

空接口类型断言实质是将eface中_type与要匹配的类型进行对比,匹配成功在内存中组装返回值,匹配失败直接清空寄存器,返回默认值。

如果x是非空接口类型:

*非空接口类型断言的实质是 iface 中 itab 的对比。itab 匹配成功会在内存中组装返回值。匹配失败直接清空寄存器,返回默认值。*

English post: https://programmerscareer.com/golang-syntactic-sugar/
作者:Wesley Wei – Twitter Wesley Wei – Medium
注意:本文为作者原创,转载请注明出处。

常见(20+)软件面试问题(+答案)关于MySQL/Redis/Kafka use-channel

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×