一.数组
如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储。但是,问题是班级有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\n",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\n",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\n",&arr1)
for i := 0;i < len(arr1);i++{
fmt.Printf("%p\n",&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\n",len(arr))
fmt.Printf("此二维数组的列数为%d\n",len(arr[0]))
//遍历二维数组
for i := 0;i < len(arr);i++{
for j := 0;j < len(arr[0]);j++{
fmt.Printf("%d\t",arr[i][j])
}
fmt.Println()
}
fmt.Println("----------------------")
//使用range遍历二维数组
for _,d := range arr{
for _,v := range d{
fmt.Printf("%d\t",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\n",len(slice))
fmt.Printf("容量是:%d\n",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\n",slice)
}
0xc00008e030
我们也可以通过for循环来得到切片中每个数据的内存地址:
func main() {
slice := []int{1,2,3,4,5}
for i := 0;i < len(slice);i++{
fmt.Printf("%p\n",&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\n", len(s), cap(s))
}
结果是:
len = 5,cap = 8
并且前面我们讲解过,长度是指已经初始化的空间,现在切片s没有赋值,但是默认值为0
验证如下所示:
func main() {
s := make([]int, 5, 8)
fmt.Printf("len = %d,cap = %d\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n", 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\n", len(slice), cap(slice))
fmt.Printf("len(slice2) = %d,cap(slice2) = %d\n", 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语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。
什么是值传递?什么是引用传递?
值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值
引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

浙公网安备 33010602011771号