开始正式学习 Go,参考该网站 link

前言

Import

1
2
3
4
5
6
7
8
9
package main

import (
"fmt"
)

func main() {
fmt.Println("Hello World!")
}

类似 C 语言,Go 程序通过调起各种包(Packages)运行,在 Import 中是调起的其他包的路径。除此之外,Go 默认也是从 main 开始运行,所以 main 函数仍然是不可缺少的。

为引入这些包,需要这样写

1
2
3
4
5
6
import "fmt" 

import (
"fmt"
"main"
)

Export

在 Go 中,大写字母开头的值是视为 Exported 的,可以为外界调用,反之则不是。

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"math"
)

func main() {
fmt.Println(math.Pi)
}

例如在下述的程序中,math.pi就是不可调用的,并得到这样的报错:

1
2
./prog.go:9:14: cannot refer to unexported name math.pi
./prog.go:9:14: undefined: math.pi

Functions

函数定义基本和 C 语法相同,但仍存在一些差异:例如他的形参数据类型放置在形参名字后面,例如:

1
2
3
func add(x int, y int) int {
return x + y
}

此外,当连续多个形参使用相同的数据类型时,可以省略除了该序列中最后一个外的所有数据类型声明,例如上面的例子可以重写为:

1
2
3
func add(x, y int) int {
return x + y
}

定义多返回值的函数也是可以的,形式如下:

1
2
3
func swap(x, y string) (string, string) {
return y, x
}

此外,可以在函数头上声明返回的值的 name,从而在 return 语句时不加强调。然而这种方法并未得到提倡,因为在很长的函数中,似乎可读性并不强。

1
2
3
4
5
6
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x

return
}

上述函数应当返回(7,10),即(x,y)对应的值。

Variables

使用 var 语句声明变量。声明的方法如同函数形参,例如:

1
2
3
4
5
6
var c, python, java bool

func main() {
var i int
fmt.Println(i, c, python, java)
}

并且参数是可以定义在 package level 的,即上例中的 c, python…除了简单的声明变量,还可以初始化,变量的数据类型和初始化的数据相关。如下:

1
2
3
var i, j int = 1, 2
var c, python, java = true, false, "no!"
c, python, java := true, false, "no!"

如果未显式地声明,而仅仅是使用 var 定义,则同时定义不同的数据类型是可以接受的。此外可以使用:=代替这种情况。

数据结构

基本数据类型

1
2
3
4
5
6
7
8
bool
string
int int8 int16 int32 int64
uint ...
byte
rune (int32)
float32 float64
complex64 complex128

这些数据类型没有初始化时,会被赋给 0 值,如int对应 0,srting对应空字符串。

类型转换

类型转换使用a = T(b)完成,如下例:

1
2
3
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

在 Go 中,类型转换是必须显式声明的。

常量

常量的声明和普通变量相同,但是需要在开头加上一个 const。如下:

1
const Pi = 3.14

需要注意的是,常量不可以使用:=定义。

指针

指针的形式和 C 相似,都是通过*T表示。定义如下:

1
2
3
4
var *p int
i := 1
p = &i
fmt.Println(*p)

取地址方法及取值方法也和 C 相同。

Struct 语句

声明一个新类型方法如下:

1
2
3
4
5
6
type Vertex struct {
X int
Y int
}
v := Vertex{1, 2}
x := v.X

struct 中的值可以用.获得。也可以通过指针的方法:

1
2
3
v := Vertex(1, 2)
p := &v
p.X = 1e9

理论上需要通过(*p).X访问 X 值,然而 Go 允许,仅仅使用p.X直接对其进行访问。

数组

1
2
3
4
5
var a [2]string
a[0] = "hello"
a[1] = "world"

primes := [6]int{2, 3, 5, 7, 11}

Go 通过这种方式来定义数组。他的访问和赋值都和 C 相同。Array 的长度是固定的,不可以在运行过程中修改的。在上述例子中存在一个类似 C 的初始化方法,在这个初始化中,尽管声明了 6 个整形的空间,但仅仅给了 5 个初始值,则最后的一个元素会被初始化为 0。

Slide

与之相关的是一个特殊的数据结构 Slide:

1
2
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]

这个的表现和 C 语言是完全一样的,然而和预期不同的是,这个 Slide 并不储存数据,而是仅仅类似于地址和指针一样的东西。对于 Slide 的更改会导致对于其截取的原数组的更改,并且其他包含相同元素的 Slide 也会立刻应用这些更改(因为他们只是取地址)。

对于 Slide 的元素截取和 Python 一样,可以使用:符号表示截取范围。

1
2
3
4
5
var a [10]int
a[0:10]
a[:10]
a[0:]
a[:]

以上四种表达是等价的。Slide 存在 length 和 capacity 两个变量。前者为 Slide 包含的元素数量,后者为 Slide 所指向的 Array,从 Slide 包含的第一个元素开始计算的元素数。这两个值分别可以通过函数len()cap()获取。

