最简单的 hello world 代码:

1
2
3
4
5
6
7
package main

import "fmt"

func main() {
fmt.Printf("hello world")
}

对于独立运行的执行文件必须是 package main,package 总是首先出现。当Go 程序在执行的时候,首先调用的函数是 main.main()。

注释是被包裹于 /**/ 之间的;

终端上使用如下执行:

1
$ go run hello.go

变量、类型和关键字

Go 同其他语言不同的地方在于变量的类型在变量名的后面。

不是:int a,而是 a int。

在 Go 中,声明和赋值是两过程,但是可以连在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a int
var b bool
a = 15
b = false

c := 18 /* 这一形式只可用在函数内 */
d := true

var ( /* 一组声明, const 和 import 也可以这样做 */
x int
b bool
)

var x, y int

a, b := 20, 16

_, b := 34, 35 /* 将 35 赋值给 b,同时丢弃 34,与 lua 当中的哑元变量一样 */

Go 的编译器对声明却未使用的变量在报错。

下面的代码会产生这个错误:

1
2
3
4
package main
func main() {
var i int
}

布尔类型是 bool,值有 true 和 false。

int,这个类型根据你的硬件决定适当的长度。如果你希望明确其长度,你可以使用 int32 或者 uint32。

完整的整数类型列表(符号和无符号)是 int8,int16,int32,int64 和byte,uint8,uint16,uint32,uint64。

byte 是 uint8 的别名。浮点类型的值有 float32 和 float64 (没有float 类型)。

64 位的整数和浮点数总是 64 位的,即便是在 32 位的架构上。

混合用这些类型向变量赋值会引起编译器错误。

1
2
3
4
5
6
7
8
package main
func main() {
var a int 通用整数类型
var b int32 32 位整数类型
a = 15
b = a + a 混合这些类型是非法的
b = b + 5 5 是一个(未定义类型的)常量,所以这没问题
}

常量在 Go 中,也就是 constant。它们在编译时被创建,只能是数字、字符串或布尔值。

1
2
3
4
5
6
7
8
9
10
11
12
const x = 42

/* 可以使用 iota 生成枚举值。 */
const (
a = iota /* 第一个 iota 表示为 0 */
b = iota /* 第二个 iota 表示为 1 */
)

const (
a = 0
b string = "0"
)

赋值字符串的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
s := "Hello World ! "

s := "Starting part"
+ "Ending part"
上面的赋值会被转换为:
s := "Starting part" ;
+ "Ending part" ;
这是错误的语法,应当这样写:
s := "Starting part" +
"Ending part"
另一种方式是使用反引号 ` 作为原始字符串符号:
s := `Starting part
Ending part`

字符串在 Go 中是 UTF-8 的由双引号(”)包裹的字符序列。如果你使用单引号(’)则表示一个字符(UTF-8 编码)——这种在 Go 中不是 string。

一旦给变量赋值,字符串就不能修改了:在 Go 中字符串是不可变的。

运算符和内建函数

Go 支持普通的数字运算符,全部是从左到右结合的。

运算符优先级和关键字不多介绍。

控制结构

在Go 中没有 do 或者 while 循环,只有 for。

1
2
3
4
5
if x > 0 {   { 是强制的,必须同 if 在同一行
return y
} else {
return x
}

if 和 switch 接受初始化语句,通常用于设置一个(局部)变量。

1
2
3
4
if err := Chmod(0664) ; err ! = nil {   /* nil 与C 的NULL 类似 */
fmt.Printf(err) /* err 的作用域被限定在 if 内 */
return err
}

goto 语句

1
2
3
4
5
6
7
func myfunc() {
i := 0
Here: /* 这行的第一个词,以分号结束作为标签 */
println(i)
i++
goto Here /* 跳转 */
}

for 循环

Go 的 for 循环有三种形式,只有其中的一种使用分号。

1
2
3
4
5
6
7
8
9
10
11
12
13
for init ; condition ; post { }
for condition { }
for { }

sum := 0
for i := 0 ; i < 10 ; i++ {
sum += i
}

// Reverse a
for i, j := 0, len(a)-1 ; i < j ; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}

利用 break 可以提前退出循环,break 终止当前的循环。

循环嵌套循环时,可以在 break 后指定标签。用标签决定哪个循环被终止:

1
2
3
4
5
6
7
8
J: for j := 0 ; j < 5 ; j++ {
for i := 0 ; i < 10 ; i++ {
if i > 5 {
break J /* 现在终止的是 j 循环,而不是 i 的那个 */
}
println(i)
}
}

利用 continue 让循环进入下一个迭代。

1
2
3
4
list := [] string {"a", "b", "c", "d", "e", "f"}
for k, v := range list {
// 对k 和v 做想做的事情..2
}

关键字 range 可用于循环。它可以用在 slice、array、string、map 和 channel。range 是个迭代器,当被调用的时候,从它循环的内容中返回一个键值对。基于不同的内容,range 返回不同的东西。

switch

Go 的 switch 非常灵活。表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项,而如果 switch 没有表达式,它会匹配true

1
2
3
4
5
6
7
8
9
10
11
func unhex(c byte) byte {
switch {
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
}

它不会匹配失败后自动向下尝试即每个 case 后面不需要写 break, 但是可以使用 fallthrough 使其这样做。

1
2
3
4
5
6
7
8
9
10
11
switch i {
case 0: // 空的case 体
case 1:
f() // 当 i == 0 时,f 不会被调用!
}

switch i {
case 0: fallthrough
case 1:
f() // 当 i == 0 时,f 会被调用!
}

分支可以使用逗号分隔的列表。

1
2
3
4
5
6
7
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+':
return true
}
return false
}

array、slices 和 map

array

array 由[n]<type> 定义,n 标示 array 的长度,而<type> 标示希望存储的内容的类型。

1
2
3
var arr [10] int
arr[0] = 42
arr[1] = 13

数组大小是类型的一部分,不同的大小是不同的类型,因此不能改变数组大小。数组同样是值类型的,将一个数组赋值给另一个数组,会复制所有的元素。尤其是当向函数内传递一个数组的时候,它会获得一个数组的副本,而不是数组的指针。

1
2
3
a := [3]int{1, 2, 3}
a := [...]int{1, 2, 3} //Go 会自动统计元素的个数。
a := [3][2] int { {1,2}, {3,4}, {5,6} }

slice

slice 与 array 接近,但是在新的元素加入的时候可以增加长度。

slice 总是指向底层的一个 array。

slice 是一个指向 array 的指针;slice 是引用类型,这意味着当赋值某个 slice 到另外一个变量,两个引用会指向同一个 array。

1
2
sl := make([]int , 10) //创建了一个保存有 10 个元素的 slice
`

