若是還沒來得及安裝Go環境,想體驗一下Go語言,能夠在Go在線編譯器 上運行Go程序。java
讓全部人都遵循同樣的編碼風格是一種理想,如今Go語言經過gofmt程序
,讓機器來處理大部分的格式化問題。gofmt程序
是go標準庫提供的一段程序,能夠嘗試運行它,它會按照標準風格縮進,對齊,保留註釋,它默認使用製表符進行縮進。Go標準庫的全部代碼都通過gofmt程序
格式化的。golang
Go註釋支持C風格的塊註釋/* */
和C++風格的行註釋//
。塊註釋主要用做包的註釋。Go官方提倡每一個包都應包含一段包註釋,即放置在包子句前的一個塊註釋。對於有多個文件的包,包註釋只須要出如今其中一個文件便可。c#
godoc 既是一個程序,又是一個 Web 服務器,它對 Go 的源碼進行處理,並提取包中的文檔內容。 出如今頂級聲明以前,且與該聲明之間沒有空行的註釋,將與該聲明一塊兒被提取出來,做爲該條目的說明文檔。數組
常量必須在定義的時候就進行初始化。常量只能是數字、字符、字符串、布爾值等基本類型,定義它們的表達式必須是在編譯期就能夠求值的類型。使用const
來定義一個常量:安全
const LENGTH int = 10 const WIDTH int = 5
在Go中,枚舉常量
使用iota
來建立,iota
是一個自增加的值:服務器
type AudioOutput int const ( OutMute AudioOutput = iota // 0 OutMono // 1 OutStereo // 2 _ _ OutSurround // 5 )
iota
老是用於increment,但它也能夠用於表達式,在《effective Go》展現了一個定義數量級的表示:數據結構
type ByteSize float64 const ( _ = iota // 使用_來忽略iota=0 KB ByteSize = 1 << (10 * iota) // 1 << (10*1) MB // 1 << (10*2) GB // 1 << (10*3) TB // 1 << (10*4) PB // 1 << (10*5) EB // 1 << (10*6) ZB // 1 << (10*7) YB // 1 << (10*8) )
源文件能夠定義無參數init函數
,該函數在真正執行函數邏輯以前被自動調用,下面的程序簡單說明這一點:app
package main import "fmt" func init() { fmt.Print("執行init函數0\n") } func init() { fmt.Print("執行init函數1\n") } func init() { fmt.Print("執行init函數2\n") } func main() { fmt.Print("執行main函數\n") } //output : 執行init函數0 執行init函數1 執行init函數2 執行main函數
能夠看到,在執行main函數中的邏輯前,init函數會先被調用,並且同一個源文件中能夠定義多個init函數。init函數一般被用在程序真正執行以前對變量、程序狀態進行校驗。它的執行機制是這樣的:函數
Go使用更加通用的for來代替do與while循環,for的三種形式爲:post
// Like a C for for init ; condition;post { } //Like a C while for condition{ } //Like a C for(;;) for {}
對於數組、切片、字符串、map,或者從信道讀取消息,可使用range子句
:
for key ,value := range oldMap { newMap[key] = value }
Go的switch要更加靈活通用,當switch
後面沒有表達式的時候,它將匹配ture
,這也意味着if-else-if-else
鏈可使用switch
來實現:
func unhex(c byte) byte { switch { //switch將匹配true case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 0 }
Go的函數能夠進行多值返回。在C語言中常常有這種笨拙的用法:函數經過返回值來告知函數的執行狀況,例如返回0表明無異常,返回-1表示EOF
等,而經過指針實參來傳遞數據給外部。如今使用Go函數的多值返回能夠解決解決這個問題。下面是Go標準庫中打開文件的File.Write
的簽名:
func (file *File) Write(b []byte) (n int, err error)
Write
函數返回寫入的字節數以及一個錯誤。若是正確寫入了,則err
爲nil
,不然,err
爲一個非nil
的error
錯誤值,這在Go中是一種常見的編碼風格。
Go函數的返回值能夠被命名。Go的返回值在函數體內能夠做爲常規的變量來使用,稱爲結果「形參」
,結果「形參」
在函數開始執行時被初始化與其類型相應的零值。若是函數執行了不帶參數的return
,則把結果形參的當前值返回:
func abs(i int) (result int){ if i < 0{ result = -i //返回值result能夠直接當成常規變量使用 } return }
這樣作的好處是函數的簽名即爲文檔,返回值的含義也寫到了函數簽名中,提升了代碼的可讀性。
Go提供defer語句用於延遲執行函數。defer語句修飾的函數,在外層函數結束以前被調用。能夠這樣來使用defer語句
:
func printStr (a string){ fmt.Print(a); } func main() { defer printStr("one\n") defer printStr("two\n") defer printStr("three\n") fmt.Print("main()\n") } //output : main() three two one
關於defer語句
:
使用defer語句
時還有一些細節須要注意。下面這段代碼:
func main() { fmt.Print(test()) } func test() (r int) { defer func() { r = 1 return }() r = 2 return 3 } //output: 1
輸出並非3
,而是1
.緣由是return
的操做實際包括了:
r = 0 //結果「形參」在函數開始執行時被初始化爲零值 r = 2 r = 1 //defer語句執行 return r
Go提供了兩種分配原語new
與make
:
func new(Type) *Type func make(t Type, size ...IntegerType) Type
new(T)
用於分配內存,它返回一個指針,指向新分配的,類型爲T的零值,經過new
來申請的內存都會被置零。這意味着若是設計了某種數據結構,那麼每種類型的零值就沒必要進一步初始化了。
make(T,args)
的目的不一樣於new(T)
,它只用於建立切片(slice)、映射(map)、信道(channel),這三種類型本質上與引用數據類型,它們在使用前必須初始化。make
返回類型爲一個類型爲T
的已初始化的值,而非*T
。
下面是new
與make
的對比:
var p *[]int = new([]int) // 分配切片結構;*p == nil;基本沒用 var v []int = make([]int, 100) // 切片 v 如今引用了一個具備 100 個 int 元素的新數組 // 不必的複雜: var p *[]int = new([]int) *p = make([]int, 100, 100) // 習慣用法: v := make([]int, 100)
Go的數組與C語言的數組有很大的區別:
[10]int
與[20]int
是兩種類型。若是想要像C語言那樣傳遞數組指針,須要這樣作:
func Sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } r eturn } a rray := [...]float64{7.0, 8.5, 9.1} x := Sum(&array) // 注意顯式的取址操做
但在Go中一般不會這樣作,而是經過切片來實現引用的傳遞。切片保存了對底層數組的引用,若你將某個切片賦予另外一個切片,它們會引用同一個數組。
切片是一個很小的對象,它對底層數組進行了抽象,並提供相應的操做方法,切片包含3個字段,其的內部實現爲:
能夠經過一些方式來定義切片:
var slice0 []type //經過聲明一個未指定大小的數組來定義切片 var slice1 []type = make([]type, len) //經過make來建立切片,長度與容量都是5個元素 make([]T, length, capacity) //能夠經過make來指定它的容量
聲明的時候,只要在[]
運算符裏指定了一個值,那麼建立的就是數組而不是切片,只有不指定值的時候,纔會建立切片。
切片之因此稱爲切片,是由於建立一個新的切片就是把底層數組切出一部分,例如代碼:
slice := [] int {10,20,30,40,50} //建立一個切片,長度與容量都是5 newSlice := slice[1:3] //建立一個新切片,其長度爲5,容量爲4
對底層數組容量是k的新切片slice[i,j]
來講,長度是j-i
,容量是k-i
,建立的新切片內部實現爲:
因爲兩個切片共享一部分的底層數組,因此修改newSlice的第2個元素,也將一樣修改了slice的第三個元素。
可使用append
來增加切片的長度,這有兩種狀況:
當append
函數形成切片容量拓展時,該切片將擁有一個全新的底層數組。
映射與切片同樣,也是引用類型。若是經過一個不存在的key
來獲取value
,將返回與該映射中項的類型對應的零值:
var map1 map[string] int map1 = make(map[string]int ,10) map1["one"]=1 map1["two"]=2 fmt.Print(map1["three"]) //output: 0
若是map1["three"]
的value
恰好是0,該怎麼區分呢?能夠採用多重賦值
的形式來分辨這種狀況:
i, ret := map1["three"] if ret == true{ fmt.Print("map1[\"three\"]存在,值爲:", i) } else { fmt.Print("map1[\"three\"] 不存在\n") }
或者這樣寫更好一些,《effective Go》稱爲the 「comma ok」 idiom
,逗號OK慣用法
if i, ret := map1["three"] ;ret { fmt.Print("map1[\"three\"]存在,值爲:", i) } else { fmt.Print("map1[\"three\"] 不存在\n") }
若是僅是須要判斷某個key是否存在,能夠用空白標識符_
來代替value
:
if _, ret := map1["three"] ;ret { fmt.Print("map1[\"three\"]存在\n") } else { fmt.Print("map1[\"three\"]不存在,值爲:") }
使用內建函數delete
函數來刪除鍵值對,即便對應的鍵不在該映射中,delete操做也是安全的
在函數的一節中,咱們已經看到了write
函數的聲明爲:
func (file *File) Write(b []byte) (n int, err error)
咱們能夠抽象出Go中函數的結構爲:
func [(p mytype)] funcname([pram type]) [(parm type)] {//body}
其中,函數的(p mytype)
爲可選部分,具有此部分的函數稱爲方法(method)
,這部分稱爲接收者(receiver)
。咱們能夠爲任何已命名的類型,包括本身定義的結構體類型,定義方法。經過receiver
,把方法綁定到類型上。下面是一個示例:
package main import "fmt" //定義一個矩形類型 type rect struct { width ,height int } //這個方法擴大矩形邊長爲multiple倍 //這個方法的reciever爲*rect //表示這是定義在rect結構體上的方法 func (r *rect) bigger(multiple int){ r.height *=multiple r.height *=multiple } //方法的reciever能夠爲結構體類型 //也能夠爲結構體指針類型 //區別在於當reciever爲類型指針時 //能夠在該方法內部修改結構體成員 func (r rect) area() int{ return r.width*r.height } func main(){ r := rect{width:10,height:5} fmt.Print("r 's area:",r.area(),"\n") r.bigger(10) fmt.Print("r's area:",r.area()) } //output: r 's area:50 r's area:5000
以指針或值做爲reciever的區別在於::
值方法能夠經過指針和值調用,因此下面語句是合法的:
func main(){ r := rect{width:10,height:5} //經過指針調用 fmt.Print("r 's area:",(&r).area(),"\n") //經過值調用 fmt.Print("r 's area:",r.area(),"\n") } //output: r 's area:50 r 's area:50
而對於指針方法只能經過指針來調用,你可能會感到疑惑,由於下面的語句也是合法的:
func main(){ r := rect{width:10,height:5} fmt.Print("r 's area:",r.area(),"\n") //經過值來調用指針方法(爲何合法?) r.bigger(10) fmt.Print("r's area:",r.area()) } //output:
實際上是這樣的:若是值是能夠尋址的,那麼Go會自動插入取址操做符來對付通常的經過值調用的指針方法。在這個例子中,r
是可尋址的,所以r.Bigger(10)
將被編譯器改寫爲(&r).Bigger
。
另外,方法也能夠"轉換"爲函數,這一點便不在這裏詳談。
經過方法與接口,Go語言定義了一種與java/C++等OOP語言大相徑庭的「繼承」的形態。經過實現接口定義的方法,即可將reciever
的類型變量賦值給接口類型變量,經過接口類型變量來調用到reciever
類型的方法,用C++來類比,就是經過父類指針來調用到了派生類的成員函數(不過Go沒有這些概念)。下面是一個示例:
package main import ( "fmt" "math" ) //定義了一個接口geometry表示幾何類型 type geometry interface { area() float64 bigger(float64) } //矩形和圓形要實現這接口的兩個方法 type rect struct { width, height float64 } type circle struct { radius float64 } //在Go中,實現接口,只須要實現該接口定義的全部方法便可 //矩形的接口方法實現 func (r *rect) bigger(multiple float64) { r.height *= multiple r.height *= multiple } func (r *rect) area() float64 { return r.width * r.height } //圓形的接口方法實現 func (c *circle) bigger(multiple float64){ c.radius *= multiple } func (c *circle) area() float64 { return math.Pi * c.radius * c.radius } //能夠把rect和circle類型的變量做爲實參 //傳遞給geometry接口類型的變量 func measure (g geometry){ fmt.Print("geometry 's area:",g.area(),"\n") g.bigger(2) fmt.Print("after bigger 2 multiple, area :",g.area(),"\n") } func main() { r := rect{width: 10, height: 5} c := circle{radius:3} measure(&r) measure(&c) } //output: geometry 's area:50 after bigger 2 multiple, area :200 geometry 's area:28.274333882308138 after bigger 2 multiple, area :113.09733552923255
func main() { var myInt int32 =5 var myFloat float64 = 6 fmt.Print(myInt,"\n") fmt.Print(myFloat) }
這裏的6
爲整型類型的字面值常量 Integer literals.。它賦值給了float64
類型變量,編譯器進行了隱式類型轉換。
func main() { var myInt int32 =5 //var myFloat float64 = myInt //error var myFloat float64 = float64(myInt) //須要顯式轉換 fmt.Print(myInt,"\n") fmt.Print(myFloat) }
這裏還要區分靜態類型
與底層類型
:
type IntA int32 type IntB int32 func main() { var a IntA =1 //var b IntB = a //error var b IntB = IntB(a) fmt.Print(a,"\n") fmt.Print(b) }
這裏IntA
爲變量a的靜態類型,而int32
爲變量a的底層類型。即便兩個類型的底層類型相同,在相互賦值時仍是須要強制類型轉換的。
接口類型變量的類型轉換,有兩種狀況:
根據Go 官方文檔 所說,全部的類型,都實現了空接口interface{}
,因此普通類型均可以向interface{}
進行類型轉換:
func main() { var x interface{} = "hello" // 字符串常量->interface{} var y interface{} = []byte{'w','o','r','l','d'} //[]byte ->interface{} fmt.Print(x," ") fmt.Printf("%s",y) }
而接口類型向普通類型的轉換,則須要由Comma-ok斷言
或switch測試
來進行了。
語法: value,ok := element.(T)
element必須爲ingerface類型,斷言失敗,ok爲false,不然爲true,下面是例程:
func main() { var vars []interface{} = make([]interface{},5) vars[0] = "one" vars[1] = "two" vars[2] = "three" vars[3] = 10 vars[4] = []byte{'a', 'b', 'c'} for index, element := range vars { if value, ok := element.(int); ok { fmt.Printf("vars[%d] type is int,value is %d \n",index,value) }else if value,ok := element.(string);ok{ fmt.Printf("vars[%d] type is string,value is %s \n",index,value) }else if value,ok := element.([]byte);ok{ fmt.Printf("vars[%d] type is []byte,value is %s \n",index,value) } } } //output: vars[0] type is string,value is one vars[1] type is string,value is two vars[2] type is string,value is three vars[3] type is int,value is 10 vars[4] type is []byte,value is abc
Comma-ok斷言也能夠這樣使用:
value := element.(T)
但一旦斷言失敗將產生運行時錯誤,不推薦使用。
switch測試只能在switch語句中使用。將上面的例程改成switch測試:
func main() { var vars []interface{} = make([]interface{}, 5) vars[0] = "one" vars[1] = "two" vars[2] = "three" vars[3] = 10 vars[4] = []byte{'a', 'b', 'c'} for index, element := range vars { switch value := element.(type) { case int: fmt.Printf("vars[%d] type is int,value is %d \n", index, value) case string: fmt.Printf("vars[%d] type is string,value is %s \n", index, value) case []byte: fmt.Printf("vars[%d] type is []byte,value is %s \n", index, value) } } }
(完)