@@ -1,58 +0,0 @@ 11.切片(slice) | 凤凰涅槃进阶之路

11.切片(slice)

Abel sun2023年1月6日约 2588 字大约 9 分钟

11.切片(slice)

1,概述

数组的长度在定义之后无法再次修改,数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法满足开发者的真实需求。Go语言提供了数组切片(slice)来弥补数组的不足。

切片并不是数组或数组指针,它通过内部指针和相关属性引用数组片段,以实现边长方案。

slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。

2,认识切片

m_bd2b5f11d0694dfc63cbf270e566fcf2_r

a := [...]int{1,2,3,4,5}
s := a[0:3:5]

定义一个数组a,然后通过s对其进行切片。
a[0:3:5]表示:a[low:high:max]
low:表示下标的起点,如果是0,则从第一个开始。
high:表示下标的终点,(不包括此下标),那么实际终点应该是high-1

得有两个概念:长度和容量。
长度表示切片的长度=high-low
容量表示切片的容量=max-low

用代码举例:

package main

import "fmt"

func main() {
 a := [5]int{1, 2, 3, 4, 5}
 s := a[0:3:5]
 fmt.Println("s = ", s)          //切片的内容
 fmt.Println("len(s) =", len(s)) //切片的长度=3-0
 fmt.Println("cap(s) =", cap(s)) //切片的容量=5-0

 s = a[1:4:5]
 fmt.Println("s = ", s)          //从下标1开始,取4-1=3个
 fmt.Println("len(s) =", len(s)) //切片的长度=4-1
 fmt.Println("cap(s) =", cap(s)) //切片的容量=5-1
}

3,数组和切片的区别

package main

import "fmt"

func main() {
 //切片和数组的区别
 //数组[]里面的长度是一个固定的常量,数组不能修改长度,len和cap永远都是5
 a := [5]int{}
 fmt.Printf("len = %d, cap = %d\n", len(a), cap(a))

 //切片的[]里面为空,或者为...
 //切片的长度或容量不固定
 s := []int{}
 fmt.Printf("1:len = %d, cap = %d\n", len(s), cap(s)) //如果没定义,则默认为0

 s = append(s, 3) //append表示给切片末尾追加一个元素
 fmt.Printf("2:len = %d, cap = %d\n", len(s), cap(s))

}

4,切片的初始化

package main

import "fmt"

func main() {
 //自动推导类型,同时初始化
 s := []int{1, 2, 3, 4}
 fmt.Println("s = ", s)

 //借助make函数,格式为: make(切片类型,长度,容量)
 s1 := make([]int, 5, 10)
 fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1))

 //其中容量可以省略,没写的话,容量等于长度
 s2 := make([]int, 5)
 fmt.Printf("len = %d, cap = %d\n", len(s2), cap(s2))
}

5,切片截取

操作含义
s[n]切片s中下标为n的项
s[:]从切片s的下标为0到len(s)-1处所获得的切片内容
s[low:]从切片s的下标为low到len(s)-1处所获得的切片
s[:high]从切片s的下标为0到high处所获得的切片
s[low:high]从切片s的下标为low到high处所获得的切片
s[low:high:max]从切片s的下标为low到high处所获得的切片
len(s)切片s的长度,总是<=cap(s)
cap(s)切片s的容量,总是>=len(s)

示例说明:

有如下切片类型:

a := []int{0,1,2,3,4,5,6,7,8,9}

那么:

操作结果lenmap说明
a[:][0 1 2 3 4 5 6 7 8 9]1010等价于[0:len(a):cap(a)]
a[3][2]
a[3:6:7][3 4 5]35如见
a[:6][0 1 2 3 4 5]610等价于[0:6:cap(a)] 常用
a[5:][5 6 7 8 9]55等价于[5:len(a):cap(a)]

代码示例如下:

package main

import "fmt"

func main() {
 a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

 s1 := a[:] //等价于[0:len(a):cap(a)]
 fmt.Println("s1=", s1)
 fmt.Printf("len=%d, cap=%d\n", len(s1), cap(s1))
 
 //操作切片中的某个元素,方法和数组的一样
 s2 := a[5]
 fmt.Println("s2=", s2)

 s3 := a[3:6:7] //[a[3], a[4], a[5]] len=6-3 cap=7-3
 fmt.Println("s3=", s3)
 fmt.Printf("len=%d, cap=%d\n", len(s3), cap(s3))

 s4 := a[:6] //等价于[0:6:cap(a)] len=6-0 cap=10-0
 fmt.Println("s4=", s4)
 fmt.Printf("len=%d, cap=%d\n", len(s4), cap(s4))

 s5 := a[3:] //等价于[3:len(a):cap(a)]
 fmt.Println("s5=", s5)
 fmt.Printf("len=%d, cap=%d\n", len(s5), cap(s5))
}

