這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
1.基本概念和文法
import "fmtimport"math/rand" 或寫為如下形式import("fmt""math/rand")
- 函數:定義變數時,變數名在類型之前,這與很多語言不一致。且
x int,y int可以表示為x,y int。 定義一個函數形式如下:
func add(x int,y int)int{return x+y}
函數可以返回多個值:
package mainimport "fmt"func swap(x,y string)(string,string){return y,x}func main(){a,b:= swap("hello","world")fmt.Println(a,b)}
註:傳回值可以指定變數名,並且像變數一樣使用。
func (p myType) funcName (a,b int, c string) (r,s int){return}
func->關鍵字funcName->方法名a,b int,c string->傳入參數r,s int->傳回值{}->函數體函數的定義可以使用var:var m int = 10或者,var i,j,k = 1,2,3函數內變數賦值使用:=操作符
函數也是一個類型,函數類型是go語言中一個重要的類型。
var recorder func(name string,age int,seniority int)(done bool)//後面所有符合這個函數類型的實現都可以賦給變數recorder,如下recorder = func(name string,age int,seniority int)(done bool){//相應語句return}
上述中,我們將一個函數字面上賦給了變數recorder,我們可以在一個函數類型的變數上直接應用調用運算式來調用它,如下:
done := recorder("Harry",32,10)
type Encipher func(planintext string)[]byte //type專門用於聲明自訂類型,此處聲明的
Encipher實際上就是函數類型func(planintext string)[]byte的一個別名資料型別。
資料類型:資料類型的轉換運算式為T(v),含義將v轉換為類型T。資料類型包含如下形式:
1.bool 2.string 3.int int8 int16 int32 int644.uint uint8 unint16 uint32 uint645.uintptr 6.byte(等價於uint8)7.rune(等價於int32,用於表示一個unicode code point)8.float32,float64 9.complex64,complex128註:使用constant來定義常量,constant Pi= 3.14
控制語句:
- for語句 golang中使用(且只使用)for來進行循序(沒有while語句)
package mainfunc main(){sum := 0for i := 0;i < 10;i++{ sum += i}//下述寫法等價於C/C++等語言中的while語句for sum < 1000{ sum + =sum}}註:for迴圈語句不需要()且{}是必須要使用的,if、switch文法處理與此一樣。若使用無限迴圈,則可使用:for{}
2. if語句,if語句可以在執行條件判斷前帶一個語句(這常被叫做if帶上一個短語句),詞語句中變數的生命週期在if語句結束後結束,如下。
package mainimport( "fmt" "math/rand")func main(){ if n := rand.Intn(6);n <= 2{ fmt.Println("[0,2]",n)}else{fmt.Println("[3,5]",n)}//此處開始無法使用變數n}
3. switch語句,golang中不需要使用break語句來跳出switch,且switch中可以沒有條件。
package mianimport( "fmt" "runtime")func main(){ fmt.Print("Go runs on") //switch類似於if可以帶上一個短語句 switch os := runtime.GOOS;os{ case "darwin":fmt.Println("OS X.") case "linux":fmt.Println("Liunx.") default://othersfmt.Printf("%s.",os) }}//無條件使用switc語句func main(){ t := time.Now() switch{ case t.Hour() < 12:fmt.Println("Good morning!") case t.Hour() < 17:fmt.Println("Good afternoon.") default:fmt.Println("Good eventing.") }}
4. defer,defer語句能夠將一個函數調用加入一個列表中(這個函數調用被叫做deferred函數調用),在當前函數調用結束時調用列表中的函數。如下:
func CopyFile(dstName, srcName string)(written int64,err error){ src,err := os.Open(srcName) if err != nil{return } defer src.Close() dst,err := os.Create(dstName) if err !=nil{ return } defer dst.close() return io.Copy(dst,src)}//註:deferred函數調用按先進後出的順序執行:func main(){ //輸出43210 defer fmt.Print(i)}
1. 結構,(structs),結構是一個域的集合:
type Vertex struct{ X int Y int}func main(){ v := Vertex{1,2} v.X = 4 fmt.Println(v) }
2. 數組,[n]T在golang中是一個類型(像*T一樣),表示一個長度為n的數組其元素類型為T,數組長度無法改變
func main(){ var a[2]string a[0] = "hello" a[1] = "world" fmt.println(a[0],a[1]) fmt.println(a)}
3.指標,golang中的指標不支援算術運算:
p := Vertex{X,Y} //{x,y}為struct literalq := &p //q類型為*Vertexq.X = 2 //直接存取地區Xstruct的literal由{}包裹,實際過程我們可以使用Name:這樣的文法為特定域值設定值:type Vertex struct{ X,Y int}r := Vertex{X:3} //此時Y的值為0
4.slice,slice是可變長,其是一個指標而不是一個值。[]T為slice類型,其中元素類型為T:
p := []int{2,3,4,7,11,13}fmt.Println("p==",p)for i := 0;i < len(p);i++{ fmt.Printf("p[%d] == %d\n",i,p[i]) }}註:運算式s[lo:hi]用於建立一個slice,新建立的slice的元素為s中的[lo,hi)位置的元素。建立slice使用make函數,(不用new了建立)如a := make([]int,5) //len(a)為5此處make函數建立一個數組(元素初始化為0)且返回一個slice指向詞數組。make可以帶三個參數,用於指定容量:b := make([]int,0,5) //len(b)=0,cap(b)=5一個沒有值的slice是nil,長度和容量都為0var z[]intfmt.Println(z,len(z),cap(z))if z == nil{ fmt.Println("nil!")}
5.map,用於映射key到value(值),map可以通過make來建立,而不是new
type Vertex Struct{ Lat,Long float64}var m map[string]Vertexfunc main(){ m = make(map[string]Vertex) m["Bell Labs"] = Vertex{40.23332,-31.32143, } fmt.Println(m["Bell Labs"])}//註:使用[]訪問map中的值,使用delete刪除map中的值m[key] = elem //訪問delete(m,key) //刪除var m = map[string]Vertex{//此處Vertex可以省略不寫 "Bell Labs":Vertex{ 34.34345,53,32123 }, "google Labs":Vertex{ 34.34345,53,32123 },}//若需要檢查map中的key是否存在//elem ,ok = m[key] //elem表示key的值(key不存在時,elem為0),ok表示key是否存在
6.range,用於在for中迭代一個slice或者一個map
var s = []int{1,2,3}func main(){ for i,v := range s{fmt.Println(i,v) } //只需要值,使用_忽略索引 for _,v := range s{fmt.Println(v) } //只需要索引 for i := range s{fmt.Println(i) }}
7.閉包,golang中函數也是一個值(就像int值一樣),且函數可以是一個閉包。閉包是一個引用了外部變數的函數。
package mianimport "fmt"func adder()func(int)int{ sum := 0 //返回一個閉包,此閉包引用了外部變數sum return func(x int)int{ sum += xreturn sum }}func main(){ a := adder() fmt.Println(a(9527))}
2.方法與介面
- 方法,方法是附屬於某個自訂的資料類型的函數,具體而言,一個方法就是一個與某個接收者關聯的函數。因此,在方法的簽名中不但包含了函數簽名,還包含了一個與接收者有關的聲明,也即是,方法的聲明中包含關鍵字func、接收者聲明、方法名稱、參數聲明列表、結果聲明列表和方法體。在golang中沒有類,可以為結構體定義方法,執行個體如下:
package mainimport( "fmt" "math")type Vertex struct{ X,Y float64}//結構體Vertex的方法//此處的方法接收者v的類型為 *Vertexfunc(v *Vertex)Abs()float64{return math.Sqrt(v.X*v.X+v.Y*v.Y)}func main(){ v := &Vertex{3,4} fmt.Println(v.Abs())}
註:此處方法的接收者使用指標類型而非實值型別的原因如下:
1.避免方法每次調用時,對接收者的不必要拷貝 2.在方法內可以修改接收者的值我們可以為任意類型定義方法,但以下情況除外: 1.如果類型定義在其他包中,不能為其定義方法 2.如果類型是基礎類型,不能為其定義方法。
- 介面,介面也是一種類型(像結構體一樣)。一個介面類型包含了一組方法,一個介面類型能夠持有那些實現了這些方法的值,如下:
//定義介面Absertype Abser interface{ Abs() float}//定義結構體Vertextype Vertex struct{ X,Y float64}//實現方法Absfunc(v *Vertex)Abs()float64{ return math.Sqrt(v.X*v.X+v.Y+v.Y)}func main(){ v := Vertex{3,4} //成功,能夠持有*Vertex類型的值 var a Abser = &v //出錯,不能持有Vertex類型的值 //因為在*Vertex上定義了方法Abs,而在Vertex上未定義 var b Abser = v}
- 匿名域,結構體中可以存在只有類型而沒有名字的域,它們被叫做匿名域。如:
struct{ T1 *T2}
一個結構體中的匿名域中的域或者方法可以被此結構體執行個體直接存取:
package mainimport "fmt"type Car struct{ wheelCount int}func(car *Car)numberofWheels()int{ return car.wheelCount}type Ferrari struct{ Car}func main(){ f := Ferrari{Car{4}} fmt.Println("A Ferrari has this many wheels:",f.numberOfWheels())}
3.並發支援
- golang在運行時(runtime)管理了一種輕量級線程,被稱為goroutime。建立數十萬級的goroutine沒有問題。使用go 關鍵字就開啟了一個線程樣本如下:
package mainimport( "fmt" "time")func say(s string){ for i := 0;i < 5;i++{time.Sleep(100 * time.Millesecond)fmt.Println(s) }}func main(){ //開啟一個goroutine執行say函數 go say("world") say("hello")}
4 其他
panic 是用來表示非常嚴重的不可恢複的錯誤的。在Go語言中這是一個內建函數,接收一個interface{}類型的值(也就是任何值了)作為參數。panic的作用就像我們平常接觸的異常。不過Go可沒有try…catch,所以,panic一般會導致程式掛掉(除非recover)。所以,Go語言中的異常,那真的是異常了。你可以試試,調用panic看看,程式立馬掛掉,然後Go運行時會列印出調用棧。 但是,關鍵的一點是,即使函數執行的時候panic了,函數不往下走了,運行時並不是立刻向上傳遞panic,而是到defer那,等defer的東西都跑完了,panic再向上傳遞。所以這時候 defer 有點類似 try-catch-finally 中的 finally。 panic就是這麼簡單。拋出個真正意義上的異常。
上面說到,panic的函數並不會立刻返回,而是先defer,再返回。這時候(defer的時候),如果有辦法將panic捕獲到,並阻止panic傳遞,那就異常的處理機制就完善了。Go語言提供了recover內建函數,前面提到,一旦panic,邏輯就會走到defer那,那我們就在defer那等著,調用recover函數將會捕獲到當前的panic(如果有的話),被捕獲到的panic就不會向上傳遞了,於是,世界恢複了和平。你可以幹你想乾的事情了。不過要注意的是,recover之後,邏輯並不會恢複到panic那個點去,函數還是會在defer之後返回。
//go實作類別似try catch的事情package mainfunc Try(fun func(),handler func(interface{})){defer func(){if err := recover();err != nil{handler(err)}}()fun()}func main(){Try(func(){panic("foo")},func(e interface{}){print(e)})}