Go快速入門


整理一些Go最基本的語法,旨在快速入門。

 

最簡單的hello world

1 package main
2 
3 import "fmt"
4 
5 func main() {
6    fmt.Println("Hello, World!")
7 }

幾點說明:

1、package main 定義了包名。你必須在源文件中非注釋的第一行指明這個文件屬於哪個包,如:package main。package main表示一個可獨立執行的程序,每個 Go 應用程序都包含一個名為 main 的包。

2、import "fmt" 告訴 Go 編譯器這個程序需要使用 fmt 包(的函數,或其他元素),fmt 包實現了格式化 IO(輸入/輸出)的函數。在導入包之后,你只能訪問包所導出的名字,任何未導出的名字是不能被包外的代碼訪問的。在 Go 中,首字母大寫的名稱是被導出的。

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 func main() {
 9     fmt.Println(math.pi)
10 }

比如這個例子,應該是Pi而不是pi,因為pi是不導出的,所以會報如下錯誤:

tmp/sandbox507287813/main.go:9:14: cannot refer to unexported name math.pi
tmp/sandbox507287813/main.go:9:14: undefined: math.pi

3、當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那么使用這種形式的標識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導入這個包),這被稱為導出(像面向對象語言中的 public);標識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內部是可見並且可用的(像面向對象語言中的 protected )。

4、在 Go 程序中,一行代表一個語句結束。每個語句不需要像 C 家族中的其它語言一樣以分號 ; 結尾,因為這些工作都將由 Go 編譯器自動完成。如果你打算將多個語句寫在同一行,它們則必須使用 ; 人為區分,但在實際開發中我們並不鼓勵這種做法。

 

關鍵字

下面列舉了 Go 代碼中會使用到的 25 個關鍵字或保留字:

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

除了以上介紹的這些關鍵字,Go 語言還有 36 個預定義標識符:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

intuintuintptr 類型在32位的系統上一般是32位,而在64位系統上是64位。當你需要使用一個整數類型時,你應該首選 int,僅當有特別的理由才使用定長整數類型或者無符號整數類型。

 

變量聲明

1、指定變量類型,聲明后若不賦值,使用默認值。

1 var v_name v_type
2 v_name = value

2、根據值自行判定變量類型。

1 var v_name = value

3、省略var, 注意 :=左側的變量不應該是已經聲明過的,否則會導致編譯錯誤。

1 v_name := value

4、多變量聲明

 1 //類型相同多個變量, 非全局變量
 2 var vname1, vname2, vname3 type
 3 vname1, vname2, vname3 = v1, v2, v3 //這種並行賦值也被用於當一個函數返回多個返回值時
 4 
 5 var vname1, vname2, vname3 = v1, v2, v3 //和python很像,不需要顯示聲明類型,自動推斷
 6 
 7 vname1, vname2, vname3 := v1, v2, v3 //出現在:=左側的變量不應該是已經被聲明過的,否則會導致編譯錯誤
 8 
 9 
10 // 這種因式分解關鍵字的寫法一般用於聲明全局變量
11 var (
12     vname1 v_type1
13     vname2 v_type2
14 )

5、如果你聲明了一個局部變量卻沒有在相同的代碼塊中使用它,會得到編譯錯誤;但是全局變量是允許聲明但不使用。

(1)看一個綜合的例子

 1 package main
 2 
 3 var x, y int
 4 var (  // 這種因式分解關鍵字的寫法一般用於聲明全局變量
 5     a int
 6     b bool
 7 )
 8 
 9 var c, d int = 1, 2
10 var e, f = 123, "hello"
11 
12 //這種不帶聲明格式的只能在函數體中出現
13 //g, h := 123, "hello"
14 
15 func main(){
16     var aa int = 10
17     var bb = 10
18     cc := 10
19     g, h := 123, "hello"
20     var ptr * int = &aa
21     println(x, y, a, b, c, d, e, f, g, h, aa, bb, cc, ptr)
22 }

運行結果:

0 0 0 false 1 2 123 hello 123 hello 10 10 10 0x7f2746856f08

