<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      go語言學習筆記

      go語言學習筆記(初級)

      最近一直在學習go語言,因此打算學習的時候能夠記錄
      一下筆記。我這個人之前是從來沒有記錄筆記的習慣,
      一直以來都是靠強大的記憶力去把一些要點記住。
      讀書的時候因為一直都是有一個很安靜和很專心的環(huán)境,
      因此很多事情都能記得很清楚,思考的很透徹。但是隨著
      年紀不斷增加,也算是經(jīng)歷了很多的事情,加上工作有時會讓人
      特別煩悶,很難把心好好靜下來去學習,去思考大自然的終極
      奧秘,因此需要記錄一些東西,這些東西一方面可以作為一種自我激勵
      的機制,另一方面,也算是自己成長的軌跡吧。

      一. 順序編程

      1. 變量

      go語言變量定義的關鍵字是var。類型放在變量名后:

         var v1 int
         var v2 string
         var v3 [10]int  //數(shù)組
         var v4 []int    //切片
         var v5 struct{  //結構體
              f int
         }              
         var v6 *int  //指針
         var v7 map[string]int  //map
         var v8 func(a int) int  //函數(shù)
      

      每一行不需要以分號作為結尾。 var
      關鍵字還有一種是把多個變量的申明放在一起,

        var(
            v1 string
            v2 int
          )
      

      2. 變量初始化

      有人說,變量初始化有什么好提的,那么簡單。是的,但是這里面確實還是有一些值得注意的點。

      var a int = 10  //完整定義
      var a = 10  //自動推斷是int型
      a := 10    //自動推斷是int型,申明并未該變量賦值
      

      第三種初始化方式無疑是最簡單的。
      但是要注意的是,這里面第三種方式是和特別的,比如

      var a int
      a := 10
      

      等價于

      var a int
      var a int
      a = 10
      

      這時候就會報一個重復定義的錯誤。

      3. 變量賦值

      變量賦值和我們通常的語言基本是一致的,但是多了多重賦值功能。

      i,j=j,i

      這就直接實現(xiàn)了兩個變量的交換。

      4. 匿名變量

      go語言的函數(shù)是多返回值的,因此可能有些值并沒有被用到,這時我們就需要一個占位符去忽略這些返回值。

      func GetName() (firstName, lastName, nickName string) {
       return "May", "Chan", "Chibi Maruko"
      }
      _, _, nickName := GetName()
      

      5. 定義常量

      通過const關鍵字,可以用來定義常量。

      const Pi float64 = 3.1415926
      const zero = 0.0 //自動推斷類型
      const (             //多定義
        size int64 = 10
        hello = -1
      )
      const u , v float32 = 0.0 , 3.0 //多重賦值
      const a , b , c = 1 , 2 , “hello” //自動推斷類型
      

      常量的定義也可以跟一個表達式, 但是這個表達式應該是編譯的時候就可以求值的.

      const mask = 1 << 3 //正常
      const Home = os.GetEnv("HOME") //錯誤,運行時才能確定
      

      6. 預定義常量

      這里要講一個很有意思的東西, 叫做iota.

      這個東西每一個const出現(xiàn)的位置被置為0,沒出現(xiàn)一個iota出現(xiàn),都自增1,到寫一個const出現(xiàn)的時候,又置為0.

      const (
        c1 = iota //0
        c2 = iota //1
        c3 = iota //2
      )
      const x = iota // x == 0 (因為iota又被重設為0了)
      const y = iota // y == 0 (同上)
      

      如果兩個const賦值表達式是一樣的,可以省略后面的賦值表達式.

      const (
        c1 = iota //0
        c2 //1
        c3 //3
      )
      const (
        a = 1 <<iota // a == 1 (iota在每個const開頭被重設為0)
        b // b == 2
        c // c == 4
      )
      

      6. 枚舉

      const (
        Sunday = iota
        Monday
        Tuesday
        Wednesday
        Thursday
        Friday
        Saturday
        numberOfDays // 這個常量沒有導出
      )
      

      大寫字母開頭的包外可見, 小寫字母開頭的包外不可見.

      7. 類型

      • 整型

        int8, uint8, int16, uint16,int32, uint32, int64, uint64, int, uint, uintptr

        不同類型不能相互比較.

      • 浮點類型

        float32, float64

        涉及的一些運算比較簡單, 我們不做細講.

      • 字符串類型

        下面我們展示一個完整的go語言程序, 也是以hello
        world為主題, 畢竟hello world是一個萬斤油的主題.

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
          fmt.Println("hello,world!")
        }
        

        這基本上是一個最簡單的程序了,但是對于我們的學習非常有用,用這個模板可以寫出非常好的東西出來.

        字符串串本身非常簡單,主要就是一些字符串操作, 比如取特定位置的字符等.

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
          var str string = "hello,world!"
          fmt.Println(str)
          ch := str[0]  //取某個特定位置的字符
          fmt.Printf("%c\n",ch)
          length := len(str)
          fmt.Println(length) //len用來獲取長度
          str = "你好,世界"
          ch = str[0]
          fmt.Printf("%c\n",ch)
          length = len(str)
          fmt.Println(length)
        }
        

        輸出結果為:

        hello,world!
        h
        12
        ?
        13
        

        這正好說明[]和len都不能處理中文.

        字符串連接也是用+.

        字符串的遍歷:

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
          var str string = "hello,world!"
          n := len(str)
          for i := 0; i < n; i++ {
            ch := str[i]
            fmt.Printf("%c\n",ch)
          }
        }
        

        輸出結果:

        h
        e
        l
        l
        o
        ,
        w
        o
        r
        l
        d
        !
        

        對于中文, 結果是亂碼, 因為是一個字節(jié)一個字節(jié)輸出的, 但是默認是UTF8編碼, 一個中文對應3個字節(jié).
        這里我們要提到一個range的關鍵字, 它可以把字符串按鍵值對的方式返回.

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
          var str string = "hello,world! 你好,世界!"
          for _, ch := range str {
            fmt.Printf("%c\n",ch)
          }
        }
        

        輸出結果為:

        h
        e
        l
        l
        o
        ,
        w
        o
        r
        l
        d
        !
        
        你
        好
        ,
        世
        界
        !
        

        事實上, 字符類型有兩種, 一種就是byte(uint8), 另一種是rune. 第一種遍歷字符串ch是byte, 而第二種是rune.

      • 數(shù)組

        數(shù)組這種類型是非常常見的

        [32]byte
        [2*N] struct { x, y int32 }
        [1000]*float64
        [3][5]int
        [2][2][2]float64
        

        數(shù)組的遍歷和字符串一樣,這里不再重復.

        數(shù)組是值類型,在賦值時會拷貝一份.

      • 數(shù)組切片

        數(shù)組切片的概念比較復雜, 它有點類似于c++中vector的概念, 但又不完全一樣.

        我們這里詳細提幾點.

        1. 切片的創(chuàng)建

          切片有兩種創(chuàng)建方式, 一種是基于數(shù)組創(chuàng)建, 另一種是用make創(chuàng)建.

          package main
          
          import "fmt" //引入依賴包
          
          func main() {
            //從數(shù)組創(chuàng)建
            var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,10}
            var sa []int = myArray[5:]
            for _, e := range sa {
                fmt.Println(e)
            }
            fmt.Println(len(sa))
            fmt.Println(cap(sa))
            //從make創(chuàng)建
            var mySlice2 []int = make([]int, 5, 10)
            for _, e := range mySlice2 {
                fmt.Println(e)
            }
            fmt.Println(len(mySlice2))
            fmt.Println(cap(mySlice2))
            //賦值
            var mySlice3 []int = []int{1,2,3}
            for _, e := range mySlice2 {
                fmt.Println(e)
            }
          }
          

          slice是引用類型.

          package main
          
          import "fmt" //引入依賴包
          
          func test(a [10]int)  {
            a[0] = 10
          }
          
          func printArray(a [10]int){
            for _, e := range a {
                fmt.Println(e)
            }
          }
          
          func main() {
            var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,10}
            printArray(myArray)
            test(myArray)
            printArray(myArray)
          }
          

          輸出結果:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          

          我們發(fā)現(xiàn)數(shù)組確實是按照值來傳遞. 那么如果是slice呢, 會發(fā)生什么?

          package main
          
          import "fmt" //引入依賴包
          
          func test(a []int)  {
            a[0] = 10
          }
          
          func printArray(a []int){
            for _, e := range a {
                fmt.Println(e)
            }
          }
          
          func main() {
            var myArray []int = []int{1,2,3,4,5,6,7,8,9,10}
            printArray(myArray)
            test(myArray)
            printArray(myArray)
          }
          

          輸出結果:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          10
          2
          3
          4
          5
          6
          7
          8
          9
          10
          

          確實是按照引用來傳遞的.

          append函數(shù)可以往切片尾部增加元素.

          mySlice = append(mySlice, 1, 2, 3)

          mySlice = append(mySlice, mySlice2...)

          ...表示把一個slice拆成元素來處理.

          package main
          
          import "fmt" //引入依賴包
          
          func main() {
            var slice1 []int = make([]int,5,10)
            var slice2 []int = []int{1,2,3}
          
            fmt.Println(slice1)
            fmt.Printf("%p\n",slice1)
            slice1 = append(slice1,slice2...)
            fmt.Println(slice1)
            fmt.Printf("%p\n",slice1)
            slice1 = append(slice1,slice2...)
            fmt.Println(slice1)
            fmt.Printf("%p\n",slice1)
          }
          

          輸出結果:

          [0 0 0 0 0]
          0xc820012190
          [0 0 0 0 0 1 2 3]
          0xc820012190
          [0 0 0 0 0 1 2 3 1 2 3]
          0xc82005e000
          

          在這里我們看到,slice的地址是所隨著內(nèi)內(nèi)存的改變而變化的,因此是需要仔細思考的.我個人不覺得
          go語言這種特性有什么好的,反正也是奇葩極了. 不過slice還提供copy, 也算是一些彌補吧.

      • map

        go語言中,map使用非常簡單.基本上看代碼就會了.

        package main
        
        import "fmt" //引入依賴包
        
        //定義一個Person的結構體
        type Person struct{
          name string
          age int
        }
        
        func main() {
          var dic map[string]Person = make(map[string]Person , 100) //初始化map
          dic["1234"] = Person{name:"lilei",age:100}
          dic["12345"] = Person{name:"hanmeimei",age:20}
          dic["123456"] = Person{name:"dagong",age:30}
          fmt.Println(dic)
          //刪除dagong
          delete(dic,"123456")
          fmt.Println(dic)
          //查找某個key
          value,ok := dic["123456"]
          if ok {
              fmt.Println(value)
          }
          value,ok = dic["1234"]
          if ok {
              fmt.Println(value)
          }
        
          for k,v := range dic {
            fmt.Println(k + ":" + v.name)
          }
        }
        

        輸出結果為:

        map[12345:{hanmeimei 20} 123456:{dagong 30} 1234:{lilei 100}]
        map[1234:{lilei 100} 12345:{hanmeimei 20}]
        {lilei 100}
        12345:hanmeimei
        1234:lilei
        

        map很簡單吧. 數(shù)據(jù)結構我們講完了, 接下來可以看看代碼的程序控制了.

      8. 程序控制

      程序控制本人只提一些關鍵性的東西,不會啰嗦太多.

      • switch語句

        switch語句不需要在每個case地下寫break,默認就是執(zhí)行break.如果要執(zhí)行多個case, 在case最后加入fallthrough.
        條件表達式不限制為常量或者整數(shù).單個case自然可以有多個結果可以選.

        package main
        
        import "fmt" //引入依賴包
        
        func test(a int)  {
            switch {
              case a < 0:
                  fmt.Println("hello")
              case a == 10:
                  fallthrough
              case a > 10 && a < 100:
                  fmt.Println("world")
              default:
                  fmt.Println("nima")
            }
        }
        
        func main() {
            test(-1)
            test(10)
            test(100)
        }
        
      • 循環(huán)

        go語言的循環(huán)比較特別, 它用一個for就把for和while的活都干了.

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
            sum := 0
            for i := 0; i <= 100; i++ {
                sum += i
            }
            fmt.Println(sum)
        
            sum = 0
            i := 0
            for(i <= 100){
              sum += i
              i++
            }
            fmt.Println(sum)
        }
        

        break還支持break到指定的label處.

        for j := 0; j < 5; j++ {
         for i := 0; i < 10; i++ {
         if i > 5 {
         break JLoop
         }
         fmt.Println(i)
         }
        }
        JLoop:
        // ...
        
      • 函數(shù)

        函數(shù)是一個非常重要的概念, 也很簡單. go的函數(shù)以func關鍵字定義, 支持不定參數(shù)和多返回值. 函數(shù)名首字符的大小寫是很有講究的:

        小寫字母開頭的函數(shù)只在本包內(nèi)可見,大寫字母開頭的函數(shù)才能被其他包使用。這個規(guī)則也適用于類型和變量的可見性。

        package main
        
        import "fmt" //引入依賴包
        
        //...int是不定參數(shù),實際上就是一個slice, a,b是多返回值
        func SumAndAverage(sample ...int) (a , b float64)  {
          a , b = 0 , 0
          for _, d := range sample {
            a += float64(d)
          }
          if len(sample) == 0 {
            b = 0
          }else{
              b = a / float64(len(sample))
          }
          return a , b
        }
        
        func main() {
          a , b := SumAndAverage(1, 2 , 3)
          fmt.Println(a , b)
        }
        

        很簡單吧. 注意, 如果是函數(shù)里面調(diào)了其他函數(shù), 那么這個sample怎么傳給其他喊函數(shù)呢?

        sample... //...表示是拆成一個個元素傳遞

        匿名函數(shù)的概念也很簡單, 只要看代碼就會明白.

        package main
        
        import "fmt" //引入依賴包
        
        func main() {
          var myFunc func(...int)(float64, float64)= func(sample ...int) (a , b float64)  {
            a , b = 0 , 0
            for _, d := range sample {
              a += float64(d)
            }
            if len(sample) == 0 {
              b = 0
            }else{
                b = a / float64(len(sample))
            }
            return a , b
          }
        
          a , b := myFunc(1, 2 , 3)
          fmt.Println(a , b)
        }
        

        下面是關于閉包的概念. 這個概念在許式偉的書中被詮釋的非常好:

        閉包是可以包含自由(未綁定到特定對象)變量的代碼塊,這些變量不在這個代碼塊內(nèi)或者任何全局上下文中定義,
        而是在定義代碼塊的環(huán)境中定義。
        要執(zhí)行的代碼塊(由于自由變量包含在代碼塊中,所以這些自由變量以及它們引用的對象沒有被釋放)為自由變量提供綁定定的計算環(huán)境(作用域)。
        閉包的實現(xiàn)確保只要閉包還被使用,那么被閉包引用的變量會一直存在.
        *

        我們來看來兩個閉包的例子.

        package main
        
        import "fmt" //引入依賴包
        
        func test(i int) func()  {
          return func(){
            fmt.Println(10+i)
            fmt.Printf("%p\n",&i)
          }
        }
        
        func main() {
          a := test(1);
          b := test(2)
          a()
          b()
        }
        

        輸出結果:

        11
        0xc82000a288
        12
        0xc82000a2c0
        

        我們從這個結果中發(fā)現(xiàn), i的地址是會變的, 因為是作為一個局部變量傳進去的.

        package main
        
        import "fmt" //引入依賴包
        
        func test(x int) func(int) int {
          return func(y int) int {
            fmt.Printf("%p\n",&x)
            return x + y
          }
        }
        
        func main() {
          a := test(1);
          fmt.Println(a(10))
          fmt.Println(a(20))
        }
        

        輸出結果:

        0xc82000a288
        11
        0xc82000a288
        21
        

        因為x只傳入了一次, 因此沒有改變.

        package main
        
        import (
         "fmt"
        )
        
        func main() {
           var j int = 5
           a := func() (func()) {
                var i int = 10
                return func() {
                  fmt.Printf("i, j: %d, %d\n", i, j)
                }
           }()
           a()
           j *= 2
           a()
         }
        

        此時輸出:

        i, j: 10, 5
        i, j: 10, 10
        

      b = a b.Mofify()

      如果b改變, a不發(fā)生改變, 就是值語義. 如果b改變, a也發(fā)生改變, 就是引用語義.

      go語言大多數(shù)類型都是值語義, 比如:

      基本類型: byte, int, float32, float64, string
      復合類型: struct, array, pointer

      也有引用語義的, 比如:
      slice, map, channel, interface.

      這是我們要牢記的.

      我們的筆記整體式按照許式偉的書來安排, 但是由于許的書提綱性很強, 內(nèi)容上不是很詳細, 基本上會重新整理補充一些東西進去.

      • 結構體

      結構體是用struct來申明的, 不多廢話, 直接上代碼.

      package main
      
      import (
       "fmt"
      )
      
      //申明一個結構體
      type Person struct{
        Name string
        Age int
      }
      
      func main() {
      
        //結構體的初始化方式
        //1. 直接賦值
        var p Person
        p.Name = "dingding"
        p.Age = 10
        fmt.Println(p)
      
        //2.順序賦值
        p1 := Person{"dingding",10}
        fmt.Println(p1)
      
        //3. key value 賦值
        p2 := Person{Name:"dingding",Age:10}
        fmt.Println(p2)
      
        //4.指針賦值
        p3 := &Person{Name:"dingding",Age:10}
        fmt.Println(p3)
        p4 := new(Person)
        fmt.Println(p4)
      
        fmt.Println("---------------------------")
      
        a := p
        a.Name = "dongdong"
        b := p3
        b.Name = "dongdong"
        fmt.Println(p)
        fmt.Println(p3)
      
      }
      

      輸出結果:

      {dingding 10}
      {dingding 10}
      {dingding 10}
      &{dingding 10}
      &{ 0}
      ---------------------------
      {dingding 10}
      &{dongdong 10}
      

      這說明,struct確實是值語義.

      下面討論一下結構體的組合問題. 這點許的書中并沒有過多涉及, 但是還是很有必要的, 因為在實際場合中用的會很多.

      package main
      
      import (
       "fmt"
      )
      
      //申明一個結構體
      type Human struct{
        Name string
        Age int
        Phone string
      }
      
      //再申明一個結構體
      type Employee struct {
          Person Human  // 匿名字段Human
          Speciality string
          Phone string  // 雇員的phone字段
      }
      
      func main() {
        e := Employee{
          Person:Human{
            Name:"dingding",
            Age:11,
            Phone:"6666666",
          },
          Speciality:"aaa",
          Phone:"77777777",
        }
        fmt.Println(e.Phone)
      
      }
      

      這段代碼看上去非常ok, 但是如果我們稍微改一下代碼呢? 比如把
      Person改成匿名結構, 會有些好玩的現(xiàn)象.

      package main
      
      import (
       "fmt"
      )
      
      //申明一個結構體
      type Human struct{
        Name string
        Age int
        Phone string
      }
      
      //再申明一個結構體
      type Employee struct {
          Human  // 匿名字段Human
          Speciality string
          //Phone string  // 雇員的phone字段
      }
      
      func main() {
        e := Employee{
          Human{"dingding",11,"6666666"},
          "aaa",
          //Phone:"77777777",
        }
        fmt.Println(e.Phone)
      
      }
      

      此時輸出的是6666666, 因為相當于它把Human的字段Phone繼承下來了.
      如果Employee里也定義Phone字段呢?

      package main
      
      import (
       "fmt"
      )
      
      //申明一個結構體
      type Human struct{
        Name string
        Age int
        Phone string
      }
      
      //再申明一個結構體
      type Employee struct {
          Human  // 匿名字段Human
          Speciality string
          Phone string  // 雇員的phone字段
      }
      
      func main() {
        e := Employee{
          Human{"dingding",11,"6666666"},
          "aaa",
          "77777777",
        }
        fmt.Println(e.Phone)
      
      }
      

      此時輸出的時77777777, 因為相當于是覆蓋. 那么怎么輸出6666666呢?

      package main
      
      import (
       "fmt"
      )
      
      //申明一個結構體
      type Human struct{
        Name string
        Age int
        Phone string
      }
      
      //再申明一個結構體
      type Employee struct {
          Human  // 匿名字段Human
          Speciality string
          Phone string  // 雇員的phone字段
      }
      
      func main() {
        e := Employee{
          Human{"dingding",11,"6666666"},
          "aaa",
          "77777777",
        }
        fmt.Println(e.Phone)
        fmt.Println(e.Human.Phone)
      }
      

      輸出結果:

      77777777
      6666666
      

      所以, 匿名結構體的組合相當于有繼承的功能.

      • 為類型添加方法

      這個概念和java或者是C++非常不一樣, 它的理念是把似乎是把方法綁定到特定類型上去.
      這個概念已經(jīng)不僅僅是對象的概念了, 事實上,
      我也不知道google這幫人腦子是怎么想的, 這種搓劣的復古風格,
      也是讓我打開眼界, 我個人覺得, go雖然仗著google的名氣, 似乎顯得很厲害, 但是,
      比起java和C++, 簡直就是個玩具, 說的不好聽一點,
      比起scala這樣的一出生就是個大胖子, go更像是個缺胳膊少腿的畸形兒.

      好了, 不說了, 直接上代碼.

      package main
      
      import (
       "fmt"
      )
      
      //go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
      //因此需要type一下, Integer就是本包內(nèi)定義的類型
      type Integer int
      
      //為int綁定一個Print方法
      func (i Integer) Println() {
        fmt.Println(i)
      }
      
      func main() {
        var a Integer = 10
        a.Println()
      }
      

      結果輸出10, 如果是如下呢?

      package main
      
      import (
       "fmt"
      )
      
      //go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
      //因此需要type一下, Integer就是本包內(nèi)定義的類型
      type Integer int
      
      //為int綁定一個Print方法
      func (i Integer) Println() {
        fmt.Println(i)
      }
      
      func main() {
        a := 10
        a.Println()
      }
      

      輸出結果:

      # command-line-arguments
      ./main.go:18: a.Println undefined (type int has no field or method Println)
      

      因為a := 10, go會把a推斷為int, 但int并沒綁上Println方法.

      注意:

      //為int綁定一個Print方法
      func (i Integer) Println() {
        fmt.Println(i)
      }
      

      這里的i并不是引用類型,因此對i的修改不會反應到a上去.

      package main
      
      import (
       "fmt"
      )
      
      //go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
      //因此需要type一下, Integer就是本包內(nèi)定義的類型
      type Integer int
      
      //為int綁定一個Print方法
      func (i Integer) Add() {
        i += 2
      }
      
      func main() {
        var a Integer = 10
        a.Add()
        fmt.Println(a)
      }
      

      輸出10.

      如果我們用引用呢?

      package main
      
      import (
       "fmt"
      )
      
      //go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
      //因此需要type一下, Integer就是本包內(nèi)定義的類型
      type Integer int
      
      //為int綁定一個Print方法
      func (this *Integer) Add() {
        *this += 2
      }
      
      func main() {
        var a Integer = 10
        a.Add()
        fmt.Println(a)
      }
      

      這時輸出12. 我們發(fā)現(xiàn), 這個this就像是我們C++里面的this指針一樣.
      不過也傻13復古的多.

      我們在看一個例子,

      package main
      
      import (
       "fmt"
      )
      
      //go綁定方法必須是本包內(nèi)的,int不是main包內(nèi)定義的.
      //因此需要type一下, Integer就是本包內(nèi)定義的類型
      type Integer int
      
      //為int綁定一個Print方法
      func (this *Integer) Add() {
        fmt.Println(this)
        *this += 2
      }
      
      func main() {
        var b Integer = 10
        var a *Integer = &b
        a.Add()
        fmt.Println(a)
      }
      

      輸出結果:

      0xc82000a288
      0xc82000a288
      

      我們發(fā)現(xiàn),

      //為int綁定一個Print方法
      func (this *Integer) Add() {
        fmt.Println(this)
        *this += 2
      }
      

      該方法用指針調(diào)用和用值去調(diào)用, 效果是一樣的. this都是指向原來對象的指針而已.

      下面這個例子來自于許的書中,

      package main
      
      type Integer int
      
      func (a Integer) Less(b Integer) bool {
       return a < b
      }
      
      func (a *Integer) Add(b Integer) {
       *a += b
      }
      
      type LessAdder interface {
       Less(b Integer) bool
       Add(b Integer)
      }
      
      func main() {
        var a Integer  = 1
        var b LessAdder = a
      }
      

      輸出:

      # command-line-arguments
      ./main.go:20: cannot use a (type Integer) as type LessAdder in assignment:
      Integer does not implement LessAdder (Add method has pointer receiver)
      

      這個例子似乎有點奇怪, 為什么呢?

      package main
      
      type Integer int
      
      func (a Integer) Less(b Integer) bool {
       return a < b
      }
      
      func (a *Integer) Add(b Integer) {
       *a += b
      }
      
      type LessAdder interface {
       Less(b Integer) bool
       Add(b Integer)
      }
      
      type Less interface {
       Less(b Integer) bool
      }
      
      type Adder interface {
      Add(b Integer)
      }
      
      func main() {
        var a Integer  = 1
        var b Adder = a
        b.Add(10)
      }
      

      我們可以看得更清楚:

      ./main.go:28: cannot use a (type Integer) as type Adder in assignment:
      Integer does not implement Adder (Add method has pointer receiver)
      

      但如果是:

      package main
      
      type Integer int
      
      func (a Integer) Less(b Integer) bool {
       return a < b
      }
      
      func (a *Integer) Add(b Integer) {
       *a += b
      }
      
      type LessAdder interface {
       Less(b Integer) bool
       Add(b Integer)
      }
      
      type Less interface {
       Less(b Integer) bool
      }
      
      type Adder interface {
      Add(b Integer)
      }
      
      func main() {
        var a Integer  = 1
        var b Integer = a
        b.Add(10)
      }
      

      就沒有任何問題. 對比起來, 就是這兩行代碼:

      var b Integer = a
      var b Adder = a
      

      我們接下去會娓娓道來其中的奧妙.

      package main
      
      import(
        "fmt"
      )
      
      //定義對象People、Teacher和Student
      type People struct {
        Name string
      }
      
      type Teacher struct{
        People
        Department string
      }
      
      type Student struct{
        People
        School string
      }
      
      //對象方法實現(xiàn)
      func (p *People) SayHi() {
        fmt.Printf("Hi, I'm %s. Nice to meet you!\n",p.Name)
      }
      
      func (t *Teacher) SayHi(){
        fmt.Printf("Hi, my name is %s. I'm working in %s .\n", t.Name, t.Department)
      }
      
      func (s *Student) SayHi() {
        fmt.Printf("Hi, my name is %s. I'm studying in %s.\n", s.Name, s.School)
      }
      
      func (s *Student) Study() {
        fmt.Printf("I'm learning Golang in %s.\n", s.School)
      }
      
      //定義接口Speaker和Learner
      type Speaker interface{
        SayHi()
      }
      
      type Learner interface{
        SayHi()
        Study()
      }
      func main() {
      
          //初始化對象
          people := People{"張三"}
        //  teacher := &Teacher{People{"鄭智"}, "Computer Science"}
        //  student := &Student{People{"李明"}, "Yale University"}
      
          var speaker Speaker   //定義Speaker接口類型的變量
      
          speaker = people
          speaker.SayHi()
      }
      

      這時就會出現(xiàn)上面我們提到的錯誤. 盡管如果我們這么去調(diào)用:

      package main
      
      import(
        "fmt"
      )
      
      //定義對象People、Teacher和Student
      type People struct {
        Name string
      }
      
      type Teacher struct{
        People
        Department string
      }
      
      type Student struct{
        People
        School string
      }
      
      //對象方法實現(xiàn)
      func (p *People) SayHi() {
        fmt.Printf("Hi, I'm %s. Nice to meet you!\n",p.Name)
      }
      
      func (t *Teacher) SayHi(){
        fmt.Printf("Hi, my name is %s. I'm working in %s .\n", t.Name, t.Department)
      }
      
      func (s *Student) SayHi() {
        fmt.Printf("Hi, my name is %s. I'm studying in %s.\n", s.Name, s.School)
      }
      
      func (s *Student) Study() {
        fmt.Printf("I'm learning Golang in %s.\n", s.School)
      }
      
      //定義接口Speaker和Learner
      type Speaker interface{
        SayHi()
      }
      
      type Learner interface{
        SayHi()
        Study()
      }
      
      func main() {
      
        //初始化對象
        people := People{"張三"}
      //  teacher := &Teacher{People{"鄭智"}, "Computer Science"}
      //  student := &Student{People{"李明"}, "Yale University"}
      
        //var speacker Speaker   //定義Speaker接口類型的變量
        //speacker = people
      
        people.SayHi()
      }
      

      或者

      package main
      
      import(
        "fmt"
      )
      
      //定義對象People、Teacher和Student
      type People struct {
        Name string
      }
      
      type Teacher struct{
        People
        Department string
      }
      
      type Student struct{
        People
        School string
      }
      
      //對象方法實現(xiàn)
      func (p *People) SayHi() {
        fmt.Printf("Hi, I'm %s. Nice to meet you!\n",p.Name)
      }
      
      func (t *Teacher) SayHi(){
        fmt.Printf("Hi, my name is %s. I'm working in %s .\n", t.Name, t.Department)
      }
      
      func (s *Student) SayHi() {
        fmt.Printf("Hi, my name is %s. I'm studying in %s.\n", s.Name, s.School)
      }
      
      func (s *Student) Study() {
        fmt.Printf("I'm learning Golang in %s.\n", s.School)
      }
      
      //定義接口Speaker和Learner
      type Speaker interface{
        SayHi()
      }
      
      type Learner interface{
        SayHi()
        Study()
      }
      
      func main() {
      
        //初始化對象
        people := People{"張三"}
      //  teacher := &Teacher{People{"鄭智"}, "Computer Science"}
      //  student := &Student{People{"李明"}, "Yale University"}
      
        var speacker Speaker   //定義Speaker接口類型的變量
        speacker = &people
      
        speacker.SayHi()
      }
      

      這樣就都沒有任何問題, 這就是說什么呢? 這說明對于對象的方法, 無論接受者是對象還是對象指針, 都沒
      任何問題. 但是如果是借口,如果接口中存在某個方法,綁定的接收者是對象指針,那么這個接口
      也只能被該對象指針賦值
      . 如此奇葩的設計, 我只能說, go的設計者真是個腦殘.

      • 繼承

      好了, 下面正式講繼承的語法, 話說那玩意兒的真的算不上繼承, 比C++的繼承真的時不知道low到哪里去了. 但是我也不知道為啥這是go愛好者們最愛標榜的東西,
      有時我想想, 程序員也真是單純的人, 一點點的蠱惑, 就會讓他們激動不已,
      感覺就要去參加革命了似的.

      go的繼承非常簡陋, 就是一個匿名結構組合的問題. 不廢話,直接上代碼.

      package main
      
      import(
        "fmt"
      )
      
      //基類
      type Base struct{
        Name string
      }
      
      //綁定Say方法
      func (b *Base) Say()  {
        fmt.Println(b.Name)
      }
      
      //綁定ok方法
      func (b *Base) Ok()  {
        fmt.Println("ok")
      }
      
      //Foo有個匿名結構Base
      type Foo struct{
        Base
        Name string
      }
      
      //重寫Say方法
      func (f *Foo) Say()  {
        f.Base.Say()
        fmt.Println(f.Name)
      }
      
      func main() {
        var f *Foo = &Foo{Base{"father"},"sun"}
        f.Ok()
        f.Say()
      }
      

      輸出結果:

      ok
      father
      sun
      

      ok,下面我們看看多繼承二義性的問題.

      package main
      
      import(
        "fmt"
      )
      
      //father
      type Father struct{
      }
      
      func (f *Father)Say()  {
        fmt.Println("father")
      }
      
      
      //mother
      type Mother struct{
      }
      
      func (f *Mother)Say()  {
        fmt.Println("mother")
      }
      
      //sun
      type Sun struct{
        Father
        Mother
      }
      
      func main() {
        var s *Sun = new(Sun)
        s.Say()
      }
      

      輸出:

      # command-line-arguments
      ./main.go:32: ambiguous selector s.Say
      

      果然展現(xiàn)了二義性. 消歧義方式也是土的掉渣:

      package main
      
      import(
        "fmt"
      )
      
      //father
      type Father struct{
      }
      
      func (f *Father)Say()  {
        fmt.Println("father")
      }
      
      
      //mother
      type Mother struct{
      }
      
      func (f *Mother)Say()  {
        fmt.Println("mother")
      }
      
      //sun
      type Sun struct{
        Father
        Mother
      }
      
      func main() {
        var s *Sun = new(Sun)
        s.Father.Say()
        s.Mother.Say()
      }
      

      我也是醉了.

      此外, 我們也會看到還有一種用法,

      package main
      
      import(
        "fmt"
      )
      
      //基類
      type Base struct{
        Name string
      }
      
      //綁定Say方法
      func (b *Base) Say()  {
        fmt.Println(b.Name)
      }
      
      //綁定ok方法
      func (b *Base) Ok()  {
        fmt.Println("ok")
      }
      
      //Foo有個匿名結構Base
      type Foo struct{
        *Base  //base是個指針
        Name string
      }
      
      //重寫Say方法
      func (f *Foo) Say()  {
        f.Base.Say()
        fmt.Println(f.Name)
      }
      
      func main() {
        var f *Foo = &Foo{&Base{"father"},"sun"}
        f.Ok()
        f.Say()
      }
      

      這里Foo的Base是一個指針類型, 因此我們傳入的也必須是個指針, 我們可以看到這種用法.

      • 接口

      這個我不想吐槽什么, 前面已經(jīng)吐槽過了, 但是這種設計, 確實也是詭異之極.

      go的借口是非侵入式的, 只要實現(xiàn)了接口定義的方法, 就等于實現(xiàn)了該接口. 換句話說, 接口的實現(xiàn)和定義式可以分離
      不相關的.

      接口的例子還是之前那個:

      package main
      
      import(
        "fmt"
      )
      
      type Integer int
      
      func (a Integer) Less(b Integer) bool {
        return a < b
      }
      
      func (a *Integer) Add(b Integer) {
        *a += b
      }
      
      //定義接口
      type LessAdder interface {
        Less(b Integer) bool //函數(shù)聲明
        Add(b Integer) //函數(shù)聲明
      }
      
      func main() {
        var a Integer = 10
        var b LessAdder = &a //道理我們前面提到過了,Add接收者是個對象指針
        fmt.Println(b.Less(5))
        b.Add(20)
        fmt.Println(a)
      }
      

      輸出:

      false
      30
      

      只要兩個接口擁
      有相同的方法列表(次序不同不要緊),那么它們就是等同的,可以相互賦值。

      接口賦值并不要求兩個接口必須等價。如果接口A的方法列表是接口B的方法列表的子集,
      那么接口B可以賦值給接口A。

      幾個接口也可以組合出一個接口.

      type ReadWriter interface {
       Reader
       Writer
      }
      

      等價于:

      type ReadWriter interface {
       Read(p []byte) (n int, err error)
       Write(p []byte) (n int, err error)
      }
      
      • Any類型

      由于Go語言中任何對象實例都滿足接口interface{},所以interface{}看起來是任何對象的Any類型

      var v1 interface{} = 1
      var v2 interface{} = "abc"
      var v3 interface{} = &v2
      var v4 interface{} = struct{ X int }{1}
      var v5 interface{} = &struct{ X int }{1}
      func Printf(fmt string, args ...interface{})
      func Println(args ...interface{})
      
      • 接口轉(zhuǎn)換和類型查詢

      接口轉(zhuǎn)換

      package main
      
      import(
        "fmt"
      )
      
      type Integer int
      
      func (a Integer) Less(b Integer) bool {
        return a < b
      }
      
      func (a *Integer) Add(b Integer) {
        *a += b
      }
      
      //定義接口
      type LessAdder interface {
        Less(b Integer) bool //函數(shù)聲明
        Add(b Integer) //函數(shù)聲明
      }
      
      //定義接口
      type Adder interface {
        Add(b Integer) //函數(shù)聲明
      }
      
      func main() {
        var a Integer = 10
        var b LessAdder = &a //道理我們前面提到過了,Add接收者是個對象指針
        if  c , ok = b.(Adder); ok{
          c.Add(10)
          fmt.Println(a)
          //fmt.Println(c.Less(100)) //報錯,c.Less undefined (type Adder has no field or method Less)
        }
      }
      

      類型查詢

      package main
      
      import(
        "fmt"
      )
      
      
      func main() {
        b := "a"
        var a interface{} = b
        switch a.(type) {
        case int:
          fmt.Println("int")
        case float32:
          fmt.Println("float32")
        case int32:
          fmt.Println("int32")
        case float64:
          fmt.Println("float64")
        case bool:
          fmt.Println("bool")
        case string:
          fmt.Println("string")
        default:
          fmt.Println("ok")
      
        }
      }
      
      • 補充

      我們補充一些defer-panic-recover的案列.

      package main
      
      import (
        "fmt"
      )
      
      func f()  {
        fmt.Println("a")
      }
      
      func main() {
      
        defer func() {
          if err := recover(); err != nil{
            fmt.Println(err)
          }
        }()
        f()
        fmt.Println("b")
      }
      

      輸出結果:

      a
      b
      

      如果我們在f中panic呢? 這會發(fā)生什么呢?

      package main
      
      import (
        "fmt"
      )
      
      func f()  {
        fmt.Println("a")
        panic("error!")
      }
      
      func main() {
      
        defer func() {
          if err := recover(); err != nil{
            fmt.Println(err)
          }
        }()
        f()
        fmt.Println("b")
      }
      

      輸出:

      a
      error!
      

      我們發(fā)現(xiàn), b沒有輸出. panic會拋出一個異常, 由recover去捕獲.f拋出異常后, 事實上,
      main剩下的部分都不會執(zhí)行, 但是因為我們defer了,
      defer是一定會執(zhí)行的,因此我們在defer中捕獲了panic拋出的
      異常. 這就是為什么b沒有輸出. 似乎和try catch很像. 如果我們希望把b也輸出, 但也能捕獲異常呢?

      package main
      
      import (
        "fmt"
      )
      
      func f()  {
        fmt.Println("a")
        panic("error!")
      }
      
      func main() {
        func(){
          defer func() {
            if err := recover(); err != nil{
              fmt.Println(err)
            }
            }()
            f()
        }()
        fmt.Println("b")
      }
      

      輸出結果:

      a
      error!
      b
      

      如果是這樣呢?

      package main
      
      import (
        "fmt"
      )
      
      func f()  {
        fmt.Println("a")
        panic("error!")
      }
      
      func main() {
        func(){
            f()
            defer func() {
              if err := recover(); err != nil{
                fmt.Println(err)
              }
            }()
        }()
        fmt.Println("b")
      }
      

      此時, 在定義defer之前, f已經(jīng)panic了, 沒有recover去捕獲, 這個panic會一直拋出.
      直到被go虛擬機捕獲.

      輸出:

      a
      panic: error!
      
      goroutine 1 [running]:
      main.f()
      /Users/fengyan/code/go/test/main.go:9 +0x11e
      main.main.func1()
      /Users/fengyan/code/go/test/main.go:14 +0x18
      main.main()
      /Users/fe
      

      go里面有個東西很好玩, nil類似于java的null, 那么java如果對null調(diào)用方法, 會直接拋出一個空指針異常.
      那么go會怎么樣呢?

      package main
      
      func main() {
        nil.Println("a")
      }
      

      輸出結果:

      # command-line-arguments
      ./main.go:4: use of untyped nil
      

      看來還是不行的.

      所以調(diào)用前我們還是要進行空的判斷.

      三. go并發(fā)編程

      并發(fā)不是并行. 并發(fā)比并行更加優(yōu)秀. 并發(fā)是時間片輪換, 并行是多核計算. 事實上, 并行可以由并發(fā)指定到多個cpu執(zhí)行.

      我們馬上看一個具體的例子.

      package main
      
      import (
        "fmt"
      )
      
      func Add(x, y int)  {
        z := x + y
        fmt.Println(z)
      }
      
      func main() {
        for i := 0; i < 10; i++ {
          go Add(i,i)
        }
      }
      

      結果我們發(fā)現(xiàn)什么都沒有輸出, 這是因為go是異步執(zhí)行的, main不會等Add完成, 它會繼續(xù)執(zhí)行下去, 于是發(fā)生了main
      函數(shù)先結束, 于是進程就結束了.

      所以這里我們需要做同步, 所謂同步, 就是主線程去等待子線程完成(go里的線程其實是協(xié)程, 協(xié)程你可以認為是輕量級的線程).

      線程間通信模型基本上常用的也就是兩種,共享內(nèi)存和消息隊列.
      后者目前看來比較流行, 比如akka(actor模型), zmq(消息隊列模型)等 都是基于消息隊列的并發(fā)模型.

      go也是采用了消息隊列的方式, 這里就是channel.

      channel的申明形式:

      var chanName chan ElementType

      ElementType是該chan能夠支持的數(shù)據(jù)類型.

      chan的初始化有兩種:

      ch := make(chan int)

      ch := make(chan int,capacity)

      前者寫入和讀取是阻塞的, 后者自帶了一個buffer,
      如果沒有達到capacity, 是非阻塞的, 達到capacity才會阻塞. 讀取的話, 如果為buffer空,
      會阻塞.

      chan寫入數(shù)據(jù)

      ch <- value

      chan讀取數(shù)據(jù)

      value : = <-ch

      對于帶buffer的chan也可以用for和range讀取:

      for i := range ch {
       fmt.Println("Received:", i)
      }
      

      下面我們把同步后的代碼貼上:

      package main
      
      import (
        "fmt"
      )
      
      func Add(x, y int, ch chan int)  {
        z := x + y
        fmt.Println(z)
        //寫入后就阻塞
        ch <- 1
      }
      
      func main() {
        //chan int slice, make([]chan int, 10)是創(chuàng)建slice的方法
        //容量是10,超過這個容量,會發(fā)生內(nèi)存拷貝
        //var chs []chan int = make([]chan int, 10)
      
        //其實這里用數(shù)組更好
        var chs [10]chan int
      
        for i := 0; i < 10; i++ {
          chs[i] = make(chan int)
          go Add(i,i,chs[i])
        }
        //同步
        for _, ch := range chs{
          //如果線程沒有寫入數(shù)據(jù), 會阻塞
          <- ch
        }
      }
      

      輸出結果:

      18
      0
      2
      4
      12
      14
      16
      10
      8
      6
      

      下面我們介紹一下select語法,

      select {
        case <-chan1:
          //讀取數(shù)據(jù)
        case chan2 <- 1:
          //寫入數(shù)據(jù)
        default:
          //默認操作
      }
      

      select會去注冊事件, 比如是否可讀,是否可寫, 根據(jù)對應的事件去執(zhí)行相應的代碼.

      package main
      
      import (
        "fmt"
      )
      
      func RandomSignal(ch chan int)  {
        for{
          select{
            //寫入1事件
            case ch <- 1:
            //寫入0事件
            case ch <- 0:
          }
        }
      }
      
      func main() {
        //var ch chan int = make(chan int)效果一樣
        //但是有些微妙的不同
        var ch chan int = make(chan int,1)
        go RandomSignal(ch)
        for value := range ch{
          fmt.Println(value)
        }
      }
      

      這是一個隨機產(chǎn)生0或者1的信號發(fā)生器.

      單向channel

      顧名思義,單向channel只能用于發(fā)送或者接收數(shù)據(jù)。channel本身必然是同時支持讀寫的,
      否則根本沒法用。假如一個channel真的只能讀,那么肯定只會是空的,因為你沒機會往里面寫數(shù)
      據(jù)。同理,如果一個channel只允許寫,即使寫進去了,也沒有意義,因為沒有機會讀取里面
      的數(shù)據(jù)。所以的單向channel概念,其實只是對channel的一種使用限制。

      var ch1 chan int // 雙向
      var ch2 chan<- float64// 只寫
      var ch3 <-chan int // 只讀
      

      那么單向channel如何初始化呢?之前我們已經(jīng)提到過,channel是一個原生類型,因此不僅
      支持被傳遞,還支持類型轉(zhuǎn)換。只有在有了單向channel的概念后,讀者才會明白類型轉(zhuǎn)換對于
      channel的意義:就是在單向channel和?向channel之間進行轉(zhuǎn)換。示例如下:

      ch4 := make(chan int)
      ch5 := <-chan int(ch4) // 只讀
      ch6 := chan<- int(ch4) //只寫
      

      關閉channel

      close(ch)

      如何判斷一個channel是否已經(jīng)被關
      閉?我們可以在讀取的時候使用多重返回值的方式:

      x, ok := <-ch

      package main
      
      import (
        "fmt"
      )
      
      func RandomSignal(ch chan int)  {
        for i:= 0; i <= 9; i++{
          select{
            //寫入1事件
            case ch <- 1:
            //寫入0事件
            case ch <- 0:
          }
        }
        close(ch)
      }
      
      func main() {
        //var ch chan int = make(chan int)效果一樣
        //但是有些微妙的不同
        var ch chan int = make(chan int,1)
        go RandomSignal(ch)
        for value := range ch{
          fmt.Println(value)
        }
      }
      

      輸出:

      1
      0
      1
      1
      0
      1
      1
      0
      0
      0
      

      close(ch)之后, 對ch的for循環(huán)會終止.

      并行計算:

      package main
      
      import (
        "runtime"
        "sync"
        "fmt"
      )
      
      type Vector []float64
      
      func min(a int, b int) int {
        if a < b {
          return a
        }
        return b
      }
      
      func max(a int, b int) int {
        if a < b {
          return b
        }
        return a
      }
      
      func mul(u, v Vector, k int) (res float64) {
          n := min(k+1, len(u))
          j := min(k, len(v)-1)
          for i := k - j; i < n; i, j = i+1, j-1 {
              res += u[i] * v[j]
          }
          return
      }
      
      func Convolve(u, v Vector) (w Vector) {
          n := len(u) + len(v) - 1
          w = make(Vector, n)
      
          size := max(1, 1<<20/n)
      
          wg := new(sync.WaitGroup)
          wg.Add(1 + (n-1)/size)
          for i := 0; i < n && i >= 0; i += size {
              j := i + size
              if j > n || j < 0 {
                  j = n
              }
      
              go func(i, j int) {
                  for k := i; k < j; k++ {
                      w[k] = mul(u, v, k)
                  }
                  wg.Done()
              }(i, j)
          }
          wg.Wait()
          return
      }
      
      func main() {
        //numcpu := runtime.NumCPU()
       numcpu := 2
       runtime.GOMAXPROCS(numcpu) //設置使用多少個cpu核心
      
        var a Vector = make([]float64,1000000)
        var b Vector = make([]float64,1000000)
      
        for i := 0; i < 1000000 - 1; i++ {
          a[i] = 1
          b[i] = 1
        }
      
        w := Convolve(a,b)
        fmt.Println(w)
      }
      
      

      runtime.GOMAXPROCS(numcpu)

      該函數(shù)是用來設置cpu使用的核心個數(shù), 在許的書中, 如果不設置這個環(huán)境變量, 默認是1個核心, 但是事實上,
      在我這個版本的go語言中, 已經(jīng)支持默認多核并發(fā)啦.

      下面我們來看看同步的概念.

      Go語言包中的sync包提供了兩種?類型:
      sync.Mutex和sync.RWMutex。sync.Mutex
      是讀寫都鎖, sync.RWMutex是寫鎖讀不鎖.

      用法:

      var l sync.Mutex
      func foo() {
       l.Lock()
       defer l.Unlock()
       //...
      }
      

      對于從全局的角度只需要運行一次的代碼,比如全局初始化操作,Go語言提供了一個Once
      類型來保證全局的唯一性操作,具體代碼如下:

      var a string
      var once sync.Once
      func setup() {
       a = "hello, world"
      }
      func doprint() {
       once.Do(setup)
       print(a)
      }
      func twoprint() {
       go doprint()
       go doprint()
      }
      

      如果不考慮線程安全, 等價于:

      var done bool = false //全局變量
      func setup() {
       a = "hello, world"
       done = true
      }
      func doprint() {
       if !done {
       setup()
       }
      print(a)
      }
      

      WaitGroup

      var wg sync.WaitGroup

      該類型有三個指針方法,即Add、Done和Wait。
      類型sync.WaitGroup是一個結構體類型。在它之中有一個代表計數(shù)的字段。
      當一個sync.WaitGroup類型的變量被聲明之后,其值中的那個計數(shù)值將會是0。
      我們可以通過該值的Add方法增大或減少其中的計數(shù)值。

      雖然Add方法接受一個int類型的值,并且我們也可以通過該方法減少計數(shù)值,但是我們一定不要讓計數(shù)值變?yōu)樨摂?shù)。因為這樣會立即引發(fā)一個運行恐慌。
      這也代表著我們對sync.WaitGroup類型值的錯誤使用。
      除了調(diào)用sync.WaitGroup類型值的Add方法并傳入一個負數(shù)之外,我們還可以通過調(diào)用該值的Done來使其中的計數(shù)值減一。

      當我們調(diào)用sync.WaitGroup類型值的Wait方法的時候,它會去檢查該值中的計數(shù)值。
      如果這個計數(shù)值為0,那么該方法會立即返回,且不會對程序的運行產(chǎn)生任何影響。 但是,如果這個計數(shù)值大于0,
      那么該方法的調(diào)用方所屬的那個Goroutine就會被阻塞。
      直到該計數(shù)值重新變?yōu)?之時,為此而被阻塞的所有Goroutine才會被喚醒。

      我們來看第一個例子:

      package main
      
      import (
        "fmt"
        "sync"
      )
      
      func Add(x, y int, wg *sync.WaitGroup) {
        z := x + y
        fmt.Println(z)
        wg.Done()
      }
      
      func main() {
      
        var wg *sync.WaitGroup = &sync.WaitGroup{}
      
        wg.Add(10)
      
        for i := 0; i < 10; i++ {
          go Add(i,i,wg)
        }
      
        wg.Wait()
      }
      

      初級教程我們就這樣快速結束了, 咱們高級見. 嗯, 233333333.

      posted @ 2016-01-04 15:59  逝雪  閱讀(1001)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 丰满岳妇乱一区二区三区| 成年女性特黄午夜视频免费看| 日韩一区二区三区东京热| 午夜爽爽爽男女污污污网站| 在线一区二区中文字幕| 国产欧美日韩精品丝袜高跟鞋| 亚洲第一国产综合| 亚洲午夜福利精品无码不卡| 日本三级香港三级三级人!妇久| 国产不卡一区二区在线视频| 国产精品久久中文字幕| 国产精品视频午夜福利| 又爽又黄又无遮掩的免费视频 | 大香伊蕉在人线国产av| 亚洲精品中文字幕一区二| 亚洲色大成网站WWW永久麻豆| 国产精品综合av一区二区| 色8久久人人97超碰香蕉987| 国产在线精品一区二区夜色| 亚洲欧美日韩在线码| 少妇的丰满3中文字幕| 久久这里都是精品二| 亚洲国产一区二区三区久| 丁香五香天堂网| 国产精品综合在线免费看| 精品人妻日韩中文字幕| 闸北区| 亚洲一区二区美女av| 国产亚洲精品AA片在线播放天 | 亚洲自拍偷拍中文字幕色| 国产亚洲精品成人aa片新蒲金| 亚洲国产精品毛片av不卡在线| 福海县| 中文字幕国产在线精品| 亚洲欧洲精品一区二区| 欧洲无码一区二区三区在线观看| 亚洲人成在线观看网站不卡| 日本伊人色综合网| 国产不卡精品一区二区三区| 高级艳妇交换俱乐部小说| 免费人成在线视频无码|