title: golang学习记录
tags:

  • go
  • 学习
    abbrlink: 74fdcb9a
    date: 2021-09-25 14:53:21

0x01 值

字符串可以通过+连接

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("go" + "lang")
  5. fmt.Println("1+1 =", 1+1)
  6. fmt.Println("7.0/3.0 =", 7.0/3.0)
  7. fmt.Println(true && false)
  8. fmt.Println(true || false)
  9. fmt.Println(!true)
  10. }

0x02 变量

可以使var声明1个或多个变量,也可以一次性声明多个变量,Go可以自动判断其数据类型,声明时如果无初值,则会初始化为零值

例如var f string = "short"可以写为f := "short"

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a = "initial"
  5. fmt.Println(a)
  6. var b, c int = 1, 2
  7. fmt.Println(b, c)
  8. var d = true
  9. fmt.Println(d)
  10. var e int
  11. fmt.Println(e)
  12. f := "short"
  13. fmt.Println(f)
  14. }

0x03 常量

const用于声明一个常量,可以出现在任何var语句可以出现的地方,常数表达式可以执行任意精度的运算,数值型常量没有确定的类型,直到被给定某个类型。

一个数字可以根据上下文的需要(变量赋值,常数调用)自动确定类型。例如下文math.Sin需要一个float64的参数,n会自动确定类型。

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. const s string = "constant"
  7. func main() {
  8. fmt.Println(s)
  9. const n = 500000000
  10. const d = 3e20 / n
  11. fmt.Println(d)
  12. fmt.Println(int64(d))
  13. fmt.Println(math.Sin(n))
  14. }

0x04 For循环

for是Go中唯一的循环结构,用法类同于C类语言

  1. package main
  2. import "fmt"
  3. func main() {
  4. i := 1
  5. for i <= 3 {
  6. fmt.Println(i)
  7. i = i + 1
  8. }
  9. for j := 7; j <= 9; j++ {
  10. fmt.Println(j)
  11. }
  12. for {
  13. fmt.Println("loop")
  14. break
  15. }
  16. for n := 0; n <= 5; n++ {
  17. if n%2 == 0 {
  18. continue
  19. }
  20. fmt.Println(n)
  21. }
  22. }

0x05 If/Else分支

用法类同于C类语言

需要注意的是,在 Go 中,条件语句的圆括号不是必需的,但是花括号是必需的。Go 没有三目运算符, 即使是基本的条件判断,依然需要使用完整的 if 语句。

  1. package main
  2. import "fmt"
  3. func main() {
  4. if 7%2 == 0 {
  5. fmt.Println("7 is even")
  6. } else {
  7. fmt.Println("7 is odd")
  8. }
  9. if 8%4 == 0 {
  10. fmt.Println("8 is divisible by 4")
  11. }
  12. if num := 9; num < 0 {
  13. fmt.Println(num, "is negative")
  14. } else if num < 10 {
  15. fmt.Println(num, "has 1 digit")
  16. } else {
  17. fmt.Println(num, "has multiple digits")
  18. }
  19. }

0x06 Switch语句

Go中switch可以不使用常量,从而达到实现if/else逻辑的另一种方式

类型开关 (type switch) 比较类型而非值。可以用来发现一个接口值的类型。 在这个例子中,变量 t 在每个分支中会有相应的类型。

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. i := 2
  8. fmt.Print("write ", i, " as ")
  9. switch i {
  10. case 1:
  11. fmt.Println("one")
  12. case 2:
  13. fmt.Println("two")
  14. case 3:
  15. fmt.Println("three")
  16. }
  17. switch time.Now().Weekday() {
  18. case time.Saturday, time.Sunday:
  19. fmt.Println("It's the weekend")
  20. default:
  21. fmt.Println("It's a weekday")
  22. }
  23. t := time.Now()
  24. switch {
  25. case t.Hour() < 12:
  26. fmt.Println("It's before noon")
  27. default:
  28. fmt.Println("It's after noon")
  29. }
  30. whatAmI := func(i interface{}) {
  31. switch t := i.(type) {
  32. case bool:
  33. fmt.Println("I'm a bool")
  34. case int:
  35. fmt.Println("I'm an int")
  36. default:
  37. fmt.Printf("Don't know type %T\n", t)
  38. }
  39. }
  40. whatAmI(true)
  41. whatAmI(1)
  42. whatAmI("hey")
  43. }

0x07 数组

在Go中,数组是一个具有编号且长度固定的元素序列,数组默认值均为零值,内置len函数可以返回数组的长度。

