# Go 语言基础

# 常量

常量使用关键字 const 声明,下面有几个例子:

const limit = 512
const top uint16 = 1421
const Pi float64 = 3.1415926
const x,y int = 1,3 //多重赋值
1
2
3
4
package main

import "fmt"

func main() {

    // 字符串可以通过 `+` 连接。
    fmt.Println("go" + "lang")

    // 整数和浮点数
    fmt.Println("1+1 =", 1+1)
    fmt.Println("7.0/3.0 =", 7.0/3.0)

    // 布尔型,还有你想要的逻辑运算符。
    fmt.Println(true && false)
    fmt.Println(true || false)
    fmt.Println(!true)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Go 支持字符、字符串、布尔和数值 _常量_ 。
package main

import "fmt"
import "math"

// `const` 用于声明一个常量。
const s string = "constant"

func main() {
    fmt.Println(s)

    // `const` 语句可以出现在任何 `var` 语句可以出现
    // 的地方
    const n = 500000000

    // 常数表达式可以执行任意精度的运算
    const d = 3e20 / n
    fmt.Println(d)

    // 数值型常量是没有确定的类型的,直到它们被给定了一个
    // 类型,比如说一次显示的类型转化。
    fmt.Println(int64(d))

    // 当上下文需要时,一个数可以被给定一个类型,比如
    // 变量赋值或者函数调用。举个例子,这里的 `math.Sin`
    // 函数需要一个 `float64` 的参数。
    fmt.Println(math.Sin(n))
}
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
26
27
28
29

Go 的常量定义可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么该常量就是无类型常量,也叫字面常量。

当需要设置多个常量的时候,不必重复使用 const 关键字,可以使用以下语法:

const (
    Cyan = 0
    Black = 1
    White = 2
)
1
2
3
4
5

Go 语言还预定义了这些常量:truefalseiota

iota 是一个可以被编译器修改的常量,在 const 关键字出现时被重置为 0,在下一个 const 出现之前,每出现一次 iota,其所代表的数字自动加 1。下面通过一个例子讲解 iota 的用法:

const (
    a = iota  //a == 0
    b = iota  //b ==1
    c = iota  //c == 2
)

const d = iota //d==0,因为const的出现,iota被重置为0
1
2
3
4
5
6
7

# 变量

变量是所有语言最基本和最重要的组成部分。Go 语言引入了关键字 var 对变量进行声明,也可以使用 := 来对变量直接进行初始化,Go 编译器会自动推导出该变量的类型,这大大的方便了开发者的工作。但是需要注意的是 := 左侧的变量不能是已经被声明过的,否则会导致编译器错误。

以下是 Go 声明和初始化变量的各种方法:

var a int
var b string
var c float64
var d [5] int  //数组
var e [] int   //数组切片
var f * int    //正确
var v1 int = 5 //正确
var v2 = 5     //正确,编译器自动推导出V2类型
v3 := 5        //正确,编译器自动推导出V3的类型
1
2
3
4
5
6
7
8
9

# 多重赋值

Go 语言提供了大多数语言不支持的多重赋值,这使得变量的交换变得十分简单。下面通过一个例子来了解 Go 语言的多重赋值:

i := 2
j := 3
i, j = j, i  //交换i和j的值,此时i == 3,j == 2
1
2
3

这样的方式可以一行代码实现变量的交换,明显的减少代码的行数,而不需要像 C/C++ 那样引入一个中间变量。

package main

import "fmt"

func main() {

    // `var` 声明 1 个或者多个变量。
    var a string = "initial"
    fmt.Println(a)

    // 你可以申明一次性声明多个变量。
    var b, c int = 1, 2
    fmt.Println(b, c)

    // Go 将自动推断已经初始化的变量类型。
    var d = true
    fmt.Println(d)

    // 声明变量且没有给出对应的初始值时,变量将会初始化为
    // _零值_ 。例如,一个 `int` 的零值是 `0`。
    var e int
    fmt.Println(e)

    // `:=` 语句是申明并初始化变量的简写,例如
    // 这个例子中的 `var f string = "short"`。
    f := "short"
    fmt.Println(f)
}
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
26
27
28

# 数据类型

# 整型

Go 语言提供了 11 种整型,如下列表所示。

类型 说明
byte 等同于 uint8
int 依赖于不同平台下的实现,可以是 int32 或者 int64
int8 [-128, 127]
int16 [-32768, 32767]
int32 [-2147483648, 2147483647]
int64 [-9223372036854775808, 9223372036854775807]
rune 等同于 int32
uint 依赖于不同平台下的实现,可以是 uint32 或者 uint64
uint8 [0, 255]
uint16 [0, 65535]
uint32 [0, 4294967295]
uint64 [0, 18446744073709551615]
uintptr 一个可以恰好容纳指针值的无符号整型(对 32 位平台是 uint32, 对 64 位平台是 uint64)

C 语言中我们可以通过 sizeof 操作符查看类型的字节长度,在 Go 语言中可以通过 unsafe.Sizeof 函数进行,创建源文件 type_length.go,输入以下代码:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    a := 12
    fmt.Println("length of a: ", unsafe.Sizeof(a))
    var b int = 12
    fmt.Println("length of b(int): ", unsafe.Sizeof(b))
    var c int8 = 12
    fmt.Println("length of c(int8): ", unsafe.Sizeof(c))
    var d int16 = 12
    fmt.Println("length of d(int16): ", unsafe.Sizeof(d))
    var e int32 = 12
    fmt.Println("length of e(int32): ", unsafe.Sizeof(e))
    var f int64 = 12
    fmt.Println("length of f(int64): ", unsafe.Sizeof(f))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

以上代码中,首先声明了目前的源文件属于 main 包,然后导入了 fmtunsafe 包,fmt 包用于格式化字符串,unsafe 包含了用于获取 Go 语言类型信息的方法。然后在 main() 函数中,我们分别声明了几种类型的整型变量,并通过 unsafe.Sizeof 方法获取该类型的字节长度。最后我们通过以下方法运行 type_length.go,同时打印出了输出:

$ go run type_length.go
length of a:  8
length of b(int):  8
length of c(int8):  1
length of d(int16):  2
length of e(int32):  4
length of f(int64):  8
1
2
3
4
5
6
7

# 浮点型

Go 语言提供了两种浮点类型和两种复数类型,具体如下:

类型 说明
float32 ±3.402 823 466 385 288 598 117 041 834 845 169 254 40x1038 计算精度大概是小数点后 7 个十进制数
float64 ±1.797 693 134 862 315 708 145 274 237 317 043 567 981x1038 计算精度大概是小数点后 15 个十进制数
complex32 复数,实部和虚部都是 float32
complex64 复数,实部和虚部都是 float64

# 布尔类型

Go 语言提供了内置的布尔值 truefalse。Go 语言支持标准的逻辑和比较操作,这些操作的结果都是布尔值。值得注意的地方是可以通过 !b 的方式反转变量 b 的真假。需要注意的是布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。实例代码如下:

var a bool
a = true
b := (2 == 3) //b也会被推导为bool类型

//错误示范
var b bool
b = 1 //编译错误
b = bool(1) //编译错误
1
2
3
4
5
6
7
8

# 字符串

Go 语言中的字符串是 UTF-8 (opens new window) 字符的一个序列(当字符为 ASCII 码时则占用 1 个字节,其它字符根据需要占用 2-4 个字节)。UTF-8 是被广泛使用的编码格式,是文本文件的标准编码,其它包括 XML 和 JSON 在内,也都使用该编码。由于该编码对占用字节长度的不定性,Go 中的字符串也可能根据需要占用 1 至 4 个字节,这与其它语言如 C++、Java 或者 Python 不同。Go 这样做的好处是不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

Go 语言中字符串的可以使用双引号 (") 或者反引号 (```) 来创建。双引号用来创建可解析的字符串字面量,所谓可解析的是指字符串中的一些符号可以被格式化为其他内容,如 \n 在在输出时候会被格式化成换行符,如果需要按照原始字符输出必须进行转义。而反引号创建的字符串原始是什么样,那输出还是什么,不需要进行任何转义。以下是几个例子:

t1 := "\"hello\""             //内容: "hello"
t2 := `"hello"`               //内容:和t1一致
t3 := "\u6B22\u8FCE"          //内容:欢迎
1
2
3

Go 语言中的部分转义字符如下表所示:

转义字符 含义
\\ 表示反斜线
\' 单引号
\" 双引号
\n 换行符
\uhhhh 4 个 16 进制数字给定的 Unicode 字符

在 Go 语言中单个字符可以使用单引号 (') 来创建。之前的课程中,我们有学习过 rune 类型,它等同于 int32,在 Go 语言中,一个单一的字符可以用一个单一的 rune 来表示。这也是容易理解的,因为 Go 语言的字符串是 UTF-8 编码,其底层使用 4 个字节表示,也就是 32 bit。

在 Go 语言中,字符串支持切片操作,但是需要注意的是如果字符串都是由 ASCII 字符组成,那可以随便使用切片进行操作,但是如果字符串中包含其他非 ASCII 字符,直接使用切片获取想要的单个字符时需要十分小心,因为对字符串直接使用切片时是通过字节进行索引的,但是非 ASCII 字符在内存中可能不是由一个字节组成。如果想对字符串中字符依次访问,可以使用 range 操作符。另外获取字符串的长度可能有两种含义,一种是指获取字符串的字节长度,一种是指获取字符串的字符数量。字符串支持以下操作:

语法 描述
s += t 将字符串 t 追加到 s 末尾
s + t 将字符串 s 和 t 级联
s[n] 从字符串 s 中索引位置为 n 处的原始字节
s[n:m] 从位置 n 到位置 m-1 处取得的字符(字节)串
s[n:] 从位置 n 到位置 len(s)-1 处取得的字符(字节)串
s[:m] 从位置 0 到位置 m-1 处取得的字符(字节)串
len(s) 字符串 s 中的字节数
len([]rune(s)) 字符串 s 中字符的个数,可以使用更快的方法 utf8.RuneCountInString()
[]rune(s) 将字符串 s 转换为一个 unicode 值组成的串
string(chars) chars 类型是 []rune 或者 []int32, 将之转换为字符串
[]byte(s) 无副本的将字符串 s 转换为一个原始的字节的切片数组,不保证转换的字节是合法的 UTF-8 编码字节

让我们尝试一个例子,创建源文件 string_t.go,然后输入以下源代码:

package main

import (
    "fmt"
)

func main() {
    t0 := "\u6B22\u8FCE\u6765\u5230" // t0内容:欢迎来到
    t1 := "\u5B9E\u9A8C\u697C"       // t1内容:实验楼
    t2 := t0 + t1
    for index, char := range t2 {
        fmt.Printf("%-2d    %U      '%c'    %X      %d\n",
            index, char, char, []byte(string(char)), len([]byte(string(char))))
    }
    fmt.Printf("length of t0: %d, t1: %d, t2: %d\n", len(t0), len(t1), len(t2))
    fmt.Printf("content of t2[0:2] is: %X\n", t2[0:2])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后通过以下方式运行,在这里一起显示了程序的输出:

$ go run string_t.go
0     U+6B22      '欢'    E6ACA2      3
3     U+8FCE      '迎'    E8BF8E      3
6     U+6765      '来'    E69DA5      3
9     U+5230      '到'    E588B0      3
12    U+5B9E      '实'    E5AE9E      3
15    U+9A8C      '验'    E9AA8C      3
18    U+697C      '楼'    E6A5BC      3
length of t0: 12, t1: 9, t2: 21
content of t2[0:2] is: E6AC
1
2
3
4
5
6
7
8
9
10

说明:

通过前面的课程我们知道通过 \uhhhh 的方式我们可以通过创建 Unicode 字符。

在以上程序中,首先通过 := 符号创建了变量 t0,其值为 \u6B22\u8FCE\u6765\u5230,是 欢迎来到 中文字符的 unicode 编码,然后以同样的方式创建了变量 t1,其值为 实验楼,然后通过 + 操作符将 t0t1 拼接赋值给 t2。然后我们通过 range 操作符号对 unicode 字符串 t2 中的每一个 unicode 字符依次操作,我们这里只是简单的打印出每个字符在 t2 中的位置,每个字符的 unicode 码值,每个字符的字面量,每个字符的十六进制值,以及每个字符的字节长度。

这里我们使用 fmt 包种支持的格式指令,如果读者学习过 C 语言的话就一目了然。接着,我们通过 len 操作符计算出了每个字符串的字节长度。最后,我们使用切片访问了字符串 t2 的第 0-1 个字节,也就是前两个字节,其内容为 E6AC。前面我们说到不能使用切片的方式访问非 ASCII 字符串中的字符,原因在这里一目了然。字符 其底层使用了三个字节表示,内容是 E6ACA2,如果只是简单的使用切片(只取切片中的一项)访问的是不能访问到整个字符的,因为字符的切片是通过字节数来索引的。

# 格式化字符串

Go 语言标准库中的 fmt 包提供了打印函数将数据以字符串形式输出到控制台,文件,其他满足 io.Writer 接口的值以及其他字符串。目前为止我们使用了 fmt.Printffmt.Println,对于前者的使用,就像 C 语言中的 printf 函数一样,我们可以提供一些格式化指令,让 Go 语言对输出的字符串进行格式化。同样的我们可以使用一些格式化修饰符,改变格式化指令的输出结果, 如左对齐等。常用的格式化指令如下:

格式化指令 含义
%% % 字面量
%b 一个二进制整数,将一个整数格式化为二进制的表达方式
%c 一个 Unicode 的字符
%d 十进制数值
%o 八进制数值
%x 小写的十六进制数值
%X 大写的十六进制数值
%U 一个 Unicode 表示法表示的整形码值,默认是 4 个数字字符
%s 输出以原生的 UTF-8 字节表示的字符,如果 console 不支持 UTF-8 编码,则会输出乱码
%t 以 true 或者 false 的方式输出布尔值
%v 使用默认格式输出值,或者使用类型的 String() 方法输出的自定义值,如果该方法存在的话
%T 输出值的类型

常用的格式化指令修饰符如下:

  • 空白 如果输出的数字为负,则在其前面加上一个减号 -。如果输出的是整数,则在前面加一个空格。使用 %x 或者 %X 格式化指令输出时,会在结果之间添加一个空格。例如 fmt.Printf("% X", "实") 输出 E5 AE 9E。

  • #
    
    1
    • %#o 输出以 0 开始的八进制数据。
    • %#x 输出以 0x 开始的十六进制数据。
  • + 让格式化指令在数值前面输出 + 号或者 - 号,为字符串输出 ASCII 字符(非 ASCII 字符会被转义),为结构体输出其字段名。

  • - 让格式化指令将值向左对齐(默认值为像右对齐)。

  • 0 让格式指令以数字 0 而非空白进行填充。

让我们练习一下,创建源文件 fmt_t.go,输入以下源码:

package main

import (
    "fmt"
)

func main() {
    text := "\u5B9E\u9A8C\u697C"
    fmt.Printf("bool output:\n%t\n%t\n\n", true, false)
    fmt.Println("number output, origin value: 64")
    fmt.Printf("|%b|%8b|%-8b|%08b|% 8b|\n", 64, 64, 64, 64, 64)
    fmt.Printf("|%x|%8x|%-8x|%08X|% 8X|\n\n", 64, 64, 64, 64, 64)
    fmt.Println(`text output, origin value: \u5B9E\u9A8C\u697C`)
    fmt.Printf("content: %s\n", text)
    fmt.Printf("hex value: % X\nUnicode value: ", text)
    for _, char := range text {
        fmt.Printf("%U ", char)
    }
    fmt.Println()
    bytes := []byte(text)
    fmt.Printf("value of bytes: %s\n", bytes)
    fmt.Printf("hex value of bytes: % X\n", bytes)
    fmt.Printf("origin value of bytes: %v\n", bytes)

}
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

运行代码,输出如下:

$ go run fmt_t.go
bool output:
true
false

number output, origin value: 64
|1000000| 1000000|1000000 |01000000| 1000000|
|40|      40|40      |00000040|      40|

text output, origin value: \u5B9E\u9A8C\u697C
content: 实验楼
hex value: E5 AE 9E E9 AA 8C E6 A5 BC
Unicode value: U+5B9E U+9A8C U+697C
value of bytes: 实验楼
hex value of bytes: E5 AE 9E E9 AA 8C E6 A5 BC
origin value of bytes: [229 174 158 233 170 140 230 165 188]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

代码一目了然,就不详细解释了。

Go 在传统的printf 中对字符串格式化提供了优异的支持。这里是一些基本的字符串格式化的人物的例子。

package main

import "fmt"
import "os"

type point struct {
    x, y int
}

func main() {

    // Go 为常规 Go 值的格式化设计提供了多种打印方式。例
    // 如,这里打印了 `point` 结构体的一个实例。
    p := point{1, 2}
    fmt.Printf("%v\n", p)

    // 如果值是一个结构体,`%+v` 的格式化输出内容将包括
    // 结构体的字段名。
    fmt.Printf("%+v\n", p)

    // `%#v` 形式则输出这个值的 Go 语法表示。例如,值的
    // 运行源代码片段。
    fmt.Printf("%#v\n", p)

    // 需要打印值的类型,使用 `%T`。
    fmt.Printf("%T\n", p)

    // 格式化布尔值是简单的。
    fmt.Printf("%t\n", true)

    // 格式化整形数有多种方式,使用 `%d`进行标准的十进
    // 制格式化。
    fmt.Printf("%d\n", 123)

    // 这个输出二进制表示形式。
    fmt.Printf("%b\n", 14)

    // 这个输出给定整数的对应字符。
    fmt.Printf("%c\n", 33)

    // `%x` 提供十六进制编码。
    fmt.Printf("%x\n", 456)

    // 对于浮点型同样有很多的格式化选项。使用 `%f` 进
    // 行最基本的十进制格式化。
    fmt.Printf("%f\n", 78.9)

    // `%e` 和 `%E` 将浮点型格式化为(稍微有一点不
    // 同的)科学技科学记数法表示形式。
    fmt.Printf("%e\n", 123400000.0)
    fmt.Printf("%E\n", 123400000.0)

    // 使用 `%s` 进行基本的字符串输出。
    fmt.Printf("%s\n", "\"string\"")

    // 像 Go 源代码中那样带有双引号的输出,使用 `%q`。
    fmt.Printf("%q\n", "\"string\"")

    // 和上面的整形数一样,`%x` 输出使用 base-16 编码的字
    // 符串,每个字节使用 2 个字符表示。
    fmt.Printf("%x\n", "hex this")

    // 要输出一个指针的值,使用 `%p`。
    fmt.Printf("%p\n", &p)

    // 当输出数字的时候,你将经常想要控制输出结果的宽度和
    // 精度,可以使用在 `%` 后面使用数字来控制输出宽度。
    // 默认结果使用右对齐并且通过空格来填充空白部分。
    fmt.Printf("|%6d|%6d|\n", 12, 345)

    // 你也可以指定浮点型的输出宽度,同时也可以通过 宽度.
    // 精度 的语法来指定输出的精度。
    fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)

    // 要左对齐,使用 `-` 标志。
    fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)

    // 你也许也想控制字符串输出时的宽度,特别是要确保他们在
    // 类表格输出时的对齐。这是基本的右对齐宽度表示。
    fmt.Printf("|%6s|%6s|\n", "foo", "b")

    // 要左对齐,和数字一样,使用 `-` 标志。
    fmt.Printf("|%-6s|%-6s|\n", "foo", "b")

    // 到目前为止,我们已经看过 `Printf`了,它通过 `os.Stdout`
    // 输出格式化的字符串。`Sprintf` 则格式化并返回一个字
    // 符串而不带任何输出。
    s := fmt.Sprintf("a %s", "string")
    fmt.Println(s)

    // 你可以使用 `Fprintf` 来格式化并输出到 `io.Writers`
    // 而不是 `os.Stdout`。
    fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 字符类型

在 Go 语言中支持两个字符类型,一个是 Byte(实际上是 Unit8 的别名),代表 UTF-8 字符串的单个字节的值;另一个是 rune,代表单个 Unicode 字符。

处于简化语言的考虑,Go 语言的多数 API 都假设字符串为 UTF-8 编码。尽管 Unicode 字符在标准库中有支持,但实际很少使用。

# 数组

Go 语言的数组是一个定长的序列,其中的元素类型相同。多维数组可以简单地使用自身为数组的元素来创建。数组的元素使用操作符号 [ ] 来索引,索引从 0 开始,到 len(array)-1 结束。数组使用以下语法创建:

  • [length]Type
  • [N]Type{value1, value2, ..., valueN}
  • [...]Type{value1, value2, ..., valueN}

如果使用了 ...(省略符)操作符,Go 语言会为我们自动计算数组的长度。在任何情况下,一个数组的长度都是固定的并且不可修改。数组的长度可以使用 len() 函数获得。由于数组的长度是固定的,因此数组的长度和容量都是一样的,因此对于数组而言 cap()len() 函数返回值都是一样的。数组也可以使用和切片一样的语法进行切片,只是其结果为一个切片,而非数组。同样的,数组也可以使用 range 进行索引访问。

// 在 Go 中,_数组_ 是一个固定长度的数列。

package main

import "fmt"

func main() {

    // 这里我们创建了一个数组 `a` 来存放刚好 5 个 `int`。
    // 元素的类型和长度都是数组类型的一部分。数组默认是
    // 零值的,对于 `int` 数组来说也就是 `0`。
    var a [5]int
    fmt.Println("emp:", a)

    // 我们可以使用 `array[index] = value` 语法来设置数组
    // 指定位置的值,或者用 `array[index]` 得到值。
    a[4] = 100
    fmt.Println("set:", a)
    fmt.Println("get:", a[4])

    // 使用内置函数 `len` 返回数组的长度
    fmt.Println("len:", len(a))

    // 使用这个语法在一行内初始化一个数组
    b := [5]int{1, 2, 3, 4, 5}
    fmt.Println("dcl:", b)

    // 数组的存储类型是单一的,但是你可以组合这些数据
    // 来构造多维的数据结构。
    var twoD [2][3]int
    for i := 0; i < 2; i++ {
        for j := 0; j < 3; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}
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
26
27
28
29
30
31
32
33
34
35
36
37

# 关联数组----哈希/字典

// _map_ 是 Go 内置[关联数据类型](http://zh.wikipedia.org/wiki/关联数组)(
// 在一些其他的语言中称为_哈希_ 或者_字典_ )。

package main

import "fmt"

func main() {

    // 要创建一个空 map,需要使用内建的 `make`:
    // `make(map[key-type]val-type)`.
    m := make(map[string]int)

    // 使用典型的 `make[key] = val` 语法来设置键值对。
    m["k1"] = 7
    m["k2"] = 13

    // 使用例如 `Println` 来打印一个 map 将会输出所有的
    // 键值对。
    fmt.Println("map:", m)

    // 使用 `name[key]` 来获取一个键的值
    v1 := m["k1"]
    fmt.Println("v1: ", v1)

    // 当对一个 map 调用内建的 `len` 时,返回的是键值对
    // 数目
    fmt.Println("len:", len(m))

    // 内建的 `delete` 可以从一个 map 中移除键值对
    delete(m, "k2")
    fmt.Println("map:", m)

    // 当从一个 map 中取值时,可选的第二返回值指示这个键
    // 是在这个 map 中。这可以用来消除键不存在和键有零值,
    // 像 `0` 或者 `""` 而产生的歧义。
    _, prs := m["k2"]
    fmt.Println("prs:", prs)

    // 你也可以通过这个语法在同一行申明和初始化一个新的
    // map。
    n := map[string]int{"foo": 1, "bar": 2}
    fmt.Println("map:", n)
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 切片

一般而言,Go 语言的切片比数组更加灵活,强大而且方便。数组是按值传递的(即是传递的副本),而切片是引用类型,传递切片的成本非常小,而且是不定长的。而且数组是定长的,而切片可以调整长度。创建切片的语法如下:

  • make([ ]Type, length, capacity)
  • make([ ]Type, length)
  • [ ]Type{}
  • [ ]Type{value1, value2, ..., valueN}

内置函数 make() 用于创建切片、映射和通道。当用于创建一个切片时,它会创建一个隐藏的初始化为零值的数组,然后返回一个引用该隐藏数组的切片。该隐藏的数组与 Go 语言中的所有数组一样,都是固定长度,如果使用第一种语法创建,那么其长度为切片的容量 capacity;如果是第二种语法,那么其长度记为切片的长度 length。一个切片的容量即为隐藏数组的长度,而其长度则为不超过该容量的任意值。另外可以通过内置的函数 append() 来增加切片的容量。切片可以支持以下操作:

我们练习下,创建源文件 slice_array.go,输入以下代码:

package main

import (
    "fmt"
)

func main() {
    a := [...]int{1, 2, 3, 4, 5, 6, 7}
    fmt.Printf("len and cap of array %v is: %d and %d\n", a, len(a), cap(a))
    fmt.Printf("item in array: %v is:", a)
    for _, value := range a {
        fmt.Printf("% d", value)
    }

    fmt.Println()

    s1 := a[3:6]
    fmt.Printf("len and cap of slice: %v is: %d and %d\n", s1, len(s1), cap(s1))
    fmt.Printf("item in slice: %v is:", s1)
    for _, value := range s1 {
        fmt.Printf("%d", value)
    }

    fmt.Println()

    s1[0] = 456
    fmt.Printf("item in array changed after changing slice: %v is:", s1)
    for _, value := range a {
        fmt.Printf("%d", value)
    }

    fmt.Println()

    s2 := make([]int, 10, 20)
    s2[4] = 5
    fmt.Printf("len and cap of slice: %v is: %d and %d\n", s2, len(s2), cap(s2))
    fmt.Printf("item in slice %v is:", s2)
    for _, value := range s2 {
        fmt.Printf("%d", value)
    }

    fmt.Println()
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

以上代码中,我们首先创建了一个数组,数组的长度是由 Go 语言自动计算出的(省略号语法),然后通过切片操作从数组 a 中创建了切片 s1,接着我们修改了该切片的第一个位置的数值,然后发现数组 a 中的值也发生了变化。最后我们通过 make() 函数创建了一个切片,该切片的长度和容量分别为 10 和 20,还可以发现 Go 语言将未初始化的项自动赋予零值。运行代码输出如下:

$  go run slice_array.go
len and cap of array [1 2 3 4 5 6 7] is: 7 and 7
item in array: [1 2 3 4 5 6 7] is: 1 2 3 4 5 6 7
len and cap of slice: [4 5 6] is: 3 and 4
item in slice: [4 5 6] is: 4 5 6
item in array changed after changing slice: [456 5 6] is: 1 2 3 456 5 6 7
len and cap of slice: [0 0 0 0 5 0 0 0 0 0] is: 10 and 20
item in slice [0 0 0 0 5 0 0 0 0 0] is: 0 0 0 0 5 0 0 0 0 0
1
2
3
4
5
6
7
8
// _Slice_ 是 Go 中一个关键的数据类型,是一个比数组更
// 加强大的序列接口

package main

import "fmt"

func main() {

    // 与数组不同,slice 的类型仅有它所包含的元素决定(不像
    // 数组中还需要元素的个数)。要创建一个长度非零的空
    // slice,需要使用内建的方法 `make`。这里我们创建了一
    // 个长度为3的 `string` 类型 slice(初始化为零值)。
    s := make([]string, 3)
    fmt.Println("emp:", s)

    // 我们可以和数组一起设置和得到值
    s[0] = "a"
    s[1] = "b"
    s[2] = "c"
    fmt.Println("set:", s)
    fmt.Println("get:", s[2])

    // 如你所料,`len` 返回 slice 的长度
    fmt.Println("len:", len(s))

    // 作为基本操作的补充,slice 支持比数组更多的操作。
    // 其中一个是内建的 `append`,它返回一个包含了一个
    // 或者多个新值的 slice。注意我们接受返回由 append
    // 返回的新的 slice 值。
    s = append(s, "d")
    s = append(s, "e", "f")
    fmt.Println("apd:", s)

    // Slice 也可以被 `copy`。这里我们创建一个空的和 `s` 有
    // 相同长度的 slice `c`,并且将 `s` 复制给 `c`。
    c := make([]string, len(s))
    copy(c, s)
    fmt.Println("cpy:", c)

    // Slice 支持通过 `slice[low:high]` 语法进行“切片”操
    // 作。例如,这里得到一个包含元素 `s[2]`, `s[3]`,
    // `s[4]` 的 slice。
    l := s[2:5]
    fmt.Println("sl1:", l)

    // 这个 slice 从 `s[0]` 到(但是不包含)`s[5]`。
    l = s[:5]
    fmt.Println("sl2:", l)

    // 这个 slice 从(包含)`s[2]` 到 slice 的后一个值。
    l = s[2:]
    fmt.Println("sl3:", l)

    // 我们可以在一行代码中申明并初始化一个 slice 变量。
    t := []string{"g", "h", "i"}
    fmt.Println("dcl:", t)

    // Slice 可以组成多维数据结构。内部的 slice 长度可以不
    // 同,这和多位数组不同。
    twoD := make([][]int, 3)
    for i := 0; i < 3; i++ {
        innerLen := i + 1
        twoD[i] = make([]int, innerLen)
        for j := 0; j < innerLen; j++ {
            twoD[i][j] = i + j
        }
    }
    fmt.Println("2d: ", twoD)
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# range遍历

// _range_ 迭代各种各样的数据结构。让我们来看看如何在我们
// 已经学过的数据结构上使用 `range` 吧。

package main

import "fmt"

func main() {

    // 这里我们使用 `range` 来统计一个 slice 的各元素的值之和。
    // 数组也可以采用这种方法。
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)

    // `range` 在数组和 slice 中都同样提供每个项的索引和
    // 值。上面我们不需要索引,所以我们使用 _空值定义符_
    // `_` 来忽略它。有时候我们实际上是需要这个索引的。
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }

    // `range` 在 map 中迭代键值对。
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }

    // `range` 在字符串中迭代 unicode 编码。第一个返回值是
    // `go` 的起始字节位置,然后第二个是 `go` 自己。
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39

#

这一节我们介绍 Go 语言基础的最后一个知识点——包。前面我们了解过 Go 语言组织代码的方式是包,包是各种类型和函数的集合。在包中,如果标示符(类型名称,函数名称,方法名称)的首字母是大写,那这些标示符是可以被导出的,也就是说可以在包以外直接使用。前面我们也提到了 $GOPATH 环境变量(指向一个或多个目录),以及其子目录 src 目录的,当我们使用 import 关键字导入包的时候,Go 语言会在 $GOPATHGOROOT 目录中搜索包。

我们创建的自定义的包最好放在 $GOPATHsrc 目录下,如果这个包只属于某个应用程序,可以直接放在应用程序源代码的子目录下,但如果我们希望这个包可以被其他的应用程序共享,那就应该放在 $GOPATHsrc 目录下,每个包单独放在一个目录里,如果两个不同的包放在同一目录下,会出现名字冲突的编译错误。作为惯例,包的源代码应该放在一个同名的文件夹下面。同一个包可以有任意多的源文件,文件名的名字也没有任何规定。