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)
"
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("优秀!
" )     
20       case grade == "B", grade == "C" :
21          fmt.Printf("良好
" )      
22       case grade == "D" :
23          fmt.Printf("及格
" )      
24       case grade == "F":
25          fmt.Printf("不及格
" )
26       default:
27          fmt.Printf("差
" );
28    }
29    fmt.Printf("你的等级是 %s
", 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
", a)
15    }
16 
17    for a < b {
18       a++
19       fmt.Printf("a 的值为: %d
", a)
20       }
21 
22    for i,x:= range numbers {
23       fmt.Printf("第 %d 位 x 的值 = %d
", 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("这是无限循环。
");
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
", v, v.Abs())
24     v.Scale(5)
25     fmt.Printf("After scaling: %+v, Abs: %v
", 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
", v, v.Abs())
24     v.Scale(5)
25     fmt.Printf("After scaling: %+v, Abs: %v
", 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
", y, array[y])
10     }
11 
12     for j, x := range array {
13         fmt.Printf("第 %d 位的值 = %d
", j, x)
14     }
15     
16     var i int;
17     for i = 0; i < 3; i++ {
18         fmt.Printf("第 %d 位的值 = %d
", 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
", book.title);
26     fmt.Printf( "Book author : %s
", book.author);
27     fmt.Printf( "Book subject : %s
", book.subject);
28     fmt.Printf( "Book book_id : %d
", 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
", 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
", 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/

原文地址:https://www.cnblogs.com/abc-begin/p/7953894.html