Golang 速查表

譯文地址:blog
原文:golang-cheat-sheethtml

簡要歸納 Go 語法及特性。linux

目錄

  1. 基礎語法
  2. 運算符git

    • 算術運算符
    • 比較運算符
    • 邏輯運算符
    • 其餘
  3. 聲明
  4. 函數github

    • 函數做爲值和回調使用
    • 可變參數函數
  5. 內置類型
  6. 類型轉換
  7. package
  8. 流程控制結構golang

    • 條件判斷(if)
    • 循環(for)
    • 多條件分支(switch)
  9. array, slice, range編程

    • array
    • slice
    • array 和 slice 的操做函數
  10. map
  11. 結構體
  12. 指針
  13. 接口
  14. 結構體和接口的組合嵌入
  15. Errors
  16. 併發數組

    • goroutine
    • channel
    • channel 開發原則
  17. 輸出
  18. 代碼片斷安全

    • Http-Server

前言

參考

文中大部分代碼都摘抄自 A Tour of Go,對新手來講是很好的參考資料。數據結構

Go 特性

  • 命令式編程
  • 靜態類型
  • 類 C 語法(括號使用頻率更少 & 無需分號),類 Oberon-2 的語法結構
  • 代碼能編譯爲本地可執行文件(無需 JVM 類的虛擬機)
  • structmethod 取代類的概念
  • 接口
  • 類型組合 取代顯式繼承
  • 頭等函數
  • 有回調函數
  • 函數可有多個返回值
  • 保留指針,但不能直接參與算術運算
  • 內置併發原語:goroutinechannel

基礎語法

Hello World

文件 hello.go併發

package main

import "fmt"

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

運行:$ go run hello.go

運算符

算術運算符

運算符 描述
+
-
*
/
% 取餘
& 按位與
¦ 按位或
^ 按位異或
&^ 按位清除(AND NOT)
<< 左移
>> 右移

&^ 便是 AND NOT(x, y) = AND(x, NOT(Y)),如:

package main

import "fmt"

func main() {
    x := 0xDC    // 11011100
    y := 0xF0    // 11110000
    z := x &^ y    // 00001100    // y 中爲 1 的位所有被清除爲 0
    fmt.Printf("%08b", z)
}

比較運算符

運算符 描述
== 相等
!= 不等
< 小於
<= 小於等於
> 大於
>= 大於等於

邏輯運算符

運算符 描述
&& 邏輯與
¦¦ 邏輯或
! 取反

其餘

運算符 描述
& 尋址(生成指針)
* 獲取指針指向的數據
<- 向 channel 中發送 / 接收數據

聲明

與 C 不一樣,類型放在標識符後面

var foo int             // 無初值的聲明
var foo int = 42         // 帶初值的聲明
var foo, bar int = 42, 1302    // 一次性聲明並初始化多個變量
var foo = 42             // 類型推斷,由使用的上下文決定
foo := 42             // 簡短聲明,只能用在函數內部
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{
    // 編譯錯誤,兩個變量不在此函數做用域內
    // undefined: outer_var
    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,但只在 inner() 內部有效
    }
    return inner, outer_var    // 返回值是 inner, 2 (outer_var 還是 2)
}

inner, outer_var := outer();    // inner, 2
inner();    // 返回 101
inner();    // 返回 200    // 回調函數的特性

可變參數函數

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 {    // 使用迭代器逐個訪問參數
        total += v
    }
    return total
}

內置類型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 類型的別名    // 存儲 raw data

rune // int32 類型的別名    // 一個 Unicode code point 字符

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)

package

  1. package 在源文件開頭聲明
  2. main package 纔是可執行文件
  3. 約定:package 名字與 import 路徑的最後一個單詞一致(如導入 math/rand 則 package 叫 rand)
  4. 大寫開頭的標識符(變量名、函數名…):對其餘 package 是可訪問的
  5. 小寫開頭的標識符:對其餘 package 是不可見的

流程控制結構

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

Loops

// Go 語言中循環結構只有 for,沒有 do、while、until、foreach 等等
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")
        // case 語句自帶 break,想執行全部 case 須要手動 fallthrough
    case "linux":
        fmt.Println("Linux Geek")
    default:
        // Windows, BSD, ...
        fmt.Println("Other")
}

// 和 if、for 語句同樣,可在判斷變量以前加入一條賦值語句
switch os := runtime.GOOS; os {
    case "darwin": ...
}

// 在 switch 中還能作比較,至關於 switch (true) {...}
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")
}

Arrays, Slices, Ranges

Arrays

var a [10]int // 聲明長度爲 10 的 int 型數組,注意數組類型 = (元素類型 int,元素個數 10)
a[3] = 42     // 設置元素值
i := a[3]     // 讀取元素值

// 聲明並初始化數組
var a = [2]int{1, 2}
a := [2]int{1, 2}     // 簡短聲明
a := [...]int{1, 2}    // 數組長度使用 ... 代替,編譯器會自動計算元素個數

slices

var a []int                   // 聲明 slice,至關於聲明未指定長度的數組
var a = []int {1, 2, 3, 4}    // 聲明並初始化 slice (基於 {} 中給出的底層數組)
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 的 slice 
var b = a[1:4]        // 建立從 1  到 3    的 slice
var b = a[:3]        // 缺省 start index 則默認爲 0 
var b = a[3:]        // 缺省 end   index 則默認爲 len(a)
a =  append(a,17,3)    // 向 slice a 中追加 17 和 3
c := append(a,b...)    // 合併兩個 slice

