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 頁面瞭解更多資訊。