数组(Array)
- 数组是同类元素的集合,它的元素排列在连续的空间中,按下标来标记和访问数组类型定义包括元素类型,数组长度(元素个数)
- 元素类型相同的两个数组,数组长度不同则类型不同,相互不能复制
- 数组变量声明后,其元素类型、数组长度均不可变
数组声明
var identifier [n]dataType
//只声明未赋值
//数组元素都被初始化为对应类型零值
var arr1 [5]int
//声明3个元素的整型数组
//直接赋值
arr2 := [3]int{11, 12, 13}
//声明整型数组
//直接赋值
//数组长度由初始化值的数量来确定
arr3 := [...]int{11, 12, 13, 14, 15} //...不可省略
//声明4个元素的整型数组
//对下标为0和3的元素直接赋值
//其余元素保持零值
arr4 := [4]int{0: 99, 3: 100}
fmt.Printf("%v,%v,%v,%v", arr1, arr2, arr3, arr4)
//[0 0 0 0 0],[11 12 13],[11 12 13 14 15],[99 0 0 100]
数组复制
数组变量之间进行复制时会拷贝整个数组(值拷贝)
a := [...]string{"USA", "China", "India", "Germany"}
b := a
b[0] = "Singapore"
fmt.Println("a is ", a)
fmt.Println("b is ", b)
//a is [USA China India Germany]
//b is [Singapore China India Germany]
数组传参
实参拷贝一份给形参,二者相互独立,传递大数组时效率较低,尽量用指向数组的指针来传参
func changeLocal(num [5]int) {
num[0] = 55
fmt.Println("inside function ", num)
}
func main() {
num := [...]int{5, 6, 7, 8, 8}
fmt.Println("before passing to function ", num)
changeLocal(num) //num is passed by value
fmt.Println("after passing to function ", num)
}
//before passing to function [5 6 7 8 8]
//inside function [55 6 7 8 8]
//after passing to function [5 6 7 8 8]
数组遍历
for 遍历
a := [...]float64{67.7, 89.8, 21, 78}
for i := 0; i < len(a); i++ {
fmt.Printf("%d th element of a is %.2f\n", i, a[i])
}
range遍历
a := [...]float64{67.7, 89.8, 21, 78}
for i, v := range a { // 第一个参数为序号,第二个为变量
fmt.Printf("%d the element of a is %.2f\n", i, v)
}
//0 the element of a is 67.70
//1 the element of a is 89.80
//2 the element of a is 21.00
//3 the element of a is 78.00
多维数组
a := [3][2]string{
{"lion", "tiger"},
{"cat", "dog"},
{"pigeon", "peacock"}, //此处,不可忽略,否则报错
}
for _, v1 := range a {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
切片(Slice)
- 数组的定长性和值拷贝限制其使用
- 切片封装底层的数组,提供长度可变的数组引用
- 切片是引用类型,不支持==运算(除了nil)
- 切片包括三个变量
- 底层数组指针
- 切片当前长度
- 切片容量(小于等于底层数组长度,超过时要变更底层数组)
切片声明
- 切片声明时,不能给定底层数组大小,否则变成数组声明
- 声明语法 var identifier [ ]dataType
- 声明后创建切片变量,但底层数组指针为nil,所以切片变量也为nil
- 也可使用内置函数make 来声明和初始化,切片底层数组会分配空间,并设置为对应类型零值
i := make([]int, 5, 5) // 为什么不用构造函数
fmt.Println(i)
// [0 0 0 0 0]
new和make区别
- new和make是内建的两个函数,主要用来在堆上创建分配类型的内存
- new用于值类型的内存分配,内存清零,返回该类型指针
- make用于引用类型如slice、map以及channel的初始化,返回引用
创建切片
指定底层数组创建
a := [5]int{76, 77, 78, 79, 80}//底层数组
s1 := a[0:4] // from a[0] to a[3]
s2 := a[:4] // from a[0] to a[3]
s3 := a[2:5] // from a[2] to a[4]
s4 := a[2:] // from a[2] to a[4]
fmt.Printf("%v\n%v\n%v\n%v", s1, s2, s3, s4)
//[76 77 78 79]
//[76 77 78 79]
//[78 79 80]
//[78 79 80]
同时创建数组和切片
//指定数组大小,只创建数组
c := [3]int{6, 7, 8}
//不指定数组大小,返回切片引用,底层数组匿名
d := []int{6, 7, 8}
//用...推断数组大小,只创建数组
e := [...]int{6, 7, 8}
切片长度和容量
内置函数 len() 返回切片当前长度
内置函数cap()返回切片底层数组容量
arr := [7]int{9, 8, 7, 6, 5, 4, 3}
sli := arr[1:3]
fmt.Printf("length of array %d capacity %d\n", len(arr), cap(arr))
fmt.Printf("length of slice %d capacity %d\n", len(sli), cap(sli))
//length of array 7 capacity 7
//length of slice 2 capacity 6 // why 6?
切片共享底层数组
多个切片可共享同一底层数组
arr := [7]int{9, 8, 7, 6, 5, 4, 3}
s1 := arr[1:3]
s2 := arr[1:4]
arr[1] = 100
s1[1] = 99
s2[1] = 88
fmt.Printf("%v\n", arr)
fmt.Printf("%v\n", s1)
fmt.Printf("%v\n", s2)
//[9 100 88 6 5 4 3]
//[100 88]
//[100 88 6]
切片动态增加
内置函数 append() 动态扩展切片,在底层数组容量范围内,会直接覆盖底层数组元素
arr := [7]int{9, 8, 7, 6, 5, 4, 3}
sli := arr[1:3]
sli = append(sli, 20) //增加一个20,切片容量扩展一倍?
fmt.Printf("%v\n", arr)
//[9 8 7 20 5 4 3]
切片动态增加时,当超过底层数组容量大小时,会重新创建底层数组,并转移数据
切片增长在元素小于1000时,成倍增长,超过1000,增长速率大概为1.25
切片合并
内置函数 append() 还支持切片的合并,用…运算符把对应切片所有元素都取出
veggies := []string{"potatoes", "tomatoes", "brinjal"}
fruits := []string{"oranges", "apples"}
food := append(veggies, fruits...) //... 不可忽略
fmt.Println("food:", food)
//food: [potatoes tomatoes brinjal oranges apples]
切片传参
切片可视为如下的结构体
//$GOROOT/src/runtime/slice.go
type slice struct {
Length int
Capacity int
ZerothElement *byte
}
在函数传参时,复制的是结构体拷贝,实现引用传递
多维切片
多维切片比多维数组灵活,每行元素个数不必相同
切片性能
- 切片引用底层数组,导致底层数组不能被垃圾回收
- 在底层数组很大,切片很小时,存在明显内存浪费
- 这时可以用内置函数make()新建目标切片,再用内置函数copy(目标切片,源切片) 实现切片与原较大底层数据的分离
Map
- Map用于存储一系列无序的键值对
- 在GO中Map跟数组一样直接使用,无须引用外部库
- Map也是引用类型,不支持==运算(除了nil)
- Map的键(key) 只支持值类型(可以使用==、!=运算符作比较)
- Map的值(value)不限制,但同一Map元素的值类型一致
- Map 不是线程安全的,不支持并发写
Map初始化
- Map零值不可用,只声明不初始化为nil值,不分配底层存储空间,不能添加元素
- 用字面量或make函数进行初始化后可以添加元素
var m1 map[string]int
fmt.Println(m1 == nil)
//true
//m1["a"] = 1 //error
m2 := map[string]int{}
fmt.Println(m2 == nil)
//false
m2["a"] = 1 //ok
m3 := make(map[string]int)
fmt.Println(m3 == nil)
//false
m3["a"] = 1 //ok
元素赋值
可在Map初始化后进行元素赋值
也可在Map初始化时直接元素赋值
personSalary := make(map[string]int)
personSalary["steve"] = 12000
personSalary["jamie"] = 15000
personSalary["mike"] = 9000
//初始化时,直接赋值
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
Mapy元素查找
- Map元素通过下标直接查找访问
- 存在对应key的,返回对应value
- 不存在对应key的,返回value 类型的零值
Map元素通过下标访问其实可以返回两个值(底层实际为函数,Comma-ok 法)
对应的value
对应的key是否存在的布尔值
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
value, ok := personSalary["joe"]
if ok == true {
fmt.Println("Salary of joe is", value)
} else {
fmt.Println("joe not found")
}
Map元素操作
- Map元素可以使用range 遍历,但不保证顺序
personSalary := map[string]int{
"steve": 12000,
"jamie": 15000,
}
personSalary["mike"] = 9000
for key, value := range personSalary {
fmt.Printf("personSalary[%s] = %d\n", key, value)
}
//personSalary[mike] = 9000
//personSalary[steve] = 12000
// personSalary[jamie] = 15000
- 使用内置函数delete()删除Map元素
- key存在,对应元素被删除
- key不存在,什么都不发生