Go是一種併發的、帶垃圾回收的、快速編譯的語言。數組
一個例子:數據結構
//當前程序的包名 package main //導入其它的包 import ( "flag" "fmt" "os" ) //常量定義 const PI = 3.14 //全局變量的聲明與賦值 var name = "go" //通常類型的聲明 type intType int //結構聲明 type newType struct { } //接口聲明 type newInterface interface { } //main函數,程序的入口 func main() { fmt.Println(os.Args) flag.Parse() fmt.Println(flag.Args()) }
說明:閉包
一、每一個go源代碼文件的開頭都是一個package聲明,表示該go代碼所屬的包;包是go語言中最基本的分發單位,也是工程管理中依賴關係的體現。要生成go可執行程序,必須創建一個名爲main的package,而且在該package中包含一個叫main()的函數;併發
二、Go語言的main()函數不能帶參數,也不能定義返回值,傳入的命令行參數在os.Args變量中保存;app
三、在包聲明以後,是一系列的import語句,用於導入改程序所依賴的包,但不能導入在源代碼文件中沒有用到的包,不然Go編譯器會報錯;函數
四、Go語言中,語句不用以分號結束;oop
編譯、運行:ui
go run test.go
使用這個命令,會將編譯、連接和運行3個步驟合併爲一步,運行完成後在當前目錄下看不到任何中間文件和最終的可執行文件。spa
若是隻要生成編譯結果,而不自動運行,可使用:命令行
go build test.go
變量聲明語句:
var 變量名 變量類型
例子:
var v1 int var v2 string // 字符串 var v3 [10]int // 數組 var v4 []int // 數組切片 var v5 struct { // 接口 f int } var v6 *int // 指針 var v7 map[string]int // map,key爲string類型,value爲int類型 var v8 func(a int) int
可使用一個var關鍵字同時聲明多個變量:
var ( v1 int v2 string )
若是在聲明變量的同時還進行了初始化,則var關鍵字能夠省略,而且Go編譯器能夠從初始化表達式的右值推導出該變量應該聲明爲哪一種類型,這有點相似於動態類型,但Go其實是強類型的語言(靜態類型語言)。
以下所示三種用法的效果是同樣的:
var v1 int = 10 v2 int = 10 // 編譯器自動推導出v2的類型 v3 := 10 // 編譯器自動推導出v3的類型
說明:
":="用於明確表達同時進行變量聲明和初始化工做,但要注意出如今:=左側的變量不該該是已經被聲明過的,不然會致使編譯器報錯,以下:
var i int i := 2 //error
在Go語言中,常量是指編譯期間就已知且不可改變的值。
-12 // 整型常量 3.14 // 浮點型常量 3.2+12i // 複數類型的常量 true // 布爾型常量 "foo" // 字符串常量
在C語言中,常量一般有類型,好比-12在C語言中會認爲是一個int類型,若是要指定它爲long類型,須要寫成-12L。
在Go語言中,字面常量沒有類型,只要這個常量在相應類型的值域範圍內,就能夠做爲該類型的常量,好比上面的常量-12,能夠賦值給int、uint、int3二、int6四、float3二、float6四、complex6四、complex128等類型的變量。
經過const常量,能夠給字面常量指定一個友好的名字:
const Pi float64 = 3.1415 const zero = 0.0 //無類型浮點常量 cosnt ( size int64 = 1024 eof = -1 //無類型整型常量 ) const u,v float32 = 0, 3 const a,b,c = 3,4,"foo"
const mask = 1<<3
常量的賦值是一個編譯期行爲,因此右值不能出現任何須要運行期才能得出結果的表達式,例如:
const HOME = os.GetEnv("HOME") // error
Go語言預約義常量有:true、false、iota,前兩個爲bool常量;
iota是一個可被編譯器修改的常量,在每個const關鍵字出現時被重置爲0,而後在下一個const出現以前,每出現一次iota,其所表明的數字會自動增1,例如:
const ( // rest iota c0 = iota // c0=0 c1 = iota // c1=1 c2 = iota // c2=2 )
在const後跟一對園括號的方式定義一組常量,例如:
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday numberofDays // 這個常量沒有導出 )
同Go語言的其餘符號同樣,以大寫字母開頭的常量在包外可見;
以上例子中,numberofDays爲包內私有,其它符號則可被其它包訪問。
Go語言中使用的類型包括:
基礎類型 |
||
布爾類型(bool) |
var b1 bool = true |
|
整型 |
var v1 int = 12 |
|
浮點類型(float3二、float64) |
var f1 float32 = 12.0 |
|
複數類型(complex6四、complex128) |
var c1 complex64 = 3.2 + 12i |
|
字符串(string) |
var s string = 「sina」 |
|
字符類型(rune) |
表明單個的unicode字符 |
|
錯誤類型(error) |
|
|
|
|
|
複合類型 |
||
指針(pointer) |
|
|
數組(array) |
var array [32] byte |
|
切片(slice) |
var slice [] int |
|
字典(map) |
var word_count map[string] int |
|
通道(chan) |
var ch chan int |
|
結構體(struct) |
var s struct {} |
|
接口(interface) |
|
布爾類型不能接受其它類型的賦值,不支持自動或強制的類型轉換,如下的示例是一些錯誤的用法:
var b bool b = 1 // error b = bool(1) // error
如下的用法是正確的:
var b bool b = (1!=0)
類型 |
長度 |
值範圍 |
int8 |
1 |
-128 ~ 127 |
uint8(即byte) |
1 |
0 ~ 255 |
int16 |
2 |
-32768 ~ 32767 |
uint16 |
2 |
0 ~ 65535 |
int32 |
4 |
-2147483648 ~ 2147483647 |
uint32 |
4 |
0 ~ 4294967295 |
int64 |
8 |
(-2^63) ~ (2^63-1) |
uint64 |
8 |
0 ~ (2^64-1) |
int |
平臺相關 |
平臺相關 |
uint |
平臺相關 |
平臺相關 |
uintptr |
同指針 |
32位平臺下爲4字節,64位平臺下爲8字節 |
須要注意的是,int和int32是不一樣的類型, 不能相互賦值,例如:
var val2 int32 val1 := 64 // val1會被自動推導爲int類型 var2 = val1 // error
var2 = int32(val1) // ok
此外,不一樣類型的整型數不能直接比較,好比int8類型的數和int類型的數不能直接比較,但各類類型的整型變量均可以直接與字面常量(literal)進行比較,好比:
var i int32 var j int64 i,j = 1,2 if i==j { // error fmt.Println("i and j are equal.") } if i==1 || j==2 { // ok fmt.Println("i and j are equal.") }
Go語言中的float32和float64分別等價於C語言的float、double類型;
var i float32 = 12.1 j := 64.0 // 自動推導爲float64類型 j = i // error j = float64(i) // ok
判斷兩個浮點數是否相等,是根據不一樣精度來的:
import "math" func IsEqual(f1, f2, p float64) bool { return math.Fdim(f1, f2) < p }
其中,p是用戶自定義的比較精度,好比p=0.00001。
在Go語言中支持兩個字符類型,一個是byte(其實是uint8的別名),表明UTF-8字符串的單個字節的值;另外一個是rune,表明單個Unicode字符。
關於rune相關的操做,可查閱Go標準庫的unicode包;另外unicode/utf8包也提供了UTF8和Unicode之間的轉換。
字符串支持下標讀取操做:
str := "Hello world" ch := str[0] fmt.Printf("The length of \"%s\" is %d\n", str, len(str)) fmt.Printf("The 1st character of \"%s\" is '%c'\n", str, ch)
但字符串的內容在初始化後不能被修改,例如:
str := "Hello world" str[0] = 'X' // error
經常使用的字符串操做:
操做 |
含義 |
s1 + s2 |
字符串鏈接 |
len(s) |
字符串長度 |
s[i] |
取字符 |
字符串遍歷有兩種方式:
str := "Hello,世界"
// 以字節數組的方式遍歷
for i := 0; i<len(str); i++ {
ch := str[i]
fmt.Println(i, ch)
}
// 以unicode字符方式遍歷,每一個字符的類型是rune
for i, ch := range str {
fmt.Println(i, ch)
}
數組的聲明方法比較多,好比:
[32] byte // 字節數組 [2*N] struct {x, y int 32} // 結構體數組 [1000] *float64 // 指針數組 [3][5] int // 二維數組 [2][2][2] float64
[...]int{1,2,3} // 省略數組長度,Go會根據元素個數來計算長度
在聲明數組時長度能夠爲一個常量或一個常量表達式,數組長度在定義之後就不能夠再改變。
數組支持按下標讀寫元素,也支持range關鍵字的遍歷,例如:
var array = [5] int {10,20,30,40,50} for i, v := range array { array[i] = v*2; } for i, v := range array { fmt.Println(i, v) }
另外,數組是值類型,若是將數組做爲函數參數傳遞,則在函數調用的時候該參數將發生數據複製,所以,在函數體中沒法修改傳入的數組的內容。
數組切片相似於C++中STL的std::vector<>,支持動態擴展數組,而且能夠被做爲函數參數傳遞而不會致使元素被複制。
數組切片的數據結構能夠抽象爲如下3個變量:
其結構大體以下:
type slice struct { first *T len int cap int }
建立數組切片有下面多種方式:
一、基於數組建立的方式
var myArray [10] int = [10] int {1,2,3,4,5,6,7,8,9,10} var s1 = myArray[:] // 基於myArray的全部元素建立數組切片 var s2 = myArray[:5] // 基於myArray的前5個元素建立數組切片 var s3 = myArray[5:] // 基於myArray從第5個元素開始的全部元素建立數組切片
二、直接建立數組切片的方式
s1 := make([] int,5) // 建立一個初始元素個數爲5的數組切片,元素初始值爲0 s2 := make([] int,5, 10) // 建立一個初始元素個數爲5的數組切片,元素初始值爲0,並預留10個元素的存儲空間 s3 := []int{1,2,3,4,5} // 建立並初始化包含5個指定元素的數組切片
三、基於數組切片建立的方式
oldSlice := []int{1,2,3,4,5} newSlice := oldSlice[:3]
操做數組元素的全部方法都適用於數組切片,好比數組切片也能夠按下標讀寫元素,用len()獲取元素個數,並支持使用range關鍵字來快速遍歷全部元素。
數組切片支持可動態增減元素,內置的cap()和len()函數,分別返回數組切片分配的空間大小、當前存儲的元素個數。
s := make([] int,5, 10) fmt.Println("len(s)=",len(s)) // 5 fmt.Println("cap(s)=",cap(s)) // 10
使用append函數能夠在數組切片尾端添加新元素:
s = append(s, 1,2,3)
若是追加的內容長度超過當前已分配的存儲空間(即cap()返回值),數組切片會自動分配一塊足夠大的內存。
還能夠將另外一個數組切片追加到一個數組切片末端:
s2 := []int{8,9,10} s = append(s, s2...) // s2後面的省略號必需要有
前面提到Slice內部是一個指針,因此修改Slice的元素,會影響其所指向的數組,例如:
var myArray [5] int = [5] int {1,2,3,4,5} var s1 = myArray[:] var s2 = s1 s1[3] = 90 fmt.Println(myArray) // [1 2 3 90 5] fmt.Println(s1) // [1 2 3 90 5] fmt.Println(s2) // [1 2 3 90 5]
數組切片的複製,若是兩個slice不同大,就會按其中較小的slice的元素個數進行復制,例如:
s1 := []int {1,2,3,4,5} s2 := []int {5,4,7} copy(s1, s2) //只複製s2的3個元素到s1的前3個位置 copy(s2, s1) //只複製s1的前3個元素到s2中
map是key-value結構的一個字典,相似於C++中STL的std::map<>。
例子:
type PersonInfo struct { ID string Name string Address string } func main() { var personDB map[string] PersonInfo // 變量聲明 personDB = make(map[string] PersonInfo) // 變量建立 personDB["1"] = PersonInfo{"12345","Tom","Room 203"} // 增長了一個鍵 person, ok := personDB["1"] // 查找 if ok { fmt.Println("found person", person.Name, "with ID 1") } else { fmt.Println("Did not find person with ID 1") }
delete(personDB, "1") // 刪除一個鍵 }
遍歷map的例子:
m1 := map[string]int{"one": 1, "two": 2, "three": 3} fmt.Println(m1) for key, val := range m1{ fmt.Printf("%s => %d \n", key, val) }
注意:range支持對 string, array, slice, map, buffered chan的遍歷;
定義一個struct:
type Rect struct { x, y float64 width, height float64 }
初始化的幾種方式:
rect1 := new(Rect) rect2 := &Rect{} rect3 := &Rect{0, 0, 100, 200} rect4 := &Rect{width:100, height:200}
在Go語言中,未進行顯式初始化的變量都會被初始化爲該類型的零值,例如bool類型的零值爲false,int類型的零值爲0,string類型的零值爲空字符串。
Go支持指針,例如:
var i int = 1 var pInt *int = &i //輸出:i=1 pInt=0xf8400371b0 *pInt=1 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) *pInt = 2 //輸出:i=2 pInt=0xf8400371b0 *pInt=2 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) i = 3 //輸出:i=3 pInt=0xf8400371b0 *pInt=3 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)
import "flag" import "fmt" import "os"
import ( "flag" "fmt" "os" )
Go具備兩個分配內存的機制,分別是內建函數new()和make()。
new 是一個分配內存的內建函數,new(T)爲一個類型爲T的新對象分配了值爲零的存儲空間並返回其地址,也就是一個類型爲*T的值。用Go的術語來講,就是它返回了一個指向新分配的類型爲T的零值的指針。
make(T, args)僅用於建立slice、map和chan,並返回類型T(不是*T)的一個被初始化了的(不是零)實例。這三種類型實質上是對在使用前必須進行初始化的數據結構的引用:
slice := make([] type, len, cap) map := make(map[key_type] value_type) chan := make(chan type, len)
如下示例說明了new和make的不一樣:
var p *[]int = new([]int) // 爲切片結構分配內存,*p == nil var v []int = make([]int, 10) // 切片v如今是對一個新的有10個整數的數組的引用 fmt.Println(*p) // [] fmt.Println(v) // [0 0 0 0 0 0 0 0 0 0]
if 條件表達式 { ... } else if{ ... } else {
...
}
注意:
switch語句:
switch i { // 左花括號"{"必須與switch處於同一行 case 0: fmt.Printf("0") case 1: fmt.Printf("0") case 2: fallthrough case 3: fmt.Printf("3") case 4,5,6: fmt.Printf("multi") default: fmt.Printf("default") }
注意:
Num := 8 switch { case 0<= Num && Num <=3: fmt.Println("0-3") case 4<= Num && Num <=6: fmt.Println("4-6") case 7<= Num && Num <=9: fmt.Println("7-9") }
當缺失條件表達式時,整個switch結構與多個if...else...的邏輯做用相同。
Go語言的循環控制只支持for語句,不支持while結構。
for循環語句的循環表達式也不須要使用()括起來,例如:
sum := 0 for i:=0; i<10; i++ { sum += i }
精簡的for循環語句:
i := 1 for i<10 { fmt.Println(i) i++ }
for循環也支持continue和break語句,例如:
sum := 0 for { // 死循環 sum ++ if sum>100 { break } }
對於嵌套循環,break還能夠選擇中斷哪一個循環,例如:
var i int var j int JLoop: for j=0; j<5; j++ { for i=0; i<10; i++ { if i>5 { break JLoop } } }
Go語言仍支持使用goto關鍵字在函數體內進行跳轉,例如:
func foo() { i := 0 HERE: i++ if i<10 { goto HERE } }
函數聲明語句:
func 函數名(參數列表) (返回值列表) { // 函數體 }
注意:
一、 參數列表和返回值列表都是變量名在前,變量類型在後;
二、 Go函數支持多返回值,但並非全部返回值都必須賦值,在函數返回時沒有被明確賦值的返回值都會被設置爲默認值。
三、 函數左起的花括號」{」不能另起一行,不然會報錯;
以一個簡單的計算加法的函數爲例:
func add(a int, b int) (ret int, err error) { if a<0 || b<0 { // 假設這個函數只支持兩個非負數的加法 err = errors.New("Should be non-negative numbers!") return } return a+b, nil }
若是參數列表中若干個相鄰的參數類型相同,則能夠在參數列表中省略前面變量的類型聲明,例如:
func add(a, b int) (ret int, err error) { ... }
若是返回值列表中多個返回值的類型相同,也能夠用一樣的方式合併;另外,若是函數只有一個返回值,能夠這樣寫:
func Add(a, b int) int { ... }
Go語言支持多重賦值,好比:
i, j = j, i
用於交換兩個變量的值,在不支持多重賦值的語言中,交換兩個變量的內容須要引入一個臨時變量:
t = i; i = j; j = t
Go函數帶回多個返回值時,可使用多重賦值語句,將不一樣的返回值賦值給不一樣的變量,而且容許使用匿名變量("_")接受不須要使用的返回值,例如:
func GetName() (firstName, lastName, nickName string) { return "May", "Chan", "Chibi Maruko" } _, _, nickName := GetName
函數調用很是方便,只要事先導入該函數所在的包,就能夠調用了:
import "mymath" c := mymath.Add(1,2)
注意:小寫字母開頭的函數只在本包內可見,大寫字母開頭的函數才能被其它包使用。
例如:
func foo(args ...int) { // 接受不定數量的參數,這些參數都是int類型 for _, arg := range args { fmt.Println(arg) } } foo(2,3,4) foo(1,3,7,13)
形如"...type"格式的類型只能做爲函數的參數類型存在,而且必須是最後一個參數。
"...type"本質上是一個數組切片,也就是[]type,這也是爲何上面的參數args能夠用for循環來得到每一個傳入的參數。
若是但願不定參數傳任意類型,能夠指定類型爲interface{},如標準庫中的fmt.Printf()的函數原型:
func Printf(format string, args ...interface{}) { ... }
例如:
func foo(args ...interface{}) { for _, arg := range args { switch arg.(type) { case int: fmt.Println(arg, "is an int value.") case string: fmt.Println(arg, "is a string value.") case float32: fmt.Println(arg, "is a float32 value.") default: fmt.Println(arg, "is an unknown type.") } } }
匿名函數能夠直接賦值給一個變量,例如:
f := func(x, y int) int { return x+y }
或者直接執行一個匿名函數:
func(ch chan int) { ch <- ACK } (reply_chan) // 花括號後面直接跟參數列表表示函數調用
閉包:當一個函數內部嵌套另外一個函數定義時,內部的函數體能夠訪問外部函數的局部變量。
a := func() (func()) { var i int = 10 return func(){ fmt.Printf("i=%d\n", i) i++ } } c1 := a() c2 := a() c1() // 10 c1() // 11 c1() // 12 c2() // 10
c1和c2是創建在同一個函數上,但做用在同一個局部變量的不一樣實例上的兩個不一樣的閉包。