GO语言趣学指南-Notes
预备
在 Linux 中编译 .go
文件:
1 |
|
将编译好的 go 二进制安装到系统中:
1 |
|
示例代码:
1 |
|
用 package
声明该文件代码 所属的包 , 如这里包的名字就是 main
.
用 import()
导入所需的包, 可以连续导入多个包, 每个似乎占一行即可.
用 func name() {}
定义函数.
调用包内函数用 .
注意, main
函数, 在运行 GO 程序的时候, 它总是从 main
包的 main
函数开始运行. 如果 mina
函数不存在, GO 编译器会报错.
唯一允许的大括号放置风格
左大括号 {
与 func
关键字位于同一行, 右大括号 }
独占一行.
(不仅是 func
, if
等语句也相同)
如:
1 |
|
这种风格的原因为: 方便编译器自动为每条语句添加分号 ;
.
注释
用 //
做单行注释.
用 /**/
做多行注释.
1 命令式编程
fmt.Println
会自动换行, 同 Perl 语言的 say
.
fmt.Print
不会自动换行.
格式化输出用 fmt.Printf
, 用法和 C 相同.
常量和变量
常量用 const
声明.
变量用 var
声明.
可以一次声明多个变量:
1 |
|
操作符
不支持前置增量操作, ++count
引入子模块
如:
1 |
|
2 循环和分支
预声明 Boolean 变量
true
, 唯一真值false
, 唯一假值
也就是说 1
不算真值, 0
也不算假值.
比较运算符
比较字符串和数字用同一套. 但是不允许数字和字符进行比较.
if 和 else if
如:
1 |
|
可以不添加括号 ()
逻辑运算符
&&
, 逻辑与||
, 逻辑或!
, 逻辑非
switch
1 |
|
不会自动下降, 需添加 fallthrougn
:
1 |
|
注意这里的 case
不需要 {}
.
variable
处也可以添加括号 ()
.
case
和 switch
不一定要在同一列.
循环
1 |
|
可以用 break
跳出循环.
也可以为:
1 |
|
3 变量作用域
简短声明
1 |
|
这两行等效.
若要在 for
循环时声明变量, 则需要用简短声明:
1 |
|
(同 if
, switch
等)
4 实数
在不指定类型时, Go 编译器会自动推断.
如:
1 |
|
Go 会把所有带小数点的数字设置为 float64
类型.
显示声明的语法:
1 |
|
单精度浮点数用 float32
零值
每种类型的默认值都称为 零值 (zero value)
注意:
1 |
|
结果为 false
, 因为 test
的实际值为 0.3000000000004
5 整数
整数类型包括:
int
uint
还有如:
int8
uint8
int16
uint16
等.
查看变量类型
1 |
|
用 %b
可以以二进制的形式打印出整数值:
1 |
|
6 大数
处理很大的数字用 big
包.
7 多语言文本
声明字符串类型用 string
, 如:
1 |
|
用反引号包裹的字符串和 Perl 中用单引号包裹类似. (可以很方便输出多行文本)
字符, 代码点, 符文和字节
两个类型:
rune
(int32
的别名)byte
(uint8
的别名)
拉弦
可以将不同的字符串赋值给同一个变量, 但无法对字符串本身进行修改:
1 |
|
(是可以的)
用 []
访问字符串中的特定字符:
1 |
|
8 类型转换
在 Go 中变量类型不能混合使用.
如:
1 |
|
12 函数
声明如:
1 |
|
调用如:
1 |
|
注意, 在 Go 中, 以大写字母开头的函数, 变量以及其他标识符都会被导出并对其他包可用. (以小写字母开头的函数无法从外部访问)
返回多个值的声明:
1 |
|
可变参数
在变量类型前添加 ...
:
1 |
|
返回值还是用 return
编写函数
注意, 在同一个包中声明的函数在调用时不需要加上包名作为前缀.
13 方法
声明新类型 (注意不是给类型起别名, 属于不同类型了), 用 type
:
1 |
|
通过方法为类型添加行为
Go 提供了方法, 但是没有提供类和对象.
可以将方法和同一个包中声明的任何类型相关联.
声明方法:
1 |
|
这里 func
关键字之后的称为 接收者 , 每一个方法有且只能有一个接收者, 接收者的类型即绑定的类型.
14 一等函数
在 Go 语言里, 函数是 一等值 , 即: 可以将函数赋值给变量, 可以将函数传递给函数, 也可以编写创建并返回函数的函数.
将函数赋值给变量
如:
1 |
|
将函数传递给其他函数
如:
1 |
|
有返回值则需要指出.
声明函数类型
如:
1 |
|
将一个不接受任何参数, 返回值为 test
类型的函数的类型令为 testfunc
.
则声明一个需要以函数作为参数的函数为:
1 |
|
等价于:
1 |
|
闭包和匿名函数
将匿名函数赋值给变量:
1 |
|
直接调用一个匿名函数:
1 |
|
15 数组
如:
1 |
|
同一个数组中的每个元素都具有相同的类型.
查看数组长度
用 len
(内置函数) 查看数组长度.
1 |
|
初始化
1 |
|
(也就是用大括号 {}
)
自动计算数组长度
用 ...
如:
1 |
|
注意结尾的逗号 ,
不能省略
二维数组
如:
1 |
|
16 数组切片
注意切片和数组是两种不同的类型.
如:
1 |
|
获得一个跟底层数组具有同样元素的切片:
1 |
|
直接创建一个切片 (好处在于不用指定长度):
1 |
|
传递给一个函数切片时, 也不用指定长度:
1 |
|
在 Go 中, 很多时候都使用切片而不是数组
带有方法的切片
给切片绑定方法:
1 |
|
17 更大的切片
给切片追加元素
用 append
函数:
1 |
|
当切片容量不足以执行 append
时, 才会创建新数组并复制旧数组中的内容.
长度和容量
用 len
函数查看切片长度, 用 cap
查看切片容量.
切片的容量是按倍数增长的
当切片容量改变时, 则会分配新的数组.
三索引切片操作
第三个参数用于指定容量:
1 |
|
用 make 函数对切片实行预分配
用 make
指定切片的长度和容量:
1 |
|
前者为长度, 后者为容量.
18 映射 (即字典, 哈希)
如:
1 |
|
即, 以 string
类型为键, int
类型为值.
如果键不存在, 则返回相应类型的零值.
“逗号与ok” 语法
1 |
|
用 make 为映射预分配
如:
1 |
|
遍历映射
1 |
|
19 结构
声明:
1 |
|
访问, 用 .
点标记法:
1 |
|
复用结构
1 |
|
初始化
1 |
|
或直接:
1 |
|
结构切片
1 |
|
17 Go 没有类
Go 没有为构造器提供特殊的语言特性, 单纯利用函数即可.
构造器的命名惯例为: newtype
或 NewType
18 组合与转发
组合, 指用不同的组件来描述一个行为. 如你有描述走路, 跑步, 学习的函数, 可以通过这些组件来描述学生, 也可以用来描述一个机器人, 而不同于类, 将函数绑定在一个类别上.
转发, 指让一个类别能使用的方法, 也能让另一个类别使用.
自动转发
将类型嵌入到结构的定义中:
1 |
|
如这里的 temperaturel
和 location
, 只写类型, 不指定字段名.
此时 report
类型就能使用 temperature
和 location
的方法.
虽然这里没有指明字段名, 但结构会自动为被嵌入的类型生成同名的字段:
1 |
|
嵌入不仅会转发方法, 还能够让外部结构直接访问内部结构中的字段.
(感觉就是隐藏了 .temperature
这种)
18 接口
用于写入的接口 Writer
接口类型
接口通过列举类型必须满足的一组方法来声明
如:
1 |
|
这里列举出的方法为 talk() string
若一个类型绑定了一个名为 talk
, 返回值类型为 string
的方法, 其就可以被赋值给 interface.
如:
1 |
|
一般接口类型的名称以 -er
为后缀.
19 指针
Go 的指针操作和 C 语言相同:
&
, 为地址操作符 (取址)*
, 接引用 (提供内存地址指向的值)
注意, go 中不允许 address++
这样的指针运算操作.
声明如:
1 |
|
指向结构体的指针
1 |
|
指向数组的指针
1 |
|
20 nil
nil
是 Go 中的一种零值, 如指针, 切片, 映射和接口等.
对空指针解引用常常会导致程序崩溃.
21 错误处理
Go 的错误处理利用其返回多个值的机制.
在没有发生错误的情况下, 函数返回的错误值为 nil
如:
1 |
|
关键字 defer
defer
关键字能保证操作会在函数返回之前触发:
1 |
|
这里保证了文件能被关闭.
创造性的错误处理
如:
1 |
|
新的错误
用 errors
包来生成一个错误信息, 但这个错误信息不会导致程序退出, 如:
1 |
|
这里的 error.New
生成一个错误.
有时, 可以先声明错误变量来保存错误信息:
1 |
|
Panic
其会导致程序退出. 如:
1 |
|
22 并发编程, goroutine 和 并发
Go 用 goroutine
(不是一个关键字或函数, 只是表明 go 的 routine) 并发代码, 并用通道 (channel) 实现 goroutine
之间的通信.
启动一个 goroutine
只需加上 go
如:
1 |
|
不止一个 goroutine
这里实际上不是都同时运行, 而是采用 分时 的方式.
通道
通道 (channel) 可以在多个 goroutine 之间传递值.
创建通道, 用 make
函数:
1 |
|
这里创建了一个 int
类型的通道, 可用于发送和接收整数值.
用 <-
(左箭头操作符) 用于接收和发送值.
发送如:
1 |
|
注意 , 发送操作会等待直到有另一个 goroutine
尝试对相同的通道执行接收操作为止.
接收如:
1 |
|
注意 , 接收操作会等待直到有另一个 goroutine
尝试对相同的通道执行发送操作为止.
用 select 处理多个通道
select
和 switch
语句类似, 其包含的每一个 case
分支都持有一个针对通道的接收和发送操作.
time.After
函数会返回一个通道, 该通道会在经过特定时间之后接收到一个值.
如:
1 |
|
阻塞和死锁
goroutine
被阻塞时并不消耗任何资源.
当一个或多个 goroutine
因为某些永远无法发生的事情而被阻塞时, 被称为 死锁, 出现死锁的程序通常会崩溃或被挂起.
23 并发状态
把多个 goroutine
争相使用值的情况称之为 竞态条件
互斥锁
goroutine
可以利用互斥锁阻止其他 goroutine
在同一时间进行某些事情.
这里的 “互斥”, 指 “相互排斥”.
互斥锁 有 Lock
和 Unlock
两个方法.
通过 sync
包引入.
使用如:
1 |
|
这里 sync.Mutex
声明的锁是一个 “未上锁的互斥