(2)再看一個例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math/cmplx"
 6 )
 7 
 8 var (
 9     ToBe   bool       = false
10     MaxInt uint64     = 1<<64 - 1
11     z      complex128 = cmplx.Sqrt(-5 + 12i)
12 )
13 
14 func main() {
15     const f = "%T(%v)\n"
16     fmt.Printf(f, ToBe, ToBe)
17     fmt.Printf(f, MaxInt, MaxInt)
18     fmt.Printf(f, z, z)
19 }

運行結果:

bool(false)
uint64(18446744073709551615)
complex128((2+3i))

 

常量

1、常量的定義格式:

1 const identifier [type] = value

2、多個相同類型的聲明可以簡寫為:

1 const c_name1, c_name2 = value1, value2

3、常量不能使用 := 語法定義。

4、常量還可以用作枚舉:

1 const (
2     Unknown = 0
3     Female = 1
4     Male = 2
5 )

5、常量可以用len(), cap(), unsafe.Sizeof()函數計算表達式的值。常量表達式中,函數必須是內置函數,否則編譯不過。下面來看一個例子:

 1 package main
 2 
 3 import "unsafe"
 4 const (
 5     a = "abc"
 6     b = len(a)
 7     c = unsafe.Sizeof(a)
 8 )
 9 
10 func main(){
11     const aa, bb, cc = 1, "hello", 8.8
12     
13     println(a, b, c, aa, bb, cc)
14 }

運行結果:

abc 3 16 1 hello +8.800000e+000

關於上面的Sizeof,字符串類型在 go 里是個結構, 包含指向底層數組的指針和長度,這兩部分每部分都是 8 個字節,所以字符串類型大小為 16 個字節。

 

特殊常量iota

iota,特殊常量,可以認為是一個可以被編譯器修改的常量。在每一個const關鍵字出現時,被重置為0,然后再下一個const出現之前,每出現一次iota,其所代表的數字會自動增加1。iota 可以被用作枚舉值。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     const (
 7             a = iota   //0
 8             b          //1
 9             c          //2
10             d = "ha"   //獨立值,iota += 1
11             e          //"ha"   iota += 1
12             f = 100    //iota +=1
13             g          //100  iota +=1
14             h = iota   //7,恢復計數
15             i          //8
16     )
17     fmt.Println(a,b,c,d,e,f,g,h,i)
18 }

運行結果:

0 1 2 ha ha 100 100 7 8

再看一個例子:

 1 package main
 2 
 3 import "fmt"
 4 const (
 5     i=1<<iota
 6     j=3<<iota
 7     k
 8     l
 9 )
10 
11 func main() {
12     fmt.Println("i=",i)
13     fmt.Println("j=",j)
14     fmt.Println("k=",k)
15     fmt.Println("l=",l)
16 }

運行結果:

i= 1
j= 6
k= 12
l= 24

iota表示從0開始自動加1,所以i=1<<0,j=3<<1(<<表示左移的意思),即:i=1,j=6,這沒問題,關鍵在k和l,從輸出結果看,k=3<<2,l=3<<3。

 

條件語句

1、if... else ...和C語言類似,Go 的 if 語句也不要求用 ( ) 將條件括起來,同時, { } 還是必須有的。另外還需要特別注意else不能新起一行寫,否則會報錯:syntax error: unexpected semicolon or newline before else

2、if的便捷語句

if 語句可以在條件之前執行一個簡單語句。由這個語句定義的變量的作用域僅在 if 范圍之內,如果有else的話,else也是具有相同作用域的,可以使用到 if 中的變量

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 func pow(x, n, lim float64) float64 {
 9     if v := math.Pow(x, n); v < lim {
10         return v
11     }
12     return lim
13 }
14 
15 func main() {
16     fmt.Println(
17         pow(3, 2, 10),
18         pow(3, 3, 20),
19     )
20 }

運行結果:

9 20

