• go语言学习笔记


    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.

  • 相关阅读:
    笔记35 跨重定向请求传递数
    判断邮箱的正则表达式
    按钮
    async await 的用法
    笔记34 Spring MVC的高级技术——处理multipart形式的数据
    Convert Sorted Array to Binary Search Tree
    Binary Tree Zigzag Level Order Traversal
    Unique Binary Search Trees,Unique Binary Search Trees II
    Validate Binary Search Tree
    Populating Next Right Pointers in Each Node,Populating Next Right Pointers in Each Node II
  • 原文地址:https://www.cnblogs.com/zhuiluoyu/p/7289541.html
Copyright © 2020-2023  润新知