public:it:go

Go Lang

  • 了解该语言的出生背景与适用环境
  • 了解输入输入出流,文件流操作
    • 打印:
      fmt.Println("i's value is ", i)
      fmt.Printf("i's type is %T, i's value is %v \n", i, i)

      格式文档:fmt

  • 了解程序代码和可执行代码的组织机制,运行时模块加载、符号查找机制
    • 主程序入口 package main;
    • 包管理,导入包使用 import, 可单导入 import “fmt”, 也可多导入 import (“fmt”;“math/rand”);
    • 包名与导入路径的最后一个元素一致 import “math/rand”; rand.xxx;
  • 了解该语言的基本数据类型,基本语法和主要语言构造,主要数学运算符和输入输出函数的使用
    • 首字母大写为已导出名
    • 声明时类型后置,例如 func add(x int, y int) int, 后置原因:关于go的声明语法
    • 类型相同的声明可只留最后一个,例如 func add(x, y int) int
    • 变量声明 var a, b, c bool
    • 变量声明可有初始值,有初始值时可省略类型:
       var a, b, c = true, 10, 'hello!' 
    • 变量无初始值时将初始为零值0,false“”
    • 在函数内时,变量声明可用:= 来省略 var:
       a, b, c := true, 10, 'hello!'
    • 基本类型:
      bool
      string
      int  int8  int16  int32  int64
      uint uint8 uint16 uint32 uint64 uintptr
      byte // uint8 的别名
      rune // int32 的别名 表示一个 Unicode 码点
      float32 float64
      complex64 complex128
    • 类型转换必须显式转换 T(x)
      var f float64 = 3.14
      var i int = int(f)
    • 常量定义:const Pi = 3.14
    • for循环,没有小括号
      for i := 0; i < 10; i++ {
          sum += i
      } 

      可省略

      sum := 0
      for ; sum < 10; {
          sum += sum
      }

      可进一步省略(形似while, go语言没有while)

      sum := 0
      for sum < 10 {
          sum += sum
      }

      进一步无限循环

      for {
      }
    • if也没有小括号,并且可类似 for 在条件表达式前有一个简单语句, 作用域延伸至else
      if v := math.Pow(2, 10); v < 1000{
          fmt.Println(v)
      } else {
          fmt.Println("%v < 1000", v)
      }
    • switch 语法, case 默认自带 break, 不会往下一个 case 走,除非显式调用 fallthrough
    • case 可带变量或表达式。从上到下顺次执行,一旦匹配成功时停止,不会执行之后的case表达式(除非 fallthrough)。
    • switch省略表达式时,等同于 switch true, 这种形式能将一长串 if-then-else 写得更漂亮
      t := time.Now()
      switch {
      case t.Hour() < 12:
          fmt.Println("Good morning!")
      case t.Hour() < 17:
          fmt.Println("Good afternoon.")
      default:
          fmt.Println("Good evening.")
      }
    • go 拥有指针 var p *int = &i, 指针零值为 nil, go没有指针运算。
    • go 结构体 struct 与 C/C++ 类似。 结构体指针可直接用点号获取结构字段值 p.X
  • 了解数组和其他集合类的使用
    • 数组array的声明 var a [10]int,操作类似C语言。长度必须声明且不能改变。
    • 长度为空时的声明是数组切片,切片(slice)可理解为数组部分区间的引用 var s []int = a[1:4], 范围左闭右开。范围可缺省,a[:],等价于 a[0:10]
    • 切片拥有 长度len(s) 和 容量 cap(s), 可以通过重新切片来扩展一个切片的容量s = s[:0]
      • 切片的长度就是它所包含的元素个数。
      • 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
    • 切片空值为 nil
    • make 来创建“动态”数组, make 函数会分配一个元素为零值的数组并返回一个引用了它的切片
      b := make([]int, 0, 5) // len(b)=0, cap(b)=5
      b = b[:cap(b)] // len(b)=5, cap(b)=5
      b = b[1:]      // len(b)=4, cap(b)=4
    • append 函数来追加切片元素,当底层数组长度不够时,append会默认分配更长的数组并指给切片。
    • range 配合 for 循环切片
      var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
      for i, v := range pow {
          fmt.Printf("2**%d = %d\n", i, v)
      }

      第一个变量i表示下标,第二个v标识值,可用_来忽略其中一个:for _, v := range pow,或只留一个变量来表示下标for i := range pow

    • map 的声明 var m map[string]int, 可用 make 初始化 m := make(map[string]int)
    • map 赋值 m[key] = elem; 获取 elem = m[key]; 删除 delete(m, key)
    • map 双赋值
      elem, ok = m[key]

      若 key 在 m 中,ok 为 true ;否则,ok 为 false, elem 为该值类型的零值。

  • 了解字符串的处理
  • 了解该语言在面向对象,函数式编程,泛型,元编程等编程范式的特性
    • 函数(func)也为值,可传递,可作为参数与返回值
    • 支持函数闭包(Function closures)
      func adder() func(int) int { //<oak>: 这类型后置也有点绕眼
          sum := 0  //<oak>: 这sum的作用域也有点绕脑
          return func(x int) int {
              sum += x
              return sum
          }
      }
       
      func main() {
          pos, neg := adder(), adder()
          for i := 0; i < 3; i++ {
              fmt.Println(pos(i), neg(-2*i))
          }
      }
      /* 输出
      0 0
      1 -2
      3 -6
      */
    • 没有类的概念,但是可以定义带reciever(接收者)的函数作为自定义类型的方法:
      type Vertex struct {
      	X, Y float64
      }
      func (v Vertex) Abs() float64 {  // reciever (v Vertex) 放置在 func 与  函数名之间
      	return math.Sqrt(v.X*v.X + v.Y*v.Y)
      } // 本质上,方法与函数没有区别,即这个方法与 func Abs(v Vertex) float64 没有本质区别,仅仅是可以使用语法糖 v.Abs() 
      func main() {
      	v := Vertex{3, 4}
      	fmt.Println(v.Abs())
      }

      只能为在同一包内定义的类型的接收者声明方法,而不能为其它包内定义的类型(包括 int 之类的内建类型)的接收者声明方法。

    • 方法的 reciever 也是值传递,所以如果要修改 reciever 原值,需要把 reciever 定义为指针。在使用指针接收者方法时,默认不需要指针,即 v.scale() 等效于 (&v).scale();反过来也成立,对于指针p := &v, p.Abs() 等效于 (*p).Abs()
    • go 使用 interface type (接口类型),接口类型变量可赋值实现了该接口方法的类型的变量,不需要像其它语言需要专门关键字 implement 接口,只要有对应的方法存在即可。fmt 包中定义的 Stringer 是最普遍的接口之一.
    • 即便接口的具体值为 nil(接口本身不为nil),方法仍然会正常调用,此时 reciever 为 nil。
    • 指定了零个方法的接口值被称为 空接口, 空接口可赋予任意类型的值,可用来处理未知类型的值
    • type assertion类型断言:t := i.(T) t, ok := i.(T),判断一个接口值是否保存了一个特定的类型
      var i interface{} = "hello"
      s, ok := i.(string)
      fmt.Println(s, ok) // hello true
    • type switch类型选择
      switch v := i.(type) {  // 这里 type 为固定关键字
      case T:
          // v 的类型为 T
      case S:
          // v 的类型为 S
      default:
          // 没有匹配,v 与 i 的类型相同
      }
  • 了解特有的语法糖
    • 多值返回,函数可返回任意数量返回值,也可对返回值命名 func split(sum int) (x, y int),没有参数的 return 语句返回已命名的返回值。
    • defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
  • 了解该语言错误处理,调试方式以及对测试的支持
    • 接口 error; 通常 fmt 包库函数会返回error, error 为 nil 时表示成功;非 nil 的 error 表示失败
      i, err := strconv.Atoi("42") 
      if err != nil {
          fmt.Printf("couldn't convert number: %v\n", err)
          return
      }
      fmt.Println("Converted integer:", i)
  • 了解该语言的内存分配机制或GC,线程,进程等运行时效率相关
    • goroutine 是由 Go 运行时管理的轻量级线程。
    • 信道(channel)操作符
      ch := make(chan int, 1) // 创建int值信道, 第二个参数为缓冲区大小
      ch <- v    // 将 v 发送至信道 ch。
      v := <-ch  // 从 ch 接收值并赋予 v
       

      信道缓冲区(默认1)满时阻塞发送端,空时阻塞接收端。这让 goroutine 可以在没有显式的锁或竞态变量的情况下进行同步。

    • 发送者可通过 close 关闭一个信道,只有发送者可关闭信道。
    • 接收者可以通过表达式第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
      v, ok := <-ch

      之后 ok 会被设置为 false

    • 循环 for i := range ch 会不断从信道接收值,直到它被关闭
    • select 语句使一个 goroutine 协程可以等待多个通信操作。select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。如果有 default, 则在所有分支阻塞时执行 default。
    • sync.Mutex 互斥锁
  • 了解该语言的编译/解释机制
  • How goroutines work :

    Go 运行时负责调度 Goroutines。Goroutines 的调度是协作式的,而线程不是。

    这意味着每次一个线程发生切换,你都需要保存/恢 复所有寄存器,包括16个通用寄存器、PC (程序计数器)、SP(栈指针)、段寄存器( segment register )、16个 XMM 寄存器、FP 协处理器状态、X AVX 寄存器以及所有 MSR 等。

    而当另一个 Goroutine 被调度时,只需要保存/恢复三个寄存器,分别是 PC、SP 和 DX。

    Go 调度器和任何现代操作 系统的调度器都是 O(1) 复杂度的,这意味着增加线程 /goroutines 的数量不会增加切换时间,但改变寄存器的代价是不可忽视的。

    • 在Go 1.4 之前, 当创建一个goroutine时,Go运行时会分配一段8K字节的内存用于栈供goroutine运行使用, 如果不够, 则使用分段栈(Segmented Stacks)来扩展
    • 分段栈(Segmented Stacks)方式, 在栈缩小时的操作代价比较高(要频繁处理分段间的跨越).
    • 在Go 1.4 中, 使用栈拷贝(stack copying)方式来处理栈伸缩, 新方案创建一个两倍于原stack大小的新stack,并将旧栈拷贝到其中。这意味着当栈实际使用的空间缩小为原先的大小时,go不用做额外操作,此时栈缩小是一个无任何代价的操作。
    • 栈拷贝需把原栈指针重定向,而获取原栈指针依靠的是垃圾回收机制(会记录栈指针).但是使用C代码写的运行时调用不参与go的垃圾回收机制, 将会缺失栈指针信息而使用老机制(分段栈)
  • public/it/go.txt
  • 最后更改: 2022/11/10 12:37
  • oakfire