给定一个 array 或者其他 slice,一个新 slice 通过 a[I:J] 的方式创建。这会创建一个新的 slice,指向变量 a,从序号 I 开始,结束在序号 J 之前。长度为J - I。

1
2
3
4
5
6
a := [...] int {1, 2, 3, 4, 5}
s1 := a[2:4] //它包含元素3, 4;
s2 := a[1:5] //它包含元素2, 3, 4, 5;
s3 := a[:] //这是 a[0:len(a)] 的简化写法
s4 := a[:4] //这是a[0:4] 的简化写法
s5 := s2[:] //从 slice s2 创建 slice,注意 s5 仍然指向 array a

函数 append 向 slice s 追加零值或其他 x 值,并且返回追加后的新的、与 s 有相同类型的 slice。如果 s 没有足够的容量存储追加的值,append 分配一个足够大的、新的 slice 来存放原有 slice 的元素和追加的值。因此,返回的 slice 可能指向不同的底层 array。

1
2
3
4
s0 := [] i n t {0, 0}
s1 := append(s0, 2) //s1 == []int{0, 0, 2};
s2 := append(s1, 3, 5, 7) //s2 == []int{0, 0, 2, 3, 5, 7};
s3 := append(s2, s0...) //s3 == []int{0, 0, 2, 3, 5, 7, 0, 0}。

函数 copy 从源 slice src 复制元素到目标 dst,并且返回复制的元素的个数。源和目标可能重叠。元素复制的数量是 len(src) 和 len(dst) 中的最小值。

1
2
3
4
var a = [...] int {0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int , 6)
n1 := copy(s, a[0:]) //n1 == 6, s == []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:]) //n2 == 4, s == []int{2, 3, 4, 5, 4, 5}

map

一般定义map 的方法是:map[<from type>]<to type>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
monthdays := map[ string ] int {
"Jan": 31, "Feb": 28, "Mar": 31,
"Apr": 30, "May": 31, "Jun": 30,
"Jul": 31, "Aug": 31, "Sep": 30,
"Oct": 31, "Nov": 30, "Dec": 31, //逗号是必须的
}

monthdays := make(map[string]int)

fmt.Printf("%d\n", monthdays["Dec"])

year := 0
for _, days := range monthdays { 键没有使用,因此用_, days
year += days
}

//向 map 增加元素,可以这样做:
monthdays["Undecim"] = 30 //添加一个月
monthdays["Feb"] = 29 //闰年时重写这个元素

//检查元素是否存在,如果存在 ok 为 true

v, ok := monthdays["Jan"]

delete(monthdays, "Mar")

【课后练习】

go-leran

—EOF—