GO語言語法入門
引言
Go
Go語言是谷歌2009發布的編程語言,它是一種並發的、帶垃圾回收的、快速編譯的語言。
它結合了解釋型語言的遊刃有余,動態類型語言的開發效率,以及靜態類型的安全性。它也打算成為現代的,支持網絡與多核計算的語言。要滿足這些目標,需要解決一些語言上的問題:一個富有表達能力但輕量級的類型系統,並發與垃圾回收機制,嚴格的依賴規範等等。這些無法通過庫或工具解決好,因此Go也就應運而生了。
優勢
- 語法簡單,上手快;
- 性能高,編譯快,開發效率也不低;
- 豐富的標準庫;
- 原生支持並發,協程模型是非常優秀的服務端模型,同時也適合網絡調用;
- 部署方便,編譯包小,除 glibc 外沒有其他外部依賴;
- 自帶完善的工具鏈, 大大提高了團隊協作效率和一致性。 比如 gofmt ,gofix,,govet 等工具。
Docker等很多 Go 產品的流行,更證明了 Go 語言的優秀。
適用場景
- 服務器編程,如:處理日誌、數據打包、虛擬機處理、文件系統等;
- 分布式系統,數據庫代理器等;
- 內存數據庫,google開發的groupcache,couchbase的部分組建;
- 雲平臺,目前國外很多雲平臺在采用Go開發,如:CloudFoundy(VMware推出的業界第一個開源PaaS雲平臺)的部分組建。
缺點
1. Go的import包不支持版本,有時候升級容易導致項目不可運行,需要自己控制相應的版本信息;
2. Go的goroutine一旦啟動之後,不同的goroutine之間切換不是受程序控制,需要嚴謹的邏輯;
3. 沒什麽太多應用場景非要 Golang 才能做的
3.1 開發 web 沒有 php ruby 成熟、快速
3.2 開發 server 沒有 java 現成解決方案多
GO指南
環境搭建
安裝Golang的SDK
(1) http://www.golangtc.com/download
(2) 安裝完成之後,打開終端,輸入go、或者go version查看安裝版本
配置Go環境變量
配置Go環境變量GOPATH和GOBIN
(1)打開終端,cd ~
(2)查看是否有.bash_profile文件:
ls -all
(3)有則跳過此步,沒有則:
1)創建:touch .bash_profile
2)編輯:open -e .bash_profile
3)自定義GOPATH和GOBIN位置:
GOPATH:日常開發的根目錄。GOBIN:是GOPATH下的bin目錄。
export GOPATH=/Users/yuan/go export GOBIN=$GOPATH/bin export PATH=$PATH:$GOBIN |
(4)編譯:source .bash_profile
(5)*查看Go環境變量:go env
開發工具配置
sublime text
一定要先配置好Go環境變量GOPATH和GOBIN,再安裝此插件,要不插件會提示找不到GOPATH和GOBIN;
選用 sublime text 安裝 gosublime 插件進行開發( golang 語法高亮提示)
(1)安裝 package controll(若已安裝,請跳過)
使用Ctrl+`快捷鍵或者通過View->Show Console菜單打開命令行,粘貼如下代碼:
import urllib.request,os; pf = ‘Package Control.sublime-package‘; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), ‘wb‘).write(urllib.request.urlopen( ‘http://sublime.wbond.net/‘ + pf.replace(‘ ‘,‘%20‘)).read()) 2 |
(2)install go sublime
Command+shift+p 輸入並選擇packageControl: install package
然後輸入並選擇goSublime
安裝完成就OK啦~~
Gogland
選擇Gogland, 下載安裝即可,3個月
https://www.jetbrains.com/go/download/
LiteIDE
國產IDE
http://golangtc.com/download/liteide
小試牛刀
在你的gopath目錄下,新建main.go文件即可以進行編碼了。
package main import ( "fmt" ) func main() { fmt.Println("hello gopher~") } |
代碼編寫完成之後,使用command+b打開sublime text終端
(一)編譯+執行
使用go build main.go對其進行編譯,編譯通過的結果信息如下:
[ `go build main.go` | done: 420.495985ms ]
提示編譯成功之後,再執行shell命令,執行剛剛編譯之後的文件./main即可看到運行結果:
[ `./main` | done: 10.532868ms ]
hello go
(二)直接執行
如果僅僅是只需要看到運行的結果,而不產生可執行文件(文件名和項目名一樣)則在sublime text終端中直接使用go run xxx.go即可:
[ `go run main.go` | done: 314.476988ms ]
hello go
基礎
包、變量、函數
package main import "fmt" func main() { var hello string = "Hello" who := "gopher" var s = hello+"," + who fmt.Println(s) } |
包
1. 每個 Go 程序都是由包組成的。
2. 程序運行的入口是包 main 。
3. 按照慣例,包名與導入路徑的最後一個目錄一致。例如,"math/rand" 包由 package rand 語句開始。
變量
- 變量聲明使用關鍵字var
- 初始值存在時可省略類型聲明
- 短賦值語句:= 可以用於替代 var 的隱式類型聲明(:=結構不能使用在函數外,函數外的每個語法塊都必須以關鍵字開始)
var name1 string //聲明變量 name1 = "tom" //給變量賦值 var name2 string = "tom" //聲明變量+賦值 var name3 = "tom" // 聲明時同時賦值,可以省略變量類型 name4 := "tom" //短賦值語句 // 多個變量 var x, y, z int var c, python, java bool var x, y, z int = 1, 2, 3 var c, python, java = true, false, "no!" c, python, java := true, false, "no!" |
函數
- 可以返回任意數量的返回值
- 類型聲明在變量名之後
- 同一類型的多個參數,最後一個參數需聲明類型
func swap(x, y string) (string, string) { return y, x } |
- 命名返回值的參數
func split(sum int) (x, y int) { x = sum * 4/9 y = sum - x return } |
基本類型
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr byte // uint8 的別名 rune // int32 的別名 // 代表一個Unicode碼位 float32 float64 complex64 complex128 |
常量
const Pi = 3.14 const World = "世界" const Truth = true |
運算符
http://www.yiibai.com/go/go_operators.html
註意:
- 沒有++i,--i,只有i++、i--
- 不能使用i++、i--對變量直接賦值
流程控制
for
Go 只有一種循環結構—— for 循環
for i := 0; i < 10; i++ { //do something } i := 0 for ; i < 1000; { //do something } for i < 1000 { //do something } for { //死循環 } |
if else
if x < 0 { return x } if v := 0; v < 5 { return v } return 9 |
switch
case 語句匹配後會自動終止(無需break),除非用 fallthrough 語句作為結尾,則會強制執行下一條case的語句或者default語句,而不判斷expression。
x := 2 switch x { case 1: fmt.Println(1) case 2: fmt.Println(2) fallthrough case 3: fmt.Println(x > 1) default: fmt.Println("default") } // 結果 // 2 // true |
defer
延遲(defer)處理
Defer用於確保在稍後的程序中,執行函數調用。
defer語句在封裝函數(main)結束時執行。
package main import "fmt" import "os" func main() { f := createFile("defer-test.txt") defer closeFile(f) writeFile(f) } func createFile(p string) *os.File { fmt.Println("creating") f, err := os.Create(p) if err != nil { panic(err) } return f } func writeFile(f *os.File) { fmt.Println("writing") fmt.Fprintln(f, "data") } func closeFile(f *os.File) { fmt.Println("closing") f.Close() } // output creating writing closing |
復雜類型
struct
要定義結構,必須使用type和struct語句。struct語句定義了一個新的數據類型,在程序中有多個成員。type語句在例子中綁定一個類型為struct的名字。 struct語句的格式如下:
type person struct { name string age int } |
訪問結構體成員使 .
package main import "fmt" type person struct { name string age int } func main() { fmt.Println(person{"Bob", 20}) fmt.Println(person{name: "Alice", age: 30}) fmt.Println(person{name: "Fred"}) fmt.Println(&person{name: "Ann", age: 40}) s := person{name: "Sean", age: 50} fmt.Println(s.name) sp := &s fmt.Println(sp.age) sp.age = 51 fmt.Println(sp.age) } #output {Bob 20} {Alice 30} {Fred 0} &{Ann 40} Sean 50 51 |
slice
因為切片(Slice)是數組上的抽象。 它實際上使用數組作為底層結構體.len()函數返回切片中存在的元素數量,其中cap()函數返回切片(Slice)的容量(大小),即可容納多少個元素。
package main import "fmt" func main() { var numbers = make([]int, 3, 5) printSlice(numbers) } func printSlice(x []int) { fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x) } //output len=3 cap=5 slice=[0 0 0] |
map
var map_variable map[key_data_type]value_data_type
map_variable = make(map[key_data_type]value_data_type)
map_variable := make(map[key_data_type]value_data_type)
delete(map_variable, key)
package main import "fmt" func main() { var countryCapitalMap map[string]string = make(map[string]string) /* create a map*/ // countryCapitalMap = make(map[string]string) /* insert key-value pairs in the map*/ countryCapitalMap["France"] = "Paris" countryCapitalMap["Italy"] = "Rome" countryCapitalMap["Japan"] = "Tokyo" countryCapitalMap["India"] = "New Delhi" /* print map using keys*/ for country := range countryCapitalMap { fmt.Println("Capital of", country, "is", countryCapitalMap[country]) } /* test if entry is present in the map or not*/ capital, ok := countryCapitalMap["United States"] /* if ok is true, entry is present otherwise entry is absent*/ if ok { fmt.Println("Capital of United States is", capital) } else { fmt.Println("Capital of United States is not present") } /* delete an entry */ delete(countryCapitalMap, "France") fmt.Println("Entry for France is deleted") fmt.Println("Updated map") /* print map */ for country := range countryCapitalMap { fmt.Println("Capital of", country, "is", countryCapitalMap[country]) } } |
range
range函數可以用來遍歷array,slice和map。
當用於遍歷array和slice時,range函數返回索引和元素;
當用於遍歷map的時候,range函數返回key和value。
package main import "fmt" func main() { nums := []int{2, 3, 4} sum := 0 for _, num := range nums { sum += num } fmt.Println("sum:", sum) for i, num := range nums { if num == 3 { fmt.Println("index:", i) } } kvs := map[string]string{"a": "apple", "b": "banana"} for k, v := range kvs { fmt.Printf("%s -> %s\n", k, v) } for k := range kvs { fmt.Println("key:", k) } } |
方法和接口
方法
Go中沒有類,但是可以為結構體定義方法,方法就是一類帶有特殊的接受者參數的函數。
方法接受者位於func關鍵字和方法名之間。
可以為非結構體類型聲明方法,但不能為其它包內定義的類型的接收者聲明方法,且不能為內建類型聲明方法。
package main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) } |
package main import "fmt" type rect struct { width, height int } func (r *rect) area() int { return r.width * r.height } func (r rect) perim() int { return 2*r.width + 2*r.height } func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) fmt.Println("perim:", r.perim()) rp := &r fmt.Println("area: ", rp.area()) fmt.Println("perim:", rp.perim()) } // output area: 50 perim: 30 area: 50 perim: 30 |
函數是完全閉包的
package main import "fmt" // 函數 adder 返回一個閉包。每個閉包被綁定到自己的 sum 變量上 func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } } |
接口
接口類型是由一組方法簽名的集合。
接口類型的值可以保存任何實現了接口方法的變量。
類型通過實現了一個接口的所有方法來實現這個接口,而不需要專門的顯示聲明也就是”implements”關鍵字來聲明。
package main import "fmt" type geometry interface { func (r rect) area() float64 { // output |
error
Go編程提供了一個非常簡單的錯誤處理框架,以及內置的錯誤接口類型,如下聲明:
type error interface { Error() string } |
Go函數通常返回錯誤作為最後一個返回值。 可使用errors.New來構造一個基本的錯誤消息
package main import "errors" import "fmt" import "math" func Sqrt(value float64) (float64, error) { if value < 0 { return 0, errors.New("Math: negative number passed to Sqrt") } return math.Sqrt(value), nil } func main() { result, err := Sqrt(-1) if err != nil { fmt.Println(err) } else { fmt.Println(result) } result, err = Sqrt(9) if err != nil { fmt.Println(err) } else { fmt.Println(result) } } |
並發
goroutine
goroutine 是由 Go 運行時環境管理的輕量級線程。
使用 go f(x, y, z) 開啟一個新的 goroutine 執行。
package main import "fmt" func f(from string) { for i := 0; i < 3; i++ { fmt.Println(from, ":", i) } } func main() { f("direct") go f("goroutine") go func(msg string) { fmt.Println(msg) }("going") var input string fmt.Scanln(&input) fmt.Println("done") } // output $ go run goroutines.go direct : 0 direct : 1 direct : 2 goroutine : 0 going goroutine : 1 goroutine : 2 <enter> done |
channel
channel 是有類型的管道,可以用 channel 操作符 <- 對其發送或者接收值。
ch <- v // 將 v 送入 channel ch。
v := <-ch // 從 ch 接收,並且賦值給 v。
默認情況下,在另一端準備好之前,發送和接收都會阻塞。這使得 goroutine 可以在沒有明確的鎖或競態變量的情況下進行同步。
package main import "fmt" func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) } |
channel 可以是帶緩沖的。為 make 提供第二個參數作為緩沖長度來初始化一個緩沖 channel:
ch := make(chan int, 100)
向緩沖 channel 發送數據的時候,只有在緩沖區滿的時候才會阻塞。當緩沖區清空的時候接受阻塞。
package main import "fmt" func main() { c := make(chan int, 2) c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } |
close
發送者可以 close 一個 channel 來表示再沒有值會被發送了。接收者可以通過賦值語句的第二參數來測試 channel 是否被關閉:當沒有值可以接收並且 channel 已經被關閉,那麽
v, ok := <-ch
ok 會被設置為 false。
註意: 只有發送者才能關閉 channel,而不是接收者。向一個已經關閉的 channel 發送數據會引起 panic。
package main import "fmt" func main() { jobs := make(chan int, 5) done := make(chan bool) go func() { for { j, more := <-jobs if more { fmt.Println("received job", j) } else { fmt.Println("received all jobs") done <- true return } } }() for j := 1; j <= 3; j++ { jobs <- j fmt.Println("sent job", j) } close(jobs) fmt.Println("sent all jobs") <-done } |
select
Go語言的選擇(select)可等待多個通道操作。將goroutine和channel與select結合是Go語言的一個強大功能。
select 會阻塞,直到條件分支中的某個可以繼續執行,這時就會執行那個條件分支。如果有多個都準備好的時候,會隨機選一個。
package main import "time" import "fmt" func main() { c1 := make(chan string) c2 := make(chan string) c3 := make(chan string) t1 := time.Now().UnixNano() go func() { time.Sleep(time.Second * 1) c1 <- "one" }() go func() { time.Sleep(time.Second * 2) c2 <- "two" }() go func() { time.Sleep(time.Second * 2) c3 <- "three" }() for i := 0; i < 3; i++ { select { case msg1 := <-c1: fmt.Println("received", msg1) case msg2 := <-c2: fmt.Println("received", msg2) case msg3 := <-c3: fmt.Println("received", msg3) } } t2 := time.Now().UnixNano() dt := (t2 - t1) / 1e6 fmt.Println(dt) } |
Goroutine 調度
結構——M, P, S Go的調度器內部有三個重要的結構:M,P,SM: 內核OS線程
G: 一個goroutine,它有自己的棧,instruction pointer和其他信息(正在等待的channel等等),用於調度。
P: 代表調度的上下文,可以把它看做一個局部的調度器,使go代碼在一個線程上跑,它是實現從N:1到N:M映射的關鍵。 P的數量可以通過GOMAXPROCS()來設置,它其實也就代表了真正的並發度,即有多少個goroutine可以同時運行。 線程阻塞——投奔其他線程
- 圖中看到,當一個OS線程M0陷入阻塞時,P轉而在OS線程M1上運行。調度器保證有足夠的線程來運行所以的context P。
- 當MO返回時,它必須嘗試取得一個context P來運行goroutine,一般情況下,它會從其他的OS線程那裏steal偷一個context過來,
分配不均——steal work
-
global runqueue
- 其他的P
- 開大括號不能放在單獨的一行
- 未使用的變量
- 未使用的Imports
- 不支持前置版本的自增和自減,也無法在表達式中使用這兩個操作符。
。。。
查看更多陷阱,請點擊↓
http://studygolang.com/articles/8382
GO語言語法入門