GO 数组与切片
一:数组
数组是存放元素的容器,必须指定存放的元素的类型和容量(长度)
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
注:数组的长度是数组类型的一部分,即如果两个数组的长度不一样是无法进行比较的
(一):数组定义
var 数组变量名 [元素数量]类型
数组的长度必须是常量,并且长度是数组的一部分。一旦定义,长度不能变。[3]int 和 [4]int是不同的类型
数组可以通过下标进行访问,下标是从0开始的,最后一个元素下标是len-1。
var a [3]int
(二):数组初始化
如果不初始化,默认元素都是零值(布尔值:false,整型和浮点型都为0,字符串为 “”)
方式一:
指定长度
package mainimport "fmt"func main() {var a1 [3]bool// 初始化方式一a1 = [3]bool{true, true, true}fmt.Println(a1)
}
方式二:
根据初始值自动推断数组的长度是多少
package mainimport "fmt"func main() {// 初始化方式二a2 := [...]int{0,1,2,3,43,44}fmt.Println(a2)
}
方式三
根据索引初始化
package mainimport "fmt"func main() {// 初始化方式三a3 := [5]int{0:1,4:5}fmt.Println(a3)
}
(三):数组遍历
方式一
for循环遍历
package mainimport "fmt"func main() {// 方式一,for循环name := [...]string{"小红","小明","李华"}for i := 0; i }
方式二
for range
package mainimport "fmt"func main() {// 方式二,for rangefor k,v := range name {fmt.Println(k,v)}
}
(四):多维数组
多维数组创建
package mainimport "fmt"func main() {// 多维数组// [[1,2],[3,4],[5,6]]var a5 [3][2]inta5 = [3][2]int{[2]int{1,2},[2]int{3,4},[2]int{5,6},}fmt.Println(a5)
}
多维数组的遍历
package mainimport "fmt"func main() {// 多维数组遍历for _,v1 := range a5{fmt.Println(v1)for _, v2 := range v1{fmt.Println(v2)}}
}
二:切片
因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。比如求和时只能对相同个数的数组求和,而且也无法继续向数组中添加新元素。
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
切片不保存数据,所以针对切片的修改都是针对底层数据的修改。
(一):切片的定义及初始化
package mainimport "fmt"func main() {// 切片定义var s1 []int // 定义一个int类型的切片var s2 []string // 定义一个string类型的切片fmt.Println(s1 == nil) // 判断s1是否为空var s3 = []bool{true,false,true} // 初始化fmt.Println(s1,s2,s3)fmt.Println(s3 == nil) // 判断s3是否为空
}
(二):切片长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量
切片指向一个底层的数组,是一个引用,并没有具体的值
切片的长度就是它元素的个数
切片的容量为底层数组从切片的第一个元素到最后的元素的数量
package mainimport "fmt"func main() {// 长度和容量s1 = []int{1,2,3}s2 = []string{"小红","小明","李华"}fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1),cap(s1))fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2),cap(s2))// 由数组得到切片的长度和容量s4 := [...]int{1,3,5,7,9,11,13}s5 := s4[0:4] // 对数组按索引切割,左包含右不包含s6 := s4[3:]// 容量为底层数组从切片的第一个元素到最后的元素的数量fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5),cap(s5))fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6),cap(s6))// 对切片进行切片s7 := s6[1:2]fmt.Printf("len(s7):%d cap(s7):%d\n", len(s7),cap(s7))
}// 结果
// len(s1):3 cap(s1):3
// len(s2):3 cap(s2):3
// len(s5):4 cap(s5):7
// len(s6):4 cap(s6):4
// len(s7):1 cap(s7):3
(三):make函数创建切片
make([]type, 数量,容量)
如果不传入容量的值则容量的值默认等于数量
package mainimport "fmt"func main() {// make函数创造切片s1 := make([]int, 5, 10)fmt.Printf("s1: %d, len(s1): %d, cap(s1): %d", s1, len(s1), cap(s1))
}
(四):切片详解
切片就是一个框,框住了一块连续的内存。属于引用类型,真正的数据都是保存在底层数组中的。
切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等的元素。切片唯一合法的比较操作时和nil比较。一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0.但是我们不能说一个长度和容量都是0的切片一定是nil。
var s1 []int // s1 == nil
// 切片为空,但s2 != nil,因为s2已开辟了一块内存空间
s2 := []int{}
s3 := make([]int,0) // s3 与 s2 同理
所以要判断一个切片是否是空的,要用 len() 方法来确定,不能用 s==nil 来判定
(五):切片的赋值拷贝
package mainimport "fmt"func main() {// 切片赋值s2 := []int{1,2,3,4}s3 := s2 // s2,s3都指向了同一个底层数组fmt.Printf("s2为:%d, s3为: %d\n", s2, s3)s3[3] = 1000fmt.Printf("s2为:%d, s3为: %d\n", s2, s3)
}// 结果
// s2为:[1 2 3 4], s3为: [1 2 3 4]
// s2为:[1 2 3 1000], s3为: [1 2 3 1000]
s2,s3都指向了同一个底层数组[1 2 3 4],相当于在底层数组上框了一个框,当底层的数组发生改变时,s2,s3都会发生改变。
(六):切片的遍历
package mainimport "fmt"func main() {// 切片的遍历// for索引遍历for i := 0; i }
(七):切片追加元素
append()
当使用索引添加元素时需注意索引越界问题,当切片的索引超过切片的容量时就会提示索引越界。可使用append()方法进行添加。
Go语言的内建函数append()可以为切片动态添加元素。每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时
注:必须用变量接受append的返回值
package mainimport "fmt"func main() {s1 := []string{"北京","上海","南京"}fmt.Printf("s1:%v, len(s1):%d, cap(s1):%d\n", s1, len(s1), cap(s1))// 调用append函数必须使用原来的切片变量接受返回值// append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个s1 = append(s1, "山西") fmt.Printf("s1:%v, len(s1):%d, cap(s1):%d\n", s1, len(s1), cap(s1))// append 只能添加字符串,当传入变量时需将变量拆开s2 := []string{"成都","杭州","西安"}s1 = append(s1,s2...)
}
扩容策略:
- 首先判断,如果新申请容量大于旧容量的2倍,最终容量就是新申请的容量
- 否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍
- 否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的1/4,直到最终容量大于等于新申请的容量
- 如果最终容量计算值溢出,则最终容量就是新申请容量
需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样
(八):copy复制
copy复制相当于将底层数组拷贝了出来放入另外一块内存空间
package mainimport "fmt"func main() {a1 := []int{1,3,5}a2 := a1a3 := make([]int,3,3)copy(a3,a1)fmt.Println(a1,a2,a3)a1[0] = 100fmt.Println(a1,a2,a3)
}// 结果
[1 3 5] [1 3 5] [1 3 5]
[100 3 5] [100 3 5] [1 3 5]
(九):从切片中删除元素
Go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。代码如下:
package mainimport "fmt"func main() {a1 := []int{1,3,5,7,9}// 将3和5删掉,...为解包的意思a1 = append(a1[:1], a1[3:]...)fmt.Println(a1)
}
(十):指针
Go语言中不存在指针操作,只需要记住两个符号:
- & 取地址
- * 根据地址取值
package mainimport "fmt"func main() {// 取地址s := 123p := &sfmt.Println(p)fmt.Printf("%T\n", p)// 根据内存地址取值m := *pfmt.Println(m)fmt.Printf("%T\n", m)
}// 结果
0xc0000a2058
*int
123
int
(十一):new
内存分配
make和new的区别:
- make和new都是用来申请内存的
- new很少用,一般用来给基本数据类型申请内存,string/int返回的是对应类型的指针
- make是用来给slice、map、chan申请内存的,make函数返回的是对应的这三个类型本身
(十二):map
Go语言中提供的映射关系容器为map,其内部使用散列表(hash)实现
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。
形式:map[keyType]valueType
package mainimport "fmt"func main() {var m1 map[string]intfmt.Println(m1 == nil) // 还没有初始化(还没有开辟内存空间)// 初始化m1 = make(map[string]int, 10) // 要估算好该map的容量,避免在程序运行过程中动态扩容m1["xiaoming"] = 18m1["kid"] = 25fmt.Println(m1)fmt.Println(m1)
}# 结果
true
map[kid:25 xiaoming:18]
25
判断key是否存在
package mainimport "fmt"func main() {var m1 map[string]intm1 = make(map[string]int, 10) m1["xiaoming"] = 18m1["kid"] = 25// 判断map中是否有某个值// 方式一:直接打印,如果不存在则返回0值fmt.Println(m1["hack"])// 方式二:判断(推荐使用),使用value和ok两个变量来接受map的值,value为存在key时的值,ok是一个布尔值,如果存在则为true,如果不存在则为falsevalue,ok := m1["hack"]if !ok {fmt.Println("不存在这个值")} else {fmt.Println(value)}
}// 结果
0
不存在这个值
map的遍历
package mainimport "fmt"func main() {var m1 map[string]intm1 = make(map[string]int, 10) m1["xiaoming"] = 18m1["kid"] = 25// map的遍历for k,v := range m1 {fmt.Println(k,v)}
}// 结果
xiaoming 18
kid 25
删除键值对
delete(map,key)
map:表示要删除键值对的map
key:表示要删除的额键值对的键
package mainimport "fmt"func main() {var m1 map[string]intm1 = make(map[string]int, 10) m1["xiaoming"] = 18m1["kid"] = 25// 删除键值对delete(m1,"kid")fmt.Println(m1)delete(m1,"hack") // 如果删除的key不存在,则不进行任何操作
}# 结果
map[xiaoming:18]
元素为map类型的切片
注:对于map和切片都必须进行初始化
package mainimport "fmt"func main() {// 元素类型为map的切片var s1 = make([]map[int]string, 1, 10)// 必须对内部的map做初始化s1[0] = make(map[int]string, 1)s1[0][1] = "张三"fmt.Println(s1)
}
值为切片类型的map
注:对于map和切片都必须进行初始化
package mainimport "fmt"func main() {// 值为切片类型的mapm1 := make(map[string][]int, 5)m1["体重"] = []int{1,2,3,4}fmt.Println(m1)
}