Go 语言新手笔记(二)

Go 语方的数组声明方式别具一格,把 [] 看得太重了,看下面的各种声明方式
1var a = [...]int{18, 20}            // 这是一个数组,如果省略 ..., 写成  []int{18, 20} 就是一个 slice
2var b = [5]string{"hello", 3: "ok"} //指定位置初始化
3var c = [...]float32{2.0, 2.3}   // ... 可省略
4var d [20]int                 // d 的类型是 [20]int
5var e = new([20]int)          // e 的类型是 *[20]int, 这是一个数组指针
6var g = [2][2][2]float64      // 多维数组
7var h = [...][5]int{{10,20}, {30,40}}   // 类似其他语言一样,只有第一维才能用 [...]
再次强调 Go 的数组是值类型,作为函数参数传递会产生副本。避免传入数组产生副本消耗过多内存,办法是可传数组指针或用切片,切片是第一选择。

数组的大小最大为 2GB,用 ==!= 比较两个数组时它们必须类型和长度一致。

切片(slice) 是对数组的一个连续片断引用, 切片的各种创建方式
 1var arr = [...]int{2,3,5,6,8,4}  // 初始一个数组
 2var s1 []int       // 创建新切片
 3
 4fmt.Println(s1, s1==nil, len(s1))                      // [] true 0
 5fmt.Println(reflect.TypeOf(arr), reflect.TypeOf(s1))   // [6]int []int
 6
 7var s2 = arr[2:4]     // 新建切片指定数组 arr 的索引从 2 到 4 的元素
 8var s3 = []int{1,2,3}
 9var s4 = make([]int, 2, 5)  // 分配一个长度为 5 的数组,并创建容量为 2 的 slice 指向前 2 个元素
10var s5 = s3[0:2]      // 从切片生成新的切片, 可简写为 s3[:2]
11var s6 = arr[2:4:5]   //a[low:high:max], 长度为 high-low, 容量为 max-low
切片相对于数组有些像数据库的视图与数组的关系。切片的一个基本操作 append(),超出容量时会指向新的数组。

map 的基本声明和操作
 1var map1 map[string]int   // 空 map
 2map2 := make(map[string]int) // 用 make 创建 map
 3var map3 = make(map[string]int, 10)  // 指定容量为 10
 4var map4 = map[string]int{"a": 1, "b": 2}
 5
 6fmt.Println(map4["a"])  // 读取元素
 7map4["c"] = 3         // 添加元素
 8fmt.Println(map4)
 9delete(map4, "a")  // 删除元素
10
11for key, val := range map4 {  // for..range 遍历
12  fmt.Println(key, val)
13}
type 定义类型或别名的用法
 1type IZ int     // 定义新类型
 2var a IZ = 5
 3fmt.Println(reflect.TypeOf(a), a)
 4
 5var b int = int(a) // 必须显式转型
 6
 7type (
 8  FZ float64   // 新型 FZ 没有 float64 的相应方法
 9  STR string
10)
11
12type B = byte  // 类型别名定义,B 和 byte 就是完全一样的了
Go 的函数也是一种类型,与函数的签名一致,有点像 C/C++ 的函数指针一样
1func foo(a int, b int) int {
2  return a + b
3}
4
5func main() {
6  type barFun func(int, int) int
7  var f1 barFun = foo
8  fmt.Println(f1(3, 3))
9}
Go 的错误处理类型是 error, Go 居然没有像多数现代语言那样的 try/catch 方式,而是通常函数在有错误的时候额外返回一个  error
 1func foo(a int) (x int, err error) {
 2  if a > 1 {
 3    return 0, errors.New("greater than 1")
 4  } else if a < 0 {
 5    return 0, fmt.Errorf("smaller than zero %d", a)
 6  }
 7  return a + 1, nil
 8}
 9func main() {
10  val, err := foo(2)
11  if err != nil {
12    fmt.Println(val)
13  }
14}
个人不太喜欢这种异常的处理方法,看看 panic, differ 和 recover。下面是 panic, recover 和 differ  的用法, 以及理解它们是怎么工作的
 1func div(a, b int) {
 2  defer func() {
 3    if r:=recover(); r!=nil {
 4      fmt.Printf("caught error: %s\n", r)
 5    }
 6  }()                         // 这时定义并调用了 defer 函数, 所以可用 defer 来执行一个函数,如 defer fmt.Println("xx")
 7
 8  if b < 0 {
 9    panic("not negative")  // panic() 会调用到 defer 函数
10  }
11
12  fmt.Println("result: ", a/b)  // a/b 有异常时也会调用 defer 函数
13}
14
15func div1(a, b int)  {    // 测试函数中没有定义 defer 函数的情况
16  fmt.Println("result: ", a/b)
17}
18
19func main() {
20  div(10, 0)   // caught error: runtime error: integer divide by zero
21  div(10, -1)  // caught error: not negative
22  div1(10, 0)  // panic: runtime error: integer divide by zero
23}
输出
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  那样定义一个函数并立即执行它
1func main() {
2  func(a, b int) {
3    fmt.Println("result: ", a+b)
4  }(4, 5)
5}
defer 在 Go 语言中还是比较新鲜的东西,又有一种 finally 的味道,defer 的三个规则
  1. defer 声明时其后面函数参数被实时解析
  2. defer 执行顺序为先进后出 (FILO)
  3. defer 可读取函数的有名返回值

