Go核心开发学习笔记(十八) —— 切片Slice

思路引入:NBA每个球队的球员需要用一个数组保存,但是每个队由于交易问题,球员个数不确定,应该如何解决 —— 使用切片。

  1. 切片可以理解为动态数组,类似python列表中的.append(),切片长度可以变化的,数组长度是不可以变化的。
  2. 切片是数组的引用类型,在进行传递时遵守引用传递的机制。
  3. 遍历,访问,len(slice) 与数组都一样。
  4. 动态变化,随着元素增加动态增加。
  5. 基本语法: 与数组[n]xx不同,切片定义[]是没有值的: var slice1 []int , var slice2 []float64 …

切片基本使用案例:

package main
import "fmt"
func main() {
	 var intArray [5]int = [...]int{20,40,29,46,1}

	 //与python下标计算一样,取出来的值是(冒号后-冒号前),从下标为1的开始取,取三个
	 slice1 := intArray[3:4]
	 fmt.Println(slice1)               //打印出slice切片的取值
	 fmt.Println(cap(slice1))          //查看切片容量,动态智能增加,不会说增加一个元素,cap + 1,分批次
}

切片在内存中分布:

  1. 切片是一个引用类型,并且是一个结构体 struct。

  2. 切片在内存中包含三个部分:
    (1) 切片位置起始元素的内存地址,例如slice1 := intArray[3:4] 那么就是intArray中的: &intArray[3]。
    (2) 切片长度,例如slice1 := intArray[3:4]长度就是 : 4 - 3= 1。
    (3) 切片容量,如同前面所说的,动态智能增加的。

  3. 接第2条,var intArray [5]int = […]int{20,40,29,46,1} ,slice1 := intArray[1:4]

    slice1 在内存中分布如下: &intArray[1]->(ptr) | 3->(len) | 4->(cap)
    换成结构体写法:
    *type slice1 struct {
    ptr [3]int
    len int
    cap int
    }

  4. 由于是引用类型,因此对切片内的值进行修改,则会影响源数组的值。

package main
import "fmt"
func main() {
	 var intArray [5]int = [...]int{20,40,29,46,1}

	 slice1 := intArray[0:4]
	 slice1[0] = 200
	 fmt.Println(intArray)      //这里发现intArray第一个值已经变为200
}

切片使用的三种方式:

  1. 使用创建好的数组作为切片对象:var intArray [5]int = […]int{20,40,29,46,1};slice1 := intArray[0:4]
    Tips:
    接对数组使用切片,元素值可以通过数组和切片维护,数组对程序员可见。

  2. 使用make()来创建切片,与new()分配一个内存空间一样,make()用来针对引用类型。
    var slice2 []int = make([]int,,) //长度决定切片元素个数,cap >= len,元素全部为0

    Tips:
    使用make()创建切片后,make也会创建一个数组,数组没有名称,程序员不可见,访问切片中的内存数据,只能通过slice作为入口来访问,元素数据是对外不可见的。

  3. 直接定义切片: var slice2 []int = []int {2,3,4} ,var slice2 []string = []string {“Durant”,“James”,“Kobe”}

切片注意事项:
4. 不可越界,0 ~ len(slicex)。
5. slice := intArray[n1:] //n1开始直到最后
slice := intArray[:n2] //n2到0所有值
slice := intArray[:] //数组所有值

  1. 定义完一个切片还不能使用,因为是空的,要么直接从数组拿值,要么后续添加。

  2. 切片可以迭代,切片的切片,slice2 := slice1[:],即便迭代,改变slice2中某个元素值,slice1和intArray都会改变,因为指向同一个内存空间的值!

  3. 切片的动态增加使用append()函数添加到切片末尾, append()为切片添加元素后需要一个变量接收

    package main
    import "fmt"
    func main() {
    	/*
    	append()原理分析:底层新建一个数组,将新传递的值和slice的原值一并增加到一个新的数组里,让slice接收则指针指向新数组的首地址,
    	与此同时,slice原来指向的匿名数组就被当成垃圾回收了;让新的变量接收就不存在上述问题了,直接新的变量指向append后的数组。
    	 */
    	var slice []int = []int{100,200,300}
    	slice1 := append(slice,200,300,400)      
    	fmt.Println(slice1)                    //[100 200 300 200 300 400] append()本身并不改变源切片的值
    	fmt.Println(slice)                     //[100 200 300]  
    	slice = append(slice,slice...)         // 自己追加自己,再把新的切片赋值给自己,也可以;slice...是固定写法
    } 
    
  4. 切片的copy(slice-dest,slice-src)内置函数,完成切片拷贝,两个参数都是slice类型,不能为其他类型

    package main
    import "fmt"
    func main() {
    	var slice1 []int = []int{1,2,3,4,5}
    	var slice2 []int = []int{6,7,8,9,0,11,12,13}    
    	copy(slice1,slice2)                  //后面的参数会覆盖前面参数的slice对应位置的值
    	fmt.Println(slice1)
    	fmt.Println(slice2)
    }
    

    注意事项:
    (1) 两个切片的数据类型必须一致!
    (2) 源切片的长度如果超过目标切片长度,那么目标切片最终会取得源切片对应的值,源切片超出的部分会被丢失。
    (3) 源切片的长度如果小于目标切片长度,那么目标切片会根据源切片的位置,改变自己的值,剩下未覆盖的部分不变。

  5. string和slice的相关注意事项:
    (1) string底层是一个byte数组,所以可以进行切片处理,内存中存在形式就是 字符串:“abcd” ----> a|b|c|d
    (2) string本身不可变,不可以通过切片方式改变字符串中的某个值,例如把"abcd"改成 “accd”,切片做不到
    (3) 如果想改变string中的某个值,可以先把string转换成切片[]byte或[]rune,修改后,再回转成string

    package main
    import "fmt"
    func main() {
    	/*
    	思路:string在Golang底层是一个byte数组
    	 */
    	str := "Kurant"                   
    	arr := []byte(str)                   //把str转换成一个byte数组,只能处理英文和数字,不能处理中文
    	arr[0] = 'D'                         //把首字母改成D,记得byte的值要用单引号'',不要用""
    	str = string(arr)                    //修改后把array转换成字符string
    	fmt.Println(str)             
    	
    	str_chs := "库兰特"                   //[]rune是按照字符处理数组的,可以处理3字节的中文
    	arr1 := []rune(str_chs)
    	arr1[0] = '杜'                    
    	str_chs = string(arr1)
    	fmt.Println(str_chs)
    }
    
  6. 案例,用切片完成斐波那契数列的赋值

    package main
    import "fmt"
    func main() {
    	var n int
    	fmt.Println("请输入斐波那契数列的长度值: ")
    	fmt.Scanf("%d",&n)
    	var slice []uint64 = make([]uint64,n)          //定义一个切片接收变量,或者使用递归推导斐波那契函数
    	slice[0] = 1
    	slice[1] = 1
    	for i := 2 ; i < n ; i++ {
    		slice[i] = slice[i-1] + slice[i-2]
    	}
    	fmt.Println(slice)
    }
    
发布了49 篇原创文章 · 获赞 18 · 访问量 4011

猜你喜欢

转载自blog.csdn.net/weixin_41047549/article/details/89846668