包、变量和函数
1 | package main |
包
每个Go程序都是由包构成。
程序从main包开始运行。
导入包
分组形式导入包(推荐)
1 | import ( |
编写多个导入语句
1 | import "fmt" |
导出名
名字以大写字母开头是导出名,如Println。未导出的名字在该包外无法访问。
函数
类型在变量名之后。
1 | func add(x int, y int) int { |
当连续两个或多个函数的已命名形参类型相同时,可以只写最后一个的类型。
1 | func add(x, y int) int { |
与上面的例子一样。
多值返回
1 | func swap(x, y string) (string, string) { |
命名返回值
返回值可以被命名,它们被视作定义在函数顶部的变量。
返回值名称应当具有一定意义,它可以作为文档使用。
没有参数的return语句返回已命名的返回值,也就是直接返回。
直接返回语句应当仅用在下面的短函数中,长函数会影响代码可读性。
1 | func split(sum int) (x, y int) { |
变量
var语句用于声明一个变量列表。var语句可以出现在包或者函数级别。
1 | var c, python, java bool |
1 | 0 false false false |
初始化
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
1 | var i, j int = 1, 2 |
1 | 1 2 true false no! |
短变量声明
在函数中,简介赋值语句:=可代替var。
在函数外必须以关键字(var,func等等)开始,不能用:=。
1 | func main() { |
基本类型
基本类型有
1 | bool |
变量声明可以和导入语句一样“分组”成一个语法块。
1 | var ( |
1 | Type: bool Value: false |
零值
没有明确初始值的变量声明被赋予零值。
数值类型为0,布尔类型为false,字符串为""(空字符串)。
类型转换
表达式 T(v) 将值 v 转换为类型 T。Go 在不同类型的项之间赋值时需要必须显式转换。
一些关于数值的转换:
1 | var i int = 42 |
或者,更加简单的形式:
1 | i := 42 |
类型推导
声明变量而不指定其类型时(即不带类型的:=或var = ),变量类型由右值推导得出。
当右值声明了类型时,新变量的类型与其相同:
1 | var i int |
当右值没有明确类型时,新变量的类型取决于右值常量的精度:
1 | i := 42 // int |
常量
使用const关键字。
常量不能用:=语法声明。
1 | const Pi = 3.14 |
字符串操作
标准库strings里面有很多常用的字符串工具函数。比如contains判断一个字符串里面是否有包含另一个字符串,count,index查找某个字符串位置,join连接多个字符串。
字符串格式化
可以用标准fmt库里面的相关方法。比如用Printf时可以用%v来打印任何类型的变量,用%+v打印详细结果。
流程控制语句:for、if、else、switch和defer
for
无序小括号
1 | sum := 0 |
for是Go中的“while”
去掉分号的for
1 | sum := 1 |
无限循环
省略循环条件
1 | for { |
if
无需小括号
if简短语句
和for一样,if可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在if之内。
1 | func pow(x, n, lim float64) float64 { |
1 | 9 20 |
if和else
switch
Go 只运行选定的 case,而非之后所有的 case。Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束。
Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
1 | fmt.Print("Go runs on ") |
无条件的switch
同switch true
1 | t := time.Now() |
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
1 | func main() { |
1 | hello |
defer栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
1 | func main() { |
1 | counting |
更多类型:struct、slice和映射
指针
类型*T是指向T类型值的指针。其零值为nil。
1 | var p *int |
&操作符会生成一个指向其操作数的指针。
1 | i := 42 |
*操作符表示指针指向的底层值。
1 | fmt.Println(*p) // 通过指针p读取i |
结构体
一个结构体(struct)就是一组字段(field)。
1 | type Vertex struct { |
1 | {1 2} |
结构体字段
结构体字段使用.来访问。
1 | v.X = 4 |
结构体指针
通过(*p).X来访问其字段,也可以隐式间接引用p.X
1 | v := Vertex{1, 2} |
结构体用法
结构体通过直接列出字段的值来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
1 | type Vertex struct { |
JSON处理
对于struct类型,可以用encoding/json包里面的json.Marshaler来序列化或者反序列化。
数组
表达式
1 | var a [10]int |
切片
切片为数组提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
形式:[]T
切片通过上届和下界来界定:a[low : high],左闭右开
1 | primes := [6]int{2, 3, 5, 7, 11, 13} |
切片就像数组的引用
切片不储存任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改。
切片用法
类似于没有长度的数组。
1 | []bool{true, true, false} |
切片下界的默认值为 0,上界则是该切片的长度。
1 | var a [10]int |
切片的长度与容量
假设有切片s
长度:它所包含的元素个数。通过len(s)获取。
容量:从它的第一个元素开始数,到其底层数组元素末尾的个数。通过cap(s)获取。
1 | func main() { |
nil切片
切片的零值是nil,nil切片的长度和容量为0且没有底层数组。
1 | var s []int |
用make创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
1 | a := make([]int, 5) // len(a)=5 |
要指定它的容量,需向 make 传入第三个参数:
1 | b := make([]int, 0, 5) // len(b)=0, cap(b)=5 |
切片的切片
1 | board := [][]string{ |
向切片添加元素
用append函数。
原型:
1 | func append(s []T, vs ...T) []T |
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
1 | var s []int //len=0 cap=0 [] |
range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
1 | var pow = []int{1, 2, 4, 8} |
可以将下标或值赋予 _ 来忽略它。
1 | for i, _ := range pow |
若只需要索引,忽略第二个变量即可。
1 | for i := range pow |
map映射
映射将键映射到值。映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。
1 | type Vertex struct { |
1 | type Vertex struct { |
若顶级类型只是一个类型名,你可以在文法的元素中省略它。
1 | var m = map[string]Vertex{ |
修改映射
在映射 m 中插入或修改元素:
1 | m[key] = elem |
获取元素:
1 | elem = m[key] |
删除元素:
1 | delete(m, key) |
通过双赋值检测某个键是否存在:
1 | elem, ok = m[key] |
若 key 在 m 中,ok 为 true ;否则,ok 为 false。若 key 不在映射中或者不存在,那么 elem 是该映射元素类型的零值。
注 :若 elem 或 ok 还未声明,可以使用短变量声明:
1 | elem, ok := m[key] |
函数值
函数也是值。它们可以像其它值一样传递。函数值可以用作函数的参数或返回值。
1 | func compute(fn func(float64, float64) float64) float64 { |
方法和接口
方法
Go 没有类。不过你可以为结构体类型定义方法。方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
1 | func (v Vertex) Abs() float64 { |
在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。
方法只是个带接收者参数的函数。
1 | func Abs(v Vertex) float64 { |
在为非结构体类型声明方法时,接收者的类型定义和方法声明必须在同一包内;不能为基本类型声明方法。
1 | type MyFloat float64 |
指针接收者
可以为指针接收者声明方法。同样,接受者指针指向的类型不能是基本类型。指针用的更多。
1 | func (v *Vertex) Scale(f float64) { |
以指针为接收者的方法被调用时,接收者既能为值又能为指针:
1 | func (v *Vertex) Scale(f float64) { |
Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。
同理,而以值为接收者的方法被调用时,接收者既能为值又能为指针。
接口
错误
用一个函数来处理错误。错误处理是可以自定义的,
1 | type MyError struct { |
并发
goroutine(Go程)
goroutine是由Go运行时管理的轻量级线程。