注意,使用 fmt.Println 打印数组时,会按照 [v1 v2 v3 ...] 的格式打印。

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a [5]int
  5. fmt.Println("emp:", a)
  6. a[4] = 100
  7. fmt.Println("set:", a)
  8. fmt.Println("get:", a[4])
  9. fmt.Println("len:", len(a))
  10. b := [5]int{1, 2, 3, 4, 5}
  11. fmt.Println("dcl:", b)
  12. var twoD [2][3]int
  13. for i := 0; i < 2; i++ {
  14. for j := 0; j < 3; j++ {
  15. twoD[i][j] = i + j
  16. }
  17. }
  18. fmt.Println("2d: ", twoD)
  19. }

0x08 切片Slice

除了基本操作外,slice 支持比数组更丰富的操作。比如 slice 支持内建函数 append, 该函数会返回一个包含了一个或者多个新值的 slice。 注意由于 append 可能返回一个新的 slice,我们需要接收其返回值。

slice 还可以 copy。这里我们创建一个空的和 s 有相同长度的 slice——c, 然后将 s 复制给 c

slice 支持通过 slice[low:high] 语法进行“切片”操作。 例如,右边的操作可以得到一个包含元素 s[2]s[3]s[4] 的 slice。

我们可以在一行代码中声明并初始化一个 slice 变量。

Slice 可以组成多维数据结构。内部的 slice 长度可以不一致,这一点和多维数组不同。

slice虽然和数组是不同的类型,但是通过fmt.Println打印的输出结果是类似的

  1. package main
  2. import "fmt"
  3. func main() {
  4. s := make([]string, 3)
  5. fmt.Println("emp:", s)
  6. s[0] = "a"
  7. s[1] = "b"
  8. s[2] = "c"
  9. fmt.Println("set:", s)
  10. fmt.Println("get:", s[2])
  11. fmt.Println("len:", len(s))
  12. s = append(s, "d")
  13. s = append(s, "e", "f")
  14. fmt.Println("apd:", s)
  15. c := make([]string, len(s))
  16. copy(c, s)
  17. fmt.Println("cpy:", c)
  18. l := s[2:5]
  19. fmt.Println("sl1:", l)
  20. l = s[:5]
  21. fmt.Println("sl2:", l)
  22. l = s[2:]
  23. fmt.Println("sl3:", l)
  24. t := []string{"g", "h", "i"}
  25. fmt.Println("dcl:", t)
  26. twoD := make([][]int, 3)
  27. for i := 0; i < 3; i++ {
  28. innerLen := i + 1
  29. twoD[i] = make([]int, innerLen)
  30. for j := 0; j < innerLen; j++ {
  31. twoD[i][j] = i + j
  32. }
  33. }
  34. fmt.Println("2d: ", twoD)
  35. }
  1. $ go run slices.go
  2. emp: [ ]
  3. set: [a b c]
  4. get: c
  5. len: 3
  6. apd: [a b c d e f]
  7. cpy: [a b c d e f]
  8. sl1: [c d e]
  9. sl2: [a b c d e]
  10. sl3: [c d e f]
  11. dcl: [g h i]
  12. 2d: [[0] [1 2] [2 3 4]]

0x09 Map

map与C++的STL的map类似

要创建一个空 map,需要使用内建函数 makemake(map[key-type]val-type)。使用fmt.Println可以输出它的所有键值,内建len获取长度和delete移除一个键值对

当从一个 map 中取值时,还有可以选择是否接收的第二个返回值,该值表明了 map 中是否存在这个键。 这可以用来消除 键不存在键的值为零值 产生的歧义, 例如 0""。这里我们不需要值,所以用 空白标识符(blank identifier) _ 将其忽略。

注意,使用 fmt.Println 打印一个 map 的时候, 是以 map[k:v k:v] 的格式输出的。

  1. package main
  2. import "fmt"
  3. func main() {
  4. m := make(map[string]int)
  5. m["k1"] = 7
  6. m["k2"] = 13
  7. fmt.Println("map:", m)
  8. v1 := m["k1"]
  9. fmt.Println("v1: ", v1)
  10. fmt.Println("len:", len(m))
  11. delete(m, "k2")
  12. fmt.Println("map:", m)
  13. _, prs := m["k2"]
  14. fmt.Println("prs:", prs)
  15. n := map[string]int{"foo": 1, "bar": 2}
  16. fmt.Println("map:", n)
  17. }
  1. $ go run maps.go
  2. map: map[k1:7 k2:13]
  3. v1: 7
  4. len: 2
  5. map: map[k1:7]
  6. prs: false
  7. map: map[foo:1 bar:2]

0x10 Range遍历

