Go Cheat Sheet - go语言参考

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
官方网站的文档 https://golang.org/doc/

[TOC]

基本语法

Hello World

File hello.go:

package main

import "fmt"

func main() {
    fmt.Println("Hello Go")
}

$ go run hello.go

操作符

运算

运算符 描述
+
-
*
/
% 除余
& 逻辑与
` `
^ 逻辑异或
&^ 位清除 (取反)
<< 左位移
>> 右位移

比较

操作符 描述
== 等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于

逻辑

操作符 描述
&& 逻辑与
`
! 逻辑否

其它

操作符 描述
& 地址 / 指什
* 解引用指针
<- 发送 /接收操作符 (可以看下面的 '通送' )

声明

类型跟在标识符之后!

var foo int // 没有初始化的声明
var foo int = 42 // 初始化值的声明
var foo, bar int = 42, 1302 // 声明和初始化多个变量
var foo = 42 // 类型省略, 会自动识别
foo := 42 // 短写开工, 只在函数体内, 省略了var关键字, 类型通常是严格的
const constant = "This is a constant"

// iota 用来自增, 从0开始
const (
    _ = iota
    a
    b
    c = 1 << iota
    d
)
    fmt.Println(a, b) // 1 2 (0 is skipped)
    fmt.Println(c, d) // 8 16 (2^3, 2^4)

函数

// 简单的函数
func functionName() {}

// 带参数的函数 (再强调下, 类型跟在变量标识之后)
func functionName(param1 string, param2 int) {}

// 同一参数类型的多个参数
func functionName(param1, param2 int) {}

// 返回值类型的声明
func functionName() int {
    return 42
}

// 可以返回多个值
func returnMulti() (int, string) {
    return 42, "foobar"
}
var x, str = returnMulti()

// 用return简化返回多组值
func returnMulti2() (n int, s string) {
    n = 42
    s = "foobar"
    // n and s will be returned
    return
}
var x, str = returnMulti2()

函数作为返值和闭包

func main() {
    // 将函数赋给变量
    add := func(a, b int) int {
        return a + b
    }
    // 使用变量名称去调用方法
    fmt.Println(add(3, 4))
}

// 闭包,按词法范围:定义函数时,函数可以访问范围内的值
func scope() func() int{
    outer_var := 2
    foo := func() int { return outer_var}
    return foo
}

func another_scope() func() int{
    // 无法编译,因为在此范围内未定义outer_var和foo
    outer_var = 444
    return foo
}


// Closures
func outer() (func() int, int) {
    outer_var := 2
    inner := func() int {
        outer_var += 99 // 来自外部作用域的external_var发生了改变
        return outer_var
    }
    inner()
    return inner, outer_var // 返回内部函数和改变的external_var 
}

可变函数

func main() {
	fmt.Println(adder(1, 2, 3)) 	// 6
	fmt.Println(adder(9, 9))	// 18

	nums := []int{10, 20, 30}
	fmt.Println(adder(nums...))	// 60
}

// 通过在最后一个参数的类型名称前使用...,可以指示它接受零个或多个这些参数。
// 该函数与其他函数一样被调用,除了我们可以传递任意数量的参数。
func adder(args ...int) int {
	total := 0
	for _, v := range args { // 遍历参数不考虑数量
		total += v
	}
	return total
}

内置类型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32 ~= a character (Unicode code point) - very Viking

float32 float64

complex64 complex128

类型转换

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// 替代语法
i := 42
f := float64(i)
u := uint(f)

  • 每个源文件顶部的包声明
  • 可执行文件在包 main
  • 约定:包名称==导入路径的最后一个名字(导入路径math / rand =>包rand
  • 大写字母标识符:可导出(在其他包中可见)
  • 小写字母标识符:私有(在其他软件包中不可见)

控制结构

If

func main() {
	// 最基本的
	if x > 10 {
		return x
	} else if x == 10 {
		return 10
	} else {
		return -x
	}

	// 您可以在条件之前加一个声明
	if a := b + c; a < 42 {
		return a
	} else {
		return a - 42
	}

	// 在if中断言
	var val interface{}
	val = "foo"
	if str, ok := val.(string); ok {
		fmt.Println(str)
	}
}

循环

    // 只有 `for`, 没有 `while`, 也没有 `until`
    for i := 1; i < 10; i++ {
    }
    for ; i < 10;  { // 相尖于while循环
    }
    for i < 10  { // 如果只有条件,可以省略分号
    }
    for { // 可以省略条件,相尖于 while (true)
    }
    
    // 使用 break/continue 跳出当前循环
    // 使用 break/continue 和标记 here 跳出到循环外
here:
    for i := 0; i < 2; i++ {
        for j := i + 1; j < 3; j++ {
            if i == 0 {
                continue here
            }
            fmt.Println(j)
            if j == 2 {
                break
            }
        }
    }

there:
    for i := 0; i < 2; i++ {
        for j := i + 1; j < 3; j++ {
            if j == 1 {
                continue
            }
            fmt.Println(j)
            if j == 2 {
                break there
            }
        }
    }

Switch分支

    // switch statement
    switch operatingSystem {
    case "darwin":
        fmt.Println("Mac OS Hipster")
        // 默认情况下没有失败, 分支自动 break
    case "linux":
        fmt.Println("Linux Geek")
    default:
        // Windows, BSD, ...
        fmt.Println("Other")
    }

    // 与for和if一样,在switch值之前可以有一个赋值语句
    switch os := runtime.GOOS; os {
    case "darwin": ...
    }

    // 您还可以在switch 分支语名中进行比较
    number := 42
    switch {
        case number < 42:
            fmt.Println("Smaller")
        case number == 42:
            fmt.Println("Equal")
        case number > 42:
            fmt.Println("Greater")
    }

    // 分支条件可以用逗号分隔的列表表示
    var char byte = '?'
    switch char {
        case ' ', '?', '&', '=', '#', '+', '%':
            fmt.Println("Should escape")
    }

数组,切片,范围

数组

var a [10]int // 声明一个长度为10的int数组。数组长度是该类型的一部分!
a[3] = 42     // 修改值
i := a[3]     // 读取值

// 声明和初始化
var a = [2]int{1, 2}
a := [2]int{1, 2} //便捷写法
a := [...]int{1, 2} // 省略号->编译器计算出数组长度

切片

var a []int                              // 声明一个切片-与数组相似,但是长度未指定
var a = []int {1, 2, 3, 4}               // 声明并初始化切片(由隐式给出的数组支持)
a := []int{1, 2, 3, 4}                   // 便捷写法
chars := []string{0:"a", 2:"c", 1: "b"}  // ["a", "b", "c"]

var b = a[lo:hi]	// 创建一个切片 (数组维度) 从索引 lo 到 hi-1
var b = a[1:4]		// 索引 1 to 3
var b = a[:3]		// 缺少低位索引 ,暗指0
var b = a[3:]		// 缺少高位索引,暗指 len(a)
a =  append(a,17,3)	// 切片中增加项目
c := append(a,b...)	// 连接切片a和b

// 使用make创建切片
a = make([]byte, 5, 5)	// first arg length, second capacity
a = make([]byte, 5)	// capacity is optional

// create a slice from an array
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

数组和切片中的操作符

len(a) 数组/切片的长度。这是一个内置函数,而不是数组的属性/方法

// loop over an array/a slice
for i, e := range a {
    // i is the index, e the element
}

// if you only need e:
for _, e := range a {
    // e is the element
}

// ...and if you only need the index
for i := range a {
}

// In Go pre-1.4, you'll get a compiler error if you're not using i and e.
// Go 1.4 introduced a variable-free form, so that you can do this
for range time.Tick(time.Second) {
    // do it once a sec
}

映射

var m map[string]int
m = make(map[string]int)
m["key"] = 42
fmt.Println(m["key"])

delete(m, "key")

elem, ok := m["key"] // 测试键“ key”是否存在,如果有就返回它

// map literal
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

// iterate over map content
for key, value := range m {
}

结构体

没有类,只有结构。结构可以有方法。

// 结构体是一种类型。这也是字段的集合

// 声明
type Vertex struct {
    X, Y int
}

// 创建
var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // 通过使用键定义值来创建结构
var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化结构片段

// 访问元素
v.X = 4

// You can declare methods on structs. The struct you want to declare the
// method on (the receiving type) comes between the the func keyword and
// the method name. The struct is copied on each method call(!)
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 调用方法
v.Abs()

// For mutating methods, you need to use a pointer (see below) to the Struct
// as the type. With this, the struct value is not copied for the method call.
func (v *Vertex) add(n float64) {
    v.X += n
    v.Y += n
}

匿名的结构体:
使用 map[string]interface{}更简单安全

point := struct {
	X, Y int
}{1, 2}

指针

p := Vertex{1, 2}  // p is a Vertex
q := &p            // q 为指向 Vertex的指针
r := &Vertex{1, 2} // r 也是指向 Vertex的指针

// Vertex的指针类型为 *Vertex

var s *Vertex = new(Vertex) // 创建一个指向新结构实例的指针

接口

// 接口声明
type Awesomizer interface {
    Awesomize() string
}

// 类型不能声明实现接口
type Foo struct {}

// 换言之, 如果类型实现所有必需的方法,则它们隐式满足接口
func (foo Foo) Awesomize() string {
    return "Awesome!"
}

嵌入

Go语言没有子类。而是有接口和结构嵌入。

// ReadWriter 的实现必须都满足 Reader 和 Writer
type ReadWriter interface {
    Reader
    Writer
}

// Logger它有的方法, Server暴露所有的方法
type Server struct {
    Host string
    Port int
    *log.Logger
}

// 初始化嵌入类型的常用方法
server := &Server{"localhost", 80, log.New(...)}

// 通过嵌入式结构实现的方法
server.Log(...) // calls server.Logger.Log(...)

// 嵌入类型的字段名称是其类型名称(在本例中为Logger)
var logger *log.Logger = server.Logger

错误

没有异常处理。可能会产生错误的函数只需声明类型为Error的附加返回值即可。这是Error接口:

type error interface {
    Error() string
}

A function that might return an error:

func doStuff() (int, error) {
}

func main() {
    result, err := doStuff()
    if err != nil {
        // handle error
    } else {
        // all is good, use result
    }
}

并发

Goroutines

Goroutines 是轻量的线程 (managed by Go, not OS threads). go f(a, b) 会启动一个新的goroutine 并运行函数 f

// 只是一个函数(可以稍后作为goroutine启动)
func doStuff(s string) {
}

func main() {
    // 在goroutine中使用命名函数
    go doStuff("foobar")

    // 在goroutine中使用匿名内部函数
    go func (x int) {
        // function body goes here
    }(42)
}

通道

ch := make(chan int) // 创建一个 int类型的通道
ch <- 42             // 给ch通道发送值
v := <-ch            // 接收来自ch的值

// 非缓冲通道阻塞。如果没有可用值,则读取块,直到发生读取为止,写入块。

// 创建一个缓冲通道。如果已写入小于<buffer size>的未读值,则不会阻塞对缓冲通道的写入。
ch := make(chan int, 100)

close(ch) // 关闭频道(只有发送者应该关闭)

// 从通道读取并测试是否已关闭
v, ok := <-ch

// 如果 ok 为false, 频道则已关闭

// 从通道读取直到关闭
for i := range ch {
    fmt.Println(i)
}

// 在多个通道操作中选择块,如果一个解除阻塞,则执行相应的情况
func doStuff(channelOut, channelIn chan int) {
    select {
    case channelOut <- 42:
        fmt.Println("We could write to channelOut!")
    case x := <- channelIn:
        fmt.Println("We could read from channelIn")
    case <-time.After(time.Second * 1):
        fmt.Println("timeout")
    }
}

Channel Axioms

  • 发送到 nil通道,一直阻塞下去
  var c chan string
  c <- "Hello, World!"
  // fatal error: all goroutines are asleep - deadlock!
  • 从nil通道读到, 一直阻塞下去
  var c chan string
  fmt.Println(<-c)
  // fatal error: all goroutines are asleep - deadlock!
  • 发送到已关闭的通道
  var c = make(chan string, 1)
  c <- "Hello, World!"
  close(c)
  c <- "Hello, Panic!"
  // panic: send on closed channel
  • 来自封闭通道的接收立即返回零值
  var c = make(chan int, 2)
  c <- 1
  c <- 2
  close(c)
  for i := 0; i < 3; i++ {
      fmt.Printf("%d ", <-c)
  }
  // 1 2 0

打印

fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // 最基本的打印,新行输出
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // 打印为字符串

fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c风格格式
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // 格式化打印到字符串变量

hellomsg := `
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
` // 多行字符串文字,在开头和结尾使用反引号

反射

类型开关

类型开关类似于常规switch语句,但是类型开关中的个案指定类型(不是值),并将这些值与给定接口值所保存的值的类型进行比较。

func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("Twice %v is %v\n", v, v*2)
	case string:
		fmt.Printf("%q is %v bytes long\n", v, len(v))
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

func main() {
	do(21)
	do("hello")
	do(true)
}

代码片断

HTTP 服务端

package main

import (
    "fmt"
    "net/http"
)

// 声明响应结构体
type Hello struct{}

// 让该类型实现ServeHTTP方法(在接口http.Handler中定义)
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

// Here's the method signature of http.ServeHTTP:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }