go

go语言入门

let's go

Posted by xiechengsheng on December 27, 2017
  • go语言作为开源社区最活跃的语言之一,其在并发性方面有着突出的优势,paas云计算中炙手可热的docker、Kubernetes源代码都采用go编写;云计算开发者学习go成为必然:)

go基本语法

  • go作为一门脚本语言,其代码的可读性还是很强的,上手比较快,并且一旦编译生成可执行文件后,文件可以在任何机器运行;
    1. main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行init()函数);main 函数既没有参数,也没有返回类型;
    2. 使用 var 声明的变量的值会自动初始化为该类型的零值。
    3. Go 语言不存在隐式类型转换,因此所有的转换都必须显式说明,就像调用一个函数一样(类型在这里的作用可以看作是一种函数):
      a := 5.0
      b := int(a)
      
    4. Go 语言和 C、C++ 语言一样,都有指针的概念。 指针运算(所谓的指针算法,如:pointer+2,移动指针指向字符串的字节数或数组的某个位置)是不被允许的。Go 语言中的指针保证了内存安全。
    5. 函数的多返回值,可以监测到函数在执行过程中的错误:
      value, err := pack1.Function1(param1)
      if err != nil {
        fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
        return err
      }
      // 未发生错误,继续执行;
      
    6. 函数体命名:
      func 函数名(参数) 返回值类型 {
      }
      // eg:
      func MultiPly3Nums(a int, b int, c int) int {
        // var product int = a * b * c
        // return product
        return a * b * c
      }
      
    7. 关键字 defer 的用法类似于面向对象编程语言 Java 的 finally 语句块,它一般用于释放某些已分配的资源。
      func main() {
      function1()
      }
      func function1() {
      fmt.Printf("In function1 at the top\n")
      defer function2()
      fmt.Printf("In function1 at the bottom!\n")
      }
      func function2() {
      fmt.Printf("function2: Deferred until the end of the calling function!")
      }
      // 输出如下:
      In Function1 at the top
      In Function1 at the bottom!
      Function2: Deferred until the end of the calling function!
      
    8. 匿名函数:
      // 表示参数列表的第一对括号必须紧挨着关键字 func,因为匿名函数没有名称。花括号 {} 涵盖着函数体,最后的一对括号表示对该匿名函数的调用。
      func() {
         sum = 0.0
         for i := 1; i <= 1e6; i++ {
                 sum += i
         }
      }()
      // 可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:
      where := func() {
         _, file, line, _ := runtime.Caller(1)
         log.Printf("%s:%d", file, line)
      }
      where()
      // some codewhere()
      // some more code
      
    9. 结构体定义的一般方式如下:
      type identifier struct {
         field1 type1
         field2 type2
         // ...
      }
      // 结构体赋值语句的惯用方法是:t := new(T),变量 t 是一个指向 T的指针,此时结构体字段的值是它们所属类型的零值:
      type struct1 struct {
      i1 int
      f1 float32
      str string
      }
      ms := new(struct1)
      ms.i1 = 10
      ms.f1 = 15.5
      
    10. 数组与切片
      // 声明一个数组的格式是:
      var arr1 [5]int
      // 切片的初始化格式是:
      var slice1 []type = arr1[start:end]
      

协程与管道

协程:

  1. 一个进程可以细化成多个线程
  2. 使用多线程可能会使得应用难以准确,内存中的数据是共享的,会被多线程以无法预知的方式进行操作,这就是竞态,因此go语言不提倡使用共享内存,使用共享内存在多线程情况下是危险的。避免危险的措施是同步(sync)不同的线程,对共享的数据进行加锁处理,这样在同一时刻上只有一个线程能够对数据进行变更。这样会带来更高的复杂度、容易使得代码出错以及更低的性能。
  3. 协程通过使用关键字go执行一个函数或方法来实现,这样会在当前的计算过程中开启一个同时执行的函数,在相同的地址空间中并对该函数分配了独立的栈,开发者不需要管理分配给协程的栈的大小,协程函数在运行完之后,会静默退出,函数不会返回任何值

通道

  1. 协程之间可以使用共享变量来进行通信,但是效率低下,使用 channel(管道)进行通信,可以避免共享内存导致的问题,同样也可以使得在同一时间只有一个协程可以访问数据;
  2. 通道服务于通信的两个目的:值的交换、同步,保证两个计算协程任何时候都是可知状态
    // string类型的通道的创建:
    // 方式1:
    var ch1 chan string
    ch1 = make(chan string)
    // 方式2:
    ch1 := make(chan string)
    
  3. 对通道操作的操作符:
    • 数据流向通道:ch<-int1,使用ch通道发送int1变量
    • 数据冲通道流出:int2=<-ch,变量int2从ch通道中接收数据
    • 就是生产者与接收者的模型

槽点

  • go没有用于包管理的开源社区,开发者写好的轮子难以共享给他人用
  • 生成的二进制可执行文件较大
  • 当前,go代码阅读的官方IDE(JetBrains GoLand)每个月还需要更新一次:(

参考

Unknwon/the-way-to-go_ZH_CN
并发、并行和协程