源代碼下載: learngo-cn.go
發明Go語言是出於更好地完成工作的需要。Go不是計算機科學的最新發展潮流,但它卻提供瞭解決現實問題的最新最快的方法。
Go擁有命令式語言的靜態類型,編譯很快,執行也很快,同時加入了對於目前多核CPU的併發計算支持,也有相應的特性來實現大規模編程。
Go語言有非常棒的標準庫,還有一個充滿熱情的社區。
// 單行註釋
/* 多行
註釋 */
// 導入包的子句在每個源文件的開頭。
// Main比較特殊,它用來聲明可執行文件,而不是一個庫。
package main
// Import語句聲明瞭當前文件引用的包。
import (
"fmt" // Go語言標準庫中的包
"io/ioutil" // 包含一些輸入輸出函數
m "math" // 數學標準庫,在此文件中別名為m
"net/http" // 一個web服務器包
"os" // 系統底層函數,如文件讀寫
"strconv" // 字符串轉換
)
// 函數聲明:main是程序執行的入口。
// 不管你喜歡還是不喜歡,反正Go就用了花括號來包住函數體。
func main() {
// 往標準輸出打印一行。
// 用包名fmt限制打印函數。
fmt.Println("你好世界")
// 調用當前包的另一個函數。
beyondHello()
}
// 函數可以在括號里加參數。
// 如果沒有參數的話,也需要一個空括號。
func beyondHello() {
var x int // 變量聲明,變量必須在使用之前聲明。
x = 3 // 變量賦值。
// 可以用:=來偷懶,它自動把變量類型、聲明和賦值都搞定了。
y := 4
sum, prod := learnMultiple(x, y) // 返回多個變量的函數
fmt.Println("sum:", sum, "prod:", prod) // 簡單輸出
learnTypes() // 少於y分鐘,學的更多!
}
/* <- 快看快看我是跨行註釋_(:з」∠)_
Go語言的函數可以有多個參數和 *多個* 返回值。
在這個函數中, `x`、`y` 是參數,
`sum`、`prod` 是返回值的標識符(可以理解為名字)且類型為int
*/
func learnMultiple(x, y int) (sum, prod int) {
return x + y, x * y // 返回兩個值
}
// 內置變量類型和關鍵詞
func learnTypes() {
// 短聲明給你所想。
str := "少説話多讀書!" // String類型
s2 := `這是一個
可以換行的字符串` // 同樣是String類型
// 非ascii字符。Go使用UTF-8編碼。
g := 'Σ' // rune類型,int32的別名,使用UTF-8編碼
f := 3.14195 // float64類型,IEEE-754 64位浮點數
c := 3 + 4i // complex128類型,內部使用兩個float64表示
// var變量可以直接初始化。
var u uint = 7 // unsigned 無符號變量,但是實現依賴int型變量的長度
var pi float32 = 22. / 7
// 字符轉換
n := byte('\n') // byte是uint8的別名
// 數組(Array)類型的大小在編譯時即確定
var a4 [4] int // 有4個int變量的數組,初始為0
a3 := [...]int{3, 1, 5} // 有3個int變量的數組,同時進行了初始化
// Array和slice各有所長,但是slice可以動態的增刪,所以更多時候還是使用slice。
s3 := []int{4, 5, 9} // 回去看看 a3 ,是不是這裏沒有省略號?
s4 := make([]int, 4) // 分配4個int大小的內存並初始化為0
var d2 [][]float64 // 這裏只是聲明,並未分配內存空間
bs := []byte("a slice") // 進行類型轉換
// 切片(Slice)的大小是動態的,它的長度可以按需增長
// 用內置函數 append() 向切片末尾添加元素
// 要增添到的目標是 append 函數第一個參數,
// 多數時候數組在原內存處順次增長,如
s := []int{1, 2, 3} // 這是個長度3的slice
s = append(s, 4, 5, 6) // 再加仨元素,長度變為6了
fmt.Println(s) // 更新後的數組是 [1 2 3 4 5 6]
// 除了向append()提供一組原子元素(寫死在代碼裏的)以外,我們
// 還可以用如下方法傳遞一個slice常量或變量,並在後面加上省略號,
// 用以表示我們將引用一個slice、解包其中的元素並將其添加到s數組末尾。
s = append(s, []int{7, 8, 9}...) // 第二個參數是一個slice常量
fmt.Println(s) // 更新後的數組是 [1 2 3 4 5 6 7 8 9]
p, q := learnMemory() // 聲明p,q為int型變量的指針
fmt.Println(*p, *q) // * 取值
// Map是動態可增長關聯數組,和其他語言中的hash或者字典相似。
m := map[string]int{"three": 3, "four": 4}
m["one"] = 1
// 在Go語言中未使用的變量在編譯的時候會報錯,而不是warning。
// 下劃線 _ 可以使你“使用”一個變量,但是丟棄它的值。
_, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a3, s4, bs
// 通常的用法是,在調用擁有多個返回值的函數時,
// 用下劃線拋棄其中的一個參數。下面的例子就是一個髒套路,
// 調用os.Create並用下劃線變量扔掉它的錯誤代碼。
// 因為我們覺得這個文件一定會成功創建。
file, _ := os.Create("output.txt")
fmt.Fprint(file, "這句代碼還示範瞭如何寫入文件呢")
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全面支持垃圾回收。Go有指針,但是不支持指針運算。
// 你會因為空指針而犯錯,但是不會因為增加指針而犯錯。
func learnMemory() (p, q *int) {
// 返回int型變量指針p和q
p = new(int) // 內置函數new分配內存
// 自動將分配的int賦值0,p不再是空的了。
s := make([]int, 20) // 給20個int變量分配一塊內存
s[3] = 7 // 賦值
r := -2 // 聲明另一個局部變量
return &s[3], &r // & 取地址
}
func expensiveComputation() int {
return 1e6
}
func learnFlowControl() {
// if需要花括號,括號就免了
if true {
fmt.Println("這句話肯定被執行")
}
// 用go fmt 命令可以幫你格式化代碼,所以不用怕被人吐槽代碼風格了,
// 也不用容忍別人的代碼風格。
if false {
// pout
} else {
// gloat
}
// 如果太多嵌套的if語句,推薦使用switch
x := 1
switch x {
case 0:
case 1:
// 隱式調用break語句,匹配上一個即停止
case 2:
// 不會運行
}
// 和if一樣,for也不用括號
for x := 0; x < 3; x++ { // ++ 自增
fmt.Println("遍歷", x)
}
// x在這裏還是1。為什麼?
// for 是go裏唯一的循環關鍵字,不過它有很多變種
for { // 死循環
break // 騙你的
continue // 不會運行的
}
// 用range可以枚舉 array、slice、string、map、channel等不同類型
// 對於channel,range返回一個值,
// array、slice、string、map等其他類型返回一對兒
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// 打印map中的每一個鍵值對
fmt.Printf("索引:%s, 值為:%d\n", key, value)
}
// 如果你只想要值,那就用前面講的下劃線扔掉沒用的
for _, name := range []string{"Bob", "Bill", "Joe"} {
fmt.Printf("你是。。 %s\n", name)
}
// 和for一樣,if中的:=先給y賦值,然後再和x作比較。
if y := expensiveComputation(); y > x {
x = y
}
// 閉包函數
xBig := func() bool {
return x > 100 // x是上面聲明的變量引用
}
fmt.Println("xBig:", xBig()) // true (上面把y賦給x了)
x /= 1e5 // x變成10
fmt.Println("xBig:", xBig()) // 現在是false
// 除此之外,函數體可以在其他函數中定義並調用,
// 滿足下列條件時,也可以作為參數傳遞給其他函數:
// a) 定義的函數被立即調用
// b) 函數返回值符合調用者對類型的要求
fmt.Println("兩數相加乘二: ",
func(a, b int) int {
return (a + b) * 2
}(10, 2)) // Called with args 10 and 2
// => Add + double two numbers: 24
// 當你需要goto的時候,你會愛死它的!
goto love
love:
learnFunctionFactory() // 返回函數的函數多棒啊
learnDefer() // 對defer關鍵字的簡單介紹
learnInterfaces() // 好東西來了!
}
func learnFunctionFactory() {
// 空行分割的兩個寫法是相同的,不過第二個寫法比較實用
fmt.Println(sentenceFactory("原諒")("當然選擇", "她!"))
d := sentenceFactory("原諒")
fmt.Println(d("當然選擇", "她!"))
fmt.Println(d("你怎麼可以", "她?"))
}
// Decorator在一些語言中很常見,在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) // new string
}
}
func learnDefer() (ok bool) {
// defer表達式在函數返回的前一刻執行
defer fmt.Println("defer表達式執行順序為後進先出(LIFO)")
defer fmt.Println("\n這句話比上句話先輸出,因為")
// 關於defer的用法,例如用defer關閉一個文件,
// 就可以讓關閉操作與打開操作的代碼更近一些
return true
}
// 定義Stringer為一個接口類型,有一個方法String
type Stringer interface {
String() string
}
// 定義pair為一個結構體,有x和y兩個int型變量。
type pair struct {
x, y int
}
// 定義pair類型的方法,實現Stringer接口。
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()) // 調用pair類型p的String方法
var i Stringer // 聲明i為Stringer接口類型
i = p // 有效!因為p實現了Stringer接口(類似java中的塑型)
// 調用i的String方法,輸出和上面一樣
fmt.Println(i.String())
// fmt包中的Println函數向對象要它們的string輸出,實現了String方法就可以這樣使用了。
// (類似java中的序列化)
fmt.Println(p) // 輸出和上面一樣,自動調用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 為false,因為m中沒有1
fmt.Println("別找了真沒有")
} else {
fmt.Print(x) // 如果x在map中的話,x就是那個值嘍。
}
// 錯誤可不只是ok,它還可以給出關於問題的更多細節。
if _, err := strconv.Atoi("non-int"); err != nil { // _ discards value
// 輸出"strconv.ParseInt: parsing "non-int": invalid syntax"
fmt.Println(err)
}
// 待會再説接口吧。同時,
learnConcurrency()
}
// c是channel類型,一個併發安全的通信對象。
func inc(i int, c chan int) {
c <- i + 1 // <-把右邊的發送到左邊的channel。
}
// 我們將用inc函數來併發地增加一些數字。
func learnConcurrency() {
// 用make來聲明一個slice,make會分配和初始化slice,map和channel。
c := make(chan int)
// 用go關鍵字開始三個併發的goroutine,如果機器支持的話,還可能是並行執行。
// 三個都被髮送到同一個channel。
go inc(0, c) // go is a statement that starts a new goroutine.
go inc(10, c)
go inc(-805, c)
// 從channel中讀取結果並打印。
// 打印出什麼東西是不可預知的。
fmt.Println(<-c, <-c, <-c) // channel在右邊的時候,<-是讀操作。
cs := make(chan string) // 操作string的channel
cc := make(chan chan string) // 操作channel的channel
go func() { c <- 84 }() // 開始一個goroutine來發送一個新的數字
go func() { cs <- "wordy" }() // 發送給cs
// Select類似於switch,但是每個case包括一個channel操作。
// 它隨機選擇一個準備好通訊的case。
select {
case i := <-c: // 從channel接收的值可以賦給其他變量
fmt.Println("這是……", i)
case <-cs: // 或者直接丟棄
fmt.Println("這是個字符串!")
case <-cc: // 空的,還沒作好通訊的準備
fmt.Println("別瞎想")
}
// 上面c或者cs的值被取到,其中一個goroutine結束,另外一個一直阻塞。
learnWebProgramming() // Go很適合web編程,我知道你也想學!
}
// http包中的一個簡單的函數就可以開啓web服務器。
func learnWebProgramming() {
// ListenAndServe第一個參數指定了監聽端口,第二個參數是一個接口,特定是http.Handler。
go func() {
err := http.ListenAndServe(":8080", pair{})
fmt.Println(err) // 不要無視錯誤。
}()
requestServer()
}
// 使pair實現http.Handler接口的ServeHTTP方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用http.ResponseWriter返回數據
w.Write([]byte("Y分鐘golang速成!"))
}
func requestServer() {
resp, err := http.Get("http://localhost:8080")
fmt.Println(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("\n服務器消息: `%s`", string(body))
}
更進一步
關於Go的一切你都可以在Go官方網站找到。 在那裏你可以獲得教程參考,在線試用,和更多的資料。 在簡單的嘗試過後,在官方文檔那裏你會得到你所需要的所有資料、關於編寫代碼的規範、庫和命令行工具的文檔與Go的版本歷史。
強烈推薦閲讀語言定義部分,很簡單而且很簡潔!(趕時髦!)
你還可以前往Go在線體驗中心,在瀏覽器裏修改並運行這些代碼,一定要試一試哦!你可以將https://play.golang.org當作一個REPL,在那裏體驗語言特性或運行自己的代碼,連環境都不用配!
學習Go還要閲讀Go標準庫的源代碼,全部文檔化了,可讀性非常好,可以學到go,go style和go idioms。在文檔中點擊函數名,源代碼就出來了!
Go by example也是一個學習的好地方。
Go Mobile添加了對移動平台的支持(Android and iOS)。你可以完全用go語言來創造一個app或編寫一個可以從Java或Obj-C調用的函數庫,敬請參考Go Mobile page。
有建議?或者發現什麼錯誤?在Github上開一個issue,或者發起pull request!
原著Sonia Keys,並由2個好心人修改。
© 2022 Sonia Keys, pantaovay, lidashuang, Tim Zhang
本作品採用 CC BY-SA 3.0 協議進行許可。