1func main() {
2  i := 2
3  defer fmt.Println(func() int { return i * 2 }())
4  // 相当于 defer fmt.Println(fun() int {return 4}())
5  
6  i++
7}
1func main() {
2  defer fmt.Print(" !!!")
3  defer fmt.Print(" world")
4  defer fmt.Print("hello")
5}
输出为 hello world !!!
 1func foo() (i int) {
 2  defer func() {
 3    i += 10
 4  }()
 5  return 0
 6}
 7
 8func main() {
 9  fmt.Println(foo())
10}
输出为 10,return 语句相当于是  ret = xxx -> 调用 defer() -> return ret

函数的定义格式为 func functionName(a typea, b typeb)(r1 type1, r2 type2), 具体会有以下几种形式
 1func f1() int {
 2  return 1
 3}
 4
 5func f2(a int, b float32) (int, string) {
 6  return 2, ""       // 返回多个值用逗号分开
 7}
 8
 9func f3(a, b int) (x, y string) {...}  // 参数列表类型相同可以写在一块
10func f4(a int, values ...int) {...}  // 变参 values 是一个  slice []int<br/><br/>
11
12func f6() (i int) {  //命名返回值是有初始零值的
13  return   // 这里相当于 return i
14}
Go 的函数不允许重载,Go 默认按值传递,传入的参数会先产生一个副本。要能修改参数值的话需传入一个地址,这时候函数及调用方式为
1func foo(i *int)  {   // 参数为指针
2  *i = 10             // 指针位置赋值
3}
4
5func main() {
6  x := 3
7  foo(&x)            // 传入地址
8  fmt.Println(x)     // 输出的 x 为 10
9}
这是不是和  C/C++ 的指针一样的!

Go 的内置函数 make(T) 和 new(T)
  1. make(T): 只用于 slice, map 以及  channel, 返回为 T 的值类型
  2. new(T): 用于值类型的内存分配,并设置为零值,它返回的是一个指向 T 类型的指针

其他的一些内置函数,len(), cap(), append(), copy(), delete(), close(), complex(), real(), image(), panic(), recover(), print(), println()

Go 的匿名函数也称为闭包,闭包对变量的捕获相当于引用传递,即不不会产生副本。可以修改外层的变量
1func main() {
2  i := 1
3  func() {
4    i++
5  }()
6  println(i) //2
7}
对变参函数的调用,传入多个值,切片时要打散传入, 数组必须转换成 slice, 再打算传入
 1func foo(who ...string) { // who 是一个 []string
 2  for v := range who {
 3    fmt.Println(v)
 4  }
 5}
 6
 7func main() {
 8  foo("aa", "bb")
 9  foo([]string{"cc", "dd"}...)
10  // foo([2]string{"cc", "dd"}...) 不能传入 array
11  arr := [2]string{"cc", "dd"}
12  foo(arr[0:]...) // 不能写成一行 foo(([2]string{"cc", "dd"})[0:])
13}
Go 对新建的 array 或 slice 可以立即取值,但不能立即由新建的 array  得到一个  slice
1a := [2]int{1, 2}[1]   // 合法
2b := []int{1, 2}[1]    // 合法
3c := [2]int{1, 2}[0:1] // 不合法,invalid operation [2]int{...}[0:1] (slice of unaddressable value)
永久链接 https://yanbin.blog/go-language-notes-2/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。