3、switch語句和C語言類似,除非以 fallthrough 語句結束,否則條件從上到下的執行,當匹配成功的時候停止。switch還可以同時測試多個可能符合條件的值,使用逗號分割它們,例如:case val1, val2, val3。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6    /* 定義局部變量 */
 7    var grade string = "B"
 8    var marks int = 90
 9 
10    switch marks {
11       case 90: grade = "A" 
12       case 80: grade = "B"
13       case 50,60,70 : grade = "C"
14       default: grade = "D"  
15    }
16 
17    switch {
18       case grade == "A" :
19          fmt.Printf("優秀!\n" )     
20       case grade == "B", grade == "C" :
21          fmt.Printf("良好\n" )      
22       case grade == "D" :
23          fmt.Printf("及格\n" )      
24       case grade == "F":
25          fmt.Printf("不及格\n" )
26       default:
27          fmt.Printf("差\n" );
28    }
29    fmt.Printf("你的等級是 %s\n", grade );      
30 }

運行結果:

優秀!
你的等級是 A

4、type-switch用來判斷某個 interface 變量中實際存儲的變量類型。

基本語法如下:

1 switch x.(type){
2     case type:
3        statement(s);      
4     case type:
5        statement(s); 
6     /* 你可以定義任意個數的case */
7     default: /* 可選 */
8        statement(s);
9 }

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6    var x interface{} = "go"
 7      
 8    switch i := x.(type) {
 9       case nil:      
10          fmt.Printf(" x 的類型 :%T",i)                
11       case int:      
12          fmt.Printf("x 是 int 型")                       
13       case float64:
14          fmt.Printf("x 是 float64 型")           
15       case func(int) float64:
16          fmt.Printf("x 是 func(int) 型")                      
17       case bool, string:
18          fmt.Printf("x 是 bool 或 string 型" )       
19       default:
20          fmt.Printf("未知型")     
21    }   
22 }

運行結果:

x 是 bool 或 string 型

5、select 語句,后面再補充

 

defer

defer 語句會延遲函數的執行直到上層函數返回。延遲調用的參數會立刻生成,但是在上層函數返回前函數都不會被調用。延遲的函數調用被壓入一個棧中。當函數返回時, 會按照后進先出的順序調用被延遲的函數調用。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     fmt.Println("counting")
 7 
 8     for i := 0; i < 3; i++ {
 9         defer fmt.Println(i)
10     }
11 
12     fmt.Println("done")
13 }

運行結果:

counting
done
2
1
0

 

循環語句

1、Go 只有一種循環結構——for循環。for語句和C語言中類似;不像 C,Java,或者 Javascript 等其他語言,for 語句的三個組成部分 並不需要用括號括起來,但循環體必須用 { } 括起來。

1 for init; condition; post { }

另外,for 循環的 range 格式可以對 slice、map、數組、字符串等進行迭代循環。格式如下:

1 for key, value := range oldMap {
2     newMap[key] = value
3 }

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6 
 7    var b int = 3
 8    var a int
 9 
10    numbers := [6]int{1, 2, 3, 5} 
11 
12    /* for 循環 */
13    for a := 0; a < 2; a++ {
14       fmt.Printf("a 的值為: %d\n", a)
15    }
16 
17    for a < b {
18       a++
19       fmt.Printf("a 的值為: %d\n", a)
20       }
21 
22    for i,x:= range numbers {
23       fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
24    }   
25 }

運行結果:

a 的值為: 0
a 的值為: 1
a 的值為: 1
a 的值為: 2
a 的值為: 3
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0

2、循環初始化語句和后置語句都是可選的。基於此可以省略分號:C 的 while 在 Go 中叫做 for

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     sum := 1
 7     for sum < 1000 {
 8         sum += sum
 9     }
10     fmt.Println(sum)
11 }

運行結果:

1024

3、用for true來表示無限循環

1 for true  {
2     fmt.Printf("這是無限循環。\n");
3 }

也可以直接省略循環條件來構成無限循環

 

函數

1 func function_name( [parameter list] ) [return_types] {
2    函數體
3 }

