• go语言学习笔记


    go语言学习笔记(初级)

    最近一直在学习go语言,因此打算学习的时候能够记录
    一下笔记。我这个人之前是从来没有记录笔记的习惯,
    一直以来都是靠强大的记忆力去把一些要点记住。
    读书的时候因为一直都是有一个很安静和很专心的环境,
    因此很多事情都能记得很清楚,思考的很透彻。但是随着
    年纪不断增加,也算是经历了很多的事情,加上工作有时会让人
    特别烦闷,很难把心好好静下来去学习,去思考大自然的终极
    奥秘,因此需要记录一些东西,这些东西一方面可以作为一种自我激励
    的机制,另一方面,也算是自己成长的轨迹吧。

    一. 顺序编程

    1. 变量

    go语言变量定义的关键字是var。类型放在变量名后:

       var v1 int
       var v2 string
       var v3 [10]int  //数组
       var v4 []int    //切片
       var v5 struct{  //结构体
            f int
       }              
       var v6 *int  //指针
       var v7 map[string]int  //map
       var v8 func(a int) int  //函数
    

    每一行不需要以分号作为结尾。 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

    这就直接实现了两个变量的交换。

    4. 匿名变量

    go语言的函数是多返回值的,因此可能有些值并没有被用到,这时我们就需要一个占位符去忽略这些返回值。

    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出现的位置被置为0,没出现一个iota出现,都自增1,到写一个const出现的时候,又置为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
      ",ch)
        length := len(str)
        fmt.Println(length) //len用来获取长度
        str = "你好,世界"
        ch = str[0]
        fmt.Printf("%c
      ",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
      ",ch)
        }
      }
      

      输出结果:

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

      对于中文, 结果是乱码, 因为是一个字节一个字节输出的, 但是默认是UTF8编码, 一个中文对应3个字节.
      这里我们要提到一个range的关键字, 它可以把字符串按键值对的方式返回.

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

      输出结果为:

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

      事实上, 字符类型有两种, 一种就是byte(uint8), 另一种是rune. 第一种遍历字符串ch是byte, 而第二种是rune.

    • 数组

      数组这种类型是非常常见的

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

      数组的遍历和字符串一样,这里不再重复.

      数组是值类型,在赋值时会拷贝一份.

    • 数组切片

      数组切片的概念比较复杂, 它有点类似于c++中vector的概念, 但又不完全一样.

      我们这里详细提几点.

      1. 切片的创建

        切片有两种创建方式, 一种是基于数组创建, 另一种是用make创建.

        package main
        
        import "fmt" //引入依赖包
        
        func main() {
          //从数组创建
          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创建
          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
        

        我们发现数组确实是按照值来传递. 那么如果是slice呢, 会发生什么?

        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函数可以往切片尾部增加元素.

        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
        ",slice1)
          slice1 = append(slice1,slice2...)
          fmt.Println(slice1)
          fmt.Printf("%p
        ",slice1)
          slice1 = append(slice1,slice2...)
          fmt.Println(slice1)
          fmt.Printf("%p
        ",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的地址是所随着内内存的改变而变化的,因此是需要仔细思考的.我个人不觉得
        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很简单吧. 数据结构我们讲完了, 接下来可以看看代码的程序控制了.

    8. 程序控制

    程序控制本人只提一些关键性的东西,不会啰嗦太多.

    • switch语句

      switch语句不需要在每个case地下写break,默认就是执行break.如果要执行多个case, 在case最后加入fallthrough.
      条件表达式不限制为常量或者整数.单个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)
      }
      
    • 循环

      go语言的循环比较特别, 它用一个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:
      // ...
      
    • 函数

      函数是一个非常重要的概念, 也很简单. go的函数以func关键字定义, 支持不定参数和多返回值. 函数名首字符的大小写是很有讲究的:

      小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。

      package main
      
      import "fmt" //引入依赖包
      
      //...int是不定参数,实际上就是一个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)
      }
      

      很简单吧. 注意, 如果是函数里面调了其他函数, 那么这个sample怎么传给其他喊函数呢?

      sample... //...表示是拆成一个个元素传递

      匿名函数的概念也很简单, 只要看代码就会明白.

      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)
      }
      

      下面是关于闭包的概念. 这个概念在许式伟的书中被诠释的非常好:

      闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,
      而是在定义代码块的环境中定义。
      要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定定的计算环境(作用域)。
      闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在.
      *

      我们来看来两个闭包的例子.

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

      输出结果:

      11
      0xc82000a288
      12
      0xc82000a2c0
      

      我们从这个结果中发现, i的地址是会变的, 因为是作为一个局部变量传进去的.

      package main
      
      import "fmt" //引入依赖包
      
      func test(x int) func(int) int {
        return func(y int) int {
          fmt.Printf("%p
      ",&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
      ", i, j)
              }
         }()
         a()
         j *= 2
         a()
       }
      

      此时输出:

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

    二. 面向对象编程

    这里我们先提值语义引用语义的概念.

    b = a
    b.Mofify()
    

    如果b改变, a不发生改变, 就是值语义. 如果b改变, a也发生改变, 就是引用语义.

    go语言大多数类型都是值语义, 比如:

    基本类型: byte, int, float32, float64, string
    复合类型: struct, array, pointer

    也有引用语义的, 比如:
    slice, map, channel, interface.

    这是我们要牢记的.

    我们的笔记整体式按照许式伟的书来安排, 但是由于许的书提纲性很强, 内容上不是很详细, 基本上会重新整理补充一些东西进去.

    • 结构体

    结构体是用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改成匿名结构, 会有些好玩的现象.

    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++非常不一样, 它的理念是把似乎是把方法绑定到特定类型上去.
    这个概念已经不仅仅是对象的概念了, 事实上,
    我也不知道google这帮人脑子是怎么想的, 这种搓劣的复古风格,
    也是让我打开眼界, 我个人觉得, go虽然仗着google的名气, 似乎显得很厉害, 但是,
    比起java和C++, 简直就是个玩具, 说的不好听一点,
    比起scala这样的一出生就是个大胖子, go更像是个缺胳膊少腿的畸形儿.

    好了, 不说了, 直接上代码.

    package main
    
    import (
     "fmt"
    )
    
    //go绑定方法必须是本包内的,int不是main包内定义的.
    //因此需要type一下, Integer就是本包内定义的类型
    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绑定方法必须是本包内的,int不是main包内定义的.
    //因此需要type一下, Integer就是本包内定义的类型
    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绑定方法必须是本包内的,int不是main包内定义的.
    //因此需要type一下, Integer就是本包内定义的类型
    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绑定方法必须是本包内的,int不是main包内定义的.
    //因此需要type一下, Integer就是本包内定义的类型
    type Integer int
    
    //为int绑定一个Print方法
    func (this *Integer) Add() {
      *this += 2
    }
    
    func main() {
      var a Integer = 10
      a.Add()
      fmt.Println(a)
    }
    

    这时输出12. 我们发现, 这个this就像是我们C++里面的this指针一样.
    不过也傻13复古的多.

    我们在看一个例子,

    package main
    
    import (
     "fmt"
    )
    
    //go绑定方法必须是本包内的,int不是main包内定义的.
    //因此需要type一下, Integer就是本包内定义的类型
    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
    

    我们发现,

    //为int绑定一个Print方法
    func (this *Integer) Add() {
      fmt.Println(this)
      *this += 2
    }
    

    该方法用指针调用和用值去调用, 效果是一样的. 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
    }
    
    //对象方法实现
    func (p *People) SayHi() {
      fmt.Printf("Hi, I'm %s. Nice to meet you!
    ",p.Name)
    }
    
    func (t *Teacher) SayHi(){
      fmt.Printf("Hi, my name is %s. I'm working in %s .
    ", t.Name, t.Department)
    }
    
    func (s *Student) SayHi() {
      fmt.Printf("Hi, my name is %s. I'm studying in %s.
    ", s.Name, s.School)
    }
    
    func (s *Student) Study() {
      fmt.Printf("I'm learning Golang in %s.
    ", 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()
    }
    

    这时就会出现上面我们提到的错误. 尽管如果我们这么去调用:

    package main
    
    import(
      "fmt"
    )
    
    //定义对象People、Teacher和Student
    type People struct {
      Name string
    }
    
    type Teacher struct{
      People
      Department string
    }
    
    type Student struct{
      People
      School string
    }
    
    //对象方法实现
    func (p *People) SayHi() {
      fmt.Printf("Hi, I'm %s. Nice to meet you!
    ",p.Name)
    }
    
    func (t *Teacher) SayHi(){
      fmt.Printf("Hi, my name is %s. I'm working in %s .
    ", t.Name, t.Department)
    }
    
    func (s *Student) SayHi() {
      fmt.Printf("Hi, my name is %s. I'm studying in %s.
    ", s.Name, s.School)
    }
    
    func (s *Student) Study() {
      fmt.Printf("I'm learning Golang in %s.
    ", 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
    }
    
    //对象方法实现
    func (p *People) SayHi() {
      fmt.Printf("Hi, I'm %s. Nice to meet you!
    ",p.Name)
    }
    
    func (t *Teacher) SayHi(){
      fmt.Printf("Hi, my name is %s. I'm working in %s .
    ", t.Name, t.Department)
    }
    
    func (s *Student) SayHi() {
      fmt.Printf("Hi, my name is %s. I'm studying in %s.
    ", s.Name, s.School)
    }
    
    func (s *Student) Study() {
      fmt.Printf("I'm learning Golang in %s.
    ", 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
    

    果然展现了二义性. 消歧义方式也是土的掉渣:

    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是一个指针类型, 因此我们传入的也必须是个指针, 我们可以看到这种用法.

    • 接口

    这个我不想吐槽什么, 前面已经吐槽过了, 但是这种设计, 确实也是诡异之极.

    go的借口是非侵入式的, 只要实现了接口定义的方法, 就等于实现了该接口. 换句话说, 接口的实现和定义式可以分离
    不相关的.

    接口的例子还是之前那个:

    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 //函数声明
      Add(b Integer) //函数声明
    }
    
    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{})
    
    • 接口转换和类型查询

    接口转换

    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 //函数声明
      Add(b Integer) //函数声明
    }
    
    //定义接口
    type Adder interface {
      Add(b Integer) //函数声明
    }
    
    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呢? 这会发生什么呢?

    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!
    

    我们发现, b没有输出. panic会抛出一个异常, 由recover去捕获.f抛出异常后, 事实上,
    main剩下的部分都不会执行, 但是因为我们defer了,
    defer是一定会执行的,因此我们在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已经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调用方法, 会直接抛出一个空指针异常.
    那么go会怎么样呢?

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

    输出结果:

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

    看来还是不行的.

    所以调用前我们还是要进行空的判断.

    三. go并发编程

    并发不是并行. 并发比并行更加优秀. 并发是时间片轮换, 并行是多核计算. 事实上, 并行可以由并发指定到多个cpu执行.

    我们马上看一个具体的例子.

    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)
      }
    }
    

    结果我们发现什么都没有输出, 这是因为go是异步执行的, main不会等Add完成, 它会继续执行下去, 于是发生了main
    函数先结束, 于是进程就结束了.

    所以这里我们需要做同步, 所谓同步, 就是主线程去等待子线程完成(go里的线程其实是协程, 协程你可以认为是轻量级的线程).

    线程间通信模型基本上常用的也就是两种,共享内存和消息队列.
    后者目前看来比较流行, 比如akka(actor模型), zmq(消息队列模型)等 都是基于消息队列的并发模型.

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

    channel的申明形式:

    var chanName chan ElementType

    ElementType是该chan能够支持的数据类型.

    chan的初始化有两种:

    ch := make(chan int)

    ch := make(chan int,capacity)

    前者写入和读取是阻塞的, 后者自带了一个buffer,
    如果没有达到capacity, 是非阻塞的, 达到capacity才会阻塞. 读取的话, 如果为buffer空,
    会阻塞.

    chan写入数据

    ch <- value

    chan读取数据

    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)是创建slice的方法
      //容量是10,超过这个容量,会发生内存拷贝
      //var chs []chan int = make([]chan int, 10)
    
      //其实这里用数组更好
      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{
        //如果线程没有写入数据, 会阻塞
        <- ch
      }
    }
    

    输出结果:

    18
    0
    2
    4
    12
    14
    16
    10
    8
    6
    

    下面我们介绍一下select语法,

    select {
      case <-chan1:
        //读取数据
      case chan2 <- 1:
        //写入数据
      default:
        //默认操作
    }
    

    select会去注册事件, 比如是否可读,是否可写, 根据对应的事件去执行相应的代码.

    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)
      }
    }
    

    这是一个随机产生0或者1的信号发生器.

    单向channel

    顾名思义,单向channel只能用于发送或者接收数据。channel本身必然是同时支持读写的,
    否则根本没法用。假如一个channel真的只能读,那么肯定只会是空的,因为你没机会往里面写数
    据。同理,如果一个channel只允许写,即使写进去了,也没有意义,因为没有机会读取里面
    的数据。所以的单向channel概念,其实只是对channel的一种使用限制。

    var ch1 chan int // 双向
    var ch2 chan<- float64// 只写
    var ch3 <-chan int // 只读
    

    那么单向channel如何初始化呢?之前我们已经提到过,channel是一个原生类型,因此不仅
    支持被传递,还支持类型转换。只有在有了单向channel的概念后,读者才会明白类型转换对于
    channel的意义:就是在单向channel和Ԥ向channel之间进行转换。示例如下:

    ch4 := make(chan int)
    ch5 := <-chan int(ch4) // 只读
    ch6 := chan<- int(ch4) //只写
    

    关闭channel

    close(ch)

    如何判断一个channel是否已经被关
    闭?我们可以在读取的时候使用多重返回值的方式:

    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循环会终止.

    并行计算:

    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)

    该函数是用来设置cpu使用的核心个数, 在许的书中, 如果不设置这个环境变量, 默认是1个核心, 但是事实上,
    在我这个版本的go语言中, 已经支持默认多核并发啦.

    下面我们来看看同步的概念.

    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是一个结构体类型。在它之中有一个代表计数的字段。
    当一个sync.WaitGroup类型的变量被声明之后,其值中的那个计数值将会是0。
    我们可以通过该值的Add方法增大或减少其中的计数值。

    虽然Add方法接受一个int类型的值,并且我们也可以通过该方法减少计数值,但是我们一定不要让计数值变为负数。因为这样会立即引发一个运行恐慌。
    这也代表着我们对sync.WaitGroup类型值的错误使用。
    除了调用sync.WaitGroup类型值的Add方法并传入一个负数之外,我们还可以通过调用该值的Done来使其中的计数值减一。

    当我们调用sync.WaitGroup类型值的Wait方法的时候,它会去检查该值中的计数值。
    如果这个计数值为0,那么该方法会立即返回,且不会对程序的运行产生任何影响。 但是,如果这个计数值大于0,
    那么该方法的调用方所属的那个Goroutine就会被阻塞。
    直到该计数值重新变为0之时,为此而被阻塞的所有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.

  • 相关阅读:
    第一次结对编程作业
    第一次个人编程作业
    获取file中字段,写入到TXT文件中
    通过file中的字段查询MySQL内容
    MySQL常用语句
    MySQL乱码问题
    脚本数据编码格式转换
    mysql 常用命令操作
    thinkphp项目 Class 'finfo' not found
    POJ3255--次短路
  • 原文地址:https://www.cnblogs.com/songfy/p/5094866.html
Copyright © 2020-2023  润新知