Go 语方的数组声明方式别具一格,把 [] 看得太重了,看下面的各种声明方式
1 2 3 4 5 6 7 |
var a = [...]int{18, 20} // 这是一个数组,如果省略 ..., 写成 []int{18, 20} 就是一个 slice var b = [5]string{"hello", 3: "ok"} //指定位置初始化 var c = [...]float32{2.0, 2.3} // ... 可省略 var d [20]int // d 的类型是 [20]int var e = new([20]int) // e 的类型是 *[20]int, 这是一个数组指针 var g = [2][2][2]float64 // 多维数组 var h = [...][5]int{{10,20}, {30,40}} // 类似其他语言一样,只有第一维才能用 [...] |
再次强调 Go 的数组是值类型,作为函数参数传递会产生副本。避免传入数组产生副本消耗过多内存,办法是可传数组指针或用切片,切片是第一选择。
数组的大小最大为 2GB,用 ==
或 !=
比较两个数组时它们必须类型和长度一致。
切片(slice) 是对数组的一个连续片断引用, 切片的各种创建方式
1 2 3 4 5 6 7 8 9 10 11 |
var arr = [...]int{2,3,5,6,8,4} // 初始一个数组 var s1 []int // 创建新切片 fmt.Println(s1, s1==nil, len(s1)) // [] true 0 fmt.Println(reflect.TypeOf(arr), reflect.TypeOf(s1)) // [6]int []int var s2 = arr[2:4] // 新建切片指定数组 arr 的索引从 2 到 4 的元素 var s3 = []int{1,2,3} var s4 = make([]int, 2, 5) // 分配一个长度为 5 的数组,并创建容量为 2 的 slice 指向前 2 个元素 var s5 = s3[0:2] // 从切片生成新的切片, 可简写为 s3[:2] var s6 = arr[2:4:5] //a[low:high:max], 长度为 high-low, 容量为 max-low |
切片相对于数组有些像数据库的视图与数组的关系。切片的一个基本操作 append(),超出容量时会指向新的数组。
map 的基本声明和操作
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var map1 map[string]int // 空 map map2 := make(map[string]int) // 用 make 创建 map var map3 = make(map[string]int, 10) // 指定容量为 10 var map4 = map[string]int{"a": 1, "b": 2} fmt.Println(map4["a"]) // 读取元素 map4["c"] = 3 // 添加元素 fmt.Println(map4) delete(map4, "a") // 删除元素 for key, val := range map4 { // for..range 遍历 fmt.Println(key, val) } |
type 定义类型或别名的用法
1 2 3 4 5 6 7 8 9 10 11 12 |
type IZ int // 定义新类型 var a IZ = 5 fmt.Println(reflect.TypeOf(a), a) var b int = int(a) // 必须显式转型 type ( FZ float64 // 新型 FZ 没有 float64 的相应方法 STR string ) type B = byte // 类型别名定义,B 和 byte 就是完全一样的了 |
Go 的函数也是一种类型,与函数的签名一致,有点像 C/C++ 的函数指针一样
1 2 3 4 5 6 7 8 9 |
func foo(a int, b int) int { return a + b } func main() { type barFun func(int, int) int var f1 barFun = foo fmt.Println(f1(3, 3)) } |
Go 的错误处理类型是 error, Go 居然没有像多数现代语言那样的 try/catch 方式,而是通常函数在有错误的时候额外返回一个 error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
func foo(a int) (x int, err error) { if a > 1 { return 0, errors.New("greater than 1") } else if a < 0 { return 0, fmt.Errorf("smaller than zero %d", a) } return a + 1, nil } func main() { val, err := foo(2) if err != nil { fmt.Println(val) } } |
个人不太喜欢这种异常的处理方法,看看 panic, differ 和 recover。下面是 panic, recover 和 differ 的用法, 以及理解它们是怎么工作的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func div(a, b int) { defer func() { if r:=recover(); r!=nil { fmt.Printf("caught error: %s\n", r) } }() // 这时定义并调用了 defer 函数, 所以可用 defer 来执行一个函数,如 defer fmt.Println("xx") if b < 0 { panic("not negative") // panic() 会调用到 defer 函数 } fmt.Println("result: ", a/b) // a/b 有异常时也会调用 defer 函数 } func div1(a, b int) { // 测试函数中没有定义 defer 函数的情况 fmt.Println("result: ", a/b) } func main() { div(10, 0) // caught error: runtime error: integer divide by zero div(10, -1) // caught error: not negative div1(10, 0) // panic: runtime error: integer divide by zero } |
输出
caught error: runtime error: integer divide by zero
caught error: not negative
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.div1(...)
/Users/yanbin/Workspaces/tests/test-go/src/test.go:22
main.main()
/Users/yanbin/Workspaces/tests/test-go/src/test.go:29 +0x4f
好像有了 try/catch 的影子了。recover() 函数只能在有 defer 修饰的函数中使用,用于取得异常传递来的错误信息,没错的话它返回 nil.
panic(): 严重不可恢复,recover(): 从异常或错误中恢复,defer 延迟
Go 也可以像 JavsScript 那样定义一个函数并立即执行它
1 2 3 4 5 |
func main() { func(a, b int) { fmt.Println("result: ", a+b) }(4, 5) } |
defer 在 Go 语言中还是比较新鲜的东西,又有一种 finally 的味道,defer 的三个规则
- defer 声明时其后面函数参数被实时解析
- defer 执行顺序为先进后出 (FILO)
- defer 可读取函数的有名返回值
1 2 3 4 5 6 7 |
func main() { i := 2 defer fmt.Println(func() int { return i * 2 }()) // 相当于 defer fmt.Println(fun() int {return 4}()) i++ } |
1 2 3 4 5 |
func main() { defer fmt.Print(" !!!") defer fmt.Print(" world") defer fmt.Print("hello") } |
输出为 hello world !!!
1 2 3 4 5 6 7 8 9 10 |
func foo() (i int) { defer func() { i += 10 }() return 0 } func main() { fmt.Println(foo()) } |
输出为 10,return 语句相当于是 ret = xxx -> 调用 defer() -> return ret
函数的定义格式为 func functionName(a typea, b typeb)(r1 type1, r2 type2), 具体会有以下几种形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func f1() int { return 1 } func f2(a int, b float32) (int, string) { return 2, "" // 返回多个值用逗号分开 } func f3(a, b int) (x, y string) {...} // 参数列表类型相同可以写在一块 func f4(a int, values ...int) {...} // 变参 values 是一个 slice []int func f6() (i int) { //命名返回值是有初始零值的 return // 这里相当于 return i } |
Go 的函数不允许重载,Go 默认按值传递,传入的参数会先产生一个副本。要能修改参数值的话需传入一个地址,这时候函数及调用方式为
1 2 3 4 5 6 7 8 9 |
func foo(i *int) { // 参数为指针 *i = 10 // 指针位置赋值 } func main() { x := 3 foo(&x) // 传入地址 fmt.Println(x) // 输出的 x 为 10 } |
这是不是和 C/C++ 的指针一样的!
Go 的内置函数 make(T) 和 new(T)
- make(T): 只用于 slice, map 以及 channel, 返回为 T 的值类型
- new(T): 用于值类型的内存分配,并设置为零值,它返回的是一个指向 T 类型的指针
其他的一些内置函数,len(), cap(), append(), copy(), delete(), close(), complex(), real(), image(), panic(), recover(), print(), println()
Go 的匿名函数也称为闭包,闭包对变量的捕获相当于引用传递,即不不会产生副本。可以修改外层的变量
1 2 3 4 5 6 7 |
func main() { i := 1 func() { i++ }() println(i) //2 } |
对变参函数的调用,传入多个值,切片时要打散传入, 数组必须转换成 slice, 再打算传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func foo(who ...string) { // who 是一个 []string for v := range who { fmt.Println(v) } } func main() { foo("aa", "bb") foo([]string{"cc", "dd"}...) // foo([2]string{"cc", "dd"}...) 不能传入 array arr := [2]string{"cc", "dd"} foo(arr[0:]...) // 不能写成一行 foo(([2]string{"cc", "dd"})[0:]) } |
Go 对新建的 array 或 slice 可以立即取值,但不能立即由新建的 array 得到一个 slice
1 2 3 |
a := [2]int{1, 2}[1] // 合法 b := []int{1, 2}[1] // 合法 c := [2]int{1, 2}[0:1] // 不合法,invalid operation [2]int{...}[0:1] (slice of unaddressable value) |
本文链接 https://yanbin.blog/go-language-notes-2/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。