1、函數定義解析:

  • func:函數由 func 開始聲明
  • function_name:函數名稱,函數名和參數列表一起構成了函數簽名。
  • parameter list:參數列表,參數就像一個占位符,當函數被調用時,你可以將值傳遞給參數,這個值被稱為實際參數。參數列表指定的是參數類型、順序、及參數個數。參數是可選的,也就是說函數也可以不包含參數。
  • return_types:返回類型,函數返回一列值。return_types 是該列值的數據類型。有些功能不需要返回值,這種情況下 return_types 不是必須的。
  • 函數體:函數定義的代碼集合。

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     var a int = 1
 7     var b int = 2
 8     fmt.Println(sum(a, b))
 9     
10     var aa string = "hello"
11     var bb string = "hi"
12     aa, bb = swap(aa, bb)
13 
14     fmt.Println(aa, bb)
15     
16     var aaa = 200
17     change(&aaa)
18     fmt.Println(aaa)
19 }
20 
21 func sum(a int, b int) int {
22     return a + b
23 }
24 
25 func swap(aa, bb string) (string, string) {
26     return bb, aa
27 }
28 
29 func change(aaa *int) {
30     * aaa += 100
31 }

運行結果:

3
hi hello
300

2、函數的命令返回值

Go 的返回值可以被命名,並且就像在函數體開頭聲明的變量那樣使用。返回值的名稱應當具有一定的意義,可以作為文檔使用。沒有參數的 return 語句返回各個返回變量的當前值。這種用法被稱作“裸”返回。直接返回語句僅應當用在短函數中。在長的函數中它們會影響代碼的可讀性。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func split(sum int) (x, y int) {
 6     x = sum * 4 / 9
 7     y = sum - x
 8     return
 9 }
10 
11 func main() {
12     fmt.Println(split(17))
13 }

運行結果:

7 10

3、函數值

函數也是值。他們可以像其他值一樣傳遞,比如,函數值可以作為函數的參數或者返回值。

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 func compute(fn func(float64, float64) float64) float64 {
 9     return fn(3, 4)
10 }
11 
12 func main() {
13     hypot := func(x, y float64) float64 {
14         return math.Sqrt(x*x + y*y)
15     }
16     fmt.Println(hypot(5, 12))
17 
18     fmt.Println(compute(hypot))
19     fmt.Println(compute(math.Pow))
20 }

運行結果:

13
5
81

4、閉包

(1)Go 語言支持匿名函數,可作為閉包。匿名函數是一個"內聯"語句或表達式。匿名函數的優越性在於可以直接使用函數內的變量,不必申明。

以下實例中,我們創建了函數 getSequence() ,返回另外一個函數。該函數的目的是在閉包中遞增 i 變量,代碼如下:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func getSequence() func() int {
 6    i:=0
 7    return func() int {
 8       i+=1
 9      return i  
10    }
11 }
12 
13 func main(){
14    /* nextNumber 為一個函數,函數 i 為 0 */
15    nextNumber := getSequence()  
16 
17    /* 調用 nextNumber 函數,i 變量自增 1 並返回 */
18    fmt.Println(nextNumber())
19    fmt.Println(nextNumber())
20    fmt.Println(nextNumber())
21    
22    /* 創建新的函數 nextNumber1,並查看結果 */
23    nextNumber1 := getSequence()  
24    fmt.Println(nextNumber1())
25    fmt.Println(nextNumber1())
26 }

運行結果:

1
2
3
1
2

(2)Go 函數可以是一個閉包。閉包是一個函數值,它引用了函數體之外的變量。 這個函數可以對這個引用的變量進行訪問和賦值;換句話說這個函數被“綁定”在這個變量上。

例如,函數 adder 返回一個閉包。每個返回的閉包都被綁定到其各自的 sum 變量上。

 1 package main
 2 
 3 import "fmt"
 4 
 5 func adder() func(int) int {
 6     sum := 0
 7     return func(x int) int {
 8         sum += x
 9         return sum
10     }
11 }
12 
13 func main() {
14     pos, neg := adder(), adder()
15     for i := 0; i < 10; i++ {
16         fmt.Println(
17             pos(i),
18             neg(-2*i),
19         )
20     }
21 }

