go-cheat-sheet icon indicating copy to clipboard operation
go-cheat-sheet copied to clipboard

本文翻译自 https://github.com/a8m/go-lang-cheat-sheet 有不当之处请指出,欢迎star,希望能给大家带来一个美好的Go Tour。

Go 备忘单

索引

  1. 基础语法
  2. 运算符
    • 算术运算符
    • 比较运算符
    • 逻辑运算符
    • 其他
  3. 声明
  4. 函数
    • 作为值和闭包的函数
    • 可变参数函数
  5. 内置类型
  6. 类型转换
  7. 控制结构
    • If条件
    • 循环
    • Switch
  8. 数组、切片、范围
    • 数组
    • 切片
    • 操作数组和切片
  9. 映射
  10. 结构体
  11. 指针
  12. 接口
  13. 嵌入组合
  14. 错误
  15. 并发
    • 协程
    • 通道
    • 通道原理
  16. 打印输出
  17. 代码片段
    • HTTP服务器

引言

大部分的例子引用于 A Tour of Go,非常棒的Go语言介绍。

如果你是Go的新手,请认真阅读Tour。

Go核心技术

  • 强类型语言
  • 静态类型
  • 语法类似于C (更少的括号和分号) 和 Oberon-2的struct结构体
  • 编译为本地代码(没有java虚拟机)
  • 没有类,但有带方法的结构
  • 接口
  • 没有实现继承,只有内嵌组合就够了
  • 函数是一等公民
  • 函数可以返回多值
  • 有闭包
  • 有指针,但没有指针的运算
  • 内置的并发原语:协程和通道

基础语法

你好,世界

文件 hello.go:

package main

import "fmt"

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

$ go run hello.go

运算符

算术运算符

运算符号 描述
+
-
*
/
% 取余
&
|
^ 位异或
&^ 位与非
<< 左移
>> 右移

比较运算符

运算符 描述
== 等于
!= 不等于
< 小于
<= 小于等于
> 大于
>= 大于等于

逻辑运算符

运算符 描述
&& 逻辑与
|| 逻辑或
! 逻辑非

其他

运算符 描述
& 对象地址 / 创建指针对象
* 指针解引用
<- 发送 / 接收 运算符 (可查看文章下面的Channel)

声明

具体类型需要写在识别符之后!

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"

函数

// 一个简单的函数
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和s会被return
    return
}
var x, str = returnMulti2()

作为值和闭包的函数

func main() {
    // 赋值函数给一个变量
    add := func(a, b int) int {
        return a + b
    }
    // 用变量名字调用函数
    fmt.Println(add(3, 4))
}

// 闭包,词法作用域:其他函数可以访问在scope函数里定义的值
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
}


// 闭包:不会改变函数外的变量,而是重新声明它们!
func outer() (func() int, int) {
    outer_var := 2
    inner := func() int {
        outer_var += 99 // 试图在作用域中改变outer_var
        return outer_var // => 101 (outter_var是重新声明的变量
                         //         变量只能在函数内部可使用)
    }
    return inner, outer_var // => 101, 2 (outer_var 一直是2, 没有被闭包内部改变!)
}

可变参数函数

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
}

// 使用 ... 可以在最后一个参数的类型名之前替代0个或更多的参数。
// 这个函数跟其他函数一样可以被调用并且可以传递更多的参数
func adder(args ...int) int {
	total := 0
	for _, v := range args { // Iterates over the arguments whatever the number.
		total += v
	}
	return total
}

内置类型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8的别名

rune // int32的别名 ~= 一个字符(Unicode编码点)-非常(霸道)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包里
  • 约定: 包名 == import路径的最后一个词名
  • 大写字母标识符: 公开的 (外部包可引用的)
  • 小写字母标识符: 私有的(外部包不可引用的)

控制结构

条件

func main() {
	// 基础的
	if x > 0 {
		return x
	} 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)
}

Switch

// switch 语句
switch operatingSystem {
case "darwin":
    fmt.Println("Mac OS Hipster")
    // cases会自动break, 而不是通过defaultbreak
case "linux":
    fmt.Println("Linux Geek")
default:
    // Windows, BSD, ...
    fmt.Println("Other")
}

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

// 可以在 switch cases中做比较
number := 42
switch {
    case number < 42:
        fmt.Println("Smaller")
    case number == 42:
        fmt.Println("Equal")
    case number > 42:
        fmt.Println("Greater")
}

// 在case列表中可以用多个用逗号分隔
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
var b = a[1:4]		// 索引从1到3的切片
var b = a[:3]		// 省略前面的索引意指0
var b = a[3:]		// 省略后面的索引意指len(a)
a =  append(a,17,3)	//  在切片a后面追加item
c := append(a,b...)	//  连接切片a和b

// 用make创建切片
a = make([]byte, 5, 5)	// 第一个参数代表长度,第二个代表容量
a = make([]byte, 5)	// 容量是可选参数