特殊的是,我们可以 extend Slide 的范围。对于以下的语句:

1
2
3
s := []int{2, 3, 5, 7, 11, 13}
s = s[:0]
s = s[:4]

其并不是在 Slide s 中重新获得新的 Slide,而是在完成一次 Re-sldie。即s = s[:4]语句是针对最开始的长度为 6 的数组进行的。这仅仅发生在,数组的长度右端超过了 Slide 的长度并小于 Slide 的容量时才会发生,称为 Extend。

Nil Slide

空数组:

1
2
3
4
5
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}

Append

类似 Python 的 list,Go 提供了一个可变长的数组。这个数组在元素超出容量时会自动再分配一个空间,然后返回的地址指向一个新的数组。其使用如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func main() {
var s []int
printSlice(s)

// append works on nil slices.
s = append(s, 0)
printSlice(s)

// The slice grows as needed.
s = append(s, 1)
printSlice(s)

// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}

Range

类似 Python 的,他的 For 循环也可使用 Range 操作。Range 在每一个迭代返回一个计数器和一个对象对应的值。例如:

1
2
3
4
5
6
7
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}

如果不希望获得对应的值,则可以使用_代替位置。如果只希望使用 index,则只显式地记下一个值即可。如:

1
for i := range pow {}

Map

Map 的 0 值为nil,一个 nil 的 Map 既没有 key 也不能增加新的 key。Map 可以使用 make 函数初始化:

1
m = make(map[string]Vertex)

其中,string 是键值,Vertex 是 Map 指向的对象。Map 的访问是通过键值访问的,这个设定与 C 及 Python 都一致。下面是一个更完整的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Vertex struct {
Lat, Long float64
}

var m map[string]Vertex

func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}

此外,Map 还可以如此初始化:

1
2
3
4
5
6
7
8
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}

或者干脆省略 Vertex 声明,改为:

1
2
3
4
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}

对 Map 的其他操作

1
2
3
4
delete(m, key) // 从字典m中删去Key及其对应的值。
elem, ok := m[key]
// 如果ok == true,则字典m中存在键值key;
// 反之则不存在,并返回0值elem

Closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}

func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}

在这个例子里,函数 adder()相当于一个“函数模型”,调用这个模型获得的是一个函数的实体即 pos, neg。而这个 sum 是这个函数模型实体的参数,所以会逐渐累加,其效果如同 C 中的 static 变量。

语法

For 语句

Go 仅含有这样一种循环语句。如下:

1
2
3
4
5
6
7
8
9
10
11
for i := 0; i < 10; i++ {
sum += i
}

for ; sum < 1000; {
sum += sum
}

for sum < 1000 {
sum += sum
}

这个和 C 的语法非常像。同样的,如果不需要这三要素中的某一部分,可以完全空出来,如同 C 的操作。如上第二个或第三个 For 循环。而在 Go 中,并没有专门的 While 语句,有上述第三种 For 循环代替。

更直接的,如果希望写出一个死循环,则可以如此写:

1
2
for {
}

If 语句

和 For 语句一样,成分不需要使用括号包含。但是大括号是需要的,如下:

1
2
3
if x < 0 {
return sqrt(-x) + "i"
}

类似 For 语句,If 语句可以在条件前增加一个初始化语句。该初始化语句的内容,在后面大括号范围内有效。例如下面的写法是有效的:

1
2
3
4
5
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g", v)
}

Switch 语句

Switch 语句和 C 语言相似,然而,Go 的 Switch 语句不会运行后面所有的部分,而仅仅运行满足条件的语句。如下:

1
2
3
4
5
6
7
8
9
10
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}

以上的程序仅会运行其中的一项,而非所有。同时这个初始化语句和 If 语句相同,是可以省略的。与 C 不同的是,这里的 Case 语句不需要是 Const,同时也不需要是整型。同时,Switch 是从上往下执行的,他会在任何一个满足条件的 Case 中停下并不再考察后面的 Case。

特殊的,我们可以声明一个不带有条件的 Switch 语句。此时,这个 Switch 语句的含义是传递一个 True 值。然而我们在 Case 的声明时,仅需要返回 true 或者 false 即可。换句话说,我们可以将其作为一个 if-then-else 的链使用。如下:

1
2
3
4
5
6
7
8
9
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}

Defer 语句

Defer 语句会暂停现在的所有执行,直到它环境里的其他语句执行结束后才会执行(即一个栈)。并且按照后定义的 Defer 先执行的顺序执行。

1
2
3
4
5
6
7
func main() {
defer fmt.Println("world")

defer fmt.Println("!!")

fmt.Println("hello")
}

例如上式的输出为hello \n !! \n world

Methods