運行結果:

0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

 

方法

1、Go 沒有類。然而,仍然可以在結構體類型上定義方法。方法接收者 出現在 func 關鍵字和方法名之間的參數中。一個方法就是一個包含了接收者的函數,接收者可以是命名類型或者結構體類型的一個值或者是一個指針。所有給定類型的方法屬於該類型的方法集。語法格式如下:

1 func (variable_name variable_data_type) function_name() [return_type]{
2    /* 函數體*/
3 }

例子1:

 1 package main
 2 
 3 import (
 4    "fmt"  
 5 )
 6 
 7 /* 定義函數 */
 8 type Circle struct {
 9   radius float64
10 }
11 
12 func main() {
13   var c1 Circle
14   c1.radius = 10.00
15   fmt.Println("Area of Circle(c1) = ", c1.getArea())
16 }
17 
18 //該 method 屬於 Circle 類型對象中的方法
19 func (c Circle) getArea() float64 {
20   //c.radius 即為 Circle 類型對象中的屬性
21   return 3.14 * c.radius * c.radius
22 }

運行結果:

Area of Circle(c1) =  314

例子2:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 type Vertex struct {
 9     X, Y float64
10 }
11 
12 func (v *Vertex) Abs() float64 {
13     return math.Sqrt(v.X*v.X + v.Y*v.Y)
14 }
15 
16 func main() {
17     v := &Vertex{3, 4}
18     fmt.Println(v.Abs())
19 }

運行結果:

5

2、可以對包中的 任意 類型定義任意方法,而不僅僅是針對結構體。但是,不能對來自其他包的類型或基礎類型定義方法。

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 type MyFloat float64
 9 
10 func (f MyFloat) Abs() float64 {
11     if f < 0 {
12         return float64(-f)
13     }
14     return float64(f)
15 }
16 
17 func main() {
18     f := MyFloat(-math.Sqrt2)
19     fmt.Println(f.Abs())
20 }

運行結果:

1.4142135623730951

3、接收者為指針和非指針對比

有兩個原因需要使用指針接收者。首先避免在每個方法調用中拷貝值(如果值類型是大的結構體的話會更有效率)。其次,方法可以修改接收者指向的值。

指針的例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 type Vertex struct {
 9     X, Y float64
10 }
11 
12 func (v * Vertex) Scale(f float64) {
13     v.X = v.X * f
14     v.Y = v.Y * f
15 }
16 
17 func (v *Vertex) Abs() float64 {
18     return math.Sqrt(v.X*v.X + v.Y*v.Y)
19 }
20 
21 func main() {
22     v := &Vertex{3, 4}
23     fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
24     v.Scale(5)
25     fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
26 }

運行結果:

Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25

Scale 方法使用 Vertex 代替 *Vertex 作為接收者。當 vVertex 的時候 Scale 方法沒有任何作用。Scale 修改 v。當 v 是一個值(非指針),方法看到的是 Vertex 的副本,並且無法修改原始值。

非指針的例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6 )
 7 
 8 type Vertex struct {
 9     X, Y float64
10 }
11 
12 func (v Vertex) Scale(f float64) {
13     v.X = v.X * f
14     v.Y = v.Y * f
15 }
16 
17 func (v *Vertex) Abs() float64 {
18     return math.Sqrt(v.X*v.X + v.Y*v.Y)
19 }
20 
21 func main() {
22     v := &Vertex{3, 4}
23     fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
24     v.Scale(5)
25     fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
26 }

運行結果:

Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:3 Y:4}, Abs: 5

 

數組

1、聲明方式

指定元素個數

1 var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

不指定元素個數

1 var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

2、遍歷

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     var array = [3]int{11, 22, 33}
 7 
 8     for y := range array {
 9         fmt.Printf("第 %d 位的值 = %d\n", y, array[y])
10     }
11 
12     for j, x := range array {
13         fmt.Printf("第 %d 位的值 = %d\n", j, x)
14     }
15     
16     var i int;
17     for i = 0; i < 3; i++ {
18         fmt.Printf("第 %d 位的值 = %d\n", i, array[i])
19     }
20 }