// 使用 make 建立 slice
a = make([]byte, 5, 5)    // 第一個參數是長度,第二個參數是容量
a = make([]byte, 5)    // 容量參數是可選的

// 從數組建立 slice
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:]         // slice s 指向底層數組 x

數組和 slice 的操做函數

// 迭代數組或 slice
for i, e := range a {
    // i 是索引
    // e 是元素值
}

// 若是你只要值,可用 _ 來丟棄返回的索引
for _, e := range a {
}

// 若是你只要索引
for i := range a {
}

// 在 Go 1.4 之前的版本,若是 i 和 e 你都不用,直接 range 編譯器會報錯
for range time.Tick(time.Second) {
    // 每隔 1s 執行一次
}

map

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

delete(m, "key")

elem, ok := m["key"] // 檢查 m 中是否鍵爲 key 的元素,若是有 ok 才爲 true

// 使用鍵值對的形式來初始化 map
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

結構體

Go 語言中沒有 class 類的概念,取而代之的是 struct,struct 的方法對應到類的成員函數。

// struct 是一種類型,也是字段成員的集合體

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

// 初始化 struct
var v = Vertex{1, 2}            // 字段名有序對應值
var v = Vertex{X: 1, Y: 2}         // 字段名對應值
var v = []Vertex{{1,2},{5,2},{5,5}}    // 初始化多個 struct 組成的 slice

// 訪問成員
v.X = 4

// 在 func 關鍵字和函數名之間,聲明接收者是 struct
// 在方法內部,struct 實例被複制,傳值引用
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 調用方法(有接收者的函數)
v.Abs()

// 有的方法接收者是指向 struct 的指針
// 此時在方法內調用實例,將是傳址引用
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 的指針

var s *Vertex = new(Vertex) // new 返回的指向該實例指針

接口

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

// 無需手動聲明 implement 接口
type Foo struct {}

// 自定義類型若是實現了接口的全部方法,那它就自動實現了該接口
func (foo Foo) Awesomize() string {
    return "Awesome!"
}

結構體和接口的組合嵌入

// 實現 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(...)

// 字段同理
var logger *log.Logger = server.Logger

Errors

Go 中沒有異常處理機制,函數在調用時在有可能會產生錯誤,可返回一個 Error 類型的值,Error 接口:

type error interface {
    Error() string
}

一個可能產生錯誤的函數:

func doStuff() (int, error) {
}

func main() {
    result, err := doStuff()
    if err != nil {
        // 錯誤處理
    }
    // 使用 result 處理正常邏輯
}

併發

goroutine

goroutine(協程)是輕量級的線程(Go runtime 自行管理,而不是操做系統),代碼 go f(a, b) 就開了一個運行 f 函數的協程。

func doStuff(s string) {
}

func main() {
    // 在協程中執行函數
    go doStuff("foobar")

    // 在協程中執行匿名函數
    go func (x int) {
        // 函數實現
    }(42)
}

Channels

ch := make(chan int)     // 建立類型爲 int 的 channel
ch <- 42                 // 向 channel ch 寫數據 42
v := <-ch                // 從 channel ch 讀數據,此時 v 的值爲 42
            // 無緩衝的 channel 此時會阻塞
            // 若是 channel 中無數據,則讀操做會被阻塞,直到有數據可讀

// 建立帶緩衝的 channel
// 向帶緩衝的 channel 寫數據不會被阻塞,除非該緩衝區已滿
ch := make(chan int, 100)

close(ch) // 發送者主動關閉 channel

// 在從 channel 讀數據的同時檢測其是否已關閉
// 若是 ok 爲 false,則 ch 已被關閉
v, ok := <-ch    

// 從 channel 中讀數據直到它被關閉
for i := range ch {
    fmt.Println(i)
}

// select 語句中 任一 channel 不阻塞則自動執行對應的 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 開發原則

1.向 nil channel 寫數據將卡死,一直阻塞

2.從 nil channel 讀數據將卡死,一直阻塞

3.向已關閉的 channel 寫數據將形成 panic

package main

func main() {
    var c = make(chan string, 1)
    c <- "Hello, World!"
    close(c)
    c <- "Hello, Panic!"
}

運行:

4.從已關閉的 channel 讀數據將返回零值

package main

func main() {
    var c = make(chan int, 2)
    c <- 1
    c <- 2
    close(c)
    for i := 0; i < 3; i++ {
        println(<-c)
    }
}

運行:

輸出

fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ")         // 最基本的輸出,會自動加一個換行
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X )         // 輸出結構體字段等
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')
` 
// 聲明多行字符串,在先後均使用反引號 `

代碼片斷

HTTP Server

package main

import (
    "fmt"
    "net/http"
)

// 定義響應的數據結構
type Hello struct{}

// Hello 實現 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)
}

// http.ServeHTTP 在接口內的定義以下:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }

運行:

總結

上邊十七個知識點簡要歸納了常見語法,可複習使用,但涉及到的細節很少,細讀《Go 程序設計語言》 纔是。

相關文章
相關標籤/搜索