# Go 语言基础
# 常量
常量使用关键字 const
声明,下面有几个例子:
const limit = 512
const top uint16 = 1421
const Pi float64 = 3.1415926
const x,y int = 1,3 //多重赋值
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)
}
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))
}
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
)
2
3
4
5
Go 语言还预定义了这些常量:true
、false
、iota
。
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
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的类型
2
3
4
5
6
7
8
9
# 多重赋值
Go 语言提供了大多数语言不支持的多重赋值,这使得变量的交换变得十分简单。下面通过一个例子来了解 Go 语言的多重赋值:
i := 2
j := 3
i, j = j, i //交换i和j的值,此时i == 3,j == 2
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)
}
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))
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
以上代码中,首先声明了目前的源文件属于 main
包,然后导入了 fmt
和 unsafe
包,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
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 语言提供了内置的布尔值 true
和false
。Go 语言支持标准的逻辑和比较操作,这些操作的结果都是布尔值。值得注意的地方是可以通过 !b
的方式反转变量 b
的真假。需要注意的是布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。实例代码如下:
var a bool
a = true
b := (2 == 3) //b也会被推导为bool类型
//错误示范
var b bool
b = 1 //编译错误
b = bool(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" //内容:欢迎
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])
}
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
2
3
4
5
6
7
8
9
10
说明:
通过前面的课程我们知道通过 \uhhhh
的方式我们可以通过创建 Unicode 字符。
在以上程序中,首先通过 :=
符号创建了变量 t0
,其值为 \u6B22\u8FCE\u6765\u5230
,是 欢迎来到
中文字符的 unicode 编码,然后以同样的方式创建了变量 t1
,其值为 实验楼
,然后通过 +
操作符将 t0
和t1
拼接赋值给 t2
。然后我们通过 range
操作符号对 unicode 字符串 t2
中的每一个 unicode 字符依次操作,我们这里只是简单的打印出每个字符在 t2
中的位置,每个字符的 unicode 码值,每个字符的字面量,每个字符的十六进制值,以及每个字符的字节长度。
这里我们使用 fmt
包种支持的格式指令,如果读者学习过 C 语言的话就一目了然。接着,我们通过 len
操作符计算出了每个字符串的字节长度。最后,我们使用切片访问了字符串 t2
的第 0-1 个字节,也就是前两个字节,其内容为 E6AC
。前面我们说到不能使用切片的方式访问非 ASCII 字符串中的字符,原因在这里一目了然。字符 欢
其底层使用了三个字节表示,内容是 E6ACA2
,如果只是简单的使用切片(只取切片中的一项)访问的是不能访问到整个字符的,因为字符的切片是通过字节数来索引的。
# 格式化字符串
Go 语言标准库中的 fmt
包提供了打印函数将数据以字符串形式输出到控制台,文件,其他满足 io.Writer
接口的值以及其他字符串。目前为止我们使用了 fmt.Printf
和 fmt.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)
}
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]
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")
}
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)
}
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)
}
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()
}
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
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)
}
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)
}
}
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 语言会在 $GOPATH
和 GOROOT
目录中搜索包。
我们创建的自定义的包最好放在 $GOPATH
的 src
目录下,如果这个包只属于某个应用程序,可以直接放在应用程序源代码的子目录下,但如果我们希望这个包可以被其他的应用程序共享,那就应该放在 $GOPATH
的 src
目录下,每个包单独放在一个目录里,如果两个不同的包放在同一目录下,会出现名字冲突的编译错误。作为惯例,包的源代码应该放在一个同名的文件夹下面。同一个包可以有任意多的源文件,文件名的名字也没有任何规定。