運行結果:

第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33
第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33
第 0 位的值 = 11
第 1 位的值 = 22
第 2 位的值 = 33

 

結構體

1、結構體定義需要使用 type 和 struct 語句。struct 語句定義一個新的數據類型,結構體有中一個或多個成員。type 語句設定了結構體的名稱。結構體的格式如下:

1 type struct_variable_type struct {
2    member definition;
3    member definition;
4    ...
5    member definition;
6 }

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Books struct {
 6     title string
 7     author string
 8     subject string
 9     book_id int
10 }
11 
12 func main() {
13     Book1 := Books{"book1", "a", "aa", 111}     
14     
15     var Book2 Books
16     Book2.title = "book2"
17     Book2.author = "b"
18     Book2.subject = "bb"
19     Book2.book_id = 222
20 
21     printBook(&Book1)
22     printBook(&Book2)
23 }
24 func printBook( book *Books ) {
25     fmt.Printf( "Book title : %s\n", book.title);
26     fmt.Printf( "Book author : %s\n", book.author);
27     fmt.Printf( "Book subject : %s\n", book.subject);
28     fmt.Printf( "Book book_id : %d\n", book.book_id);
29 }

運行結果:

Book title : book1
Book author : a
Book subject : aa
Book book_id : 111
Book title : book2
Book author : b
Book subject : bb
Book book_id : 222

2、結構體文法表示通過結構體字段的值作為列表來新分配一個結構體。使用 Name: 語法可以僅列出部分字段。(字段名的順序無關。)特殊的前綴 & 返回一個指向結構體的指針。

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Vertex struct {
 6     X, Y int
 7 }
 8 
 9 var (
10     v1 = Vertex{1, 2}  // 類型為 Vertex
11     v2 = Vertex{X: 1}  // Y:0 被省略
12     v3 = Vertex{}      // X:0 和 Y:0
13     p  = &Vertex{1, 2} // 類型為 *Vertex
14 )
15 
16 func main() {
17     fmt.Println(v1, p, v2, v3)
18 }

運行結果:

{1 2} &{1 2} {1 0} {0 0}

 

切片

可以聲明一個未指定大小的數組來定義切片:

1 var identifier []type

切片不需要說明長度。或使用make()函數來創建切片:

1 var slice1 []type = make([]type, len)
2 
3 也可以簡寫為
4 
5 slice1 := make([]type, len)

也可以指定容量,其中capacity為可選參數。

1 make([]T, length, capacity)

例子:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     var numbers []int
 7     printSlice(numbers)
 8     
 9     numbers = append(numbers, 1)
10     printSlice(numbers)
11     
12     numbers = append(numbers, 2, 3, 4, 5, 6, 7)
13     printSlice(numbers)
14     
15     
16     /* 打印子切片從索引1(包含) 到索引4(不包含)*/
17     fmt.Println("numbers[1:4] ==", numbers[1:4])
18     
19     /* 默認下限為 0*/
20     fmt.Println("numbers[:3] ==", numbers[:3])
21     
22     /* 默認上限為 len(s)*/
23     fmt.Println("numbers[4:] ==", numbers[4:])
24     
25     number1 := make([]int, 0, 5)
26     printSlice(number1)
27     
28     /* 打印子切片從索引  0(包含) 到索引 2(不包含) */
29     number2 := numbers[:2]
30     printSlice(number2)
31     
32     var number3 []int
33     copy(number3, numbers[1:4])
34     printSlice(number3)
35     
36     number4 := make([]int, len(numbers[1:4]), len(numbers[1:4]))
37     copy(number4, numbers[1:4])
38     printSlice(number4)
39 }
40 
41 func printSlice(x []int){
42     fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
43 }

運行結果:

len=0 cap=0 slice=[]
len=1 cap=1 slice=[1]
len=7 cap=7 slice=[1 2 3 4 5 6 7]
numbers[1:4] == [2 3 4]
numbers[:3] == [1 2 3]
numbers[4:] == [5 6 7]
len=0 cap=5 slice=[]
len=2 cap=7 slice=[1 2]
len=0 cap=0 slice=[]
len=3 cap=3 slice=[2 3 4]