// 从数组里面创建切片
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // 一个切片引用了x的存储容量

操作数组和切片

len(a)可以算出一个数组或切片的长度,它是内建函数,而不是一个数组的属性/方法

// 对一个数组/一个切片的循环
for i, e := range a {
    // i 是索引, e 是元素
}

// 如果只要e:
for _, e := range a {
    // e 是元素
}

// 如果只需要索引
for i := range a {
}

// 在Go1.4之前,如果你不使用i和e,会得到一个编译错误。
// Go1.4引入了一种无变量形式,就可以这样使用
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
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

结构体

这里没有类,只有结构体,结构体可以有方法

// 结构体是一种类型. 代表字段的集合

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

// 创建
var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // 通过key-value创建一个结构体实体
var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化结构体 切片

// 访问字段
v.X = 4

// 您可以在struct中声明方法。想要声明的结构体方法(接收类型)在func关键字和
// 方法名之间。结构体会复制在每个方法()调用之前
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 调用方法
v.Abs()

// 对于改变变量的方法,需要通过使用结构体指针作为类型。这样在调用过程中结构体的值将不会被拷贝【所谓的值拷贝】
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 是一个 Vertex
q := &p            // q 是指向 Vertex的一个指针
r := &Vertex{1, 2} // r 也是指向 Vertex的一个指针

//  Vertex 的指针类型是 *Vertex

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

接口

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

// 类型没有声明实现的接口
type Foo struct {}

// 相反,如果它们实现了所有必需的方法,类型就会隐式地满足接口
func (foo Foo) Awesomize() string {
    return "Awesome!"
}

嵌入组合

在Go中没有子类,而是interface和struct的内嵌组合。

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

// Server 公开了Logger所有的方法
type Server struct {
    Host string
    Port int
    *log.Logger
}

// 按照通常的方式初始化组合类型
server := &Server{"localhost", 80, log.New(...)}

// 可以使用在嵌入式结构中实现的方法
server.Log(...) // 调用 server.Logger.Log(...)

// 嵌入式结构的字段名是它的类型名称(在Logger中)
var logger *log.Logger = server.Logger

错误

没有异常处理 . 可能产生错误的函数只是声明了 Error类型的返回值. 下面是 Error 的interface:

type error interface {
    Error() string
}

下面方法可能会返回一个err:

func doStuff() (int, error) {
}

func main() {
    result, err := doStuff()
    if err != nil {
        // 处理错误
    } else {
        // 无错误,使用正确结果
    }
}

并发

协程

Goroutines 是轻量级的线程 (被Go操纵管理,而不是操作系统线程). go f(a, b) 开启了一个新的运行f函数的goroutine (f是个函数).

// 只是一个函数(会被作为一个goroutine开始执行)
func doStuff(s string) {
}

func main() {
    // 用一个已命名的函数开启goroutine
    go doStuff("foobar")

    // 用一个匿名开启goroutine
    go func (x int) {
        // 方法体放在这里
    }(42)
}

通道

ch := make(chan int) // 创建一个int类型的channel
ch <- 42             // 给channel ch发送一个值
v := <-ch            // 从ch接受一个值

// 无缓冲的channle会阻塞. 没有值可读的时候会阻塞,写也是,直到有值可读。

// 创建一个带缓冲的channel,如果写的未读值小于buffer大小,那么写入缓冲通道就不会阻塞。
ch := make(chan int, 100)

close(ch) // 关闭通道(只有发送方能关闭)

// 如果channel已经关闭,可以从通道中读取和测试
v, ok := <-ch

// 如果ok是false,说明channel已经关闭

// 一直从channel里读直到关闭
for i := range ch {
    fmt.Println(i)
}

// 在多个通道操作上select ,如果一个没有阻塞,与之对应的case将被执行
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的原理

  • 给一个nil的channel发送永远会阻塞

    var c chan string
    c <- "Hello, World!"
    // fatal error: all goroutines are asleep - deadlock!
    
  • 从一个nil的channel接受也会永远阻塞

    var c chan string
    fmt.Println(<-c)
    // fatal error: all goroutines are asleep - deadlock!
    
  • 给一个已关闭的channel发送会panic

    var c = make(chan string, 1)
    c <- "Hello, World!"
    close(c)
    c <- "Hello, Panic!"
    // panic: send on closed channel
    
  • 从一个关闭的channel接受会立刻返回一个0值

    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, 你好, नमस्ते, Привет, ᎣᏏᏲ") // 基本的 print, 会自动换行
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print 结构体,整型等等
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print 字符串变量

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

hellomsg := `
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
` // 多行的字符串, 用``

代码片段

HTTP服务器

package main

import (
    "fmt"
    "net/http"
)

// 定义了一个响应体struct
type Hello struct{}

// 让Hello去实现ServeHTTP 方法 (在 interface 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)
}

// 这里是 http.ServeHTTP的方法署名:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }