
数组和切片
1. 数组
1.1 定义
什么是数组?数组是一堆相同数据类型的集合,需要强调的是这个集合里的数据类型一定是一致的 .
如何定义一个数组: 数组的格式[n]T ,n是这个集合中元素的个数,T是类型type
下面是一个简单的案例:
package main
import "fmt"
func main(){
var arr [10]int
fmt.Println(arr)
}
//[0 0 0 0 0 0 0 0 0 0]
数组的中索引是从0开始的,下面我们来看一下如何给数组赋值
package main
import "fmt"
func main(){
var arr [10]int
fmt.Println(arr)
arr[0] = 0
arr[1] = 1
arr[2] =2
fmt.Println(arr)
fmt.Println(arr[1])
}
/**
[0 0 0 0 0 0 0 0 0 0]
[0 1 2 0 0 0 0 0 0 0]
1
*/
通过上面的案例我们可以看到以下几点:
- 声明一个数组类型的变量,和其他类型一样,会有零值
- 数组的赋值,可以通过下表索引的方式进行赋值
- 数组可以通过变量名和说的方式获取数组里面的值
上面我们发现如何10个元素,赋值的话需要写十个arr[index],是否有其他简单的方式:下面是逐渐演进的过程:
func main(){
var arr [3]int = [3]int{1,2,3}//最完整的声明赋值
fmt.Println(arr)
var arr2 = [3]int{3,4,5}//省略了变量类型,让编译器进行推断
fmt.Println(arr2)
arr3:= [3]int{4,5,6}//省略了var 和变量类型,同样让编译器进行推断,当然这个只允许定义局部变量
fmt.Println(arr3)
arr4 := [...]int{8,9,0}// 三个点表示 让编译器去数他的长度
fmt.Println(arr4)
}
如果遇到长度和自己的初始化值不一致时会怎样:
arr5:=[4]int{1,2}
fmt.Println(arr5)
//[1 2 0 0]
arr6 := [2]int{1,2,3,4}
fmt.Println(arr6)
//array index 2 out of bounds [0:2]
我们发现如果传入的值和长度个数不匹配时,如果传入的值个数小于数组的长度,则会自动填充零,否则会报超出边界的错误.
1.2 值类型
数组是值类型的,这意味值,当将一个数组类型的变量赋值给另一个变量时执行的是复制操作,而不是引用,下面我们验证一下
package main
import "fmt"
func main(){
var array =[...]int{1,2,3}
array2 := array
fmt.Printf("array内存地址是%pn",&array)
fmt.Printf("array2内存地址是%pn",&array2)
}
/**
array内存地址是0xc000012340
array2内存地址是0xc000012360
*/
上面例子中我们发现,两个变量的指针指向不是一个地方,说明他们是存储了两份,也就是所谓的值类型
1.3 获取数组的长度
可以通过len
获取数组的长度,也就是这里面有多少个元素,
package main
import "fmt"
func main(){
arrayLen:=[10]int{1,2,3}
fmt.Println(len(arrayLen))
}
//10
通过上面的例子我们可以看出len获取的是其实[n]T中的n,并不是赋值的个数
1.3 遍历数组
- 通过for循环进行遍历:
go for i:=0;i<len(arrayLen);i++{ fmt.Println(arrayLen[i]) }
- 通过
range
进行遍历:
go for index,value:=range arrayLen{ fmt.Println(index,value) } }
1.4 多维数组
有些时候,一维数组并不能很好的满足我们的需求,比如做一个俄罗斯方块游戏,随机生成的积木,其实就是一个二维数组,或者你也可以称之为矩阵,下面我们看一下如何定义二维数组:
a := [2][2]string{
{"张三","李四"},
{"王五","赵六"},
}
fmt.Println(a)
//[[张三 李四] [王五 赵六]]
遍历多为数组的话和一维的类似,只不过需要多嵌套一层
2. 切片
什么是切片?
切片可以看作是数组的一个装饰器,或者有的人他是数组的视图,这些都是基于他的特性来的,他本身不包含任何数据 ,他是已有数据的 引用
,并对引用的数据增加了一些属性和处理方法.
2.1 创建切片
切片格式为[]T
,与数组不同之处是,括号里面没有长度,下面是一个实例
package main
import "fmt"
func main(){
arr :=[3]int{1,2,3}
slice := arr[:2]
fmt.Println(slice)
}
上面就是基于数组创建的切片的示例,语法结构为arr[start:end]
索引从0开始,如果没有的start表示从0开始,同理没有end表示start到结尾,上面的arr[:2]
表示索引从0-1,两个个元素,需要注意的是这个区间是左闭右开区间
当然也可以通过直接赋值的方式进行创键,这种方式其实也是go在内部创建了一个数组,实例如下:
sliceValue:=[]int{1,2,3}
2.2 修改切片中的值
因为切片并不保存任何数据,所以当修改切片中某一个元素的值时,实际改的是他所引用的数组中元素的值,下面是一个例子,我们来验证一下:
package main
import "fmt"
func main(){
arr:=[...]int{1,2,3,4,5}
slice :=arr[:2]
fmt.Println("原始的arr值:",arr)
fmt.Println(slice)
slice[1] = 100
fmt.Println("修改slice值以后,arr的值:",arr)
}
/**打印结果如下:
原始的arr值: [1 2 3 4 5]
[1 2]
修改slice值以后,arr的值: [1 100 3 4 5]
*/
2.3 切片的长度和容量
切片的长度:len
表示这个切片中的元素个数(end-start即可)
切片的容量cap
: 表示基于底层的数组,位置从start开始到末尾的个数
下面我们通过一个例子说明:
package main
import "fmt"
func main(){
var arr1 =[...]int{1,2,3,4,5}
var slice1 =arr1[1:3]
fmt.Println("arr1是:",arr1)
fmt.Println("slice1是:",slice1)
fmt.Println("slice1的length",len(slice1))
/**
解释说明:
slice1中的元素是[2,3],第一个元素2在arr1中的索引是1,那么cap就是在arr1中数,从1到末尾的长度,一共是3个元素,也就是数组的长度-slice的start=cap
*/
fmt.Println("slice1的capcity",cap(slice1))
}
2.4 通过make创建切片
在没有数组时,如何创建切片?这时可以使用make
进行创建,语法为:
make([]Type,len,cap)
type是要依托于什么类型的数组,len是切片元素数量,cap是容量
下面是一个案例
package main
import "fmt"
func main(){
sliceMake := make([]int,2)
for index,value:=range sliceMake{
fmt.Println(index,value)
}
}
2.5 切片追加元素
我们知道数组的长度在定义的时候就已经固定好了,是无法更改了,因为一旦更改了,那么他的数据类型也就变化了,因为长度是数组的数据类型的一部分,就像int8 与int32是两种类型.但是切片没有这个限制,他可以使用append
来进行扩容,下面是append方法的使用:
package main
import "fmt"
func main(){
sliceAppend:=[]string{"张三","李四","王五"}
fmt.Println(sliceAppend)
sliceAppendAfter :=append(sliceAppend,"赵六")
fmt.Println(sliceAppendAfter)
}
可能你会有这样的疑问,slice不包含任何数据,全都依赖数组,但是数组的长度又是不可变的,这是不是冲突了,其实,我们在使用append的时候,如果超出原来数组的容量后go会产生一个新的数组,下面我们来验证一下
package main
import "fmt"
func main(){
//首先创建一个数组
arr :=[...]string{"a","b","c"}
fmt.Printf("第一个元素的位置:%pn",&arr[0]) //第一个元素的位置:0xc000070330
//根据上面的数组创建slice
sliceArr:= arr[:2]//a,b
fmt.Printf("slice中的a元素的指针:%pn",&sliceArr[0]) //slice中的a元素的指针:0xc000070330
//通过上面的例子我们发现他们地址是相同的,接下来我们对sliceArr进行扩容处理
sliceArrAppend := append(sliceArr,"d")
fmt.Println(sliceArrAppend)
fmt.Printf("sliceArrAppend中a元素的位置:%pn",&sliceArrAppend[0])
//我们继续扩容
sliceArrAppend := append(sliceArrAppend,"d","esdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf","sdfsdfsdfsdfsd","sdfsdfsdfsdfsd","sdfsdfsdfsdfsdf")
fmt.Printf("sliceArrAppend2中a元素的位置%pn",&sliceArrAppend2[0])
}
/**结果为:
第一个元素的位置:0xc0000b6330
slice中的a元素的指针:0xc0000b6330
[a b d]
sliceArrAppend中a元素的位置:0xc0000b6330
sliceArrAppend2中a元素的位置0xc0000d2000
*/
通过上面的例子正好验证了上面说的sliceArrAppend
是没有超出原有数组的容量是,后面继续新增元素,结果超出原有的数组容量,所以go帮我们创建了一个新的底层数组,证据就是第一个a元素的指针发生了变化
上面都是一些使用元素追加slice的按理,其实还可以使用切片追加切片,通过使用...
三个点来操作,下面是示例
package main
import "fmt"
func main(){
sliceA := []string{"a","b","c"}
sliceB := []string{"d","e","f"}
sliceA = append(sliceA,sliceB...)
fmt.Println(sliceA)
}
//[a b c d e f]
2.6 函数传参
当slice作为函数参数使用的时候,其实更能体现切片是数组的引用这一观点,下面是一个实例
package main
import "fmt"
func passSlice(parmeter []int){
for i:=range parmeter{
parmeter[i]-=1
}
}
func main(){
slice :=[]int{1,3,4}
fmt.Println(slice)
passSlice(slice)
fmt.Println(slice)
}
//[1 3 4]
//[0 2 3]