在使用copy的時候,需要注意目標切片的len必須要足夠容納下源切片,而不僅僅是cap,否則是無法完成復制的。

 

map

map 是無序的,我們無法決定它的返回順序,這是因為 map 是使用 hash 表來實現的。

可以使用內建函數 make 也可以使用 map 關鍵字來定義 map:

1 /* 聲明變量,默認 map 是 nil */
2 var map_variable map[key_data_type]value_data_type
3 
4 /* 使用 make 函數 */
5 map_variable := make(map[key_data_type]value_data_type)

如果不初始化 map,那么就會創建一個 nil map。nil map 不能用來存放鍵值對

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6    countryCapitalMap := map[string]string{"china" : "Bei Jing"}
 7    for country := range countryCapitalMap {
 8       fmt.Println("Capital of",country,"is",countryCapitalMap[country])
 9    }
10    
11    countryCapitalMap = make(map[string]string)
12    
13    countryCapitalMap["France"] = "Paris"
14    countryCapitalMap["Italy"] = "Rome"
15    countryCapitalMap["Japan"] = "Tokyo"
16    countryCapitalMap["India"] = "New Delhi"
17    
18    for country := range countryCapitalMap {
19       fmt.Println("Capital of",country,"is",countryCapitalMap[country])
20    }
21    
22    captial1, ok1 := countryCapitalMap["France"]
23    if(ok1){
24       fmt.Println("Capital of France is", captial1)  
25    }else {
26       fmt.Println("Capital of France is not present") 
27    }
28    
29    delete(countryCapitalMap,"France");
30    captial2, ok2 := countryCapitalMap["France"]
31    if(ok2){
32       fmt.Println("Capital of France is", captial2)  
33    }else {
34       fmt.Println("Capital of France is not present") 
35    }
36 }

運行結果:

Capital of china is Bei Jing
Capital of France is Paris
Capital of Italy is Rome
Capital of Japan is Tokyo
Capital of India is New Delhi
Capital of France is Paris
Capital of France is not present

 

range

Go 語言中 range 關鍵字用於for循環中迭代數組(array)、切片(slice)、通道(channel)或集合(map)的元素。

當使用 for 循環遍歷一個 slice 時,每次迭代 range 將返回兩個值。 第一個是當前下標(序號),第二個是該下標所對應元素的一個拷貝。

對於第一個值:在數組和切片中它返回元素的索引值,在集合中返回 key-value 對的 key 值;可以通過賦值給 _ 來忽略序號和值。

 1 package main
 2 import "fmt"
 3 func main() {
 4     //這是我們使用range去求一個slice的和。使用數組跟這個很類似
 5     nums := []int{2, 3, 4}
 6     sum := 0
 7     for _, num := range nums {
 8         sum += num
 9     }
10     fmt.Println("sum:", sum)
11     //在數組上使用range將傳入index和值兩個變量。上面那個例子我們不需要使用該元素的序號,所以我們使用空白符"_"省略了。有時侯我們確實需要知道它的索引。
12     for i, num := range nums {
13         if num == 3 {
14             fmt.Println("index:", i)
15         }
16     }
17     //range也可以用在map的鍵值對上。
18     kvs := map[string]string{"a": "apple", "b": "banana"}
19     for k, v := range kvs {
20         fmt.Printf("%s -> %s\n", k, v)
21     }
22     //range也可以用來枚舉Unicode字符串。第一個參數是字符的索引,第二個是字符(Unicode的值)本身。
23     for i, c := range "ab" {
24         fmt.Println(i, c)
25     }
26 }

運行結果:

sum: 9
index: 1
a -> apple
b -> banana
0 97
1 98

 

interface

Go 語言提供了另外一種數據類型即接口,它把所有的具有共性的方法定義在一起,任何其他類型只要實現了這些方法就是實現了這個接口。

 1 /* 定義接口 */
 2 type interface_name interface {
 3    method_name1 [return_type]
 4    method_name2 [return_type]
 5    method_name3 [return_type]
 6    ...
 7    method_namen [return_type]
 8 }
 9 