Go 没有类,但是可以在类上定义方法。这类方法的定义和其他函数有些许不同,需要在func关键词到方法名间增加一个 receiver。形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
type Vertex struct {
X, Y float64
}

func (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())
}

这个方法也能为仅仅数据类型构造 Methods,然而它不能为其他 package 里的数据类型如此操作,或者对内建数据类型数次操作。如需要针对内建数据类型,则需要重定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}

func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

然而,使用这样的 Receiver,不能对其含有的值进行操作。在我们需要操作其内容的时候,我们需要使用指针的 Receiver,如下:

1
2
3
4
5
6
7
8
9
10
11
12
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}

func main() {
v := Vertex{3, 4}
v.Scale(10)

对于 Methods 来说,使用实体或者指向实体的指针操作实体中的参数都是可以的。

Interface

Interface 一种特殊的数据类型,它是一系列 Methods 签名的集合。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Abser interface {
Abs() float64
}

func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}

a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser

// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
// a = v

fmt.Println(a.Abs())
}

我们说一个 Type 实现了一个 Interface,如果它存在 Interface 中声明的函数的实现。这个实现不需要显式地声明,他们的名称相同即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type I interface {
M()
}

type T struct {
S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}

func main() {
var i I = T{"hello"}
i.M()
}

在上述例子中,存在 Interface I,Type T。其中 T 实现了 Methods M,则可以说是 Type T 实现了 Interface I。这样的 Interface 让实现和使用解耦,我们只需要关心 Interface 或者其实现,而不需要两者兼顾。

从更根本的眼光来看,Interface 是一个元组:(value, type)。它保存一个类型及其对应的值。对 Interface 调用一个 Method,相当于调用其代表的 type 对应的 Method。

关于 nil 值

存在这样的情况:Interface 对应的值不存在。但是这种情况的 Interface 并不为空,其保存了对应的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (t *T) M() {
if t == nil {
fmt.Println("<nil>")
return
}
fmt.Println(t.S)
}

func main() {
var i I

var t *T
i = t
i.M()

i = &T{"hello"}
i.M()
}

在上面的例子中,t 并没有被初始化,所以对应的值是 nil 的(因为它甚至只是一个指针。)。而若去掉代码i = t,则 Interface i 就是一个空 Interface,此时调用M()就会报错。

关于空 Interface

对于没有声明 Methods 的 Interface 被称为“Empty Interface”。这样的 Interface 可以指向任何一个数据类型。这样的情况被用在处理不确定数据类型的时候。

关于 Interface 的 Type

我们有的时候需要知道 Interface 指向的 Type 是什么,此时我们这样调用:

1
t := i.(T)

如果 Type T 和 Interface i 的值完全相同,则会返回 i 对应的实体。如果类型不同则会报错。为了得知类型的同时不 raise error,我们采用下面的方法:

1
t, ok := i.(T)

如果 ok 为真,则意味着两种数据类型相同,并返回值 t;若为 false,则数据类型不同,且会返回 T 的 0 值。在这个设定下,我们可以这样写:

1
2
3
4
5
6
7
8
9
10
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}

这个语句比较 v 的类型。

Error

Error 是一类内建的 Interface。

1
2
3
4
5
6
7
type error interface {
Error() string
}

func Sqrt(x float64) (float64, error) {
return 0, nil
}

所有的函数都会返回一个 error 值,若 error 值等于 nil,则表示成功运行。他可以像上述代码的后半部分那样使用。

Concurrency

Goroutines

一个 Goroutines 是 Go 驱动的一个线程。以下语句会调起一个新线程。

1
go f(x, y, z)

f, x, y, z 的检验都是发现在当前的 goroutine 的,而调起的新函数则会运行在一个新的 goroutine 中。不同的 goroutine 运行在一个相同的地址空间中,所以他们的值是共享的。

Channels

Channels 是一种类型相关的导管。我们可以通过这个东西接受或者发送值,通过运算符:<-。类似下面的写法:

1
2
3
ch <- v    // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.

Channels 的定义方法和 Map 及 Slide 类似,需要借助 make 函数,定义方法如下:

1
ch := make(chan int)

默认的情况下,这两个操作都会阻塞当前的 Channels。这样可以完成同步,并不需要显式地锁住线程。下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}

func main() {
s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c

fmt.Println(x, y, x+y)
}

Buffered Channels

Channels 可以存在 Buffer。通过下面的定义方法:

1
ch := make(chan int, 100)

以上定义了 100 个 Buffer。Send 命令会 Block 仅有可能为 Buffer 已满;Receive 命令会 Block 仅有可能为 Buffer 为空。这两种情况会导致死锁错误。

close

Send 的过程中,可以主动关闭 Channel,即不再发送数据;Receive 可以检验某个 Channel 是否被关闭。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}

func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
v, ok := <-c
}

事实上,Close 在这里不是必要的,除非 Receiver 需要明确地知道数据发送已经结束并终结进程。