Go 是出於完成工作的需要而創建的。這不是編程語言理論的最新趨勢,但它是解決現實世界問題的一種方法。
它從具有靜態類型的命令式語言中汲取概念。它編譯速度快,執行速度快,它增加了易於理解的併發性,因為現在多核 CPU 很常見,並且它成功地用於大型代碼庫(Google 有大約 1 億行Go代碼)。
第一分鐘: 約定
下載安裝
GoLand
項目目錄結構
- pkg:編譯後生成文件
- src :項目的源代碼
- bin:編譯後可執行的文件
第二分鐘:語法
// 單行註釋
/* 多
行註釋 */
/* 構建標籤是以 // +build 開頭的行註釋
,可以通過 go build -tags="foo bar" 命令執行。
構建標記放置在靠近或文件頂部的 package 子句之前,
後跟空行或其他行註釋。*/
// +build prod, dev, test
// package 子句啓動每個源文件。
// Main 是一個特殊的名稱,它聲明一個可執行文件而不是一個庫。
package main
// 導入聲明聲明此文件中引用的庫包。
import (
"fmt" // Go 標準庫中的一個包。
"io/ioutil" // 實現一些 I/O 實用函數。
m "math" // 具有本地別名為 m 的數學庫。
"net/http" //是的,一個網絡服務器!
“os” //操作系統功能,如:使用文件系統
“strconv” //字符串轉換。
)
// 一個函數定義。main是特別的。它是
// 可執行程序的入口點。愛也好恨也好,Go 使用大括號。
func main () {
// Println 輸出一行到標準輸出。
// 它來自包 fmt.
fmt.Println("Hello world!")
// 在這個包中調用另一個函數。
beyondHello()
}
// 函數有括號中的參數。
// 如果沒有參數,仍然需要空括號。
func beyondHello() {
var x int // 變量聲明。變量必須在使用前聲明。
x = 3 // 變量賦值。
// “短”聲明使用 := 來推斷類型、聲明和分配。
y := 4
sum, prod := learnMultiple(x, y) // 函數返回兩個值。
fmt.Println("sum:", sum, "prod:", prod) // 簡單輸出。
learnTypes() // < 15 分鐘,瞭解更多!
}
/* <- 多行註釋
函數可以有參數和(多個!)返回值。
這裏 `x`、`y` 是參數,`sum`、`prod` 是簽名(返回的內容)。
注意 `x` 和 `sum` 接收類型 `int`。
*/
func learnMultiple(x, y int) (sum, prod int) {
return x + y , x * y // 返回兩個值。
}
// 一些內置類型和文字。
func learnTypes () {
// 簡短的聲明通常會給你你想要的。
str := "學習Go!" // 字符串類型。
s2 := `“原始”字符串文字
可以包含換行符。` // 相同的字符串類型。
// 非 ASCII 文字。Go 源代碼是 UTF-8。
g := 'Σ' // rune (符文)類型,int32 的別名,包含一個 unicode 代碼點。
f := 3.14195 // float64,一個 IEEE-754 64 位浮點數。
c := 3 + 4i // complex128,內部用兩個 float64 表示。
// 帶有初始化器的 var 語法。
var u uint = 7 // 無符號,但與 int 一樣取決於實現的大小。
var pi float32 = 22. / 7
// 帶有簡短聲明的轉換語法。
n := byte('\n') // byte 是 uint8 的別名。
// 數組的大小在編譯時是固定的。
var a4 [4]int // 4 個 int 的數組,初始化為全 0。
a5 := [...]int{3, 1, 5, 10, 100} // 一個固定大小為 5 的數組初始化
// 元素,值為 3、1、5、10 和 100。
// 數組具有值語義。
a4_cpy := a4 // a4_cpy 是 a4 的副本,兩個獨立的實例。
a4_cpy[0] = 25 // 只有 a4_cpy 改變了,a4 保持不變。
fmt.Println(a4_cpy[0] == a4[0]) // false
// 切片具有動態大小。數組和切片各有優勢
// 但切片的用例更為常見。
s3 := []int{4, 5, 9} // 與 a5 比較。這裏沒有省略號。
s4 := make([]int, 4) // 分配 4 個 int 的切片,初始化為全 0。
var d2 [][]float64 // 僅聲明,此處不分配任何內容。
bs := []byte("a slice") // 類型轉換語法。
// 切片(以及地圖和通道)具有引用語義。
s3_cpy := s3 // 兩個變量都指向同一個實例。
s3_cpy[0] = 0 // 這意味着兩者都更新了。
fmt.Println(s3_cpy[0] == s3[0]) // 真
// 因為它們是動態的,切片可以按需追加。
// 要將元素附加到切片,使用內置的 append() 函數。
// 第一個參數是我們要附加的切片。通常,
// 數組變量會就地更新,如下例所示。
s := []int{1, 2, 3} // 結果是一個長度為 3 的切片。
s = append(s, 4, 5, 6) // 添加了 3 個元素。切片現在的長度為 6.
fmt.Println(s) // 更新的切片現在是 [1 2 3 4 5 6]
// 要附加另一個切片,而不是原子元素列表,我們可以
// 傳遞對切片的引用或像這樣的切片字面量,帶有一個
// 尾隨省略號,意思是獲取一個切片並解包其元素,
// 附加它們切片 s。
s = append(s, []int{7, 8, 9}...) // 第二個參數是一個切片文字。
fmt.Println(s) // 更新後的切片現在是 [1 2 3 4 5 6 7 8 9]
p, q := learnMemory() // 聲明 p, q 為指向 int 的類型指針。
fmt.Println(*p, *q) // * 跟隨一個指針。這會打印兩個整數。
// Map 是一種動態可增長的關聯數組類型,就像一些其他語言的
// 哈希或字典類型。
m := map[string]int{"three": 3, "four": 4}
m["one"] = 1
// 未使用的變量是 Go 中的錯誤。
// 下劃線讓你“使用”一個變量但丟棄它的值。
_, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a5, s4, bs
// 通常你用它來忽略其中一個函數的返回值
// 例如,在一個又髒又快的腳本中,您可能會忽略
// 從 os.Create 返回的錯誤值,並期望文件
// 將始終被創建。
file, _ := os.Create("output.txt")
fmt.Fprint(file, "This is how you write to a file, by the way")
file.Close()
// 當然,輸出算作使用變量。
fmt.Println(s, c, a4, s3, d2, m)
learnFlowControl() // 回到流程中。
}
// 與許多其他語言不同,go 中的函數
// 有可能具有命名的返回值。
// 為函數聲明行中返回的類型分配一個名稱
// 允許我們輕鬆地從函數中的多個點返回,以及
// 僅使用 return 關鍵字,而無需進一步。
func learnNamedReturns(x, y int) (z int) {
z = x * y
return // z 在這裏是隱含的,因為我們之前命名了它。
}
// Go 是完全垃圾回收的。它有指針但沒有指針算術。
// 你可以用 nil 空指針搞出錯,但不能通過遞增修改指針。
// 與 C/Cpp 不同,獲取和返回局部變量的地址也是安全的。
func learnMemory() (p, q *int) {
// 命名返回值 p 和 q 具有指向 int 的類型指針。
p = new(int) // 內置函數 new 分配內存。
// 分配的 int slice 初始化為 0,p 不再為 nil。
s := make([]int, 20) // 分配 20 個整數作為單個內存塊。
s[3] = 7 // 分配其中之一。
r := -2 // 聲明另一個局部變量。
return &s[3], &r // & 獲取對象的地址。
}
// 使用別名 數學庫(參見上面的導入)
func expensiveComputation() float64 {
return m.Exp(10)
}
func learnFlowControl() {
// If 語句需要大括號,不需要括號。
if true {
fmt.Println("told ya")
}
// 格式由命令行命令“go fmt”標準化。
if false {
// 噘嘴 - Pout
} else {
// 幸災樂禍 - Gloat
}
// 使用 switch 優先於鏈式 if 語句。
x := 42.0
switch x {
case 0:
case 1, 2: // 一個case可以有多個匹配
case 42:
// case不會“失敗”。
/*
但是有一個 `fallthrough` 關鍵字,請參閲:
https ://github.com/golang/go/wiki/Switch#fall-through
*/
case 43 :
// Unreached。
default :
// 默認情況是可選的。
}
// 類型開關允許打開某物的類型而不是值
var data interface{}
data = ""
switch c := data.(type) {
case string:
fmt.Println(c, "is a string")
case int64:
fmt.Printf("%d is an int64\n", c)
default:
// 所有其他情況
}
// 就像 if, for 也不使用括號。
// 在 for 和 if 中聲明的變量在其範圍內是本地的。
for x := 0; x < 3; x++ { // ++ 是一個語句。
fmt.Println("iteration", x)
}
// x == 42 這裏。
// For 是 Go 中唯一的循環語句,但它有其他形式。
for { // 無限循環
break // 開個玩笑
continue // 未到達
}
// 您可以使用 range 來迭代數組、切片、字符串、映射或通道。
// range 返回一個(通道)或兩個值(數組、切片、字符串和映射)。
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// 對於 map 中的每一對,打印 key 和 value
fmt.Printf("key=%s, value=%d\n", key, value)
}
// 如果只需要值,使用下劃線作為 _ 的鍵
for _, name := range []string{"Bob", "Bill", "Joe"} {
fmt.Printf("Hello, %s\n", name)
}
// 與 for 一樣,if 語句中的 := 表示先聲明和賦值
// y,然後測試 y > x。
if y := expensiveComputation(); y > x {
x = y
}
// 函數字面量(literals)是閉包(closures)。
xBig := func() bool {
return x > 10000 // 引用在 switch 語句上面聲明的 x。
}
x = 99999
fmt.Println("xBig:", xBig()) // true
x = 1.3e3 // 這使得 x == 1300
fmt.Println("xBig:", xBig()) // 現在為假。
// 更重要的是函數字面量可以被定義時立即調用,
// 作為函數的參數,只要:
// a) 函數字面量被立即調用 (),
// b) 結果類型匹配預期的參數類型.
fmt.Println("Add + double two numbers: ",
func(a, b int) int {
return (a + b) * 2
}(10, 2)) // 使用 args 10 和 2 調用
// => Add + double兩個數字:24
// 當你需要它時,你會愛上它。
goto love
love:
learnFunctionFactory() // func 返回 func is fun(3)(3)
learnDefer() // 快速繞道一個重要的關鍵字。
learnInterfaces() // 好東西來了!
}
func learnFunctionFactory() {
// 接下來兩個是等價的,第二個更實用
fmt.Println(sentenceFactory("summer")("A beautiful", "day!"))
d := sentenceFactory("summer")
fmt.Println(d("A beautiful", "day!"))
fmt.Println(d("A lazy", "afternoon!"))
}
// 裝飾器在其他語言中很常見。同樣可以在 Go
// 中使用接受參數的函數文字來完成。
func sentenceFactory(mystring string) func(before, after string) string {
return func(before, after string) string {
return fmt.Sprintf("%s %s %s", before, mystring, after) // 新字符串
}
}
func learnDefer() (ok bool) {
// defer 語句將函數調用推送到列表中。保存的
// 調用列表在周圍函數返回後執行
defer fmt.Println("延遲語句以相反 (LIFO) 順序執行.")
defer fmt.Println("\n此行首先打印,因為")
// Defer 通常用於關閉文件,因此關閉文件的函數
// 與打開文件的函數保持接近。
return true
}
// 將 Stringer 定義為具有一種方法 String 的接口類型。
type Stringer interface {
String() string
}
// 將 pair 定義為具有兩個字段的結構,int 名為 x 和 y。
type pair struct {
x, y int
}
// 在類型對上定義一個方法。Pair 現在實現了 Stringer,因為 Pair 已經定義了接口中的所有方法。
func (p pair) String() string { // p 被稱為“接收者”
// Sprintf 是 fmt 包中的另一個公共函數。
// 點語法引用 p 的字段。
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}
func learnInterfaces() {
// 大括號語法是“結構字面量”。它評估為一個初始化的
// 結構。:= 語法聲明並初始化 p 到這個結構。
p := pair{3, 4}
fmt.Println(p.String()) // 調用 p 的 String 方法,類型為 pair。
var i Stringer // 聲明 i 的接口類型為 Stringer。
i = p // 有效,因為 pair 實現了 Stringer
// 調用 i 的 String 方法,Stringer 類型。輸出同上。
fmt.Println(i.String())
// fmt 包中的函數調用 String 方法來請求對象
// 獲取其自身的可打印表示。
fmt.Println(p) // 輸出同上。Println 調用 String 方法。
fmt.Println(i) // 輸出同上。
learnVariadicParams("great", "learning", "here!")
}
// 函數可以有可變參數。
func learnVariadicParams(myStrings ...interface{}) {
// 迭代可變參數的每個值。
// 這裏的下劃線忽略了數組的索引參數。
for _, param := range myStrings {
fmt.Println("param:", param)
}
// 將可變參數值作為可變參數傳遞。
fmt.Println("params:", fmt.Sprintln(myStrings...))
learnErrorHandling()
}
func learnErrorHandling () {
// ", ok" 模式用來判斷某事是否有效。
m := map[int]string{3: "three", 4: "four"}
if x, ok := m[1]; !ok { // ok 將是假的,因為 1 不在map中。
fmt.Println("no one there")
} else {
fmt.Print(x) // x 將是值,如果它在map中。
}
// 錯誤值不僅傳達“ok”,還傳達更多關於問題的信息。
if _, err := strconv.Atoi("non-int"); err != nil { // _ 丟棄值
// 打印 'strconv.ParseInt: parsing "non-int": invalid syntax'
fmt.Println(err)
}
// 我們稍後再討論接口。併發,
learnConcurrency()
}
// c 是一個通道,一個併發安全的通信對象。
func inc(i int, c chan int) {
c <- i + 1 // <- 是當頻道出現在左側時的“發送”運算符。
}
// 我們將使用 inc 來同時增加一些數字。
func learnConcurrency() {
// 之前使用相同的 make 函數來製作切片。Make 分配和
// 初始化切片、映射和通道。
c := make(chan int)
// 啓動三個併發的 goroutine。
// 如果機器有能力並且//正確配置,數字將同時遞增,可能是並行遞增。
這三個都發送到同一個頻道。
go inc(0, c) // go 是一個啓動新 goroutine 的語句。
go inc(10, c)
go inc(-805, c)
// 從通道中讀取三個結果並打印出來。
// 不知道結果將以什麼順序到達!
fmt.Println(<-c, <-c, <-c) // 右邊的通道,<- 是“接收”操作符。
cs := make(chan string) // 另一個通道,這個通道處理字符串。
ccs := make(chan chan string) // 字符串通道的通道。
go func() { c <- 84 }() // 啓動一個新的 goroutine 只是為了發送一個值。
go func() { cs <- "wordy" }() // 同樣,這次是 cs。
// Select 的語法類似於 switch 語句,但每種情況都涉及
// 一個通道操作。它從案例中隨機選擇一個案例
// 準備好進行通信。
select {
case i := <-c: // 接收到的值可以分配給變量
fmt.Printf("it's a %T", i)
case <-cs: // 或者接收到的值可以被丟棄。
fmt.Println("it's a string")
case <-ccs: // 空通道,未準備好進行通信。
fmt.Println("didn't happen.")
}
// 此時,從 c 或 cs 中獲取了一個值。上面啓動的兩個
// goroutine 之一已經完成,另一個將保持阻塞狀態。
learnWebProgramming() // Go 做到了。你也想做。
}
// http 包中的單個函數啓動 Web 服務器。
func learnWebProgramming() {
// ListenAndServe 的第一個參數是要監聽的 TCP 地址。
// 第二個參數是一個接口,具體是http.Handler。
go func() {
err := http.ListenAndServe(":8080", pair{})
fmt.Println(err) // 不要忽略錯誤
}()
requestServer()
}
// 通過實現它的唯一方法 ServeHTTP,使配對成為一個 http.Handler。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用 http.ResponseWriter 方法提供數據。
w.Write([]byte("You learned Go in Y minutes!"))
}
func requestServer() {
resp, err := http.Get("http://localhost:8080")
fmt.Println(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("\nWebserver said: `%s`", string(body))
}
延伸閲讀
Go 的所有東西源於官方的 Go 網站。在那裏,您可以按照教程進行操作,進行交互式遊戲並閲讀大量內容。除了遊覽之外,這些文檔還包含有關如何編寫乾淨有效的 Go 代碼、包和命令文檔以及發佈歷史的信息。
強烈推薦Go 語言規範本身。它易於閲讀且非常短(就像現在的語言定義一樣。)
您可以在Go Playground上玩弄代碼。嘗試更改它並從瀏覽器運行它!請注意,您可以使用https://play.golang.org 作為 REPL 在瀏覽器中測試事物和代碼,甚至無需安裝 Go。
Go 學生的閲讀清單上是標準庫的源代碼。全面記錄,它展示了最好的可讀性和可理解的 Go、Go 風格和 Go 習慣用法。或者您可以單擊文檔中的函數名稱並顯示源代碼!
另一個學習 Go 的好資源是Go by example。
YouTube 上有很多關於 Go 的優秀會議演講和視頻教程,這裏有三個非常好的播放列表,分別為初學者、中級和高級 Gophers 量身定製:
- Golang University 101介紹了基本的 Go 概念並向您展示如何使用用於創建和管理 Go 代碼的 Go 工具
- Golang University 201 更上一層樓,解釋了測試、Web 服務和 API 等重要技術
- Golang University 301深入探討了更高級的主題,例如 Go 調度程序、地圖和通道的實現、和優化技術
Go Mobile 增加了對移動平台(Android 和 iOS)的支持。您可以編寫全 Go 原生移動應用程序或編寫包含來自 Go 包的綁定的庫,這些綁定可以通過 Java (Android) 和 Objective-C (iOS) 調用。查看Go Mobile 頁面瞭解更多信息。