在上一篇文章中,我已经介绍了Go的一些基本语法,了解这些已经可以用Go写出来一些基本的逻辑。然而Go相对于其他语言独具特色的是它的另外一些特性,比如Go的切片、函数的使用,还有最重要的特性——协程。
切片
在Go语言中,数组的长度在定义时就被确定,不可以再更改。而切片就是对数组的一种抽象,它实现了动态的数组,可以从数组中截取某一片段,也可以添加新的元素。切片的功能可以类比于Python中对于列表的一些切片操作。
切片的声明有两种形式,可以声明一个未定义长度的数组:
var identifier []type
也可以使用make()函数来创建一个切片:
slice1 := make([]type, length, capacity)
其中type是切片里元素的数据类型,length是切片的初始长度,这两者均为必须定义的参数。capacity是可选参数,定义切片的容量大小。
切片的截取类似于Python中列表的切片操作。众所周知,在Python中,对于一个列表,通过
list_B = list_A[lowerIndex:upperIndex]
的方式可以截取这两个下标之间的子列表。切片也是通过下标的方式进行子切片的截取:
s1 := s[startIndex:endIndex]
此外对于切片,提供了append和copy函数,分别用于切片的添加元素与复制操作。 append函数的使用如下:
s = append(s, 1)
s = append(s, 2, 3, 4)
append可以作用于空的切片,可以添加一个元素,也可以添加多个元素。
copy函数的用法:
copy(b, a)
以上即是把切片a中的元素复制到切片b。
range
类似于Python中的range,Go语言中的range关键字作用于所有可以迭代的数据结构上,例如数组、切片、通道和集合。range通过在for循环中使用,来循环迭代上述数据结构。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。使用方法如下:
for key, value := range oldMap {
newMap[key] = value
}
若不需要用到value,可以把value的定义省略。若不需要使用key,可以把key替换为空操作符_。
Go中的函数
Go的函数结构如下:
func function_name( [parameter list] ) [return_types] {
...
}
在参数列表中,参数的定义均为“变量名 变量类型”的格式。若多个参数的类型是相同的,则可以以“变量a,变量b 变量类型”的格式声明。类似于C,Go的函数参数支持值传递和引用传递,前者传递变量中的内容,后者通过&符,传递变量的引用。
此外,Go中的函数支持返回多个值,类似于Python。返回的各个变量的数据类型要定义在函数声明后面。
协程(Goroutine)
协程是一种轻量级的线程,其在操作系统中通常被称为用户态线程,因为它们是由用户程序自己实现的,而不是由操作系统内核实现的。相比于多线程,每个协程只占用极少的栈空间和一些内存空间,占用资源少。因为只涉及用户态的栈切换和寄存器的保存与恢复,协程的切换成本低。
协程的实现是基于 Go 运行时系统,在程序启动时,Go 运行时系统会创建一个主协程,该协程负责程序的初始化和启动。在程序运行过程中,通过 go 关键字可以创建新的协程。
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
// 协程A 输入1-10
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i * 1
}
}()
// 协程B 接收A的输出并计算平方值
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
// 主协程输出B的结果
for i := range dest {
println(i)
}
}
以上程序定义了两个子协程。被go关键字修饰的函数,都实现了并发的运行。开头定义了src和dest两个通道,通道可以实现协程之间的通信。