1. 数组
1. 语法
- 类型
[n]T
表示拥有 n 个 T 类型的值的数组。 - 数组的长度是其类型的一部分,因此数组不能改变大小。
- 数组中的所有元素都被自动赋值为数组类型的零值
- 数组是值类型而不是引用类型,就是说当数组赋值给一个新的变量或者作为参数传递给函数时,不会改变原始数组
- 多维数组也可以直接声明或者利用索引声明
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
26var a [2]string // 一般声明
a[0] = "Hello"
a[1] = "World"
a := [3]int{12, 78} // 简略声明,无需将数组中所有的元素赋值
a := [...]int{12, 78, 50} // 可以忽略声明数组的长度声明
aa := []int{12} // 如果什么都不写,则声明的是一个数组切片
a := [3]int{5, 78, 8}
var b [5]int
b = a // 报错,数组的大小是类型的一部分,[2]int 和 [5]int 是不同类型
a := [3][2]string{
{"lion", "tiger"}, // 逗号是必须的,因为根据 Go 语言的规则自动插入分号
{"cat", "dog"},
{"pigeon", "peacock"},
}
var b [3][2]string // 多维数组
b[0][0] = "apple"
b[0][1] = "samsung"
b[1][0] = "microsoft"
b[1][1] = "google"
b[2][0] = "AT&T"
b[2][1] = "T-Mobile"
2. 切片
2.1. 概念
切片是组的包装,它本身不拥有任何数据,它只是对现有数组的引用,更改切片的元素会修改其底层数组中对应的元素。
[]T
:表示一个元素类型为 T 的切片。a[start:end]
:创建一个从 a 数组索引 start 开始到 end - 1 结束的切片1
2
3
4
5letters := [3]string{"a", "b", "c"}
var b []string := letters[0:2] // 创建切片,语法类似于没有长度的数组
arr1 := letters[0:1]
arr1[0] = "d" // 原数组也被修改
2.2. 属性
- 切片拥有长度和容量。可通过表达式
len(s)
和cap(s)
来获取。容量大于等于长度。- 切片的长度就是它所包含的元素个数
- 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
- 切片的零值是 nil,其长度和容量为 0 且没有底层数组
- 数组下标取值,不能超过
len(s)
,向后拓展取值,不能超过底层数组cap(s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s) // len=6 cap=6 [2 3 5 7 11 13]
s = s[:0] // 截取切片使其长度为 0
printSlice(s) // len=0 cap=6 []
s = s[:4] // 拓展其长度
printSlice(s) // len=4 cap=6 [2 3 5 7]
s = s[2:] // 舍弃前两个值
printSlice(s) // len=2 cap=4 [5 7]
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
2.3. 方法
2.3.1. make
make()
内建函数会分配一个元素为零值的数组并返回一个引用了它的切片
a := make([]T, 5)
:len(a)=5b := make([]T, 0, 5)
:len(b)=0,,cap(b)=5
2.3.2. append
append()
内建函数的结果是一个包含原切片所有元素加上新添加元素的切片。当原数组的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
append(s []T, x ... T)
1
2cars := []string{"Ferrari", "Honda", "Ford"} // len=3,cap=3
cars = append(cars, "Toyota") // len=4,cap=6
2.3.2. range
for
循环的 range
形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值:
- 为当前元素的下标,
- 为该下标所对应元素的一份副本
可以将下标或值赋予 _
来忽略它
1 | var pow = []int{1, 2, 4, 8, 16, 32, 64} |
2.3.3. 复制切片
copy(dest, src)
内建函数,将 src 切片复制给 dest 切片
2.3.4. 删除切片元素
没有删除的内建函数,但可以通过 append
来实现删除
1 | // 删除第3个元素,由于 append 后面接收变长个元素,使用 ... 解包切片为元素 |
2. 映射
1. 语法
- map 的语法与结构体相似,不过必须有键名
- map 的零值是 nil。如果想添加元素到 nil map 中,会触发运行时 panic。因此 map 必须使用 make 函数初始化
1
2
3
4
5
6
7// 初始化 map
m1 := make(map[string]int) // 不指定容量初始化
m2 := make(map[string]int, 100) // 指定100容量初始化
m3 := map[string]string {
"name":"Tom",
"gender":"male"
}
2. 方法
2.1. 初始化
make
函数会返回给定类型的映射:make(map[K]V)
2.2. 获取元素
elem = m[key]
获取不存在的元素,会返回 value 类型的零值elem, ok := m[key]
可以检测键是否存在- 遍历 map 中所有的元素需要用 for range 循环
1 | value, ok := example3Map["first"] |
2.3. 添加元素
- 可以在声明的同时添加元素
- 添加新元素方式与数组相同
m[key] = elem
1 | example3Map := map[string]int { // 可以在声明的同时添加元素 |
2.4. 删除元素
delete(m, key)
函数无返回值
2.5. 获取长度
len(example3Map)
返回 map 长度
3. 特性
3.1. Map 是引用类型
和 slices 类似,map 也是引用类型。当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
1 | example3Map := map[string]int { |
3.2. map 的相等性
map 之间不能使用 ==
操作符判断,==
只能用来检查 map 是否为 nil。判断两个 map 是否相等的方法是遍历比较两个 map 中的每个元素。
1 | map1 := map[string]int{ |
3. 结构体
1. 语法
type
可以声明一个命名结构体,也可以不用,此时则创建一个匿名结构体- 结构体可以把相同类型的字段声明放在同一行
- 未被显式地初始化字段时,该结构体的字段将默认赋为零值
- 结构体字段使用点号
.
来访问 - 结构体可以用指针来访问。可以通过
(*p).x
来访问其字段x
,或者隐式间接引用p.x
。
1 | // 声明 |
2. 特性
2.1. 匿名字段
创建结构体时,字段可以只有类型,而没有字段名,这样的字段称为匿名字段。
虽然匿名字段没有名称,但其实匿名字段的名称就默认为它的类型。如下,可认为 Person 结构体有两个名为 string 和 int 的字段。
1 | type Person struct { // 带匿名字段的结构体 |
2.2. 嵌套结构体
结构体的字段有可能也是一个结构体。这样的结构体称为嵌套结构体。
1 | type Address struct { |
2.3. 提升字段
如果是结构体中有匿名的结构体类型字段,则该匿名结构体里的字段就称为提升字段。
这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。
1 | type Address struct { |
2.4. 导出结构体和字段
如果结构体名称以大写字母开头,则它是其他包可以访问的导出类型。
同样,如果结构体里的字段首字母大写,它也能被其他包访问到。
2.5. 结构体相等性
结构体是值类型。如果它的每一个字段都是可比较的,则该结构体也是可比较的。
如果两个结构体变量的对应字段相等,则这两个变量也是相等的。
如果结构体包含不可比较的字段,则结构体变量也不可比较。
4. 指针
1. 声明
指针是一种存储变量内存地址(Memory Address)的变量
*T
:指针变量的类型为*T
,该指针指向一个T
类型的变量。其零值为nil
&
:获取变量的地址*指针变量
:获取指针所指向的变量的值
1 | func main() { |
2. 注
- 函数传递可以使用指针参数
- 向函数传递数组的指针,不是 Go 语言惯用方式,而应该使用切片
- Go 不支持指针运算
1
2
3
4
5
6
7
8
9
10func change(val *int) { // 利用指针传递,修改原参数值
*val = 55
}
func modify(arr *[3]int) { // 传递数组的指针,修改原数组值
(*arr)[0] = 90 // 可简写为 arr[0] = 90
}
func modify(sls []int) { // 传递数组的切片
sls[0] = 90
}
5. 字符串
字符串是一个字节切片。Go 中的字符串是兼容 Unicode 编码的,并且使用 UTF-8 进行编码。
1. 构建字符串
1.1. 双引号
内容放在双引号””之间,可以创建一个字符串
1.2. 字节切片
1 | byteSlice1 := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9} // 16 进制字节 |
1.3. rune 切片
1 | runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072} // 16 进制的 Unicode 代码点 |
2. 遍历字符串
2.1. rune
直接使用切片访问,其会按照每个字节来打印,但是有的字符占据不止一个字节,此时必须使用 rune
,其是 int32 的别称,表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。
1 | func printBytes(s string) { |
2.2. for rang
1 | func printCharsAndBytes(s string) { |
3. 注意
3.1. 长度
utf8 package
包中的 func RuneCountInString(s string) (n int)
方法用来获取字符串的长度,返回字符串中的 rune 的数量。
1 | import ( |
3.2. 字符串不可变
Go 中的字符串是不可变的。一旦一个字符串被创建,那么它将无法被修改。
1 | func mutate(s string)string { |
为了修改字符串,可以把字符串转化为一个 rune 切片。然后这个切片可以进行任何想要的改变,然后再转化为一个字符串。
1 | func mutate(s []rune) string { |