Go 語言被設計成一門應用於搭載 Web 服務器,存儲集羣或相似用途的巨型中央服務器的系統編程語言。python
對於高性能分佈式系統領域而言,Go 語言無疑比大多數其它語言有着更高的開發效率。它提供了海量並行的支持,這對於遊戲服務端的開發而言是再好不過了。golang
Go 語言的基礎組成有如下幾個部分:編程
package main import "fmt" func main() { /* 這是個人第一個簡單的程序 */ fmt.Println("Hello, World!") }
讓咱們來看下以上程序的各個部分:數組
第一行代碼 package main 定義了包名。你必須在源文件中非註釋的第一行指明這個文件屬於哪一個包,如:package main。package main表示一個可獨立執行的程序,每一個 Go 應用程序都包含一個名爲 main 的包。安全
下一行 import "fmt" 告訴 Go 編譯器這個程序須要使用 fmt 包(的函數,或其餘元素),fmt 包實現了格式化 IO(輸入/輸出)的函數。服務器
下一行 func main() 是程序開始執行的函數。main 函數是每個可執行程序所必須包含的,通常來講都是在啓動後第一個執行的函數(若是有 init() 函數則會先執行該函數)。數據結構
下一行 /*...*/ 是註釋,在程序執行時將被忽略。單行註釋是最多見的註釋形式,你能夠在任何地方使用以 // 開頭的單行註釋。多行註釋也叫塊註釋,均已以 /* 開頭,並以 */ 結尾,且不能夠嵌套使用,多行註釋通常用於包的文檔描述或註釋成塊的代碼片斷。閉包
下一行 fmt.Println(...) 能夠將字符串輸出到控制檯,並在最後自動增長換行字符 \n。
使用 fmt.Print("hello, world\n") 能夠獲得相同的結果。
Print 和 Println 這兩個函數也支持使用變量,如:fmt.Println(arr)。若是沒有特別指定,它們會以默認的打印格式將變量 arr 輸出到控制檯。併發
當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那麼使用這種形式的標識符的對象就能夠被外部包的代碼所使用(客戶端程序須要先導入這個包),這被稱爲導出(像面嚮對象語言中的 public);標識符若是以小寫字母開頭,則對包外是不可見的,可是他們在整個包的內部是可見而且可用的(像面嚮對象語言中的 protected )。app
須要注意的是 { 不能單獨放在一行
Go 程序能夠由多個標記組成,能夠是關鍵字,標識符,常量,字符串,符號。如如下 GO 語句由 6 個標記組成:
fmt.Println("Hello, World!")
在 Go 程序中,一行表明一個語句結束。每一個語句不須要像 C 家族中的其它語言同樣以分號 ; 結尾,由於這些工做都將由 Go 編譯器自動完成。
var identifier type
第一種,指定變量類型,若是沒有初始化,則變量默認爲零值。
var v_name v_type v_name = value
零值就是變量沒有作初始化時系統默認設置的值。
第二種,根據值自行斷定變量類型。
var v_name = value
第三種,省略 var, 注意 := 左側若是沒有聲明新的變量,就產生編譯錯誤,格式:
v_name := value
這種不帶聲明格式的只能在函數體中出現
//類型相同多個變量, 非全局變量 var vname1, vname2, vname3 type vname1, vname2, vname3 = v1, v2, v3 var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不須要顯示聲明類型,自動推斷 vname1, vname2, vname3 := v1, v2, v3 // 出如今 := 左側的變量不該該是已經被聲明過的,不然會致使編譯錯誤 // 這種因式分解關鍵字的寫法通常用於聲明全局變量 var ( vname1 v_type1 vname2 v_type2 )
全部像 int、float、bool 和 string 這些基本類型都屬於值類型,使用這些類型的變量直接指向存在內存中的值:
當使用等號 =
將一個變量的值賦值給另外一個變量時,如:j = i
,其實是在內存中將 i 的值進行了拷貝:
基本類型是值類型
更復雜的數據一般會須要使用多個字,這些數據通常使用引用類型保存。
一個引用類型的變量 r1 存儲的是 r1 的值所在的內存地址(數字),或內存地址中第一個字所在的位置。
這個內存地址爲稱之爲指針,這個指針實際上也被存在另外的某一個字中。
同一個引用類型的指針指向的多個字能夠是在連續的內存地址中(內存佈局是連續的),這也是計算效率最高的一種存儲形式;也能夠將這些字分散存放在內存中,每一個字都指示了下一個字所在的內存地址。
當使用賦值語句 r2 = r1 時,只有引用(地址)被複制。
這是使用變量的首選形式,可是它只能被用在函數體內,而不能夠用於全局變量的聲明與賦值。使用操做符 := 能夠高效地建立一個新的變量,稱之爲初始化聲明。
若是在相同的代碼塊中,咱們不能夠再次對於相同名稱的變量使用初始化聲明
若是你聲明瞭一個局部變量卻沒有在相同的代碼塊中使用它,一樣會獲得編譯錯誤
可是全局變量是容許聲明但不使用。
同一類型的多個變量能夠聲明在同一行。
多變量能夠在同一行進行賦值,用逗號分隔。這被稱爲 並行 或 同時 賦值。
空白標識符 _ 也被用於拋棄值,如值 5 在:_, b = 5, 7 中被拋棄。
_ 其實是一個只寫變量,你不能獲得它的值。這樣作是由於 Go 語言中你必須使用全部被聲明的變量,但有時你並不須要使用從一個函數獲得的全部返回值。
並行賦值也被用於當一個函數返回多個返回值時,好比這裏的 val 和錯誤 err 是經過調用 Func1 函數同時獲得:val, err = Func1(var1)。
常量中的數據類型只能夠是布爾型、數字型(整數型、浮點型和複數)和字符串型。
常量的定義格式:
const identifier [type] = value
你能夠省略類型說明符 [type],由於編譯器能夠根據變量的值來推斷其類型。
if 布爾表達式 { /* 在布爾表達式爲 true 時執行 */ }else if 布爾表達式 {
/* 在布爾表達式爲 true 時執行 */
}else {
}
select?
for init; condition; post { }
當只有condition的時候和c語言的while相似
當什麼都沒有的時候至關於c語言的for(;;)
for 循環的 range 格式能夠對 slice、map、數組、字符串等進行迭代循環。格式以下:
for key, value := range oldMap { newMap[key] = value }
函數是基本的代碼塊,用於執行一個任務。
Go 語言最少有個 main() 函數。
你能夠經過函數來劃分不一樣功能,邏輯上每一個函數執行的是指定的任務。
函數聲明告訴了編譯器函數的名稱,返回類型,和參數。
Go 語言標準庫提供了多種可動用的內置的函數。例如,len() 函數能夠接受不一樣類型參數並返回該類型的長度。若是咱們傳入的是字符串則返回字符串的長度,若是傳入的是數組,則返回數組中包含的元素個數。
Go 語言函數定義格式以下:
func function_name( [parameter list] ) [return_types] { 函數體 }
默認狀況下,Go 語言使用的是值傳遞,即在調用過程當中不會影響到實際參數。
想實現引用傳遞就使用指針,傳參數的地址
Go裏有函數類型的變量,這樣,雖然不能在一個函數裏直接聲明另外一個函數,可是能夠在一個函數中聲明一個匿名函數類型的變量,此時的匿名函數稱爲閉包(closure)。
匿名函數是指不須要定義函數名的一種函數實現方式
閉包就是可以讀取其餘函數內部變量的函數。
只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成」定義在一個函數內部的函數」。
匿名函數能夠直接賦值給一個變量或直接執行
雖然i是局部變量可是隻要閉包還在使用,那麼被閉包引用的變量就會一直存在
而i除了在內部匿名函數中能夠訪問外,沒法經過其餘方式處理,所以保證了i的安全性
閉包被返回賦予一個同類型的變量時,同時賦值的是整個閉包的狀態,該狀態會一直存在外部被賦值的變量中,直到這個變量被銷燬,整個閉包也被銷燬。
由此得出如下兩點
1.內函數對外函數 的變量的修改,是對變量的引用
2.變量被引用後,它所在的函數結束,這變量也不會立刻被燒燬
閉包函數出現的條件:
1.被嵌套的函數引用到非本函數的外部變量,並且這外部變量不是「全局變量」;
2.嵌套的函數被獨立了出來(被父函數返回或賦值 變成了獨立的個體),而被引用的變量所在的父函數已結束.
佔坑
Go 語言中同時有函數和方法。一個方法就是一個包含了接受者的函數,接受者能夠是命名類型或者結構體類型的一個值或者是一個指針。全部給定類型的方法屬於該類型的方法集。
語法格式以下:
func (variable_name variable_data_type) function_name() [return_type]{ /* 函數體*/ }
局部變量的做用域只在函數體內,參數和返回值變量也是局部變量。
全局變量能夠在整個包甚至外部包(被導出後)使用。
形式參數會做爲函數的局部變量來使用
Go 語言數組聲明須要指定元素類型及元素個數,語法格式以下:
var variable_name [SIZE] variable_type
如下演示了數組初始化:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
void myFunction(param []int){ }
形參能夠指定數組大小也能夠不指定
nil 指針也稱爲空指針。相似於NULL
結構體定義須要使用 type 和 struct 語句。struct 語句定義一個新的數據類型,結構體有中有一個或多個成員。type 語句設定告終構體的名稱。結構體的格式以下:
type struct_variable_type struct { member definition; member definition; ... member definition; }
聲明語法格式:
variable_name := structure_variable_type {value1, value2...valuen} 或 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
使用結構體變量或者結構體指針訪問結構體成員,都使用 "." 操做符:
Go 語言切片是對數組的抽象。
Go 數組的長度不可改變,在特定場景中這樣的集合就不太適用,Go中提供了一種靈活,功能強悍的內置類型切片("動態數組"),與數組相比切片的長度是不固定的,能夠追加元素,在追加時可能使切片的容量增大。
你能夠聲明一個未指定大小的數組來定義切片
切片不須要說明長度。
或使用make()函數來建立切片:
var slice1 []type = make([]type, len) 也能夠簡寫爲 slice1 := make([]type, len)
這裏 len 是數組的長度而且也是切片的初始長度。
slice = append(slice, data1,data2......) //在slice裏追加數據 copy(slice2,slice1) //複製slice1的內容到slice2
Go 語言中 range 關鍵字用於 for 循環中迭代數組(array)、切片(slice)、通道(channel)或集合(map)的元素。在數組和切片中它返回元素的索引和索引對應的值,在集合中返回 key-value 對的 key 值。
可使用內建函數 make 也可使用 map 關鍵字來定義 Map:
/* 聲明變量,默認 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函數 */ map_variable := make(map[key_data_type]value_data_type)
若是不初始化 map,那麼就會建立一個 nil map。nil map 不能用來存放鍵值對
map必需要用make()進行初始化才能使用
range mp 會返回兩個值,key和value
mp[key]會返回兩個值,value和bool bool表示map中是否有value和key對應,若是沒有的話value是0,bool是False
delete() 函數用於刪除集合的元素, 參數爲 map 和其對應的 key。
Go 語言提供了另一種數據類型即接口,它把全部的具備共性的方法定義在一塊兒,任何其餘類型只要實現了這些方法就是實現了這個接口。
Interface 是一組抽象方法(未具體實現的方法/僅包含方法名參數返回值的方法)的集合,若是實現了 interface 中的全部方法,即該類/對象就實現了該接口。
Interface 的聲明格式:
type interfaceName interface { //方法列表 }
Interface 能夠被任意對象實現,一個類型/對象也能夠實現多個 interface;
interface的變量能夠持有任意實現該interface類型的對象。
/* 定義接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } /* 定義結構體 */ type struct_name struct { /* variables */ } /* 實現接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法實現 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法實現*/ } var inter interface_name inter = new(struct_name) inter.method_namen()
接口變量能夠直接指向實現了該接口的變量,而後這個接口變量只能調用本身定義的方法
Go 語言經過內置的錯誤接口提供了很是簡單的錯誤處理機制。
error類型是一個接口類型,這是它的定義:
type error interface { Error() string }
咱們能夠在編碼中經過實現 error 接口類型來生成錯誤信息。
函數一般在最後的返回值中返回錯誤信息。使用errors.New 可返回一個錯誤信息:
Go語言(golang)的錯誤設計是經過返回值的方式,來強迫調用者對錯誤進行處理,要麼你忽略,要麼你處理(處理也能夠是繼續返回給調用者),對於golang這種設計方式,咱們會在代碼中寫大量的if
判斷,以便作出決定。
func main() { conent,err:=ioutil.ReadFile("filepath") if err !=nil{ //錯誤處理 }else { fmt.Println(string(conent)) } }
咱們也能夠實現error接口來自定義Error類型
func test_error(x int) (int, error) { if (x != 0) { return -x, nil } else { return x, &error_test{97} } } type error_test struct { key int } func (x *error_test) Error() string { return string(x.key) + " error test !" }
Goroutines
Go 容許使用 go 語句開啓一個新的運行期線程, 即 goroutine,以一個不一樣的、新建立的 goroutine 來執行一個函數。 同一個程序中的全部 goroutine 共享同一個地址空間。
Goroutines 能夠看做是輕量級線程。建立一個 goroutine 很是簡單,只須要把 go 關鍵字放在函數調用語句前。
go f()
Channels 容許 go routines 之間相互通訊。你能夠把 channel 看做管道,goroutines 能夠往裏面發消息,也能夠從中接收其它 go routines 的消息。
通道(channel)是用來傳遞數據的一個數據結構。
通道可用於兩個 goroutine 之間經過傳遞一個指定類型的值來同步運行和通信。操做符 <-
用於指定通道的方向,發送或接收。若是未指定方向,則爲雙向通道。
聲明一個通道很簡單,咱們使用chan關鍵字便可,通道在使用前必須先建立:
ch := make(chan int)
注意:默認狀況下,通道是不帶緩衝區的。發送端發送數據,同時必須有接收端相應的接收數據。
通道能夠設置緩衝區,經過 make 的第二個參數指定緩衝區大小:
ch := make(chan int, 100)
帶緩衝區的通道容許發送端的數據發送和接收端的數據獲取處於異步狀態,就是說發送端發送的數據能夠放在緩衝區裏面,能夠等待接收端去獲取數據,而不是馬上須要接收端去獲取數據。
不過因爲緩衝區的大小是有限的,因此仍是必須有接收端來接收數據的,不然緩衝區一滿,數據發送端就沒法再發送數據了。
注意:若是通道不帶緩衝,發送方會阻塞直到接收方從通道中接收了值。若是通道帶緩衝,發送方則會阻塞直到發送的值被拷貝到緩衝區內;若是緩衝區已滿,則意味着須要等待直到某個接收方獲取到一個值。接收方在有值能夠接收以前會一直阻塞。
Go 經過 range 關鍵字來實現遍歷讀取到的數據,相似於與數組或切片。格式以下:
for i := range ch{ }
v, ok := <-ch
若是通道接收不到數據後 ok 就爲 false,這時通道就可使用 close() 函數來關閉。
Channels 阻塞 goroutines 發生在各類情形下。這能在 goroutines 各自歡快地運行以前,實現彼此之間的短暫同步。
它們不同凡響的地方在於每次只有一份數據能夠經過。
buffered 和 unbuffered channels 工做原理相似,但有一點不一樣—在須要另外一個 gorountine 取走數據以前,咱們能夠向 buffered channel 發送多份數據。
若是隻須要調用一次函數,經過這種方式咱們可讓它在本身的 goroutine 中運行,而不須要建立一個正式的函數聲明。函數匿名。
main 函數確實運行在本身的 goroutine 中!更重要的是要知道,一旦 main 函數返回,它將關掉當前正在運行的其餘 goroutines。這就是爲何咱們在 main 函數的最後設置了一個定時器—它建立了一個 channel,並在 5 秒後發送一個值。
爲了確保接收到channel裏的全部信息,咱們在處理channel裏的數據時須要遍歷channel,爲了防止range被阻塞,發送完全部數據後發送方應該調用close(channel)來關閉channel防止接收方阻塞。
利用 Go 的 select case 語句能夠實現對 channel 的非阻塞讀。經過使用這這種語句,若是 channel 有數據,goroutine 將會從中讀取,不然就執行默認的分支。
myChan := make(chan string) go func(){ myChan <- "Message!" }() select { case msg := <- myChan: fmt.Println(msg) default: fmt.Println("No Msg") } <-time.After(time.Second * 1) select { case msg := <- myChan: fmt.Println(msg) default: fmt.Println("No Msg") }
非阻塞寫也是使用一樣的 select case 語句來實現,惟一不一樣的地方在於,case 語句看起來像是發送而不是接收。
select { case myChan <- "message": fmt.Println("sent the message") default: fmt.Println("no message sent") }