一.数组
如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储。但是,问题是班级有80个学生,那么要定义80个变量吗?
像以上情况,最好是通过数组的方式来存储。
所谓的数组:是指一系列同一类型数据的集合。
1.1 数组定义
数组定义的格式为:var 数组名 [元素个数]数据类型,
例如:var a [10]int;数组定义也是通过var 关键字,后面是数组的名字a,长度是10,数据类型是整型。表示:数组a能够存储10个整型数字。也就是说,数组a的长度是10。
我们可以通过len( )函数测试数组的长度,如下所示:
func main(){ var a [10]int fmt.Println(len(a)) }
当定义完成数组a后,就在内存中开辟了10个连续的存储空间,每个数据都存储在相应的空间内,数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。
注意:数组的长度只能是常量。以下定义是错误的:
var n int = 10 var a [n]int
1.2 数组赋值
数组定义完成后,可以对数组进行赋值操作。
数组是通过下标来进行操作的,下标的范围是从0开始到数组长度减1的位置。
var a[10] int 表示的范围是a[0],a[1],a[2].......,a[9]
完成对数组赋值的第一种方法:在定义完数组后,通过数组下标为数组中的元素赋值
func main(){ var a [10]int a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5 a[5] = 6 a[6] = 7 a[7] = 8 a[8] = 9 a[9] = 10 fmt.Println(a[0]) fmt.Println(a[9]) }
但如果现在给a[10]=29, 会出现什么情况呢?
func main(){ var a [10]int a[10] = 29 }
结果如下:
invalid array index 10 (out of bounds for 10-element array)
如果通过数组中不存在的下标赋值,就会报数组下标越界的错误,这里在使用的时候要注意。
但是上面赋值方式比较麻烦,所以可以使用第二种赋值方式,如下所示:
func main(){ var a [10]int for i := 0;i < 10;i++{ a[i] = i + 1 } for i := 0;i < 10;i++{ fmt.Println(a[i]) } }
通过for循环完成数组的赋值与输出。注意:循环的条件,如果将循环条件修改成i<=10是否正确
在上一节中,我们说过可以通过len( )函数来获取 数组的长度,所以也可以对上面的程序,进行如下的修改:
func main(){ var a [10]int for i := 0;i < len(a);i++{ a[i] = i + 1 } for i := 0;i < len(a);i++{ fmt.Println(a[i]) } }
对数组中的数据输出,也可以使用range.如下所示:
func main(){ var a [10]int for i := 0;i < len(a);i++{ a[i] = i + 1 } for i,data := range a{ //fmt.Println("下标:",i) //fmt.Println("元素值:",data) fmt.Printf("a[%d]=%d ",i,data) } }
i变量存储的是数组的下标,data变量存储的是数组中的值。
如果只想输出数组中的元素值,不希望输出下标,可以使用匿名变量
func main(){ var a [10]int for i := 0;i < len(a);i++{ a[i] = i + 1 } for _,data := range a{ //fmt.Println("下标:",i) fmt.Println("元素值:",data) //fmt.Printf("a[%d]=%d ",i,data) } }
上面的案例中,首先完成了数组的赋值,然后再输出数组中的值。但是,如果定义完成数组后,没有赋值,直接输出会出现什么样的问题呢?
func main(){ var a [10]int for i := 0;i < len(a);i++{ fmt.Println(a[i]) } }
a数组中的元素类型是整型,定义完成后,直接输出,结果全部是0.
当然数组中存储的元素类型也可以是其它类型,如下所示:
var a [10]float64//如果不赋值,直接输出,结果默认全部是0
vara[10]string//如果不赋值,直接输出,结果默认全部是空字符
var a [10]bool//如果不赋值,直接输出,结果默认全部是false.
1.3 数组初始化
上一小节中,首先先定义数组,然后再完成数组的赋值。其实,在定义数组时,也可以完成赋值,这种情况叫做数组的初始化。
func main(){ //定义数组 并为数组中所有元素赋值 var arr1 [10]int = [10]int{1,2,3,4,5,6,7,8,9,0} //通过自动推导类型定义并初始化数组 arr2 := [5]int{1,2,3,4,5} //定义数组为部分数据赋值 未赋值的数据默认值为0 var arr3 [10]int = [10]int{1,2,3,4,5} //通过初始化,来确定数组长度 arr4 := [...]int{1,2,3,4} fmt.Println(arr1) fmt.Println(arr2) fmt.Println(arr3) fmt.Println(len(arr4)) }
结果如下:
[1 2 3 4 5 6 7 8 9 0] [1 2 3 4 5] [1 2 3 4 5 0 0 0 0 0] 4
1.4 数组的内存存储
先看下面这个例子:
func main(){ var arr1 [10]int = [10]int{1,2,3,4,5,6,7,8,9,0} //%p 是一个占位符 表示输出一个数据的内存地址 fmt.Printf("%p ",&arr1) for i := 0;i < len(arr1);i++{ fmt.Printf("%p ",&arr1[i]) } }
结果如下:
0xc00006a050 0xc00006a050 0xc00006a058 0xc00006a060 0xc00006a068 0xc00006a070 0xc00006a078 0xc00006a080 0xc00006a088 0xc00006a090 0xc00006a098
从代码结果可以看出,数组的内存地址其实就是数组中首元素的地址;另外由于Go语言中,整形在内存中占8个字节,由此可以确定数组中的数据是在内存中连续存储的。
1.5 数组练习
从一个整数数组中取出最大的整数,最小整数,总和,平均值。
代码如下:
func main(){ score := [10]int{89, 72, 86, 91, 74, 83, 94, 0, 100, 60} var max int var min int var sum int for i := 0;i < len(score);i++{ if score[i] > max{ max = score[i] } if score[i] < min{ min = score[i] } sum += score[i] } fmt.Printf("最大值是:%d,最小值是:%d,和为:%d,平均值为:%.2f",max,min,sum,float64(sum)/float64(len(score))) }
以上程序输出的结果是:
最大值是:100,最小值是:0,和为:749,平均值为:74.90
通过观察发现该程序输出的结果没有问题。
但是,现在将程序进行如下修改:将数组中的0元素删除,换成其他不为0的正整数
func main(){ score := [10]int{89, 72, 86, 91, 74, 83, 94, 78, 100, 60} var max int var min int var sum int for i := 0;i < len(score);i++{ if score[i] > max{ max = score[i] } if score[i] < min{ min = score[i] } sum += score[i] } fmt.Printf("最大值是:%d,最小值是:%d,和为:%d,平均值为:%.2f",max,min,sum,float64(sum)/float64(len(score))) }
运行以上程序,结果如下:
最大值是:100,最小值是:0,和为:827,平均值为:82.70
思考:数组中没有0,为什么输出的结果中最小值为0呢?
现在,在将程序进行如下修改:将数组中的数据全部修改成负数。
func main(){ //score := [10]int{89, 72, 86, 91, 74, 83, 94, 78, 100, 60} score := [5]int{-1,-2,-3,-4,-5} var max int var min int var sum int for i := 0;i < len(score);i++{ if score[i] > max{ max = score[i] } if score[i] < min{ min = score[i] } sum += score[i] } fmt.Printf("最大值是:%d,最小值是:%d,和为:%d,平均值为:%.2f",max,min,sum,float64(sum)/float64(len(score))) }
运行该程序,结果输出:
最大值是:0,最小值是:-5,和为:-15,平均值为:-3.00
思考:数组中没有0,为什么输出的结果中最大值为0呢?(整数类型默认值是0)
应该怎样解决如上的问题呢?将程序修改如下:
func main(){ score := [10]int{89, 72, 86, 91, 74, 83, 94, 78, 100, 60} var max int = score[0] var min int = score[0] var sum int for i := 0;i < len(score);i++{ if score[i] > max{ max = score[i] } if score[i] < min{ min = score[i] } sum += score[i] } fmt.Printf("最大值是:%d,最小值是:%d,和为:%d,平均值为:%.2f",max,min,sum,float64(sum)/float64(len(score))) }
1.6 数组冒泡排序
如何对数组中存储的数据,按照从大到小,或者从小到大进行排序?可以使用冒泡排序。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
具体代码实现:
func main(){ array := [10]int{8,4,2,9,1,5,7,3,6,10} for i := 0;i < len(array) - 1;i++{ for j := 0;j < len(array) - 1 - i;j++{ if array[j] > array[j+1]{ array[j],array[j+1] = array[j+1],array[j] } } } fmt.Println(array) }
结果如下:
[1 2 3 4 5 6 7 8 9 10]
1.7 数组作为函数参数
数组也可以像变量一样,作为参数传递给函数,用法如下:
func BubbleSort(array [10]int){ for i := 0;i < len(array)-1;i++{ for j := 0;j < len(array)-1-i;j++{ if array[j] > array[j+1]{ array[j],array[j+1] = array[j+1],array[j] } } } } func main() { array := [10]int{9, 1, 5, 6, 10, 8, 3, 7, 2, 4} BubbleSort(array) fmt.Println(array) }
结果如下:
[9 1 5 6 10 8 3 7 2 4]
注意:在main()函数中,定义数组array,然后调用BubbleSort()方法传递数组,同时在BubbleSort()方法中修改数组中的元素。最终输出结果发现,并不会影响main( )函数中数组array的值,这一点与其它编程语言是有区别的。如果想得到修改后的数组,可以添加返回值,具体如下:
func BubbleSort(array [10]int) [10]int{ for i := 0;i < len(array)-1;i++{ for j := 0;j < len(array)-1-i;j++{ if array[j] > array[j+1]{ array[j],array[j+1] = array[j+1],array[j] } } } return array } func main() { array := [10]int{9, 1, 5, 6, 10, 8, 3, 7, 2, 4} array = BubbleSort(array) fmt.Println(array) }
1.8 二维数组
前面定义的数组只有一个下标,称之为一维数组,如果有两个下标,称之为二维数组。
二维数组的定义如下:var 数组名 [行个数][列个数]数据类型;例如:var arr [2][3]int。
为二维数组赋值:数组名[行下标][列下标]=值,示例如下:
func main() { var arr [2][3]int arr[0][0] = 1 arr[1][2] = 4 fmt.Println(arr) }
结果如下:
[[1 0 0] [0 0 4]]
二维数组的初始化:
func main() { //定义二维数组并初始化 var arr1 [2][3]int = [2][3]int{{1,2,3},{4,5,6}} //定义二维数组并初始化部分数据的值 未初始化部分 默认值为定义的数据类型的零值 var arr2 [2][3]int = [2][3]int{{1,2},{4}} //自动推导类型创建二维数组 arr3 := [2][3]int{{1,2,3},{4,5,6}} fmt.Println(arr1) fmt.Println(arr2) fmt.Println(arr3) }
结果如下:
[[1 2 3] [4 5 6]] [[1 2 0] [4 0 0]] [[1 2 3] [4 5 6]]
遍历二维数组:
len(二维数组):计算二维数组中行的个数
len(二维数组[行下标]):计算二维数组中列的个数
func main() { var arr [2][3]int = [2][3]int{{1,2,3},{4,5,6}} fmt.Printf("此二维数组的行数为%d ",len(arr)) fmt.Printf("此二维数组的列数为%d ",len(arr[0])) //遍历二维数组 for i := 0;i < len(arr);i++{ for j := 0;j < len(arr[0]);j++{ fmt.Printf("%d ",arr[i][j]) } fmt.Println() } fmt.Println("----------------------") //使用range遍历二维数组 for _,d := range arr{ for _,v := range d{ fmt.Printf("%d ",v) } fmt.Println() } }
结果如下:
此二维数组的行数为2 此二维数组的列数为3 1 2 3 4 5 6 ---------------------- 1 2 3 4 5 6
二.切片
2.1 切片概念
在讲解切片(slice)之前,大家思考一下数组有什么问题?
第一:数组定义完,长度是固定的。
例如:
arr := [5]int{1,2,3,4,5} fmt.Println(arr)
定义的num数组长度是5,表示只能存储5个整型数字,现在向数组num中追加一个数字,这时会出错。
第二:使用数组作为函数参数进行传递时,如果实参为5个元素的整型数组,那么形参也必须5个元素的整型数组,否则出错。
针对以上两个问题,可以使用切片来进行解决。
切片:切片与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大,所以可以将切片理解成“动态数组”,但是,它不是数组。
2.2 切片与数组的区别
通过定义,来比较一下切片与数组的区别
(1)先回顾数组的基本定义初始化:
a := [5]int{1,2,3,4,5}
数组中[ ]是一个固定的数字,表示长度。定义完后,长度是固定,最多存储5个数字。
(2)切片的基本定义初始化如下:
var 切片名 []数据类型
//切片的定义 var a []int = []int{1,2,3,4,5} //使用自动推导类型定义切片 b := []int{1,2,3,4,5}
看定义的方式,发现与数组很相似,但是注意:切片中的[ ]是空的,切片的长度和容量可以不固定。
同时切片还可以动态的追加数据,使用append()函数。
切片名 = append(切片名,值1,值2,值3...)
func main() { s := []int{1,2,3,4,5} s = append(s,6,7,8,9) fmt.Println(s) }
结果如下:
[1 2 3 4 5 6 7 8 9]
也可将一个切片追加到另一个切片后
切片名 = append(切片名,切片名2...) //注意:这 ... 是必须加上的
func main() { s := []int{1,2,3,4,5} t := []int{6,7,8,9,0} s = append(s,t...) fmt.Println(s) }
结果如下:
[1 2 3 4 5 6 7 8 9 0]
2.3 切片的定义和初始化
定义并初始化切片:
var slice []int = []int{1, 2, 3, 4, 5}
声明切片:
var slice []int
只声明但没有初始化的切片是不能使用下标进行赋值,当你使用下标为切片赋值时,会报下标越界的错误。
func main() { var slice []int slice[0] = 123 fmt.Println(slice) } panic: runtime error: index out of range
我们也可以通过 make() 函数定义并初始化函数:
借助make函数, 格式 make(切片类型, 长度, 容量)
var slice []int = make([]int, 5, 10)
什么是切片的长度与容量?
长度是已经初始化的空间(以上切片slice初始空间默认值都是0)。容量是已经开辟的空间,包括已经初始化的空间和空闲的空间。
我们可以通过如下图来理解切片的长度与容量:
该切片的长度是5(存有数据,注意如果没有赋值,默认值都是0),容量是10,只不过有5个空闲区域。
即使没有给切片slice赋值,初始化的空间(长度)默认存储的数据都是0。
演示如下:
func main() { slice := make([]int,5,10) fmt.Println(slice) }
输出结果是:
[0 0 0 0 0]
在使用make( )函数定义切片时,一定要注意,切片长度要小于等于容量,例如:
slice := make([]int,10,5)
这样是错误的。
make( )函数中的容量参数是可以省略掉的,如:
slice := make([]int,10)
这时长度与容量是相等的,都是10。
GO语言提供了相应的函数来计算切片的长度与容量。
func main() { slice := make([]int,5,10) fmt.Printf("长度是:%d ",len(slice)) fmt.Printf("容量是:%d ",cap(slice)) }
接下来给切片slice赋值,可以通过下标的方式直接来进行赋值。如下所示:
func main() { slice := make([]int,5,10) slice[0] = 1 slice[1] = 2 }
也可以通过循环的方式来进行赋值。
func main() { slice := make([]int,5,10) for i := 0;i < len(slice);i++{ slice[i] = i } }
在这里一定要注意,循环结束条件是小于切片的长度,而不是容量。因为,切片的长度是指的是初始化的空间。以下方式会出现异常错误。
for i := 0;i < cap(slice);i++{ slice[i] = i }
给切片赋完值后,怎样将切片中的数据打印出来呢?
第一种方式:直接通过下标的方式输出,例如:slice[0],slice[1].....。
第二种方式:通过循环的方式,注意循环结束的条件,也是小于切片的长度,如下所示:
for i := 0;i < len(slice);i++{ fmt.Println(slice[i]) }
或者使用range方式输出:
for k,v := range slice{ fmt.Println("下标:",k) fmt.Println("值:",v) }
2.4 切片的内存存储
我们先来认识一个函数:unsafe.Sizeof(数据),是用来计算数据在内存中占的字节大小,用法如下:
func main() { a := 1 fmt.Println(unsafe.Sizeof(a)) }
结果如下:
8
然后我们再看下面这个例子:
func main() { s1 := []int{1} s2 := []int{1,2,3,4,5} s3 := []int{1,2,3,4,5,6,7,8,9,10} fmt.Println(unsafe.Sizeof(s1)) fmt.Println(unsafe.Sizeof(s2)) fmt.Println(unsafe.Sizeof(s3)) }
结果如下:
24 24 24
我们发现长度各不相同的切片在内存中占的字节都是24,这是为什么呢?
这是因为切片其实本质上是一个结构体(这个之后会学到)。
在Go源码中runtime包下的slice.go的文件中定义如下:
type slice struct { array unsafe.Pointer len int cap int }
切片包含三个属性:数组指针(指针就是地址),长度和容量,其中数组指针是指切片中数据的内存地址。
简单的图片描述如下:
切片名本身就是一个地址,指向切片数据的内存地址。
func main() { slice := []int{1,2,3,4,5} fmt.Printf("%p ",slice) }
0xc00008e030
我们也可以通过for循环来得到切片中每个数据的内存地址:
func main() { slice := []int{1,2,3,4,5} for i := 0;i < len(slice);i++{ fmt.Printf("%p ",&slice[i]) } }
结果如下:
0xc00008e030 0xc00008e038 0xc00008e040 0xc00008e048 0xc00008e050
2.5 切片的长度和容量
切片与数组很大的一个区别就是:切片的长度是不固定的,可以向已经定义的切片中追加数据。并且也给大家简单的演示过通过append的函数,在原切片的末尾添加元素。
func main() { slice := []int{1,2,3} slice = append(slice,4) //追加一个数 slice = append(slice,5,6,7) //追加多个数 slice = append(slice,[]int{8,9,10}...) //追加一个切片 }
但如果容量不够用了,该怎么办呢?
例如有以下切片:
s:= make([]int, 5, 8)
定义了切片s,长度是5,容量是8
func main() { s := make([]int, 5, 8) fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) }
结果是:
len = 5,cap = 8
并且前面我们讲解过,长度是指已经初始化的空间,现在切片s没有赋值,但是默认值为0
验证如下所示:
func main() { s := make([]int, 5, 8) fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) fmt.Println(s) }
结果是:
len = 5,cap = 8 [0 0 0 0 0]
现在开始通过append函数追加数据,如下所示:
func main() { s := make([]int, 5, 8) s = append(s, 1) fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) fmt.Println(s) }
输入结果是:
len = 6,cap = 8 [0 0 0 0 0 1]
从输出的结果上,我们完全能够体会到,append函数的作用是在末尾追加(直接在默认值后面追加数据),由于追加了一个元素,所以长度为6.
但是如果我们把程序修改成如下所示:
func main() { s := make([]int, 5, 8) s[0] = 1 fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) fmt.Println(s) }
输出结果是:
len = 5,cap = 8 [1 0 0 0 0]
由于s[0]=1是直接给下标为0的元素赋值,并不是追加,所以结果的长度不变。
下面我们继续通过append( )继续追加数据:
func main() { s := make([]int, 5, 8) s = append(s,1) s = append(s,2) s = append(s,3) fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) fmt.Println(s) }
输出结果是:
len = 8,cap = 8 [0 0 0 0 0 1 2 3]
追加完成3个数据后,长度变为了8,与容量相同。
那么如果现在通过append( )函数,继续向切片s中继续追加一个数据,那么容量会变为多少呢?
代码如下:
func main() { s := make([]int, 5, 8) s = append(s, 1) s = append(s, 2) s = append(s, 3) s = append(s, 4) fmt.Printf("len = %d,cap = %d ", len(s), cap(s)) fmt.Println(s) }
输出结果是:
len = 9,cap = 16 [0 0 0 0 0 1 2 3 4]
追加完成一个数据后,长度变为9,大于创建切片s时的容量,所以切片s自动扩容,变为16.
那么切片的容量是否是以2倍容量来进行扩容的呢?
我们可以来验证一下:
func main() { s := make([]int, 0, 1) oldCap := cap(s) for i := 0; i < 20; i++ { s = append(s, i) newCap := cap(s) if oldCap < newCap { fmt.Printf("cap: %d ====> %d ", oldCap, newCap) oldCap = newCap } } }
输出结果是:
cap: 1 ====> 2 cap: 2 ====> 4 cap: 4 ====> 8 cap: 8 ====> 16 cap: 16 ====> 32
通过以上结果分析,发现是2倍的容量进行扩容。
但是我们修改一下循环条件看一下结果,将循环结束的条件修改的大一些,如下所示:
func main() { s := make([]int, 0, 1) oldCap := cap(s) for i := 0; i < 20000; i++ { s = append(s, i) newCap := cap(s) if oldCap < newCap { fmt.Printf("cap: %d ====> %d ", oldCap, newCap) oldCap = newCap } } }
对应的结果:
cap: 1 ====> 2 cap: 2 ====> 4 cap: 4 ====> 8 cap: 8 ====> 16 cap: 16 ====> 32 cap: 32 ====> 64 cap: 64 ====> 128 cap: 128 ====> 256 cap: 256 ====> 512 cap: 512 ====> 1024 //2倍扩容 cap: 1024 ====> 1280 //非2倍扩容 cap: 1280 ====> 1696 cap: 1696 ====> 2304 cap: 2304 ====> 3072
通过以上的运行结果分析:
如果数据小于1024,每次扩容为上次的两倍;如果超过1024,扩容为上次的1/4 - 1/3
2.6 切片的截取
所谓截取就是从切片中获取指定的数据。
我们通过如下程序给大家解释一下:
func main() { //定义切片并初始化 s := []int{10, 20, 30, 0, 0} //从切片s中截取数据 slice := s[0:3:5] fmt.Println(slice) }
以上程序输出结果:
[10 20 30]
s[0:3:5]是什么意思呢?
我们可以使用s[low:high:max]来表示
第一个数(low)表示下标的起点(从该位置开始截取),如果low取值为0表示从第一个元素开始截取,也就是对应的切片s中的10
第二个数(high)表示取到哪结束,也就是下标的终点(但不包含该位置),3表示取出下标是0,1,2的数据(10,20,30),不包括下标为3的数据,那么也就是说取出的数据长度是3. 可以根据公式:3-0 计算(len=high-low),也就是第二个数减去第一个数,差就是数据长度。在这里可以将长度理解成取出的数据的个数。
第三个数用来计算容量,所谓容量:是指切片目前可容纳的最多元素个数。通过公式5-0计算(cap=max-low),也就是第三个数据减去第一个数。该案例中容量为5。
将以上程序进行修改:
func main() {
//定义切片并初始化
s := []int{10, 20, 30, 40, 50}
//从切片s中截取数据
slice := s[0:3:5]
fmt.Println(slice)
}
结果如下:
[10 20 30]
因为起点还是0,终点还是3.长度是3,容量是5。
继续修改该程序:
func main() { //定义切片并初始化 s := []int{10, 20, 30, 40, 50} //从切片s中截取数据 slice := s[0:4:5] fmt.Println(slice) }
结果如下:
[10 20 30 40]
继续修改该程序:
func main() { //定义切片并初始化 s := []int{10, 20, 30, 40, 50} //从切片s中截取数据 slice := s[1:4:5] fmt.Println(slice) }
结果如下:
[20 30 40]
那么容量是多少呢?容量为4,通过第三个数减去第一个数(5-1)计算。
通过画图的方式来表示slice切片中的容量。
通过上面的图,可以发现切片s经过截取操作以后,将结果赋值给切片slice后,长度是3,容量是4,只不过有一块区域是空闲的。
关于切片的截取还有其它的操作,如下图所示:
操作 |
含义 |
s[n] |
切片s中索引位置为n的项 |
s[:] |
从切片s的索引位置0到len(s)-1处所获得的切片 |
s[low:] |
从切片s的索引位置low到len(s)-1处所获得的切片 |
s[:high] |
从切片s的索引位置0到high处所获得的切片,len=high |
s[low:high] |
从切片s的索引位置low到high处所获得的切片,len=high-low |
s[low:high:max] |
从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low |
len(s) |
切片s的长度,总是<=cap(s) |
cap(s) |
切片s的容量,总是>=len(s) |
下面通过一个案例,演示一下:
(1)s[:]
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[:] fmt.Println("slice = ", slice) fmt.Printf("len = %d,cap = %d", len(slice), cap(slice)) }
结果如下:
slice = [1 2 3 4 5 6 7 8 9 10] len = 10,cap = 10
(2)s[low:]
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[3:] fmt.Println("slice = ", slice) fmt.Printf("len = %d,cap = %d", len(slice), cap(slice)) }
结果如下:
slice = [4 5 6 7 8 9 10] len = 7,cap = 7
(3)s[:high]
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[:6] fmt.Println("slice = ", slice) fmt.Printf("len = %d,cap = %d", len(slice), cap(slice)) }
结果如下:
slice = [1 2 3 4 5 6] len = 6,cap = 10
(4)s[low:high]
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] fmt.Println("slice = ", slice) fmt.Printf("len = %d,cap = %d", len(slice), cap(slice)) }
结果如下:
slice = [3 4 5 6] len = 4,cap = 8
s[2:6] 表示从下标为2的元素(包含该元素)开始取,到下标为6的元素(不包含该元素)结束。所以切片slice的长度是4。切片slice的容量是多少呢?是8,根据s切片的容量是10, 减去s[2:6]中的2。
现在定义一个切片s,然后对该切片s进行截取操作(范围自定义),得到新的切片slice, 并修改切片slice某个元素的值。代码如下:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] fmt.Println("slice = ",slice) }
slice切片的结果是:[3 4 5 6] 因为是从下标为2的元素(包含)开始取,到下标为6的元素(不包含)结束,取出4个元素,也就是长度为4。
现在将程序进行如下修改:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 fmt.Println("slice = ", slice) }
现在程序的输出结果是:
slice = [3 4 888 6]
接下来输出切片s的值:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 fmt.Println("slice = ", slice) fmt.Println("s = ", s) }
输出的结果如下:
slice = [3 4 888 6] s = [1 2 3 4 888 6 7 8 9 10]
我们发现切片s中的值也发生了变化,也就是修改切片slice的值会影响到原切片s的值。下面通过画图的形式来说明其原因。
创建了切片s,然后对切片s进行截取slice := s[2:6],产生了新切片slice。slice指向切片s,范围从下标2到下标6(不包括6),对应的值是3,4,5,6,然后执行slice[2] = 888,对应切片slice[2]的值变成了888,当然切片s中的值也由5变成了888.
我们再看一下两个切片中被修改的值对应的内存地址:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 fmt.Println(&slice[2]) fmt.Println(&s[4]) }
结果如下:
0xc00001e0c0 0xc00001e0c0
内存地址是一样的,所以我们可以认为slice := s[2:6],将s切片中的s[2],s[3],s[4],s[5]截取作为新切片slice,实际上是切片slice指向了原切片s(在这里并不是为切片slice新建一块区域)。所以修改slice,也会影响到s。
下面继续修改上面的程序:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 slice2 := slice[2:7] fmt.Println("slice = ", slice) fmt.Println("slice2 = ", slice2) fmt.Println("s = ", s) }
结果如下:
slice = [3 4 888 6] slice2 = [888 6 7 8 9] s = [1 2 3 4 888 6 7 8 9 10]
下面也是通过画图的形式,来解释该程序的结果:
根据切片s创建新切片,执行代码:slice := s[2:6],表示从切片s下标为2开始取出4个数据,也就是3,4,5,6。作为新切片slice的值。当执行到代码slice[2] = 888时,将切片slice中下标值为2到值修改为888,也就是将原来的5修改为888,由于slice指向了原切片s,所以s中对应的值也由5变成888。所以这时候如果输出s的值就会得到[1 2 3 4 888 6 7 8 9 10]。当执行到slice2 := slice[2:7]时,根据切片slice创建一个新切片slice2,范围从切片slice的下标2开始(注意slice[2]的值已经变成了888),截取5个数(7-2=5),但slice中一共就4个数,怎么能截取出5个数呢,因为slice指向了原切片s,所以slice2也指向了原切片s(范围是上图灰色框中的数据),所以切片slice2的值为[888 6 7 8 9]。
现在在原有的程序中又加了一行,如下所示:
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 slice2 := slice[2:7] slice2[3] = 999 fmt.Println("slice = ", slice) fmt.Println("slice2 = ", slice2) fmt.Println("s = ", s) }
最终,切片slice2与原来切片s的值分别是多少?
结果如下所示:
slice = [3 4 888 6] slice2 = [888 6 7 999 9] s = [1 2 3 4 888 6 7 999 9 10]
那么此时slice和slice2的长度和容量是多少呢?
func main() { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} slice := s[2:6] slice[2] = 888 //对下标为2的元素进行修改 slice2 := slice[2:7] slice2[3] = 999 fmt.Printf("len(slice) = %d,cap(slice) = %d ", len(slice), cap(slice)) fmt.Printf("len(slice2) = %d,cap(slice2) = %d ", len(slice2), cap(slice2)) }
结果如下:
len(slice) = 4,cap(slice) = 8 len(slice2) = 5,cap(slice2) = 6
2.7 copy函数使用
针对切片操作常用的方法除了append( )方法以外,还有copy方法.
基本语法:copy(目的切片,源切片)
将第二个切片里面的元素,拷贝到第一个切片中。
下面通过一个案例,看一下该方法的使用:
func main() { srcslice := []int{1, 2} dstslice := []int{6, 6, 6, 6, 6} copy(dstslice, srcslice) fmt.Println("dst = ", dstslice) }
上面案例中,将srcslice中的元素拷贝到dstslice切片中。结果如下:
dst = [1 2 6 6 6]
通过以上结果可以分析出,直接将srcslice切片中两个元素拷贝到dstslice元素中相同的位置。而dstslice原有的元素备替换掉。
下面将以上程序修改一下,如下所示:
func main() { srcslice := []int{1, 2} dstslice := []int{6, 6, 6, 6, 6} copy(srcslice, dstslice) fmt.Println("src = ", srcslice) }
以上程序的结果是:
src = [6 6]
通过以上两个程序得出如下结论:在进行拷贝时,拷贝的长度为两个slice中长度较小的长度值,如果长度一样则全拷贝。
思考以下程序输出的结果:
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) fmt.Println("slice2 = ", slice2) }
结果是:
slice2 = [1 2 3]
现在将程序进行如下修改:
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice1, slice2) fmt.Println("slice1 = ", slice1) }
结果是:
slice1 = [5 4 3 4 5]
思考下面代码的输出结果:
func main() { s1 := []int{1, 2, 3, 4, 5} s2 := make([]int, 5) copy(s2, s1) s2[3] = 6 fmt.Println("s2 = ", s2) fmt.Println("s1 = ", s1) }
结果是:
s2 = [1 2 3 6 5] s1 = [1 2 3 4 5]
这说明修改复制后切片中的元素,原切片不会改变。
2.8 切片作为函数参数
切片也可以作为函数参数,那么与数组作为函数参数有什么区别呢?
接下来通过一个案例,演示一下切片作为函数参数。
func InitData(num []int) { for i := 0; i < len(num); i++ { num[i] = i } } func main() { s := make([]int, 10) InitData(s) fmt.Println(s) }
输出结果:
[0 1 2 3 4 5 6 7 8 9]
通过以上案例,发现在主函数main( )中,定义了一个切片s,然后调用InitData( )函数,将切片s作为实参传递到该函数中,并在InitData( )函数中完成初始化,该函数并没有返回值,但是在主函数中直接打印切片s,发现能够输出对应的值。也就是在InitData( )函数中对形参切片num赋值,影响到了main( )函数中的切片s.
但是,大家仔细想一下,如果我们这里传递参数不是切片,而是数组,那么能否完成该操作呢?
那么我们将上面的程序,修改成以数组作为参数进行传递的形式:
func InitData(num [10]int) { for i := 0; i < len(num); i++ { num[i] = i } } func main() { var s [10]int InitData(s) fmt.Println(s) }
发现以数组的形式作为参数,并不能完成我们的要求,所以切片作为函数实参与数组作为函数实参,进行传递时,传递的方式是不一样的。
在GO语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。
什么是值传递?什么是引用传递?
值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。