range 在数组和 slice 中提供对每项的索引和值的访问。 有时我们不需要索引,所以我们使用 空白标识符 _ 将其忽略。

  1. package main
  2. import "fmt"
  3. func main() {
  4. nums := []int{2, 3, 4}
  5. sum := 0
  6. for _, num := range nums {
  7. sum += num
  8. }
  9. fmt.Println("sum:", sum)
  10. for i, num := range nums {
  11. if num == 3 {
  12. fmt.Println("index:", i)
  13. }
  14. }
  15. kvs := map[string]string{"a": "apple", "b": "banana"}
  16. for k, v := range kvs {
  17. fmt.Printf("%s -> %s\n", k, v)
  18. }
  19. for k := range kvs {
  20. fmt.Println("key:", k)
  21. }
  22. for i, c := range "go" {
  23. fmt.Println(i, c)
  24. }
  25. }
  1. $ go run range.go
  2. sum: 9
  3. index: 1
  4. a -> apple
  5. b -> banana
  6. key: a
  7. key: b
  8. 0 103
  9. 1 111

0x11 函数

Go 需要明确的 return,也就是说,它不会自动 return 最后一个表达式的值

当多个连续的参数为同样类型时, 可以仅声明最后一个参数的类型,忽略之前相同类型参数的类型声明。

  1. package main
  2. import "fmt"
  3. func plus(a int, b int) int {
  4. return a + b
  5. }
  6. func plusPlus(a, b, c int) int {
  7. return a + b + c
  8. }
  9. func main() {
  10. res := plus(1, 2)
  11. fmt.Println("1+2 =", res)
  12. res = plusPlus(1, 2, 3)
  13. fmt.Println("1+2+3 =", res)
  14. }

0x12 多返回值

Go 原生支持 多返回值。 这个特性在 Go 语言中经常用到,例如用来同时返回一个函数的结果和错误信息。(int, int) 在这个函数中标志着这个函数返回 2 个 int。如果你仅仅需要返回值的一部分的话,你可以使用空白标识符 _

  1. package main
  2. import "fmt"
  3. func vals() (int, int) {
  4. return 3, 7
  5. }
  6. func main() {
  7. a, b := vals()
  8. fmt.Println(a)
  9. fmt.Println(b)
  10. _, c := vals()
  11. fmt.Println(c)
  12. }

0x13 变参

如果你有一个含有多个值的 slice,想把它们作为参数使用, 你需要这样调用 func(slice...)

  1. package main
  2. import "fmt"
  3. func sum(nums ...int) {
  4. fmt.Print(nums, " ")
  5. total := 0
  6. for _, num := range nums {
  7. total += num
  8. }
  9. fmt.Println(total)
  10. }
  11. func main() {
  12. sum(1, 2)
  13. sum(1, 2, 3)
  14. nums := []int{1, 2, 3, 4}
  15. sum(nums...)
  16. }
  1. $ go run variadic-functions.go
  2. [1 2] 3
  3. [1 2 3] 6
  4. [1 2 3 4] 10

0x14 闭包

intSeq 函数返回一个在其函数体内定义的匿名函数。 返回的函数使用闭包的方式 隐藏 变量 i。 返回的函数 隐藏 变量 i 以形成闭包。

  1. package main
  2. import "fmt"
  3. func intSeq() func() int {
  4. i := 0
  5. return func() int {
  6. i++
  7. return i
  8. }
  9. }
  10. func main() {
  11. nextInt := intSeq()
  12. fmt.Println(nextInt())
  13. fmt.Println(nextInt())
  14. fmt.Println(nextInt())
  15. newInts := intSeq()
  16. fmt.Println(newInts())
  17. }
  1. $ go run closures.go
  2. 1
  3. 2
  4. 3
  5. 1

0x15 递归

  1. package main
  2. import "fmt"
  3. func fact(n int) int {
  4. if n == 0 {
  5. return 1
  6. }
  7. return n * fact(n-1)
  8. }
  9. func main() {
  10. fmt.Println(fact(7))
  11. }

0x16 指针

与C类语言相同

  1. package main
  2. import "fmt"
  3. func zeroval(ival int) {
  4. ival = 0
  5. }
  6. func zeroptr(iptr *int) {
  7. *iptr = 0
  8. }
  9. func main() {
  10. i := 1
  11. fmt.Println("initial:", i)
  12. zeroval(i)
  13. fmt.Println("zeroval:", i)
  14. zeroptr(&i)
  15. fmt.Println("zeroptr:", i)
  16. fmt.Println("pointer:", &i)
  17. }
  1. $ go run pointers.go
  2. initial: 1
  3. zeroval: 1
  4. zeroptr: 0
  5. pointer: 0x42131100

