基础

导出名

大写字母包名字代表可以引用,小写字母包名字代表不可以引用。

函数

函数参数名,参数类型声明在形参名之后。包括返回参数类型也要放在 ‘函数名 ()’ 之后。
当多个函数形参参数类型一样,除最后一个参数类型,其他的参数类型声明可以删除。

多值返回

有点类似 Javascript 中解构那意思

关键点在:

  • 函数中返回值声明多个形参
  • 使用函数赋值 := ```go package main

import “fmt”

func swap(x, y string) (string, string) { return y, x }

func main() { a, b := swap(“hello”, “world”) fmt.Println(a, b) }

  1. <a name="Ju5mp"></a>
  2. #### 命名返回值
  3. - Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
  4. - 返回值的名称应当具有一定的意义,它可以作为文档使用。
  5. - 没有参数的 `return` 语句返回已命名的返回值。也就是 `直接` 返回。
  6. - 直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。
  7. ```go
  8. package main
  9. import "fmt"
  10. func split(sum int) (x, y int) {
  11. x = sum * 4 / 9
  12. y = sum - x
  13. return
  14. }
  15. func main() {
  16. fmt.Println(split(17))
  17. }

变量初始值

  • 若变量已赋值,可以自动从初始值获取变量类型

var i, j int = 1, 2