6,切片和底层数组的关系

package main

import "fmt"

func main() {
 a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

 //新切片
 s1 := a[2:5]
 s1[1] = 666
 fmt.Println("s1 =", s1)
 fmt.Println("a =", a)

 //另一新切片
 s2 := s1[1:7]
 fmt.Println("s2 =", s2)
 s2[2] = 777
 fmt.Println("a =", a)
}

上边程序输出结果为:

$ go run 19_切片和底层数组的关系.go
s1 = [2 666 4]
a = [0 1 2 666 4 5 6 7 8 9]
s2 = [666 4 5 6 7 8]
a = [0 1 2 666 4 777 6 7 8 9]

第一个切片s1的结果还是比较容易理解的,但是到了第二个切片s2这里,有稍微有点让人感到费解了,而这,正式这一小节标题的意义,切片与底层数组的关系。

可画示意图帮助理解:

m_8e3e133e049627f70aca174d55362f93_r

7,内建函数

1,append

1,append简单使用

append函数用于向slice尾部追加元素。

package main

import "fmt"

func main() {
 a := []int{}
 fmt.Printf("len = %d, cap = %d, a =%d\n", len(a), cap(a), a)
 //在原切片的末尾添加元素
 a = append(a, 1)
 a = append(a, 2, 3)
 fmt.Printf("len = %d, cap = %d, a =%d\n", len(a), cap(a), a)

 b := make([]int, 5)
 fmt.Printf("len = %d, cap = %d, b =%d\n", len(b), cap(b), b)
 b = append(b, 6)
 fmt.Printf("len = %d, cap = %d, b =%d\n", len(b), cap(b), b)

}

2,append扩容特点

append函数会智能地控制底层数组的容量增长,一旦超过原底层数组容量,通常以原容量2倍的数值定义给新容量,并复制原来的数据。

package main

import "fmt"

func main() {
 //如果超过原来的容量,通常以2倍容量扩容
 s := make([]int, 0, 1)
 oldCap := cap(s)
 for i := 0; i < 10; i++ {
  s = append(s, i)
  if newCap := cap(s); oldCap < newCap {
   fmt.Printf("cap: %d===> %d\n", oldCap, newCap)
   oldCap = newCap
  }
 }

}

大概有如下特征:

  • 首先判断,如果新申请的容量(cap)大于2倍的旧容量(old.cap),那么最终容量(newcap)就是新申请容量的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)。
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)的1/4循环增加,即(newcap=old.cap,for {newcap+=newcap/4}),直到最终容量大于等于新申请的容量。
  • 如果最终容量计算溢出,则最终容量就是新申请容量(cap)。

需要注意的是,切片扩容还会根据切片中元素的类型不同而作不同的处理,比如intstring类型的处理方式就不一样。

package main

import "fmt"

func main() {
 s1 := []string{"aaa", "bbb", "ccc"}
 fmt.Println(s1)
 fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
 s1 = append(s1, "ddd")
 fmt.Println(s1)
 fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
 s2 := []string{"aa", "bb", "cc"}
 s1 = append(s1, s2...) //...表示展开s2这个数组/切片
 fmt.Println(s1)
 fmt.Printf("len=%d,cap=%d\n", len(s1), cap(s1))
}

2,copy

package main

import "fmt"

func main() {
 src := []int{1, 2}
 dst := []int{6, 6, 6, 6, 6, 6}
 copy(dst, src) //表示将src的内容copy给dst,然后替换1,2对应位置的元素
 fmt.Println("dst =", dst)

}

函数copy在两个slice间复制数据,复制长度以len小的为准,两个slice可指向同一底层数组。

package main

import "fmt"

func main() {
 a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

 b := a[8:]
 c := a[:5]
 fmt.Printf("a = %d,b = %d,c = %d\n", a, b, c)
 copy(c, b)
 fmt.Println("c = ", c)
 fmt.Println("a = ", a)

}

