GO 数组与切片
一:数组
数组是存放元素的容器,必须指定存放的元素的类型和容量(长度)
数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。
注:数组的长度是数组类型的一部分,即如果两个数组的长度不一样是无法进行比较的
(一):数组定义
var 数组变量名 [元素数量]类型
数组的长度必须是常量,并且长度是数组的一部分。一旦定义,长度不能变。[3]int 和 [4]int是不同的类型
数组可以通过下标进行访问,下标是从0开始的,最后一个元素下标是len-1。
var a [3]int
(二):数组初始化
如果不初始化,默认元素都是零值(布尔值:false,整型和浮点型都为0,字符串为 “”)
方式一:
指定长度
package main
import "fmt"
func main() {
var a1 [3]bool
// 初始化方式一
a1 = [3]bool{true, true, true}
fmt.Println(a1)
}
方式二:
根据初始值自动推断数组的长度是多少
package main
import "fmt"
func main() {
// 初始化方式二
a2 := [...]int{0,1,2,3,43,44}
fmt.Println(a2)
}
方式三
根据索引初始化
package main
import "fmt"
func main() {
// 初始化方式三
a3 := [5]int{0:1,4:5}
fmt.Println(a3)
}
(三):数组遍历
方式一
for循环遍历
package main
import "fmt"
func main() {
// 方式一,for循环
name := [...]string{"小红","小明","李华"}
for i := 0; i < len(name); i++ {
fmt.Println(name[i])
}
// 方式二,for range
for k,v := range name {
fmt.Println(k,v)
}
}
方式二
for range
package main
import "fmt"
func main() {
// 方式二,for range
for k,v := range name {
fmt.Println(k,v)
}
}
(四):多维数组
多维数组创建
package main
import "fmt"
func main() {
// 多维数组
// [[1,2],[3,4],[5,6]]
var a5 [3][2]int
a5 = [3][2]int{
[2]int{1,2},
[2]int{3,4},
[2]int{5,6},
}
fmt.Println(a5)
}
多维数组的遍历
package main
import "fmt"
func main() {
// 多维数组遍历
for _,v1 := range a5{
fmt.Println(v1)
for _, v2 := range v1{
fmt.Println(v2)
}
}
}
二:切片
因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。比如求和时只能对相同个数的数组求和,而且也无法继续向数组中添加新元素。
切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个引用类型,它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合。
切片不保存数据,所以针对切片的修改都是针对底层数据的修改。
(一):切片的定义及初始化
package main
import "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 main
import "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 main
import "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 main
import "fmt"
func main() {
// 切片赋值
s2 := []int{1,2,3,4}
s3 := s2 // s2,s3都指向了同一个底层数组
fmt.Printf("s2为:%d, s3为: %d\n", s2, s3)
s3[3] = 1000
fmt.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 main
import "fmt"
func main() {
// 切片的遍历
// for索引遍历
for i := 0; i < len(s3); i++{
fmt.Println(s3[i])
}
// for range遍历
for _,v := range s3 {
fmt.Println(v)
}
}
(七):切片追加元素
append()
当使用索引添加元素时需注意索引越界问题,当切片的索引超过切片的容量时就会提示索引越界。可使用append()方法进行添加。
Go语言的内建函数append()可以为切片动态添加元素。每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行扩容,此时切片指向的底层数组就会更换。扩容操作往往发生在append()函数调用时
注:必须用变量接受append的返回值
package main
import "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 main
import "fmt"
func main() {
a1 := []int{1,3,5}
a2 := a1
a3 := make([]int,3,3)
copy(a3,a1)
fmt.Println(a1,a2,a3)
a1[0] = 100
fmt.Println(a1,a2,a3)
}
// 结果
[1 3 5] [1 3 5] [1 3 5]
[100 3 5] [100 3 5] [1 3 5]
(九):从切片中删除元素
Go语言中没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。代码如下:
package main
import "fmt"
func main() {
a1 := []int{1,3,5,7,9}
// 将3和5删掉,...为解包的意思
a1 = append(a1[:1], a1[3:]...)
fmt.Println(a1)
}
(十):指针
Go语言中不存在指针操作,只需要记住两个符号:
- & 取地址
- * 根据地址取值
package main
import "fmt"
func main() {
// 取地址
s := 123
p := &s
fmt.Println(p)
fmt.Printf("%T\n", p)
// 根据内存地址取值
m := *p
fmt.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 main
import "fmt"
func main() {
var m1 map[string]int
fmt.Println(m1 == nil) // 还没有初始化(还没有开辟内存空间)
// 初始化
m1 = make(map[string]int, 10) // 要估算好该map的容量,避免在程序运行过程中动态扩容
m1["xiaoming"] = 18
m1["kid"] = 25
fmt.Println(m1)
fmt.Println(m1)
}
# 结果
true
map[kid:25 xiaoming:18]
25
判断key是否存在
package main
import "fmt"
func main() {
var m1 map[string]int
m1 = make(map[string]int, 10)
m1["xiaoming"] = 18
m1["kid"] = 25
// 判断map中是否有某个值
// 方式一:直接打印,如果不存在则返回0值
fmt.Println(m1["hack"])
// 方式二:判断(推荐使用),使用value和ok两个变量来接受map的值,value为存在key时的值,ok是一个布尔值,如果存在则为true,如果不存在则为false
value,ok := m1["hack"]
if !ok {
fmt.Println("不存在这个值")
} else {
fmt.Println(value)
}
}
// 结果
0
不存在这个值
map的遍历
package main
import "fmt"
func main() {
var m1 map[string]int
m1 = make(map[string]int, 10)
m1["xiaoming"] = 18
m1["kid"] = 25
// map的遍历
for k,v := range m1 {
fmt.Println(k,v)
}
}
// 结果
xiaoming 18
kid 25
删除键值对
delete(map,key)
map:表示要删除键值对的map
key:表示要删除的额键值对的键
package main
import "fmt"
func main() {
var m1 map[string]int
m1 = make(map[string]int, 10)
m1["xiaoming"] = 18
m1["kid"] = 25
// 删除键值对
delete(m1,"kid")
fmt.Println(m1)
delete(m1,"hack") // 如果删除的key不存在,则不进行任何操作
}
# 结果
map[xiaoming:18]
元素为map类型的切片
注:对于map和切片都必须进行初始化
package main
import "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 main
import "fmt"
func main() {
// 值为切片类型的map
m1 := make(map[string][]int, 5)
m1["体重"] = []int{1,2,3,4}
fmt.Println(m1)
}