Go语言编程笔记4:数组、切片和映射
数组
Go语言中的数组与C或C++中的几乎没有区别——固定大小且无法进行扩容:
package main
import "fmt"
func main() {
numbers := [10]int{}
for i := 0; i < 10; i++ {
numbers[i] = i + 1
}
fmt.Println(numbers)
}
// [1 2 3 4 5 6 7 8 9 10]
除此之外还可以在声明的同时初始化:
package main
import "fmt"
func main() {
numbers := [10]int{1, 2, 3, 4, 5}
fmt.Println(numbers)
}
// [1 2 3 4 5 0 0 0 0 0]
遍历数组也可以通过类似于Python中的for...in
语句,不过要借助range
关键字:
package main
import "fmt"
func main() {
numbers := [10]int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Println("numbers[", index, "]=", value)
}
}
// numbers[ 0 ]= 1
// numbers[ 1 ]= 2
// numbers[ 2 ]= 3
// numbers[ 3 ]= 4
// numbers[ 4 ]= 5
// numbers[ 5 ]= 0
// numbers[ 6 ]= 0
// numbers[ 7 ]= 0
// numbers[ 8 ]= 0
// numbers[ 9 ]= 0
切片
想必只要是用过Python或者PHP中的数组就会对传统数组不屑一顾,花费大量的时间对已有数组进行扩容等操作实在是一件头疼的事情。索性Go语言除了提供传统数组,还提供一种“切片”来作为变长数组的替代。
如果学过Python想必对切片不陌生,实际上在某些方面两者的确非常相似。
切片的声明方式很像是声明一个没指定长度的数组:
package main
import "fmt"
func main() {
numbers := make([]int, 10)
for i := 0; i < 10; i++ {
numbers[i] = i + 1
}
numbers = append(numbers, 999)
fmt.Println(numbers)
}
// [1 2 3 4 5 6 7 8 9 10 999]
虽然我们通过make
创建的切片长度是10
,但是通过append
函数依然可以向其中添加额外元素。
事实上切片有点像Java中的ArrayList
,其原理就是先申请一块长度“够用”的数组,在容量已满的时候,再向其中添加元素就会进行“扩容”操作,也就是申请一块容量更大的数组,然后将当前的内容复制过去。
在Java中,ArrayList
使用的初始数组长度是不需要我们操心的,这虽然很方便很傻瓜,但同样意味着某些情况下可能会降低性能,比如我们已经知道我们需要的数组绝大多数情况下是不会超过某个长度的,自然直接使用该长度作为初始化数组长度是比较合适的。
所以make
函数事实上可以指定两个长度相关的参数:切片长度和初始数组长度。而上边的例子中make([]int, 10)
是将切片长度和初始数组长度都设置为10。
当然也可以让切片长度和初始数组长度不同:
package main
import "fmt"
func main() {
numbers := make([]int, 5, 10)
fmt.Println(numbers)
numbers = append(numbers, 99)
fmt.Println(numbers)
numbers = numbers[5:10]
fmt.Println(numbers)
fmt.Println(len(numbers))
}
// [0 0 0 0 0]
// [0 0 0 0 0 99]
// [99 0 0 0 0]
// 5
其中numbers = numbers[5:10]
是进行了重新切片,让切片numbers
的内容实际上变成了初始数组的最后五个元素。
需要说明的是切片的赋值和一般的认为有所不同,实际上是引用赋值:
package main
import "fmt"
func main() {
numbers := make([]int, 10)
fmt.Println(numbers)
numbers2 := numbers[5:10]
for index, _ := range numbers2 {
numbers2[index] = 99
}
fmt.Println(numbers)
}
// [0 0 0 0 0 0 0 0 0 0]
// [0 0 0 0 0 99 99 99 99 99]
需要注意的是Go语言中的切片可以使用[5:]
或[5:len(numbers)]
这样的写法,但是并不支持负索引值。
此外,切片的声明和初始化方式可以是多样的:
names := []string{"leto", "jessica", "paul"}
checks := make([]bool, 10)
var names []string
scores := make([]int, 0, 20)
这四种方式分别用于下面几种情况:
- 用已知数据声明并初始化切片。
- 初始化一个指定长度的切片。
- 仅仅声明一个空切片,后续进行初始化或者使用
append
追加值。 - 声明并初始化一个指定长度切片,同时预留一部分长度作为可能的扩展空间(出于优化性能考虑)。
映射
Go语言中的映射对应PHP中的关系数组或者Python中的字典,我们可以使用两种方式来创建映射:
package main
import "fmt"
type Student struct {
name string
age int
}
func print_students(students map[string]*Student) {
for _, student := range students {
fmt.Println(student.name, "is", student.age, "years old")
}
}
func main() {
brus := Student{
name: "Brus Lee",
age: 12,
}
jack := Student{
name: "Jack Chen",
age: 20,
}
students1 := make(map[string]*Student)
students1[brus.name] = &brus
students1[jack.name] = &jack
students2 := map[string]*Student{
brus.name: &brus,
jack.name: &jack,
}
print_students(students1)
print_students(students2)
}
// Brus Lee is 12 years old
// Jack Chen is 20 years old
// Jack Chen is 20 years old
// Brus Lee is 12 years old
此外,和切片类似,可以像示例中那样使用for...range
语句来遍历映射。需要注意的是Go语言的映射与Python类似,同样是无序的,不会保证每次遍历的顺序都一致,这点和PHP是不同的。
和切片类似,映射也可以设置底层容器大小,比如students=make(map[string]*Students,100)
,这样或许对性能优化有用。但我并不推荐这么做,因为Python中的字典的底层实现是哈希表,而哈希表插入一个新元素的时间复杂度和容量的关系是很复杂的,虽然说粗暴指定一个非常大的容量当然可以实现常数级的添加效率,但那样显然会造成空间浪费,而要在一个可接受的空间上实现一个可接受的时间复杂度,这是一项复杂的问题。所以在我看来指定一个字典的底层实现容量并不是一个好主意,除非你通过多次尝试很确定这么做有利于性能优化。
关于Python中字典底层哈希表的实现相关的内容可以阅读Python学习笔记20:字典与集合。
要删除映射中的一个键值对也很容易,通过delete
函数即可:delete(students2, jack.name)
。