10 /* 定義結構體 */
11 type struct_name struct {
12    /* variables */
13 }
14 
15 /* 實現接口方法 */
16 func (struct_name_variable struct_name) method_name1() [return_type] {
17    /* 方法實現 */
18 }
19 ...
20 func (struct_name_variable struct_name) method_namen() [return_type] {
21    /* 方法實現*/
22 }

來看一個例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 type Phone interface {
 8     call()
 9 }
10 
11 type NokiaPhone struct {
12 }
13 
14 func (nokiaPhone NokiaPhone) call() {
15     fmt.Println("I am Nokia, I can call you!")
16 }
17 
18 type IPhone struct {
19 }
20 
21 func (iPhone IPhone) call() {
22     fmt.Println("I am iPhone, I can call you!")
23 }
24 
25 func main() {
26     var phone Phone
27 
28     phone = new(NokiaPhone)
29     phone.call()
30 
31     phone = new(IPhone)
32     phone.call()
33 
34 }

在上面的例子中,我們定義了一個接口Phone,接口里面有一個方法call()。然后我們在main函數里面定義了一個Phone類型變量,並分別為之賦值為NokiaPhone和IPhone。然后調用call()方法,運行結果如下:

I am Nokia, I can call you!
I am iPhone, I can call you!

 

錯誤處理

Go 語言通過內置的錯誤接口提供了非常簡單的錯誤處理機制。

error類型是一個接口類型,這是它的定義:

1 type error interface {
2     Error() string
3 }

可以在編碼中通過實現 error 接口類型來生成錯誤信息。函數通常在最后的返回值中返回錯誤信息。使用errors.New 可返回一個錯誤信息。

例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math"
 6     "errors"
 7 )
 8 
 9 func Sqrt(f float64) (float64, error) {
10     if f < 0 {
11         return 0, errors.New("math: square root of negative number")
12     } else {
13         return math.Sqrt(f), nil
14     }
15 }
16 
17 func main() {
18 
19     result, err:= Sqrt(-1)
20 
21     if err != nil {
22        fmt.Println(err)
23     } else {
24         fmt.Println(result)
25     }
26 
27 }

運行結果:

math: square root of negative number

再來看一個更復雜的例子:

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 // 定義一個 DivideError 結構
 8 type DivideError struct {
 9     dividee int
10     divider int
11 }
12 
13 // 實現     `error` 接口
14 func (de *DivideError) Error() string {
15     strFormat := `
16     Cannot proceed, the divider is zero.
17     dividee: %d
18     divider: 0
19 `
20     return fmt.Sprintf(strFormat, de.dividee)
21 }
22 
23 // 定義 `int` 類型除法運算的函數
24 func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
25     if varDivider == 0 {
26         dData := DivideError{
27             dividee: varDividee,
28             divider: varDivider,
29         }
30         errorMsg = dData.Error()
31         return
32     } else {
33         return varDividee / varDivider, ""
34     }
35 
36 }
37 
38 func main() {
39 
40     // 正常情況
41     if result, errorMsg := Divide(100, 10); errorMsg == "" {
42         fmt.Println("100/10 = ", result)
43     }
44     // 當被除數為零的時候會返回錯誤信息
45     if _, errorMsg := Divide(100, 0); errorMsg != "" {
46         fmt.Println("errorMsg is: ", errorMsg)
47     }
48 
49 }

運行結果:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0

 

 

 

 

只是非常非常淺顯的入門而已。最后,語法沒有高亮要我感到非常蛋疼……

 

Update:2017-12-03結合官方指南重新補充了一些內容

本文參考自:

http://www.runoob.com/go/go-tutorial.html

https://tour.go-zh.org/welcome/1

windows下的IDE可以參考:

http://www.runoob.com/go/go-ide.html

另外,推薦一個在線運行Go代碼的網站:

http://www.dooccn.com/go/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM