Hello Go

我们先创建一个文件 hello.go:

package main
 
import "fmt"
 
func main() {
    fmt.Printf("hello Golang\n");
}

执行此程序:

go run hello.go

Golang 程序由包(packages)组成,程序从 main 包开始运行:
复制代码 代码如下:

package main
此语句表示此文件属于 main 包(多个源文件可以属于同一个包)。import 语句后接上包所在的路径(被叫做包路径或导入路径),一个目录中放置一个包,通常的做法是,目录名和包名相同:

import (
    "fmt"
    "math/rand"
)

这里的 “fmt” 和 “math/rand” 为包路径(导入路径)。上面的 import 语句也可以这样写:

import "fmt"
import "math/rand"

我们导入了包之后,就可以通过 “包名.name” 来引用导出的 name 了,例如:

import "fmt"
// fmt 包导出了 Printf
fmt.Printf("hello Golang\n");

在 Golang 中,一个名字如果首字母大写则表示此名字被导出。

函数

package main
 
import "fmt"
 
func add(x int, y int) int {
    return x + y
}
 
func main() {
    fmt.Println(add(42, 13))
}

需要注意的就是,变量名在类型之前,这和很多语言都不一样。另外 x int, y int 也可以写为 x, y int:

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

函数可以返回多个值:

package main
 
import "fmt"
 
func swap(x, y string) (string, string) {
    return y, x
}
 
func main() {
    a, b := swap("hello", "world")
    fmt.Println(a, b)
}

返回值可以被指定变量名,并且像变量一样使用:

package main
 
import "fmt"
 
func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}
 
func main() {
    fmt.Println(split(17))
}

可以看到 split 函数直接使用 return 语句而不用带参数。

变量

变量的声明使用 var 语句:

var i int
var c, python, java bool

变量在声明时可以进行初始化:

var x, y int = 1, 2
var i, j = true, "hello"

我们看到,初始化时可以指定也可以不指定变量类型。
按照 Golang 的语法,在函数外的任何结构(construct)都通过一个关键字开始,例如变量使用 var 关键字开始,函数使用 func 关键字开始。在函数内,变量赋值可以使用 := 操作符:

package main
 
func main() {
    var x, y int = 1, 2
    i, j := true, "hello"
}

:= 操作符左边为变量,右边为值。

数据类型

基本数据类型:

  1. bool
  2. string
  3. int int8 int16 int32 int64
  4. uint uint8 uint16 uint32 uint64
  5. uintptr
  6. byte(等价于 uint8)
  7. rune(等价于 int32,用于表示一个 unicode code point)
  8. float32 float64
  9. complex64 complex128
    类型转换使用表达式 T(v),含义为将 v 转换为类型 T:
var i int = 42
var f float64 = float64(i)
 
i := 42
f := float64(i)

类型转换总需要显式的进行。
使用 const 来声明常量:

const Pi = 3.14
 
const (
    Big = 1 << 100
    Small = Big >> 99
)

控制语句

for 语句

Golang 使用(且只使用)for 来进行循环(没有 while 语句):

package main
 
func main() {
    sum := 0
    
    for i := 0; i < 10; i++ {
        sum += i
    }
    
    // 这种写法等价于 C/C++ 等语言中的 while 语句
    for sum < 1000 {
        sum += sum
    }
}

区别于 C/C++ 等语言,使用 for 语句时不需要 () 并且 {} 是必须的(后面谈到的 if、switch 在此语法处理上也是一样的)。如果需要无限循环,那么使用:

for {
}

if 语句

if 语句可以在执行条件判断前带一个语句(这常被叫做 if 带上一个短语句),此语句中变量的生命周期在 if 语句结束后结束。例如:

package main
 
import (
    "fmt"
    "math/rand"
)
 
func main() {
    if n := rand.Intn(6); n <= 2 {
        fmt.Println("[0, 2]", n)
    } else {
        fmt.Println("[3, 5]", n)
    }
 
    // 这里开始无法使用变量 n
}

switch

package main
 
import (
    "fmt"
    "runtime"
)
 
func main() {
    fmt.Print("Go runs on ")
    // switch 类似 if 可以带上一个短语句
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

不像 C/C++ 等语言,Golang 中无需使用 break 语句来跳出 switch。另外,switch 可以没有条件:

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    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 语句能够将一个函数调用加入一个列表中(这个函数调用被叫做 deferred 函数调用),在当前函数调用结束时调用列表中的函数。范例:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()
 
    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()
 
    return io.Copy(dst, src)
}

deferred 函数调用按先进后出的顺序执行:

package main
 
import "fmt"
 
func main() {
    for i := 0; i < 5; i++ {
        // 输出 43210
        defer fmt.Print(i)
    }
}

结构(structs)

结构是一个域的集合:

package main
 
import "fmt"
 
type Vertex struct {
    X int
    Y int
}
 
func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v)
}

Golang 中是存在指针的,但是指针不支持算术运算:

p := Vertex{1, 2} // {1, 2} 为 struct literal
q := &p // q 类型为 *Vertex
q.X = 2 // 直接访问域 X

就像上面看到的,struct 的 literal 由 {} 包裹,在 struct literal 中我们可以使用 Name: 这样的语法来为特定域设置值:

type Vertex struct {
    X, Y int
}
 
r := Vertex{X: 3} // 这时候 Y 为 0

new 函数

我们可以通过表达式 new(T) 分配一个被初始化为 0 且类型为 T 的值,并且返回指向此值的指针,用法如下:

var p *T = new(T)
p := new(T)

更详尽的例子:

package main
 
import "fmt"
 
type Vertex struct {
    X, Y int
}
 
func main() {
    v := new(Vertex)
    fmt.Println(v)
    v.X, v.Y = 11, 9
    fmt.Println(v)
}

数组和 slice

[n]T 在 Golang 中是一个类型(就像 *T 一样),表示一个长度为 n 的数组其元素类型为 T。范例:

package main
 
import "fmt"
 
func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
}

注意,数组长度无法被改变。
slice 是一个数据结构,其指向一个数组某个连续的部分。slice 用起来很像数组。[]T 为 slice 类型,其中元素类型为 T:

package main
 
import "fmt"
 
func main() {
    // 构建一个 slice
    p := []int{2, 3, 5, 7, 11, 13}
    fmt.Println("p ==", p)
 
    for i := 0; i < len(p); i++ {
        fmt.Printf("p[%d] == %d\n", i, p[i])
    }
}

表达式 s[lo:hi] 用于创建一个 slice,新创建的 slice 的元素为 s 中 [lo, hi) 位置的元素。
创建 slice 使用 make 函数(而不是用 new 函数,因为需要设置额外的参数来控制 slice 的创建):

// len(a) 为 5
a := make([]int, 5)

这里 make 函数会创建一个数组(其元素初始化为 0)并返回一个 slice 指向此数组。make 可以带第三个参数,用于指定容量:

// len(b) 为 0
// cap(b) 为 5
b := make([]int, 0, 5)
 
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4

一个没有值的 slice 是 nil,长度和容量都为 0。

package main
 
import "fmt"
 
func main() {
    var z []int
    fmt.Println(z, len(z), cap(z))
    if z == nil {
        fmt.Println("nil!")
    }
}

range

range 被用在 for 中来迭代一个 slice 或者一个 map:

package main
 
import "fmt"
 
var s = []int{1, 2, 3}
 
func main() {
    for i, v := range s {
        fmt.Println(i, v)
    }
 
    // 只需要值,使用 _ 忽略索引
    for _, v := range s {
        fmt.Println(v)
    }
 
    // 只需要索引
    for i := range s {
        fmt.Println(i)
    }
}

map

map 用于映射 key 到 value(值)。map 可以通过 make 来创建(而非 new):

package main
 
import "fmt"
 
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 iteral 很像 struct literal:

var m = map[string]Vertex{
    // 这里 Vertex 可以省略不写
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

使用 [] 来访问 map 中的值,使用 delete 来删除 map 中的值:

m[key] = elem
elem = m[key]
delete(m, key)

如果需要检查 map 中某 key 是否存在使用:

elem, ok = m[key]

elem 表示 key 的值(key 不存在时,elem 为 0),ok 表示 key 是否存在。

闭包

Golang 中函数也是一个值(就像 int 值一样),且函数可以是一个闭包。闭包是一个引用了外部变量的函数。看一个例子:

package main
 
import "fmt"
 
func adder() func(int) int {
    sum := 0
    // 返回一个闭包,此闭包引用了外部变量 sum
    return func(x int) int {
        sum += x
        return sum
    }
}
 
func main() {
    a := adder()
    fmt.Println(a(9527))
}