8,切片作为函数参数

package main

import (
 "fmt"
 "math/rand"
 "time"
)

//生成几个随机数
func InitData(s []int) {
 //设置种子
 rand.Seed(time.Now().UnixNano())

 for i := 0; i < len(s); i++ {
  s[i] = rand.Intn(100) //100以内的10个随机数
 }
}

//冒泡排序
func BubbleSort(s []int) {
 for i := 0; i < len(s); i++ {
  for j := 0; j < len(s)-1-i; j++ {
   if s[j] > s[j+1] {
    s[j], s[j+1] = s[j+1], s[j]
   }
  }
 }
}

func main() {
 n := 10
 //创建一个切片,len为n
 s := make([]int, n)
 fmt.Println("s =", s)

 InitData(s)
 fmt.Println("排序前: ", s)

 BubbleSort(s)
 fmt.Println("排序后: ", s)
}

切片做函数参数的时候,是引用传递,当函数对切片进行操作之后,切片的内容也会随之改变。

9,写一个猜数字游戏

1,生成一个4位的随机数

package main

import (
 "fmt"
 "math/rand"
 "time"
)

//生成几个随机数
func CreateNum(p *int) {
 //设置种子
 rand.Seed(time.Now().UnixNano())
 var num int
 for {
  num = rand.Intn(10000)
  if num >= 1000 {
   break
  }
 }
 *p = num
}

func main() {
 var randNum int
 //产生一个4位随机数
 CreateNum(&randNum)
 fmt.Println("randNum: ", randNum)
}

2,取出每一个数

package main

import (
 "fmt"
 "math/rand"
 "time"
)

//生成几个随机数
func CreateNum(p *int) {
 //设置种子
 rand.Seed(time.Now().UnixNano())
 var num int
 for {
  num = rand.Intn(10000)
  if num >= 1000 {
   break
  }
 }
 *p = num
}

//取出每一位
func GetNum(s []int, num int) {
 s[0] = num / 1000       //取千位
 s[1] = num % 1000 / 100 //取百位
 s[2] = num % 100 / 10   //取十位
 s[3] = num % 10

}
func main() {
 var randNum int
 //产生一个4位随机数
 CreateNum(&randNum)
 fmt.Println("randNum: ", randNum)

 randSlice := make([]int, 4)
 //保存着个4位数的每一位
 GetNum(randSlice, randNum)
 fmt.Println("randSlice: ", randSlice)

}

3,最终成型的样子

package main

import (
 "fmt"
 "math/rand"
 "time"
)

//生成几个随机数
func CreateNum(p *int) {
 //设置种子
 rand.Seed(time.Now().UnixNano())
 var num int
 for {
  num = rand.Intn(10000)
  if num >= 1000 {
   break
  }
 }
 *p = num
}

//取出每一位
func GetNum(s []int, num int) {
 s[0] = num / 1000       //取千位
 s[1] = num % 1000 / 100 //取百位
 s[2] = num % 100 / 10   //取十位
 s[3] = num % 10

}

func OnGame(randSlice []int) {
 var num int
 keySlice := make([]int, 4)
 for {
  for {
   fmt.Printf("请输入一个4位数:")
   fmt.Scan(&num)
   //限定输入的范围 999 < num < 10000
   if 999 < num && num < 10000 {
    break
   }
   //fmt.Println("输入的数字不符合规范。")
  }
  //fmt.Println("num = ", num)

  GetNum(keySlice, num)
  //fmt.Println("keySlice = ", keySlice)

  n := 0
  for i := 0; i < 4; i++ {
   if keySlice[i] > randSlice[i] {
    fmt.Printf("第%d位的数字大了\n", i+1)
   } else if keySlice[i] < randSlice[i] {
    fmt.Printf("第%d位的数字小了\n", i+1)
   } else {
    fmt.Printf("第%d位的数字猜对了\n", i+1)
    n++
   }
  }
  if n == 4 {
   fmt.Println("全部都猜对了!!")
   break
  }
 }
}

func main() {
 var randNum int
 //产生一个4位随机数
 CreateNum(&randNum)
 //fmt.Println("randNum: ", randNum)

 //取出这个四位数的每一位
 randSlice := make([]int, 4)
 GetNum(randSlice, randNum)
 //fmt.Println("randSlice: ", randSlice)

 //输入一个四位数
 OnGame(randSlice)

}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.9.1