0x17 结构体

  1. package main
  2. import "fmt"
  3. type person struct {
  4. name string
  5. age int
  6. }
  7. func main() {
  8. fmt.Println(person{"Bob", 20})
  9. fmt.Println(person{name: "Alice", age: 30})
  10. fmt.Println(person{name: "Fred"})
  11. fmt.Println(&person{name: "Ann", age: 40})
  12. s := person{name: "Sean", age: 50}
  13. fmt.Println(s.name)
  14. sp := &s
  15. fmt.Println(sp.age)
  16. sp.age = 51
  17. fmt.Println(sp.age)
  18. }
  1. $ go run structs.go
  2. {Bob 20}
  3. {Alice 30}
  4. {Fred 0}
  5. &{Ann 40}
  6. Sean
  7. 50
  8. 51

0x18 方法

个人感觉类似于C++里面的class类内的public函数吧

  1. package main
  2. import "fmt"
  3. type rect struct {
  4. width, height int
  5. }
  6. func (r *rect) area() int {
  7. return r.width * r.height
  8. }
  9. func (r rect) perim() int {
  10. return 2*r.width + 2*r.height
  11. }
  12. func main() {
  13. r := rect{width: 10, height: 5}
  14. fmt.Println("area: ", r.area())
  15. fmt.Println("perim:", r.perim())
  16. rp := &r
  17. fmt.Println("area: ", rp.area())
  18. fmt.Println("perim:", rp.perim())
  19. }
  1. $ go run methods.go
  2. area: 50
  3. perim: 30
  4. area: 50
  5. perim: 30

0x19 接口

可以通过接口实现对所有类似抽象问题的求解方法进行集合,从而简化程序的编写逻辑

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type geometry interface {
  7. area() float64
  8. perim() float64
  9. }
  10. type rect struct {
  11. width, height float64
  12. }
  13. type circle struct {
  14. radius float64
  15. }
  16. func (r rect) area() float64 {
  17. return r.width * r.height
  18. }
  19. func (r rect) perim() float64 {
  20. return 2*r.width + 2*r.height
  21. }
  22. func (c circle) area() float64 {
  23. return math.Pi * c.radius * c.radius
  24. }
  25. func (c circle) perim() float64 {
  26. return 2 * math.Pi * c.radius
  27. }
  28. func measure(g geometry) {
  29. fmt.Println(g)
  30. fmt.Println(g.area())
  31. fmt.Println(g.perim())
  32. }
  33. func main() {
  34. r := rect{width: 3, height: 4}
  35. c := circle{radius: 5}
  36. measure(r)
  37. measure(c)
  38. }
  1. $ go run interfaces.go
  2. {3 4}
  3. 12
  4. 14
  5. {5}
  6. 78.53981633974483
  7. 31.41592653589793

0x20 错误处理

符合 Go 语言习惯的做法是使用一个独立、明确的返回值来传递错误信息。 这与 Java、Ruby 使用的异常(exception) 以及在 C 语言中有时用到的重载 (overloaded) 的单返回/错误值有着明显的不同。 Go 语言的处理方式能清楚的知道哪个函数返回了错误,并使用跟其他(无异常处理的)语言类似的方式来处理错误。

errors.New 使用给定的错误信息构造一个基本的 error 值。返回错误值为 nil 代表没有错误。还可以通过实现 Error() 方法来自定义 error 类型。

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. func f1(arg int) (int, error) {
  7. if arg == 42 {
  8. return -1, errors.New("can't work with 42")
  9. }
  10. return arg + 3, nil
  11. }
  12. type argError struct {
  13. arg int
  14. prob string
  15. }
  16. func (e *argError) Error() string {
  17. return fmt.Sprintf("%d - %s", e.arg, e.prob)
  18. }
  19. func f2(arg int) (int, error) {
  20. if arg == 42 {
  21. return -1, &argError{arg, "can't work with it"}
  22. }
  23. return arg + 3, nil
  24. }
  25. func main() {
  26. for _, i := range []int{7, 42} {
  27. if r, e := f1(i); e != nil {
  28. fmt.Println("f1 failed:", e)
  29. } else {
  30. fmt.Println("f1 worked:", r)
  31. }
  32. }
  33. for _, i := range []int{7, 42} {
  34. if r, e := f2(i); e != nil {
  35. fmt.Println("f2 failed:", e)
  36. } else {
  37. fmt.Println("f2 worked:", r)
  38. }
  39. }
  40. _, e := f2(42)
  41. if ae, ok := e.(*argError); ok {
  42. fmt.Println(ae.arg)
  43. fmt.Println(ae.prob)
  44. }
  45. }
  1. $ go run errors.go
  2. f1 worked: 10
  3. f1 failed: can't work with 42
  4. f2 worked: 10
  5. f2 failed: 42 - can't work with it
  6. 42
  7. can't work with it