从例子中学习 go 语言 —— 数据结构、指针

本篇本来是接着上一篇的,但是一篇过长,效果不好。上一篇是关于基本语法以及结构的,基本类型、函数、for、if 等等,这里的是关于数据结构以及指针的,下一篇是关于方法、接口以及并发的。

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

    1). 使用 type 和 struct 定义结构体;和 c 的类似,结构体是对数据的封装,就是一个容器,里面放一些东东; 2). 结构体中类型声明和普通的一致,先写名字,再写类型; 3). 构造一个结构体值,或是对象,或叫实例,直接名字加参数值即可; 4). 结构体值可以 fmt.Println 直接输出; 5). 实际上可以看出来 go 声明或是定义东西的风格:先说明这是神马玩意,然后是名字,然后具体类型是神马东东;比如变量声明 var a int ,比如函数声明 func add(a int, b int) int ,比如结构体 type Point struct{ x, y int} ;

  2. 结构体元素访问:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Vertex struct {
    6.         X int
    7.         Y int
    8. }
    9.  
    10. func main() {
    11.         v := Vertex{1, 2}
    12.         v.X = 4
    13.         fmt.Println(v)
    14. }

    1). 和 c 类似,直接 v.X 就可以访问,既可以直接使用该值,也可以直接给该值赋值;

  3. 诡异的指针:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Vertex struct {
    6.         X int
    7.         Y int
    8. }
    9.  
    10. func main() {
    11.         p := Vertex{1, 2}
    12.         q := &p
    13.         q.X = 10
    14.         (*q).Y = 20
    15.         r := &q
    16.         //(*r).Y = 4 // ok
    17.         //(*(*r)).X = 100 //ok
    18.         s := &r
    19.         (**s).Y = 30
    20.        
    21.         fmt.Println(p,q,r,s,*q,*r,*s,**r,**s,***s)
    22. }

    1). 指针在 c 中是个重要的东西,& 和 * 一个取地址、一个解析地址,这是 c 的用法,go 中貌似也是这样,but ! 2). go 中的指针是个“透明”的东西,也就是偶们可以不通过 * 访问,而是直接访问,比如可以不是 (*q).Y = 20 而是 q.Y = 20,有些伤天害理的赶脚! 3). 结构体,结构体指针,结构体指针的指针,结构体指针的指针的指针; 4). 再说明一下:要访问指针 p 指向的结构体中某个元素 x,不需要显式地使用 * 运算,可以直接 p.x ;

  4. 结构体以及指针类型:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Vertex struct {
    6.         X, Y int
    7. }
    8.  
    9. var (
    10.         p = Vertex{1, 2}  // has type Vertex
    11.         q = &Vertex{1, 2} // has type *Vertex
    12.         r = Vertex{X: 1}  // Y:0 is implicit
    13.         s = Vertex{}      // X:0 and Y:0
    14. )
    15.  
    16. func main() {
    17.         fmt.Println(p, q, r, s)
    18.         // fmt.Printf("%T %T %T %T",p, q, r, s)        // main.Vertex *main.Vertex main.Vertex main.Vertex
    19. }

    1). 结构体的声明可以简化; 2). 结构体有多种初始化;如果不指定具体的值,也都有默认值; 3). fmt.Printf 格式化输出,可以输出值的类型 %T %v %g;

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

    1). new 构造结构体,返回指针,看来是借鉴了 c++ 的用法; 2). new 的同时貌似不能初始化,new 是个函数,不像 c++ 中是个运算符;

  6. map 类型:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Vertex struct {
    6.         Lat, Long float64
    7. }
    8.  
    9. var m map[string]Vertex
    10.  
    11. func main() {
    12.         m = make(map[string]Vertex)
    13.         m["Bell Labs"] = Vertex{
    14.                 40.68433, 74.39967,
    15.         }
    16.         m["abc"] = Vertex{
    17.                 1.2, 3.4,
    18.         }
    19.         fmt.Println(m["abc"])
    20. }

    1). map 类型就是其他地方说的,hash 表、或是字典、或是关联数组神马的,总之里面就是将 key 和 value 对应起来的对; 2). map 中的 nil 表示为空,不能被赋值; 3). make 函数构造 map,貌似不能通过 new ;

  7. map 的初始化:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. type Vertex struct {
    6.         Lat, Long float64
    7. }
    8.  
    9. var m = map[string]Vertex{
    10.         "Bell Labs": Vertex{
    11.                 40.68433, -74.39967,
    12.         },
    13.         "Google": Vertex{
    14.                 37.42202, -122.08408,
    15.         },
    16. }
    17.  
    18. func main() {
    19.         fmt.Println(m)
    20. }
    21.  
    22. // 下面是另外一个例子
    23.  
    24. package main
    25.  
    26. import "fmt"
    27.  
    28. type Vertex struct {
    29.         Lat, Long float64
    30. }
    31.  
    32. var m = map[string]Vertex{
    33.         "Bell Labs": {40.68433, -74.39967},
    34.         "Google":    {37.42202, -122.08408},
    35. }
    36.  
    37. func main() {
    38.         fmt.Println(m)
    39. }

    1). map 初始化; 2). map 初始化的简化方式;

  8. map 的用法:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6.         m := make(map[string]int)
    7.  
    8.         m["Answer"] = 42
    9.         fmt.Println("The value:", m["Answer"])
    10.  
    11.         m["Answer"] = 48
    12.         fmt.Println("The value:", m["Answer"])
    13.  
    14.         delete(m, "Answer")
    15.         fmt.Println("The value:", m["Answer"])
    16.  
    17.         v, ok := m["Answer"]
    18.         fmt.Println("The value:", v, "Present?", ok)
    19. }

    1). map 获取某个元素对应的值;修改某个元素对应的值;删除某个元素;

    m[key] = elem
    elem = m[key]
    delete(m, key)
    elem, ok = m[key]
    
  9. 数组 / 分片类型:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6.         p := []int{2, 3, 5, 7, 11, 13}
    7.         fmt.Println("p ==", p)
    8.  
    9.         for i := 0; i < len(p); i++ {
    10.                 fmt.Printf("p[%d] == %d\n",
    11.                         i, p[i])
    12.         }
    13. }

    1). 数组结构; [] int ; 将类型尽可能想后写,先说明是数组,再说明是 int 数组; 2). len 函数获取数组长度,下标从 0 开始记; 3). 访问方式 p[i] ;

  10. 子数组:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6.         p := []int{2, 3, 5, 7, 11, 13}
    7.         fmt.Println("p ==", p)
    8.         fmt.Println("p[1:4] ==", p[1:4])
    9.  
    10.         // missing low index implies 0
    11.         fmt.Println("p[:3] ==", p[:3])
    12.  
    13.         // missing high index implies len(s)
    14.         fmt.Println("p[4:] ==", p[4:])
    15.        
    16.         p[1] = 7
    17.         fmt.Println("p ==", p)
    18. }

    1). 取数组的一部分形成子数组;s[lo:hi] 取 s 中的下表从 lo 开始到 hi-1 结束的部分; 2). 省略前面 lo 部分,表示从 0 开始;省略后面的 hi 部分,到尾结束;s[:] 也是合法的,指的就是 s ; 3). s[lo:lo] 为空;s[lo:lo+1] 只有 s[lo] 一个元素;

  11. make 构造数组、数组容量:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6.         a := make([]int, 5)
    7.         printSlice("a", a)
    8.         b := make([]int, 0, 5)
    9.         printSlice("b", b)
    10.         c := b[:2]
    11.         printSlice("c", c)
    12.         d := c[2:5]
    13.         printSlice("d", d)
    14.  
    15.         a[1] = 2
    16.         //b[1]=4  // index out of range
    17.         c[1]=8
    18.         d[1]=16
    19.         printSlice("a", a)
    20.         printSlice("b", b)
    21.         printSlice("c", c)
    22.         printSlice("d", d)
    23. }
    24.  
    25. func printSlice(s string, x []int) {
    26.         fmt.Printf("%s len=%d cap=%d %v\n",
    27.                 s, len(x), cap(x), x)
    28. }

    1). make 函数可以用于构造数组,前面也构造过 map; new 用来构造结构体; 2). 空数组;nil 长度和容量都是 0;nil 还是空 map ;

    a := make([]int, 5)  // len(a)=5
    b := make([]int, 0, 5) // len(b)=0, cap(b)=5
    b = b[:cap(b)] // len(b)=5, cap(b)=5
    b = b[1:]      // len(b)=4, cap(b)=4
    
  12. 匿名函数:
    1. package main
    2.  
    3. import (
    4.         "fmt"
    5.         "math"
    6. )
    7.  
    8. func main() {
    9.         hypot := func(x, y float64) float64 {
    10.                 return math.Sqrt(x*x + y*y)
    11.         }
    12.  
    13.         fmt.Printf("%T %g",hypot,hypot(3, 4))
    14. }

    1). 函数也是值,可以像普通值那样,传来传去; 2). 匿名函数; 3). 函数的类型,类似于:func(float64,float64) float64

  13. 函数闭包:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func adder() func(int) int {
    6.         sum := 0
    7.         return func(x int) int {
    8.                 sum += x
    9.                 return sum
    10.         }
    11. }
    12.  
    13. func main() {
    14.         pos, neg := adder(), adder()
    15.         for i := 0; i < 10; i++ {
    16.                 fmt.Println(
    17.                         pos(i),
    18.                         neg(-2*i),
    19.                 )
    20.         }
    21. }
    22. // result:
    23. 0 0
    24. 1 -2
    25. 3 -6
    26. 6 -12
    27. 10 -20
    28. 15 -30
    29. 21 -42
    30. 28 -56
    31. 36 -72
    32. 45 -90

    1). 在函数式程序设计语言中,闭包都是个重要的玩意; 2). 闭包有时候让人赶脚捉摸不透; 3). 例子中 sum 的作用域、生命周期;实际上在一个函数内部返回一个匿名函数/闭包,如果这个返回函数使用了原来函数内的变量值,那么这些变量的生命周期便延长了;这不是 c 的风格,c 中的管你返回神马东西,局部 auto 变量函数返回后就消亡了(当然你可以使用 static 变量)!

  14. for - range 结构:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
    6.  
    7. func main() {
    8.         for i, v := range pow {
    9.             fmt.Printf("2**%d = %d\n", i, v)
    10.         }
    11. }

    1). 遍历数组或是 map 的一种很方便的方式;不同语言里都有类似结构;foreach 等; 2). 遍历过程中可以获取数组的下标以及值;

  15. for - range 简化结构:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. func main() {
    6.         pow := make([]int, 10)
    7.         for i := range pow {
    8.                 pow[i] = 1<<uint(i)
    9.         }
    10.         for _, value := range pow {
    11.                 fmt.Printf("%d\n", value)
    12.         }
    13. }

    1). 简化结构,可以只获取 index 或是只获取 value; 2). 通配符 _ ; 3). 类型转换方法:uint(i);

  16. switch 结构:
    1. package main
    2.  
    3. import (
    4.         "fmt"
    5.         "runtime"
    6. )
    7.  
    8. func main() {
    9.         fmt.Print("Go runs on ")
    10.         switch os := runtime.GOOS; os {
    11.         case "darwin":
    12.                 fmt.Println("OS X.")
    13.         case "linux":
    14.                 fmt.Println("Linux.")
    15.         default:
    16.                 // freebsd, openbsd,
    17.                 // plan9, windows...
    18.                 fmt.Printf("%s.", os)
    19.         }
    20. }

    1). switch 分支结构;从上往下匹配,匹配一个处理后便跳出去; 2). 可以添加前置语句; 3). runtime 包;runtime.GOOS 获取操作系统类型; 4). case 可以匹配 string ;匹配执行后主动跳过下面的;fallthrough ;

  17. 还是 switch 结构:
    1. package main
    2.  
    3. import (
    4.         "fmt"
    5.         "time"
    6. )
    7.  
    8. func main() {
    9.         fmt.Println("When's Saturday?")
    10.         today := time.Now().Weekday()
    11.         switch time.Saturday {
    12.         case today+0:
    13.                 fmt.Println("Today.")
    14.         case today+1:
    15.                 fmt.Println("Tomorrow.")
    16.         case today+2:
    17.                 fmt.Println("In two days.")
    18.         default:
    19.                 fmt.Println("Too far away.")
    20.         }
    21. }
    22. // 下面是另外一个例子
    23.  
    24. package main
    25.  
    26. import (
    27.         "fmt"
    28.         "time"
    29. )
    30.  
    31. func main() {
    32.         t := time.Now()
    33.         fmt.Println(t)
    34.         switch {
    35.         case t.Hour() < 12:
    36.             fmt.Println("Good morning!")
    37.         case t.Hour() < 17:
    38.             fmt.Println("Good afternoon.")
    39.         default:
    40.             fmt.Println("Good evening.")
    41.         }
    42. }

    1). time 时间包;time.Now().Weekday() ;time.Saturday;time.Now().Hour() ; 2). case 类型比较灵活; 3). 如果没有判断条件,则默认 switch true ; 4). switch 可以 添加 default 默认操作,也就是么有其他 case 匹配;

  18. 练习——求平方根:
    1. package main
    2.  
    3. import (
    4.         "fmt"
    5. )
    6.  
    7. func Sqrt(x float64) float64 {
    8.         z := 1.0
    9.        
    10.         for i:= 0; i<100; i++{
    11.                 z = z - (z*z-x)/(2*z)
    12.         }
    13.         return z
    14. }
    15.  
    16. func main() {
    17.         fmt.Println(Sqrt(3))
    18. }

    1). 使用的牛顿迭代法,迭代公式 z' = z - (z^2-x) / (2z);

  19. 练习——求复数立方根:
    1. package main
    2.  
    3. import (
    4.         "fmt"
    5.         "math/cmplx"
    6. )
    7.  
    8. func Cbrt(x complex128) complex128 {
    9.         z := complex128(1.0)
    10.        
    11.         for i:= 0; i<20; i++{
    12.                 z = z - (z*z*z-x)/(3*z*z)
    13.         }
    14.         return z
    15.        
    16. }
    17.  
    18. func main() {
    19.         fmt.Println(Cbrt(2i))
    20.         fmt.Println(cmplx.Pow(2i,1.0/3))
    21. }

    1). 还是使用的牛顿迭代法,迭代公式 z' = z - (z^3-x) / (3z^2); 2). 这个题目也能让让人熟悉一下 go 内置的复数运算;math/cmplx 包有 Pow 有幂指数函数;

  20. 练习——求斐波那契数列:
    1. package main
    2.  
    3. import "fmt"
    4.  
    5. // fibonacci is a function that returns
    6. // a function that returns an int.
    7. func fibonacci() func() int {
    8.         pre, next := 0,1
    9.         return func()int{
    10.                 pre, next = next, pre+next
    11.                 return pre
    12.         }
    13. }
    14.  
    15. func main() {
    16.         f := fibonacci()
    17.         for i := 0; i < 10; i++ {
    18.                 fmt.Println(f())
    19.         }
    20. }

    1). 闭包求 Fibonacci 数列; 2). 基本就是采用了官网的方法,只不过当初自己写的时候,并没有用 pre,next = next, pre+next 这种写法,而是通过一个辅助变量实现;

  21. 练习——使用 map 统计字数:
    1. package main
    2.  
    3. import (
    4.         "tour/wc"
    5.         "strings"
    6. )
    7.  
    8. func WordCount(s string) map[string]int {
    9.         words := strings.Fields(s)  // split words
    10.        
    11.         ans := make(map[string] int)
    12.        
    13.         for _,word := range words{  // get one word
    14.                 _,ok := ans[word]   // exist ?
    15.                 if !ok {
    16.                         ans[word] = 1
    17.                 }else{
    18.                         ans[word]++
    19.                 }
    20.         }
    21.         return ans
    22. }
    23.  
    24. func main() {
    25.         wc.Test(WordCount)
    26. }

    1). 只是 go tour 的一个练习,在本地貌似直接跑的话跑不通,使用的 tour/wc 测试程序貌似是 google 自己的,所以写的这个程序只能拷到这里试了。 2). 统计字数思路倒也简单,先将长句子分割成一个一个单词,然后用个 map 统计;一个一个滴读单词,如果存在那么数量 +1;如果不存在,那数量就是 1。 3). 写这个程序,e 体会到了map 变量使用前必须 make 一下,否则不能使用;

由于篇幅所限,也为了看着方便,前面的例子见上一篇文章,后面的例子见下一篇文章

Tags: 

Article type: