WDNMD

你到底想成为什么样的人?

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

一.数组

  如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储。但是,问题是班级有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语言中,数组作为参数进行传递是值传递,而切片作为参数进行传递是引用传递。

  什么是值传递?什么是引用传递?

 

  值传递:方法调用时,实参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参数的值

 

  引用传递:也称为传地址。函数调用时,实际参数的引用(地址,而不是参数的值)被传递给函数中相对应的形式参数(实参与形参指向了同一块存储区域),在函数执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。

 

posted on 2019-06-18 21:45  真的白给  阅读(404)  评论(0编辑  收藏  举报