tour-of-go
Tour of go
Part1
包
Go程序由包组成
程序入口是main包
1 | package main |
导入包有两种写法
1 | import "fmt" |
使用分组导入语句(第一种)要更好。
使用导出包需要名字以大写字母开头
例如math.Pi
变量申明
var用于申明变量
变量声明可以包含初始值,如果提供了初始值,则类型可以省略;变量会从初始值中推断出类型。
go申明变量需要把变量类型放在后面 例如
1 | var a int = 10 |
在函数中也是类似
1 | func add(x int, y int) int { |
连续多个已命名形参类型相同,可以省略前面的,只申明最后一个
函数
1 | sum := func(a, b int) int { return a+b } (3, 4) |
这句话的作用是定义匿名函数并立即调用,最终把结果赋值给 sum。
下面写法效果相同:
1 | tmp := func(a, b int) int { return a + b } |
函数的返回值数量是任意的
例如
1 | func swap(x, y string) (string, string) { |
函数的返回值甚至可以被命名,会被视作定义在函数顶部的变量
没有参数的 return 语句会直接返回已命名的返回值,也就是「裸」返回值。
1 | func split(sum int) (x, y int) { |
上述代码中return 返回了x,y
基本类型
1 | bool |
其中int、uint 和 uintptr 类型与系统位数相同
零值(未被初始化赋值的变量)
1 | 数值 0 |
Go中的类型转换均需要显式转换
1 | var i int = 42 |
新声明一个变量,用已知的另一个变量直接赋值,新变量的类型与其相同
常量
常量的声明需要const关键字
const World = "世界"
for
for是Go语言中唯一的循环结构
与C语言类似,但是没有小括号,且必须加大括号
示例如下
1 | sum := 0 |
左右两个;可以省略,省略后也就等于while
1 | sum := 1 |
如果省略循环条件,就会无限循环
1 | func main() { |
if
Go中的if判断也是无需小括号,但是要加大括号
1 | if x < 0 { |
可以在if中声明变量,声明的变量可以在else块中使用
1 | func pow(x, n, lim float64) float64 { |
switch
Go中的switch只会运行选定的case,运行之后就会break,跳过其他所有的case,如果需要继续往下运行,需要在case的语句末端加上fallthrough
switch的case无需为常量,且取值不限于整数
1 | func main() { |
switch之后可以不跟条件, 能将一长串 if-then-else 写得更加清晰。
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
推迟调用的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的调用会按照后进先出的顺序调用。
1 | func main() { |
指针
指针的零值是nil
var p *int
其余操作和C语言类似
1 | p := &i // 指向 i |
结构体
一个 结构体(struct)就是一组 字段(field)。
1 | type Vertex struct { |
访问
指针形式可以使用(*p).X,也可以直接用p.X
1 | v := Vertex{1, 2} |
结构体字面量
使用 Name: 语法可以仅列出部分字段(字段名的顺序无关)。
特殊的前缀 & 返回一个指向结构体的指针。
1 | var ( |
数组
表达式
1 | var a [10]int |
数组切片 a[low:high] 左闭右开
1 | var s []int = primes[1:4] |
切片并不储存任何数据,只是描述底层数组中的一段,因此改变切片数据会改变底层数组元素
1 | func main() { |
切片字面量类似于没有长度的数组字面量。
1 | slice := []bool{true, true, false} |
等价于
1 | tmpArray := [3]bool{true, true, false} // 编译器自动创建的底层数组 |
意为创建“一个数组 + 一个引用它的切片”
切片字面量确实会创建一个“底层数组”,但这个数组 不会绑定到一个变量名上,而是由 切片内部自动引用。你无法直接访问这个底层数组,但切片能“看见”它。
切片忽略上下界和python一样
1 | //var a[10]int |
是一个意思
切片具有长度和容量
长度:len(s) 就是它所包含的元素个数。
容量:cap() 是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片的零值是nil,长度和容量都为0且没有底层数组
1 | var s []int |
用make创建切片
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
1 | a := make([]int, 5) // len(a)=5 |
切片可以包含切片
向切片追加元素
append函数
1 | func append(s []T, vs ...T) []T |
s是一个类型T的切片,其余类型T的会追加到末尾
append的用法一般是
1 | s = append(s,2,3,4) |
append会拓展数组
range遍历
for的range形式用以遍历切片或map
1 | var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} |
range遍历每次都会返回两个值
i,v分别为下标和对应的值,如果要省略其中一个就把要省略的的写成_,只需索引可以忽略第二个变量
map映射
将键映射到值,零值为nil,没有键也不能添加键
需要用make进行初始化
1 | type Vertex struct { |
1 | type Vertex struct { |
make可以加一个可选参数指定Map的初始容量
若顶层类型只是一个类型名,那么你可以在字面量的元素中省略它。
1 | var m = map[string]Vertex{ |
函数
函数值可以作为其他函数的参数/返回值
函数可以是一个闭包
1 | func adder() func(int) int { |
sum 是在 adder 函数内部定义的变量
adder 返回一个匿名函数 func(x int) int
这个匿名函数引用了外部变量 sums
即使 adder 执行结束,sum 仍然存在
每个通过 adder() 创建出来的闭包都有自己独立的 sum
方法
Go语言没有类,可以用方法来定义类
1 | type Vertex struct { |
在func (v Vertex) Abs() 中,v是Abs方法的名字,type为Vertex的receiver
带有receiver参数的函数在Go中会被视为方法
func (v *Vertex) Scale(f float64)
加上*即为指针类型的方法,在此例中可以直接对原始 Vertex的值进行操作
方法与指针重定向
一个函数,对他的指针参数传递不同类型的值会报错,对其他类型的参数传递指针类型的值也会报错
但对于方法而言,如果一个接收者为指针的方法被调用,接收者既可以是指针也可以是非指针,反之亦然。Go语言会自动将其在指针/非指针之间解释
这样可以使方法调用更自然,使用者不需要关心接收者是否为指针
例如
1 | func (v *Vertex) Scale(f float64) { |
Go会自动将最后一段转化为(&v).Scale(5)
接口
1 | type 接口名 interface { |
1 | package main |
接口被定义为一组方法签名
接口无需显式声明,没有”implements”
一个类型通过实现一个接口的所有方法来实现这个接口
1 | type I interface { |
接口也是值,可以用作函数的参数或返回值
(value,type)
调用方法时,接口值会执行与其底层type类型相同的同名方法
nil接口
nil接口值不保存值和类型,因此调用时会报错
但如果接口内的具体值为nil,方法依然会被调用,且不会触发空指针异常(前提是这个接口自身不为nil)
空接口
空接口可以保存任意类型的值
1 | var i interface{} |
类型断言
1 | t := i.(T)//如果i并未保存T类型的值,会触发panic |
这条语句断言i保存了类型T,并且把T赋值给t
判断语句
1 | t, ok := i.(T) |
如果i保存了一个T,会把T赋值给t,ok为true
否则t为T的零值,ok为false