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 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
本帖已被设为精华帖!