basic go grammar
based on a tour of go
包 变量 函数
相同package的不同文件共享变量、函数等
import时可以分组导入写在括号内,也可以拆开写
1
2
3
4
5
6
7import (
"fmt"
"math
)
import "fmt"
import "math"只有第一个字母大写的变量函数等是已导出的(exported)。导入其他包时只能使用已导出的类型。
函数可以接收多个参数,如果参数相同可以只写最后一个,可以有多个返回值,类型在变量之后。具体关于声明位置的思考
1
2
3func add(x, y int, z float64) int {
return x + y + int(z)//z:抹去小数点后
}基于多返回值,swap可以一行完成
1
a,b = b,a
Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。返回值的名称应当具有一定的意义,它可以作为文档使用。没有参数的
return
语句返回已命名的返回值。也就是直接
返回。直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。1
2
3
4
5func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}var
语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。就像在这个例子中看到的一样,var
语句可以出现在包或函数级别。1
2
3
4
5var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
1
2var i, j int = 1, 2 //这里没有int也会定义成int
var c, python, java = true, false, "no!"基本类型
| 类型 | size(byte) | 默认初始值(表达) |
| —————————————————— | ————— | ————————— |
| bool | 1 | false |
| string | - | “” |
| int int8 int16 int32 int64 | - | 0 |
| uint uin8 uint16 uint32 uint64 uintptr | - | 0 |
| byte // uint8 别名 | 1 | 0 |
| rune // int32别名 表示一个unicode 码点 | 4 | 0 |
| float32 float64 | - | 0.0 |
| complex64 complex128 | - | 0+0i |类型转换必须显式进行
没有类似char类型的变量,s[i]取出来的都是byte类型,s[a:b]倒是一个string,要打印单个字符还是要再转换回string:
string(c)
或者格式化输出%q未指明的类型(
a := ...
;var = ...
)会尝试进行推导常量用
const
限定,可以是字符、字符串、布尔值或数值,不能用:=
声明一个未指定类型的常量由上下文来决定其类型。
1
2
3
4
5
6
7const (
// 将 1 左移 100 位来创建一个非常大的数字
// 即这个数的二进制是 1 后面跟着 100 个 0
Big = 1 << 100
// 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2
Small = Big >> 99
)//注意这个big定义是可以的,不会爆,但是如果想用int方式用就会在那里爆
格式化输出
fmt中几种输出
Print: 输出到控制台(不接受任何格式化,它等价于对每一个操作数都应用 %v)
Println: 输出到控制台并换行
- Printf : 只可以打印出格式化的字符串。只可以直接输出字符串类型的变量(不可以输出整形变量和整形 等)
- Sprintf:格式化并返回一个字符串而不带任何输出。
- Fprintf:来格式化并输出到 io.Writers 而不是 os.Stdout。
1 | fmt.Print(str) |
1 | 普通占位符 |
控制流
for
Go只有for
没有while
。基本的 for
循环由三部分组成,它们用分号隔开:
- 初始化语句:在第一次迭代前执行,可以空
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行,可以空
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { }
则是必须的。当只有条件判断时,分号可以省,这时候它长得就像while。
1 | for i := 0; i < 10; i++ { |
if
无需小括号,大括号必须。
同 for
一样, if
语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if
之内。
1 | if v := math.Pow(x, n); v < lim { |
switch
相比于C的switch
,Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break
语句。 除非以 fallthrough
语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
1 | switch os := runtime.GOOS; os { |
switch 的 case 语句从上到下顺次执行,直到匹配成功时停止,后续不会被执行,即使是个函数。
没有条件的 switch 同 switch true
一样。这种形式能将一长串 if-then-else 写得更加清晰。
1 | switch { |
defer
defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。如果return
被拆解成rval=...
和ret
,defer语句可以理解成插在中间。
它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源、打印结束语,以及错误处理(try-catch-finally)。
1 | func f() { |
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
运行推迟的函数可能对返回值有影响!!
1 | func c() (i int) { |
详细解读可见此文
指针和复杂结构
pointer
类型 *T
是指向 T
类型值的指针。其零值为 nil
: var p *int
&
操作符会生成一个指向其操作数的指针。p = &i
*
操作符表示指针指向的底层值。*p = 21
Go 没有指针运算。(如p++)
struct
一个结构体(struct
)就是一组字段(field),使用点号来访问成员。如果p是一个指针,可以用p.X
访问成员(类似c中p->x)
初始化时,使用 Name:
语法可以仅列出部分字段。(字段名的顺序无关。)
1 | type Vertex struct { |
数组
类型 [n]T
表示拥有 n
个 T
类型的值的数组。数组的长度是其类型的一部分,因此数组不能改变大小。
1 | primes := [6]int{2, 3, 5, 7, 11, 13} |
切片 slice
类型 []T
表示一个元素类型为 T
的切片。切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔,它会选择一个半开区间,包括第一个元素,但排除最后一个元素。默认上下界是0和长度,因此可以省略。
1 | primes := [6]int{2, 3, 5, 7, 11, 13} |
切片是数组的引用,切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。
切片文法类似于没有长度的数组文法。下面这样会创建一个数组,然后构建一个引用了它的切片:
1 | s := []struct { |
切片拥有 长度 和 容量。切片的长度就是它所包含的元素个数。切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。切片 s
的长度和容量可通过表达式 len(s)
和 cap(s)
来获取。可以理解为切片有一个左指针和右指针,左指针只能往右走,即左边的元素丢了就是丢了,又指针还可以扩展回去。
切片的零值是 nil
。nil 切片的长度和容量为 0 且没有底层数组。
切片可以用内建函数 make
来创建,make
函数会分配一个元素为零值的数组并返回一个引用了它的切片,要指定它的容量,需向 make
传入第三个参数:
1 | a := make([]int, 5) //len=5 cap=5 [0 0 0 0 0] |
切片可包含任何类型,甚至包括其它的切片。
1 | board := [][]string{ |
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append
函数。func append(s []T, vs ...T) []T
1 | var s []int |
要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。
1
2
3
4
5
6 > t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
> for i := range s {
> t[i] = s[i]
> } //or use copy(t,s)
> s = t
>
初始化二维切片,大致有两种方法:
1 | func main() { |
对于slice,更多分析见此文
range
for
循环的 range
形式可遍历切片或映射。当使用 for
循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。可以将下标或值赋予 _
来忽略它。若你只需要索引,忽略第二个变量即可。
1 | pow := make([]int, 10) |
映射 map
映射将键映射到值。映射的零值为 nil
。nil
映射既没有键,也不能添加键。make
函数会返回给定类型的映射,并将其初始化备用。
1 | var m map[string]int |
插入、修改、获取、删除、检测类似python字典
1 | m[key] = elem // insert or modify |
函数闭包
类似c,函数可以作为一种值,可以传递。
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
1 | func adder() func(int) int { |