Go語言是近年來最熱門的編程語言,是Google開發的一種靜態強類型、編譯型、併發型,並具備垃圾回收功能的編程語言。Go語言同時具有開發效率高和執行效率高兩大特色,被譽爲雲計算時代的C語言。本文做者經過一篇文章帶你學會Go語言。git
Go 語言是一門開源語言,可以輕鬆的構建簡單,可靠,高效的軟件。
—— Golang
在不少語言中,解決給定的問題一般有多種方式。工程師須要花費大量的時間思考什麼纔是解決問題的最優解法。而在Golang中,問題的解法一般只有一種。github
這一特性大大節約了工程師的時間,並且使得維護大型代碼庫變得更容易。在Golang中沒有maps和filter這樣"高消耗"的特性。golang
語言的特性帶來更好的表現力也帶來代價。
——Rob Pike
Golang由包組成。Golang編譯器將main包編譯爲可執行文件,而非共享庫。main包是應用的入口,一般被定義以下:web
package main
下面看一個hello world 的例子,在Golang 的工做空間建立main.go文件。編程
在Go語言中,工做空間由環境變量GOPATH定義。全部編寫的代碼須要在工做空間中。Go語言會在GOPATH和GOROOT的路徑中搜索包。GoROOT是在安裝的時候肯定的安裝路徑。json
下面來設置GOPATH,咱們將~/workspace 加入工做空間。數組
# export env export GOPATH=~/workspace # go inside the workspace cd ~/workspace
咱們在剛纔的工做空間下建立main.go 文件,代碼以下:服務器
package main import ( "fmt" ) func main(){ fmt.Println("Hello World!") }
上面的例子中,fmt是Go內置的格式化I/O函數。多線程
咱們在Go語言中使用import關鍵字導入包,func main 是入口函數。Println是fmt包中函數,用於打印 "Hello World!"。架構
讓咱們開始運行該文件。衆所周知Go是編譯型語言,咱們在運行以前先進行編譯。
go build main.go
這將會建立一個二進制運行文件main,咱們如今來運行它:
./main // Hello World!
另外一種簡單的方式是使用go run 命令:
go run main.go // Hello World!
Go中的變量類型是顯式指定的。Go語言是強類型語言,這意味着在變量聲明的時候會檢查變量類型。
變量定義以下所示:
var a int 在這個例子中,a的初始值被設置爲0。用下面的方式能夠定義並初始化變量。 var a = 1 這裏的變量被編譯器推斷爲int。更簡單的變量定義以下所示: message := "hello world" 咱們也能夠在同一行聲明多個變量: var b, c int = 2, 3
int 的類型有 int, int8, int16, int32, int64, unit, unit8, unit16, unit 32, unit64, unitptr...
[u開頭表示無符號;uintptr 是一種無符號的整數類型,沒有指定具體的bit大小可是足以容納指針。 uintptr類型只有在底層編程是才須要,特別是Go語言和C語言函數庫或操做系統接口相交互的地方。]
String類型使用byte序列存儲數據,用關鍵字string來聲明變量。
bool 關鍵字表示布爾類型。
Golang 也支持複數,用conplex64和complex128表示。
var a bool = true vat b int = 1 var c string = "hello world" var d float32 = 1.222 var x complex128 = cmplx.Sqrt(-5 +12i)
Array 是同類型元素的數組。Array在聲明的時候會指定長度且不能改變。一個數組的定義以下:
var a[5] int 也有多維數組,定義以下 var multiD [2][3]int Slices 是能隨時擴容的同類型元素的序列 。Slice的聲明方式以下: var b []int 這將會建立一個容量爲0,長度爲0的Slice。Slice也能夠定義容量和長度,格式以下: numbers := make([]int, 5, 10) 這個Slice初始長度爲5,容量爲10。
Slice是數組的封裝,其內部實現是數組,slice有三個元素,容量,長度和指向內部數組的指針。
Slice的容量能夠經過append 或者 copy函數增長。Append函數也能在數組的末尾添加元素,在容量不足的狀況下會對slice擴容。
numbers = append(numbers, 1, 2, 3, 4) 另外一種增長slice容量的方式是使用copy函數。Copy函數的原理是建立一個新的大容量的slice,並把原有的slice拷貝到新的slice中。 // 建立新的slice number2 := make([]int, 15) // 複製原有的slice到新的slice copy(number2, number)
咱們也能夠建立slice的子slice。例子以下:
package main import ( "fmt" ) func main() { // 初始化slice number2 := []int{1, 2, 3, 4} fmt.Println(number2) // -> [1 2 3 4] // 建立子slice slice1 := number2[2:] fmt.Println(slice1) // -> [3 4] slice2 := number2[:3] fmt.Println(slice2) // -> [1 2 3] slice3 := number2[1:4] fmt.Println(slice3) // -> [2 3 4] }
Go語言中的Map是鍵值對,定義以下:
var m map[string]int
m是定義的變量名,鍵的類型是string,值的類型是integers。Map中添加鍵值對的例子以下:
package main import ( "fmt" ) func main() { m := make(map[string]int) // 添加鍵值對 m["clearity"] = 2 m["simplicity"] = 3 // 打印值 fmt.Println(m["clearity"]) // -> 2 fmt.Println(m["simplicity"]) // -> 3 }
使用類型轉換可以改變數據類型,例子以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { a := 1.1 b := int(a) fmt.Println(b) //-> 1 }
If else 的例子以下,須要注意的是花括號和條件表達式位於同一行。
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } }
Switch case 能組織多條件表達式,例子以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 2 switch i { case 1: fmt.Println("one") case 2: fmt.Println("two") default: fmt.Println("none") } }
Golang中只有一個循環表達的關鍵字,不一樣形式的循環表達式以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 0 sum := 0 for i < 10 { sum += 1 i++ } fmt.Println(sum) }
上面的例子和C語言中的while循環相似,更爲正式的循環表達形式以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
Go 語言中的死循環定義以下:
for { }
Go 語言可使用指針,指針存儲變量的地址,指針用*來定義。指針的定義和所指數據的類型相關:
var ap *int 這裏的ap是指向整型數據的指針,&用於獲取所變量的地址。 a :=12 ap = &a * 用於獲取指針所指的地址的值。 fmt.Println(*ap) // => 12 指針一般用於將結構體作爲參數傳遞。
傳值一般意味着拷貝,意味着須要更多的內存。
使用指針傳遞時,在函數中改變的變量會傳遞給調用的方法或函數。
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 10 increment(&i) fmt.Println(i) //=> 11 }
main 包中main函數是golang 程序的入口。咱們能夠定義多個函數並調用。例如:
package main import ( "fmt" ) func add(a int, b int) int { c := a + b return c } func main() { fmt.Println(add(2, 1)) //=> 3 }
從上面的例子中咱們能夠看出,Golang 中的函數用func關鍵字加上函數名, 後面是附帶數據類型的參數,最後是函數的返回類型。
函數的返回值能夠被預先定義,例子以下:
package main import ( "fmt" ) func add(a int, b int) (c int) { c = a + b return } func main() { fmt.Println(add(2, 1)) //=> 3 }
這裏c定義爲返回值,所以變量c將會被自動返回,無需在函數最後的return中聲明。
你也能夠定義一個多個返回值的函數,使用,進行分割。
package main import ( "fmt" ) func add(a int, b int) (int, string) { c := a + b return c, "successfully added" } func main() { sum, message := add(2, 1) fmt.Println(message) //=> successfully added fmt.Println(sum) //=> 3 }
Golang 不是徹底的面嚮對象語言,可是支持不少面向對象的特性,例若有結構體,接口,方法等。
結構體是有類型,不一樣變量的集合。例如咱們想定義Person類型,其中包含姓名,年齡,性別。例如:
type person struct { name String age int gender string }
定義好了person結構體後,咱們如今來使用它:
//方式 1: 指定屬性和值 p = person{name: "Bob", age: 42, gender: "Male"} //方式 2: 只指定值 person{"Bob", 42, "Male"} 咱們可使用.符號訪問這些屬性: p.name //=> Bob p.age //=> 42 p.gender //=> Male
你也但是使用指針訪問結構體的屬性:
pp = &person{name: "Bob", age: 42, gender: "Male"} pp.name //=> Bob
方法是一種帶有接受器的特殊函數。接收器能夠是值或者指針。例子以下:
package main import "fmt" // 定義結構體 type person struct { name string age int gender string } // 定義方法 func (p *person) describe() { fmt.Printf("%v is %v years old.", p.name, p.age) } func (p *person) setAge(age int) { p.age = age } func (p person) setName(name string) { p.name = name } func main() { pp := &person{name: "Bob", age: 42, gender: "Male"} pp.describe() // => Bob is 42 years old pp.setAge(45) fmt.Println(pp.age) //=> 45 pp.setName("Hari") fmt.Println(pp.name) //=> Bob }
從上面的例子咱們能夠看出,使用.操做符調用方法,例如pp.describe。須要注意的是,接收器是指針的話,咱們傳遞的是值的引用,這意味着咱們在方法作修改將會反映到變量pp上。該不會建立對象的拷貝,將會節省內存。
從上面的例子咱們能夠看出,age的值被改變了,而name的值並無改變。這是由於方法setName的接受器不是指針。
Golang中的接口是方法的集合,接口有助於將同類型的屬性組合起來,讓咱們一塊兒來看一個anminal的接口。
type animal interface { description() string }
這裏的animal是接口類型,咱們來建立兩種類型的animal並實現接口。
package main import ( "fmt" ) type animal interface { description() string } type cat struct { Type string Sound string } type snake struct { Type string Poisonous bool } func (s snake) description() string { return fmt.Sprintf("Poisonous: %v", s.Poisonous) } func (c cat) description() string { return fmt.Sprintf("Sound: %v", c.Sound) } func main() { var a animal a = snake{Poisonous: true} fmt.Println(a.description()) a = cat{Sound: "Meow!!!"} fmt.Println(a.description()) } //=> Poisonous: true //=> Sound: Meow!!!
在main函數中,咱們建立可一個animal類型的變量a。咱們把 snake和cat類型賦值給animal,使用Println 輸出a.description。
咱們在cat和snake中使用不一樣的方式實現了describe方法,咱們獲得了不一樣類型的輸出。
在Golang中,咱們的代碼在某個包下。main包是程序執行的入口。在Go中有不少內置的包,例如咱們以前用過的fmt包。
Go 的包機制是大型軟件的基礎,可以將大型的工程分解成小部分。
—— Robert Griesemer
go get
// 例子 go get github.com/satori/go.uuid
安裝的包保存在GOPATH的環境中,你能夠在 $GOPATH/pkg 路徑下看到安裝的包。
首先建立一個文件夾 custom_package:
mkdir custom_package cd custom_package
建立自定義包的第一步是建立一個和包名相同的文件夾。咱們要建立person包,所以咱們在custom_package文件下建立person文件夾:
mkdir person cd person
在該路徑下建立一個文件person.go:
package person func Description(name string) string { return "The person name is: " + name } func secretName(name string) string { return "Do not share" }
如今咱們來安裝這個包,這樣咱們就能夠導入和使用它了:
go install
接下來咱們返回custom_package 文件夾中,建立一個main.go:
package main import ( "custom_package/person" "fmt" ) func main() { p := person.Description("Milap") fmt.Println(p) } // => The person name is: Milap
在這裏,咱們能夠導入以前建立的包person,須要注意的是在person包中函數secretName不能被訪問,這是由於Go中小寫字母開頭的函數是私有函數。
Golang中有內置的功能支持包文檔。運行下面的命令將生成文檔:
godoc person Description
這將會爲Description 函數生成文檔,想要在web服務器上查看文檔須要運行下面的命令:
godoc -http=":8080"
如今打開連接 http://localhost:8080/pkg/ 將會看到咱們剛纔看到的文檔。
9.4.1 fmt
fmt包實現可標準的I/O函數,咱們在以前的包中用過其中的打印輸出函數。
9.4.2 json
Golang中另外一個內置的重要包的是json,它可以對JSON進行編解碼。
編碼
package main import ( "encoding/json" "fmt" ) func main() { mapA := map[string]int{"apple": 5, "lettuce": 7} mapB, _ := json.Marshal(mapA) fmt.Println(string(mapB)) }
解碼
package main import ( "encoding/json" "fmt" ) type response struct { PageNumber int json:"page" Fruits []string json:"fruits" } func main() { str := {"page": 1, "fruits": ["apple", "peach"]} res := response{} json.Unmarshal([]byte(str), &res) fmt.Println(res.PageNumber) } //=> 1
解碼的時候使用Unmarshal方法,第一個參數是json字節,第二個參數是要映射的結構體的地址。須要注意的是json中的「page」對應的是結構體中的PageNumber。
錯誤是程序中不該該出現的結果。假設咱們編寫一個API調用外部的服務。這個API可能成功也可能失敗。當存在錯誤是,Golang程序可以識別:
resp, err := http.Get("http://example.com/")
對API的 調用可能成功也可能失敗,咱們能夠經過檢查錯誤是否爲空來選擇處理方式。
package main import ( "fmt" "net/http" ) func main() { resp, err := http.Get("http://example.com/") if err != nil { fmt.Println(err) return } fmt.Println(resp) }
當咱們在自定義函數是,某些狀況下會產生錯誤。咱們可使用error 對象返回這些錯誤:
package main import ( "errors" "fmt" ) func Increment(n int) (int, error) { if n < 0 { // return error object return 0, errors.New("math: cannot process negative number") } return (n + 1), nil } func main() { num := 5 if inc, err := Increment(num); err != nil { fmt.Printf("Failed Number: %v, error message: %v", num, err) } else { fmt.Printf("Incremented Number: %v", inc) } } // => The person name is: Milap
Go 內置的包,外部的包都有處理錯誤的機制。所以咱們調用的函數都有可能產生錯誤。這些錯誤不該該忽略而是應該向上面的例子那樣被優雅的處理。
Panic是程序運行中忽然產生未經處理的異常。在Go中,panic不是合理處理異常的方式,推薦使用error對象代替。當Panic產生時,程序將會暫停運行。當panic被defer以後,程序才能繼續運行。
//Go package main import "fmt" func main() { f() fmt.Println("Returned normally from f.") } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.") } func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1) }
Defer 在函數結尾必定會執行。在上面的函數中,使用panic() 暫停程序的運行,defer語句使程序執行結束時使用改行。Defer也能夠用做咱們想要在函數的結尾執行的語句,例如關閉文件。
Golong使用輕量級線程Go routies支持併發。
Go routine 是可以並行運行的函數。建立Go routine 很是簡單,只須要在函數前添加關鍵字go,這樣函數就可以並行運行了。Go routines 是輕量級的,咱們可以建立上千個Go routines。例如:
package main import ( "fmt" "time" ) func main() { go c() fmt.Println("I am main") time.Sleep(time.Second * 2) } func c() { time.Sleep(time.Second * 2) fmt.Println("I am concurrent") } //=> I am main //=> I am concurrent
上面的例子中,函數c是Go routine,可以並行運行。咱們想要在多線程中共享資源,可是Golang並不支持。由於這會致使死鎖和資源等待。Go 提供了另外一種共享資源的方式:channel。
咱們可使用Channel在兩個Go routine之間傳遞數據。建立channel以前須要制定接受的數據類型。例如咱們建立了一個接受string類型的channel。
c := make(chan string)
有了這個channel以後,咱們能夠經過這個channel發送和接收string類型的數據。
package main import "fmt" func main() { c := make(chan string) go func() { c <- "hello" }() msg := <-c fmt.Println(msg) //=>"hello" }
接收的channel一直等待發送的channel發送數據。
有些狀況下,咱們但願Go routine 經過channel接收數據,但不發送數據,反之亦然。這時候咱們能夠建立一個one-way channel。例如:
package main import ( "fmt" ) func main() { ch := make(chan string) go sc(ch) fmt.Println(<-ch) } func sc(ch chan<- string) { ch <- "hello" }
上面例子中,sc是一個Go routine只能給channel發送數據而不能接受數據。
有這樣一種狀況,一個函數等待多個channel,這時候咱們可使用select語句。例如:
package main import ( "fmt" "time" ) func main() { c1 := make(chan string) c2 := make(chan string) go speed1(c1) go speed2(c2) fmt.Println("The first to arrive is:") select { case s1 := <-c1: fmt.Println(s1) case s2 := <-c2: fmt.Println(s2) } } func speed1(ch chan string) { time.Sleep(2 * time.Second) ch <- "speed 1" } func speed2(ch chan string) { time.Sleep(1 * time.Second) ch <- "speed 2" }
上面例子中,main函數等待兩個channel,c1和c2。使用select語句,先從channel中收到的數據會被打印出來。
在Golang中能夠建立buffered channel,當buffer滿的時候,發送數據給channel將會被阻塞。例如:
package main import "fmt" func main() { ch := make(chan string, 2) ch <- "hello" ch <- "world" ch <- "!" // extra message in buffer fmt.Println(<-ch) } // => fatal error: all goroutines are asleep - deadlock!
Golang爲何如此成功?
簡單。。。
—— Rob-pike
咱們學習Golang以 下的主要模塊和特性:
恭喜你,你已經對Go有了很好的理解。
One of my most productive days was throwing away 1,000 lines of code.
—— Ken Thompson
不要停下腳步,繼續前進。思考一個小應用程序並開始動手。
原文連接:
原創: Milap Neupane 高可用架構
https://mp.weixin.qq.com/s?__...
https://milapneupane.com.np/2...
本文做者Milap Neupane,由何朋朋翻譯。轉載本文請註明出處,歡迎更多小夥伴加入翻譯及投稿文章的行列,詳情請戳公衆號菜單「聯繫咱們」。