GO 语言学习
开始了GO语言的学习,做点自认为有意思的笔记。
笔记摘抄自慕课教程 Go入门教程
Go 语言的 :=
单变量 :=
Go语言中新增了一个特殊的运算符 :=,可以是变量在未声明的情况下被赋值使用。
使用方法与带值声明变量类似,只是少了var关键字,使用例子如下:
1 | package main |
多变量 :=
在 Go 语言的多变量赋值体系中,也支持了:=运算符。你可以使用形如变量名,变量名:=变量值,
变量值的形式来进行多变量操作。其使用方法和多变量带值声明类似,只是少了var关键字。
1 | package main |
Go语言的整形(int)数据类型
定长类型
类型 | 范围 |
---|---|
int8 | -128~127 |
int16 | -32768~32786 |
int32 | -2147483648~2147483647 |
int64 | -9223372036854775808~9223372036854775807 |
不定长类型
1 | <br></br> |
GO语言中代替枚举的办法
枚举类型用于声明一组命名的常量,当一个变量有几种可能的取值时,可以将它定义为枚举类型。在 Go 语言中,并没有提供枚举类型,但是枚举类型又是开发过程中非常重要的数据类型。因为它可以事先定义一组有效的值,可以有效地防止用户提交无效数据,抽象到业务场景中就是我们平时网页上遇到的下拉框,或者我们选择快递地址时的省市信息,均为枚举类型的用武之地。所以在 Go 语言中对常量进行了一定的扩展,使其可以完美地替代枚举类型。
常量中的iota
为了使常量可以更好地替代枚举类型,Go 语言提供了一个iota关键字。使用iota初始化常量,可以生成一组规则类似的常量,但是不用每个常量都写一遍表达式。在一个const()表达式中,从iota开始的常量会被置为0,向后每个常量的值为前一个常量的值加一:
1 | package main |
将枚举值转换为字符串
使用iota是可以使用 Go 语言的常量代替枚举类型,但是由于输出值均为阿拉伯数字,给调试和辨识造成了一定的困难。为了解决这一问题,Go 语言还提供了使常量枚举值输出为字符串的办法。需要我们手动构造一个使用枚举类型输出字符串的方法。
1 | package main |
注:不常使用,稍作了解即可。
GO语言的循环语句
在GO语言中减少了循环语句的关键字,仅有一个for
关键字
但不仅没有减少其功能,而且还兼容了其他语言中的while
关键字的用法,甚至更强大。
for循环语句
普通用法
在GO语言中,for循环之后一样可以跟三个语句(单次表达式; 条件表达式; 末尾循环体)
但是它不需要()
来包裹这三个表达式,写法上也更加简洁。例子如下:
1 | package main |
1 | package main |
代替while的用法
在其它大部分语言中 for 循环中省略单次表达式和末尾循环体其实就和其它语句中的 while 用法一致了。
所以在 Go 语言中,直接使用省去单次表达式和末尾循环体的 for 循环语句来代替 while 的用法.
为了简便代码书写,Go 语言中 for 循环语句在省去单次表达式和末尾循环体时,可以不写分号。
1 | package main |
for语言中的break
和continue
在我们的生产环境中,经常需要用到死循环的场景。所以在 Go 语言中新增了一个 for 关键字死循环的用法,让程序员不用写无聊的for(;;){}
和do{} while(1)
同时可以使用break
和continue
来控制循环。break
和 continue
的逻辑和语法类似,故笔记只有break
。
break跳出单层死循环
1 | package main |
break跳出多层死循环
1 | package main |
Go语言的通道
Go 语言中有一个特殊的类型 chan,这是在 Go 语言的多线程操作中非常重要的一个数据类型。它的一般用于线程之间的数据传输,所以这个操作类型叫做 “ 通道 (channel)”。
通道的声明和定义
通道可以理解为一种特殊的变量,所以它的声明和其它变量的声明并没有太大的区别,声明通道变量的写法如下:
1 | var c chan int //声明一个存放int类型数据的通道 |
声明之后不能被直接使用,要通过内置函数make()
来创建一下通道变量才可以使用:
1 | var c chan int //声明一个存放int类型数据的通道 |
所以一般最好使用:=
来同时声明和创建:
1 | c := make(chan int, 1) //声明并创建一个存放int类型数据的通道 |
通道的使用
在 Go 语言中,使用 <- 符号来向通道中塞取数据。放在通道右边 chan <-,就是塞数据,放在通道左边 <- chan ,就是取数据。
1 | func main() { |
通道结合 select 流程控制
在 Go 语言中为了更方便的利用通道的功能,提供了一个仅用于通道的流程控制语句:select...case
使用这个语句可以同时监听数个通道,非常适合用于并发时的进程调度,或者模块之间的解耦合。
1 | package main |
Go语言函数中的defer
在 Go 语言中的 defer 关键字就是 Go 语言中延迟语句的标志。Go 语言会在函数即将返回时按逆序执行 defer 后的语句。也就是说先被 defer 的语句后执行,最先执行最后被 defer 的语句。defer 和有些语言中的 finally 语句块的用法类似,一般都用于释放一些资源,最常用的地方就是进程锁的释放。
defer的逆序执行
defer
会在函数即将结束的时候执行,而且是按照defer
的顺序逆序执行。
1 | package main |
输出结果:
1 | Hello Codey! |
defer中的变量
defer
关键字之后若有变量,则defer
记录的是在defer
时的变量值,而不是最后函数结束时的变量值:
1 | package main |
输出结果:
1 | print时a的值为100 |
defer注意小结
defer
是先声明后执行的语句模式。defer
会在函数即将结束的时候统一执行。defer
中的变量值是不会被defer之后的语句改变。
Go语言中的闭包
简单的说 Go 语言的闭包就是一个引用了外部自由变量的匿名函数,被引用的自由变量和该匿名函数共同存在,不会因为离开了外部环境就被释放或者删除,还可以在这个匿名函数中继续使用。
Go语言的匿名函数
匿名函数,顾名思义,就是隐藏函数名的函数。
1 | package main |
匿名函数引用外部变量
如果在匿名函数内,使用了外部环境的变量,就构成了一个闭包。简单来讲就是一个函数内,使用匿名函数来操作函数内声明的变量。
1 | package main |
运行结果:
1 | Hell Codey! |
上述例子简单的构造了一个闭包,在匿名函数中并没有声明或者定义str这个变量,但是可以直接操作,就是引用可main函数中的自由变量。这个例子可能对自由变量的引用表现不是很直观,我们接下来使用defer和闭包相结合,深入了解一下闭包中的引用外部变量。
1 | package main |
运行结果:
1 | main str = Hello Codey! |
前文提defer
的时候明确介绍了defer
后变量是保留它在defer
时的值,而不会被defer
之后的代码所改变。但是在闭包这边这个看起来不太适用,其实是适用的,只是闭包是引用了这个变量,也就是说,在defer
时被保留下来的是这个变量的地址,后续代码改变的不是地址,而是这个地址存储的值,所以后续代码对这个变量的操作,都会反应到这个 defer
中。
Go语言中的切片
Go语言中可变长度的”数组”——切片slice
切片的创建
切片的声明方式和数组类似,写法上看就是声明一个没有长度的数组:var 切片名 []切片类型
。其中切片类型可以是切片本身,也就是切片的切片,就构成了多维的切片。切片在使用之前必须要初始化,它没有零值。声明后它的值是 nil,这是因为它的底层实现是一个指向数组的指针,在你给它存入一个数组的地址之前,它只能是 nil。
1 | package main |
切片的截取
切片之所以被叫做切片是有原因的,它可以从任意长度开始切,切到任意长度为止,然后这一段拿出来就是一个新的切片。
切割形式为 切片名(s)[起始下标(begin):结束下标(end):最大容量(max)]
切片截取形式表
|操作| 含义|
| —- | —- |
|s[begin?max] | 截取切片s从begin到end的数据,构成一个容量为max-begin,长度为begin-end的切片。(用的不多)|
|s[begin:end] | 截取切片s从begin到end的数据,构成一个容量和长度均为begin-end的切片。|
|s[begin:] | 截取切片s从begin到最后的数据,构成一个容量和长度均为len(s)-end的切片。|
|s[:end] | 截取切片s从0到最后的数据,构成一个容量和长度均为end-0的切片。|
1 | package main |
切片的追加
切片使用一个 Go 语言的内置函数append(切片,待添加的值)
,来进行切片末尾元素的追加。
1 | package main |
切片的长度和容量
在切片中可以使用len()
获取切片中元素的数量,也就是切片的长度。使用cap()
可以获取切片引用的数组的长度,也就切片的容量。切片的容量一般大于等于长度,容量会随着长度的增长而增长。
在初始化一个切片的时候其实时给切片引用了一个数组,然后容量就是这个数组的长度,然后如果切片的长度超过了切片的容量,它就会让切片引用一个容量更大数组来存放这些元素。
1 | package main |
能从执行结果看到,在切片a每次添加的元素要超过它的容量时,它的地址就会发生改变,其实就是让它引用了一个新的容量更大的数组。
小结
切片在使用前需要初始化。
切片的本质是一个指针数组,但是它的地址会随着长度超过容量而改变。
在应用场景中一般都使用切片。
Go语言中的Map
map
是一种元素对的无序集合,每一个索引(key)对应一个值(value)。map
是一种能够通过索引(key)迅速找到值(value)的数据结构,所以也被称为字典。在 Go 语言中因为线程安全问题,一共实现了两种类型的map
。
无锁的map
这种类型的 map 是线程不安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,会有读写冲突,会导致系统奔溃。所以一般在单线程程序中使用的较多。
map的创建
map 的底层结构也是一个指针,所以和变量不同,并不是声明后立刻能够使用。和切片相同,需要使用make()函数进行初始化。在初始化之前为空,没有零值。
1 | package main |
map的赋值
map 的赋值有两种方式:
使用:=使map在定义的时候直接赋值。
使用map[key]=value的形式对map进行赋值。
在明确知道 map 的值的时候就可以使用第一种方式进行赋值,比如说在建立中英文对应关系的时候。在未知 map 的取值时,一般建议使用后者进行赋值。
1 | package main |
map的遍历
map 是字典结构,如果不清楚所有 key 的值,是无法对 map 进行遍历的,所以 Go 语言中使用了一个叫做range的关键字,配合for循环结构来对map结构进行遍历。
1 | package main |
注意:map是无序的,所以每次输出的顺序可能会不一样。
map的删除
map 在普通的用法中是无法移除只可以增加 key 和 value 的,所以 Go 语言中使用了一个内置函数delete(map,key)来移除 map 中的 key 和 value。
1 | package main |