短变量声明(:=

  • 在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
  • 函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。

    常量

  • 常量不能用 := 语法声明。

    for

  • for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。

  • 初始化语句和后置语句是可选的。
  • 可以去掉分号,因为 C 的 while 在 Go 中叫做 for。 ```go package main

import “fmt”

func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) // 45

  1. // --- 省略初始化和后置语句 ---
  2. sum = 1
  3. for ; sum < 1000; {
  4. sum += sum
  5. }
  6. fmt.Println(sum) // 1024
  7. // --- while ---
  8. sum = 1
  9. for sum < 1000 {
  10. sum += sum
  11. }
  12. fmt.Println(sum) // 1024
  13. // --- 无限循环 ---
  14. // for { }

}

  1. <a name="IuMU7"></a>
  2. #### if
  3. - Go 的 `if` 语句与 `for` 循环类似,表达式外无需小括号 `( )` ,而大括号 `{ }` 则是必须的。
  4. ```go
  5. package main
  6. import "fmt"
  7. func main() {
  8. x := 1
  9. if x < 0 {
  10. fmt.Println("x is less 0")
  11. } else {
  12. fmt.Println("x is greater than 0")
  13. }
  14. // x is greater than 0
  15. }
  • for 一样, if 语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内。 ```go package main

import ( “fmt” “math” )

func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } // fmt.Println(v) // undefined: v return lim }

func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }

  1. ```go
  2. package main
  3. import (
  4. "fmt"
  5. "math"
  6. )
  7. func pow(x, n, lim float64) float64 {
  8. if v := math.Pow(x, n); v < lim {
  9. return v
  10. } else {
  11. fmt.Printf("%g >= %g\n", v, lim)
  12. }
  13. // 这里开始就不能使用 v 了
  14. return lim
  15. }
  16. func main() {
  17. fmt.Println(
  18. pow(3, 2, 10),
  19. pow(3, 3, 20),
  20. )
  21. }
  1. // 练习1:sqrt函数
  2. package main
  3. import (
  4. "fmt"
  5. "math"
  6. )
  7. // Sqrt is a function
  8. func Sqrt(x float64) float64 {
  9. z := float64(1)
  10. for {
  11. if math.Abs(z*z-x) < 0.00000000000001 {
  12. break
  13. }
  14. z -= (z*z - x) / (2 * z)
  15. fmt.Println(z, z*z)
  16. }
  17. return z
  18. }
  19. func main() {
  20. fmt.Println("return", Sqrt(2))
  21. }

switch

  • 没有条件的 switch 同 switch true 一样。


指针

  1. package main
  2. import "fmt"
  3. func main() {
  4. i, j := 42, 2701
  5. p := &i // 指向 i
  6. fmt.Println("p :", p)
  7. fmt.Println("*p :",*p) // 通过指针读取 i 的值
  8. fmt.Println()
  9. *p = 21 // 通过指针设置 i 的值
  10. fmt.Println("i :", i) // 查看 i 的值
  11. fmt.Println("p :", p)
  12. fmt.Println("*p :",*p)
  13. fmt.Println()
  14. p = &j // 指向 j
  15. *p = *p / 37 // 通过指针对 j 进行除法运算
  16. fmt.Println("j :", j) // 查看 j 的值
  17. fmt.Println("p :", p)
  18. fmt.Println("*p :",*p)
  19. }

结构体

  1. package main
  2. import "fmt"
  3. type Vertex struct {
  4. X int
  5. Y int
  6. }
  7. func main() {
  8. fmt.Println(Vertex{1, 2}) // {1 2}
  9. v := Vertex{1, 2}
  10. fmt.Println(v.X) // 1
  11. v.X = 4
  12. fmt.Println(v.X) // 4
  13. }

结构体指针

  • 如果我们有一个指向结构体的指针 p,那么可以通过 (*p).X 来访问其字段 X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写 p.X 就可以。 ```go package main

import “fmt”

type Vertex struct { X int Y int }

func main() { v := Vertex{1, 2} p := &v p.X = 1e9 fmt.Println(v) }

  1. <a name="R01bz"></a>
  2. #### 结构体文法
  3. - 使用 `Name:` 语法可以仅列出部分字段。(字段名的顺序无关。)
  4. - 特殊的前缀 `&` 返回一个指向结构体的指针。
  5. ```go
  6. package main
  7. import "fmt"
  8. type Vertex struct {
  9. X, Y int
  10. }
  11. var (
  12. v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
  13. v2 = Vertex{X: 1} // Y:0 被隐式地赋予
  14. v3 = Vertex{} // X:0 Y:0
  15. p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
  16. )
  17. func main() {
  18. fmt.Println(v1) // {1 2}
  19. fmt.Println(v2) // {1 0}
  20. fmt.Println(v3) // {0 0}
  21. fmt.Println(p) // &{1 2}
  22. fmt.Println(*p) // {1 2}
  23. }

数组

  • 类型 [n]T 表示拥有 nT 类型的值的数组。
  • 数组不能改变大小 ```go var a [2]string

primes := [6]int{2, 3, 5, 7, 11, 13}

  1. <a name="DnDRG"></a>
  2. #### 切片
  3. - 切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:a[low : high]
  4. - 半开区间,包括第一个元素,但排除最后一个元素。
  5. - 切片下界的默认值为 `0`,上界则是该切片的长度。
  6. ```go
  7. package main
  8. import "fmt"
  9. func main() {
  10. primes := [6]int{2, 3, 5, 7, 11, 13}
  11. var s []int = primes[1:4]
  12. fmt.Println(s) // [3 5 7]
  13. }

可以把切片类比为js中的引用,是一种浅拷贝,更改切片会变更初始数组

切片就像数组的引用

  • 切片并不存储任何数据,它只是描述了底层数组中的一段。
  • 更改切片的元素会修改其底层数组中对应的元素。
  • 与它共享底层数组的切片都会观测到这些修改。
  1. package main
  2. import "fmt"
  3. func main() {
  4. names := [4]string{
  5. "John",
  6. "Paul",
  7. "George",
  8. "Ringo",
  9. }
  10. fmt.Println(names) // [John Paul George Ringo]
  11. a := names[0:2]
  12. b := names[1:3]
  13. fmt.Println(a, b) // [John Paul] [Paul George]
  14. b[0] = "XXX"
  15. fmt.Println(a, b) // [John XXX] [XXX George]
  16. fmt.Println(names) // [John XXX George Ringo]
  17. s := []struct {
  18. i int
  19. b bool
  20. }{
  21. {2, true},
  22. {3, false},
  23. {5, true},
  24. {7, true},
  25. {11, false},
  26. {13, true},
  27. }
  28. fmt.Println(s) // [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
  29. }

切片容量

  1. package main
  2. import "fmt"
  3. func main() {
  4. s := []int{2, 3, 5, 7, 11, 13}
  5. printSlice(s)
  6. // 截取切片使其长度为 0
  7. s = s[:0]
  8. printSlice(s)
  9. // 拓展其长度
  10. s = s[:4]
  11. printSlice(s)
  12. // 舍弃前两个值
  13. s = s[2:]
  14. printSlice(s)
  15. /*
  16. len=6 cap=6 [2 3 5 7 11 13]
  17. len=0 cap=6 []
  18. len=4 cap=6 [2 3 5 7]
  19. len=2 cap=4 [5 7]
  20. */
  21. }
  22. func printSlice(s []int) {
  23. fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
  24. }

1.png

make创建动态数组

  1. package main
  2. import "fmt"
  3. func main() {
  4. a := make([]int, 5)
  5. printSlice("a", a)
  6. b := make([]int, 0, 5)
  7. printSlice("b", b)
  8. c := b[:2]
  9. printSlice("c", c)
  10. d := c[2:5]
  11. printSlice("d", d)
  12. // len=5 cap=5 [0 0 0 0 0]
  13. // b len=0 cap=5 []
  14. // c len=2 cap=5 [0 0]
  15. // d len=3 cap=3 [0 0 0]
  16. }
  17. func printSlice(s string, x []int) {
  18. fmt.Printf("%s len=%d cap=%d %v\n",
  19. s, len(x), cap(x), x)
  20. }

range

  • 可以将下标或值赋予 _ 来忽略它。
  • 只需要索引,忽略第二个变量即可。 ```go package main

import “fmt”

func main() { pow := make([]int, 10)

  1. // 无需元素副本
  2. for i := range pow {
  3. pow[i] = 1 << uint(i) // == 2**i
  4. }
  5. // 使用 _ 代表忽略下标
  6. for _, value := range pow {
  7. fmt.Printf("%d\n", value)
  8. }

}

  1. <a name="yOC8e"></a>
  2. #### 映射
  3. > 我一直觉得 “类比” 学习法是一个不可或缺的学习方法。
  4. 可以类比“js”里的object
  5. - 在映射 `m` 中插入或修改元素:
  6. `m[key] = elem`
  7. - 获取元素:
  8. `elem = m[key]`
  9. - 删除元素:
  10. `delete(m, key)`
  11. - 通过双赋值检测某个键是否存在:
  12. `elem, ok = m[key]`
  13. - 若 `key` 在 `m` 中,`ok` 为 `true` ;否则,`ok` 为 `false`。
  14. - 若 `key` 不在映射中,那么 `elem` 是该映射元素类型的零值。
  15. - 同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
  16. > **注** :若 `elem` 或 `ok` 还未声明,你可以使用短变量声明:`elem, ok := m[key]`
  17. <a name="fOr8h"></a>
  18. #### 函数闭包
  19. go函数可以返回一个闭包
  20. ```go
  21. package main
  22. import "fmt"
  23. func adder() func(int) int {
  24. sum := 0
  25. return func(x int) int {
  26. fmt.Println("x", x)
  27. sum += x
  28. return sum
  29. }
  30. }
  31. func main() {
  32. pos, neg := adder(), adder()
  33. for i := 0; i < 10; i++ {
  34. fmt.Println(
  35. pos(i),
  36. neg(-2*i),
  37. )
  38. }
  39. }

方法

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type Vertex struct {
  7. X, Y float64
  8. }
  9. func (v Vertex) Abs() float64 {
  10. return math.Sqrt(v.X*v.X + v.Y*v.Y)
  11. }
  12. func main() {
  13. v := Vertex{3, 4}
  14. fmt.Println(v.Abs())
  15. }
  • 方法就是一类带特殊的 接收者 参数的函数。
  • 方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
  • 在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。