Go 语言入门:基础语法以及一些坑 | 青训营

72 阅读6分钟

一、语言简介:我对 Go 的印象

  • Go 语言简洁强大,这里的简洁是语义简洁,而不是语句简洁,毕竟论语句简洁谁又能简洁得过 Python 的一连串表达式呢?
  • 相较于 C/C++ 中无处不在的隐式转换,让人防不胜防。Go 语言倒好,直接禁止隐式转换,这无疑提升了代码可读性以及简洁性。
  • Go 语言有极快的编译速度,其编译用时远少于 C/C++ 的编译,而运行速度只相较于 C/C++ 慢大概 20% ,但这也远远超越 Java 等业务级语言的运行速度,这是一种几乎完美的性能表现。
  • 单一的代码规范,不像 C/C++ 每个公司有每个公司的代码规范,它们甚至大相径庭。但是 Go 语言从编译器的角度,直接将代码规范统一,这是因为编译器只允许这么做,换一种风格甚至会报错。

二、基础语法

从这里开始正式谈论 Go 语言的基础语法以及使用时的一些坑。

  • 变量

Go 语言是一种强类型语言,不允许隐式类型转换。
变量定义有如下三种方式:

var v1 int = 10
var v2 = 10
v3 := 10

Go 语言的语法是后置类型声明,顾名思义,将类型放于变量名的后面。

第一种方式是最完整的声明语法,用 var 代表声明语句,在变量 v 的后面声明变量类型 int 最后初始化为 10 ,当然也可以选择不初始化

var v int

编译器会将 v 默认初始化为类型的零值,也就是 0 。

第二种方式是自动推导类型,类似 C++ 的 auto 。也是用 var 代表声明语句,但是忽略类型 int ,由于初始化的 10 为 int 就把 v2 推导为 int ,类型与 v1 一致。
注意,Go 语言中有符号整型分为 int 、int8 、int16 、int32 、int64 ,自动推导会把字面量推导为 int 。int的大小就是操作系统的位数。

第三种方式是简短声明,与第二种方式相同,也是采用自动推导的方式推导类型。
但是要注意,简短声明只能使用在作用域中,不能使用在全局空间或者说包级变量的声明。

  • 数组

需要注意,Go 语言中的数组是值类型。

var arr [10]int

创建了一个长度为 10 ,元素全为 0 的数组。如果想要初始化元素可以这么做

var arr [10]int{11, 12, 13}

初始化了第 0 个到第 2 个元素,其余元素全为 0 。当然也可以这么初始化

var arr [10]int{2: 10, 4: 11}

用索引选择性初始化第 2 个元素和第 4 个元素为 10 和 11 ,其余元素还是全为 0 。

  • 切片

切片为引用类型,相较于数组,更加适合作为函数传参,因为引用类型拷贝性能好。

可以使用

var slice []int

声明一个零切片,因为是引用类型,所以slice的值为 nil ,这是一个空指针,使用时需要分配内存

slice = make([]int, len, cap)

使用 make 分配内存。第二个参数为长度,第三个参数为容量,所有引用类型都需要 make 来分配内存才能使用。

或者可以这样

slice := make([]int, len, cap)

直接使用简短声明分配并初始化 slice 。

使用 len 函数与 cap 函数获取长度和容量:

len(slice)
cap(slice)

使用 append 为切片添加元素

slice = append(slice, element)

append 函数的返回值需要由 slice 再次接收。因为 append 函数内部添加元素时,可能会对切片进行扩容,扩容后内部传入的局部变量切片的指针指向的内存地址会变化。但是外部的 slice 依旧指向原内存,这是错误的,需要将函数返回的局部变量切片重新赋值给外部切片,这样子外部切片的指针也指向新内存,这样的扩容才算成功,否则操作无效。因为 GC 存在,原切片指向内存不会被释放,函数内扩容的新切片内存因为局部变量切片的释放,而被 GC 回收,所以外部切片无变化。

注意,函数传参为值类型,引用类型本身也是值类型,所以函数内无法改变函数外变量的值,指针除外。

  • 二维切片的使用注意

正确做法:

slice := make([][]int, 10) // 声明一个长度为10的切片
for i := range slice {
    slice[i] = make([]int, 5) // 声明二维切片内每个切片的长度为5
}

错误做法:

slice := make([][]int, 10)
for _, s := range slice {
    s = make([]int, 5)
}

这是一个 10 行 5 列的矩阵。

为什么第一种操作对,而第二种看似正确的操作却错呢,别忘了切片可是引用类型!

对也引用,错也引用。第一种方式,因为 slice 是引用类型,所以虽然 range 后面的 slice 是一份拷贝,但是 slice[i] 由于是引用类型,所以通过指针访问到的内存是和外部 slice 一样的,所以可以正常赋值。

但是第二种操作中的s是彻彻底底的局部变量,将 slice 中的每个切片赋值给s,虽然是引用类型,但是s是局部变量,对引用类型的局部变量做索引操作没问题,因为通过指针访问到的内存是同一块。但是对局部变量进行赋值,那他会在作用域结束而被释放,无法作用到外部切片,等于没赋值,所以操作无效,等于没有,但也不会报错。

  • 神奇的 _ 变量

_ 是 Go 语言里的匿名变量。如果一个变量声明了,但未使用,那编译器会报错,Go 编译器强制你对声明的每一个变量都需要使用,那不想使用怎么办?这个时候 _ 匿名变量派上了用场。

注意:_ 变量相当于编译器已经提前声明了,所以只能赋值,不能声明。

n, _ := fmt.Println()

这里怎么回事呢?_ 似乎被 := 声明了。

但实际上不是,Go 语言对于复合声明有宽松的判定,只要声明的多个变量里,有一个变量未被声明,就可以进行连续声明,所以这里的 _ 只是被赋值了,并没有声明。

所以这样的代码是合法的:

var a int
a, b := 10, 11

a 虽然被声明,但是 b 没有被声明,所以在声明语句里,a 只是被赋值了而已。