Golang Cheat Sheet 精简备忘单

致谢

本文大部分的示例代码来自于 A Tour of Go,A Tour of Go 是最好的入门指引,如果你是新手,请一定要先学习这个指引。

Go 简介

  • 命令式语言;
  • 静态类型;
  • 语法符号与 C 类似(更少的圆括号和没有分号)并且结构很像 Oberon-2;
  • 编译为机器码(非 JVM);
  • 没有类,不过支持 structs 包含方法;
  • 接口;
  • 函数是第一公民;
  • 函数可以返回多个值;
  • 支持闭包;
  • 支持指针,不过不支持指针计算;
  • 内置并发机制:Goroutines 和 Channels。

基本语法

Hello World

hello.go 文件:

package main

import "fmt"

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

$ go run hello.go

运算符

算术

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

比较

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

逻辑

运算符 描述
&&
||
!

其它

运算符 描述
& 创建指针的地址
* 取消引用指针r
<- 发送 / 接收操作符(请参阅下面的 ‘ 频道 ’ )

声明

类型在变量名之后!

var foo int // 没有初始化声明
var foo int = 42 // 初始化声明
var foo, bar int = 42, 1302 // 一次声明并初始化多个变量
var foo = 42 // 省略类型,变量会从初始值中获得类型
foo := 42 // 简洁赋值,只能在 func 内使用,省略 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
}
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
}

// 闭包:不会改变外部变量,而是重新定义它们!
func outer() (func() int, int) {
    outer_var := 2
    inner := func() int {
        outer_var += 99 // 尝试改变外部变量 outer_var
        return outer_var // => 101 (但 outer_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
}

// 通过在最后一个参数的类型名称之前使用 ...,你可以指示它需要零个或多个参数。
// 该函数调用方式和其他函数调用方式一样,除了我们可以传递任意数量的参数之外。
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)

// alternative syntax
i := 42
f := float64(i)
u := uint(f)

  • 包声明在每一个源文件的顶部
  • 可执行文件(入口文件)在 main 包中
  • 约定:包名 == 导入路径中最后一个字段的名称(导入路径 math/rand => 包名是 rand
  • 大写标识符(一般是字母)开头: 已导出(其它包可以使用)
  • 小写标识符(一般是字母)开头: 私有的(其它包不能使用,只能本包中使用)

流程控制

If

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)
    }
}

循环

    //  在 go 语言中只有 `for` ,没有 `while` 和 `until`
    for i := 1; i < 10; i++ {
    }
    for ; i < 10;  { // while 循环(和其他语言的 while 循环一样)
    }
    for i < 10  { // 如果只有一个条件,你能省略分号
    }
    for { // 你可以省略条件,类似其他语言的 while(true)
    }

Switch

    // switch 语句
    switch operatingSystem {
    case "darwin":
        fmt.Println("Mac OS Hipster")
                // case 自动添加 break ,默认没有 fallthrough
    case "linux":
        fmt.Println("Linux Geek")
    default:
        // Windows, BSD, ...
        fmt.Println("Other")
    }

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

        // 你也能在 switch 的 case 中进行比较
    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的整型数组.数组长度也是类型的一部分!
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到3的切片
var b = a[:3]       // 低位索引缺省值为0
var b = a[3:]       // 高位索引缺省值为切片 a 的长度
a =  append(a,17,3) // 向切片 a 加入多个元素
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 {
}

// 在 Go pre-1.4, 如果你在代码中不使用 i 和 e 这两个变量,就会产生编译错误.
// Go 1.4 介绍了一种无参数形式,所以你可以像下面代码那样
for range time.Tick(time.Second) {
    // 一秒执行一次
}

映射

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" 是否存在,并查找映射相应的值

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} // 通过键值定义结构体
var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化一个结构体切片

//访问结构体成员
v.X = 4

// 你可以为结构体定义方法。你想定义方法的结构体(作为接收类型)位于 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 方法会创建一个指向新的结构实例的指针

Interfaces

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

// 类型不需要显示的去实现接口
type Foo struct {}

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

嵌套

Go 没有类的继承,不过它有接口和结构体嵌套。

// 实现 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 接口:

type error interface {
    Error() string
}

可能返回错误的函数:

func doStuff() (int, error) {
}

func main() {
    result, err := doStuff()
    if err != nil {
        // 处理错误
    } else {
        // 一切安好,处理结果 result
    }
}

并发

Goroutines (协程)

协程是轻量级的线程(由 Go 管理,而不是操作系统的线程)。  go f(a, b)  启动一个新的协程运行  f (这里的  f  是一个函数)。

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

func main() {
    // 在协程中使用一个已经命名的函数
    go doStuff("foobar")

    // 在协程中使用一个匿名函数
    go func (x int) {
        // function body goes here
    }(42)
}

Channels (通道)

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

// 没有缓冲的阻塞通道。当值没有被使用时,它会阻塞,直到写入的值被读取。

// 创建一个缓冲通道。如果已经写入并未被读取的值比 <缓冲的大小> 少时,写入到一个缓冲通道不会被阻塞。
ch := make(chan int, 100)

close(ch) // 关闭通道(只有发送者可以关闭)

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

// 如果 ok 是 false ,通道已经关闭

// 从通道中读取直到它关闭
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")
    }
}

通道的至理名言

  • 向 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!
  • 向一个已经关闭的通道发送数据会触发一个 panic

    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, 你好, नमस्ते, Привет, ᎣᏏᏲ") // basic print, plus newline
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 ) // print to string variable

fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish format
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // formatted print to string variable

hellomsg := `
 "Hello" in Chinese is 你好 ('Ni Hao')
 "Hello" in Hindi is नमस्ते ('Namaste')
` // multi-line string literal, using back-tick at beginning and end

代码块

HTTP Server

package main

import (
    "fmt"
    "net/http"
)

// 设定返回的类型
type Hello struct{}

// 让这个类型去实现 http.Handler 定义的 ServeHTTP
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)
// }
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://github.com/a8m/go-lang-cheat-she...

译文地址:https://learnku.com/go/t/23390/golang-ch...

本帖已被设为精华帖!
本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!