Golang 切片

发布于 2022-01-13 22:10:43 字数 7614 浏览 1015 评论 0

无论是C语言中的数组还是Go语言中的数组,数组的长度一旦确定就不能改变,但在实际开发中我们可能事先不能确定数组的长度,为了解决这类问题 Go 语言中推出了一种新的数据类型切片

切片可以简单的理解为长度可以变化的数组,但是 Go 语言中的切片本质上是一个结构体

切片源码

type slice struct{
  array unsafe.Pointer // 指向底层数组指针
  len int // 切片长度(保存了多少个元素)
  cap int // 切片容量(可以保存多少个元素)
}

切片创建的三种方式

方式一:通过数组创建切片 array[startIndex:endIndex]

package main
import "fmt"
func main() {
    var arr = [5]int{1, 3, 5, 7, 9}
    // 从数组0下标开始取,一直取到2下标前面一个索引
    var sce = arr[0:2]
    fmt.Println(sce) // [1 3]
    // 切片len = 结束位置 - 开始位置
    fmt.Println(len(sce)) // 2 - 0 = 2
    fmt.Println(cap(sce)) // 5 - 0 = 5
    // 数组地址就是数组首元素的地址
    fmt.Printf("%p\n", &arr) // 0xc04200a330
    fmt.Printf("%p\n", &arr[0]) // 0xc04200a330
    // 切片地址就是数组中指定的开始元素的地址
    //  arr[0:2]开始地址为0, 所以就是arr[0]的地址
    fmt.Printf("%p\n", sce) // 0xc04200a330
}

image.png

package main
import "fmt"
func main() {
  var arr = [5]int{1, 3, 5, 7, 9}
  // 根据数组的索引片段创建切片
  var sce = arr[2:4]
  fmt.Println(sce) // [5 7]
  fmt.Println(len(sce)) // 4 - 2 = 2
  fmt.Println(cap(sce)) // 5 - 2 = 3
  fmt.Printf("%p\n", &arr[2]) // 0xc042076070
  fmt.Printf("%p\n", sce) // 0xc042076070
}

指定起始位置时有三种方式可以指定

  • 开始位置和结束位置都指定
  • 只指定开始位置或结束位置
  • 开始位置和结束位置都不指定
  package main
  import "fmt"
  func main() {
	var arr = [5]int{1, 3, 5, 7, 9}
	// 同时指定开始位置和结束位置
	var sce1 = arr[0:2]
	fmt.Println(sce1) // [1 3]

	// 只指定结束位置
	var sce3 = arr[:2]
	fmt.Println(sce3) // [1 3]

	// 只指定开始位置
	var sce2 = arr[0:]
	fmt.Println(sce2) // [1 3 5 7 9]

	// 都不指定
	var sce4 = arr[:]
	fmt.Println(sce4) // [1 3 5 7 9]
  }

方式二:通过 make 函数创建 make(类型, 长度, 容量)

  • 内部会先创建一个数组,然后让切片指向数组
  • 如果没有指定容量,那么容量和长度一样
 package main
 import "fmt"
 func main() {
	// 第一个参数: 指定切片数据类型
	// 第二个参数: 指定切片的长度
	// 第三个参数: 指定切片的容量
	var sce = make([]int, 3, 5)
	fmt.Println(sce) // [0 0 0]
	fmt.Println(len(sce)) // 3
	fmt.Println(cap(sce)) // 5
	/*
	内部实现原理
	var arr = [5]int{0, 0, 0}
	var sce = arr[0:3]
	*/
 }

方式三:通过 Go 提供的语法糖快速创建

    • 和创建数组一模一样,但是 不能指定长度
    • 通过该方式创建时,切片的 长度和容量相等
 package main
 import "fmt"
 func main() {
	var sce = []int{1, 3, 5}
	fmt.Println(sce) // [1 3 5]
	fmt.Println(len(sce)) // 3
	fmt.Println(cap(sce)) // 3
 }

切片的使用

  • 切片的基本使用方式和数组一样,可以通过 切片名称[索引] 方式操作切片
 package main
 import "fmt"
 func main() {
    var sce = []int{1, 3, 5}
    // 使用切片, 往切片中存放数据
    sce[1] = 666
    // 访问切片, 从切片中获取数据
    fmt.Println(sce) // [1 666 5]
 }
  • 和数组一样,如果通过 切片名称[索引] 方式操作切片, 不能越界
  package main
  import "fmt"
  func main() {
     var sce = []int{1, 3, 5}
     // 编译报错, 越界
     sce[3] = 666
  }

如果希望切片自动扩容,那么添加数据时必须使用 append 方法

  • append 函数会在切片 末尾 添加一个元素,并返回一个追加数据之后的切片
  • 利用 append 函数追加数据时,如果追加之后没有超出切片的容量,那么返回原来的切片,如果追加之后超出了切片的容量,那么返回一个新的切片
  • append 函数每次给切片扩容都会按照原有切片容量 *2 的方式扩容
  package main
  import "fmt"
  func main() {
   var sce = []int{1, 3, 5}
   fmt.Println("追加数据前:", sce) // [1 3 5]
   fmt.Println("追加数据前:", len(sce)) // 3
   fmt.Println("追加数据前:", cap(sce)) // 3
   fmt.Printf("追加数据前: %p\n", sce) // 0xc0420600a0
   // 第一个参数: 需要把数据追加到哪个切片中
   // 第二个参数: 需要追加的数据, 可以是一个或多个
   sce = append(sce, 666)
   fmt.Println("追加数据后:", sce) // [1 3 5 666]
   fmt.Println("追加数据后:", len(sce)) // 4
   fmt.Println("追加数据后:", cap(sce)) // 6
   fmt.Printf("追加数据前: %p\n", sce) // 0xc042076b60
  }

除了 append 函数外,Go 语言还提供了一个copy函数,用于两个切片之间数据的快速拷贝

  • 格式:copy(目标切片, 源切片),会将源切片中数据拷贝到目标切片中
   package main
   import "fmt"
   func main() {
  var sce1 = []int{1, 3, 5}
  var sce2 = make([]int, 5)
  fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
  fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
  // 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组
  sce2 = sce1
  fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
  fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0
  //copy(sce2, sce1)
  fmt.Println(sce1) // [1 3 5]
  fmt.Println(sce2) // [1 3 5]
  sce2[1] = 666
  fmt.Println(sce1) // [1 666 5]
  fmt.Println(sce2) // [1 666 5]
   }
   package main
   import "fmt"
   func main() {
  var sce1 = []int{1, 3, 5}
  var sce2 = make([]int, 5)
  fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0
  fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060
  // 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组
  copy(sce2, sce1)
  fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0
  fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060
  //copy(sce2, sce1)
  fmt.Println(sce1) // [1 3 5]
  fmt.Println(sce2) // [1 3 5 0 0]
  sce2[1] = 666
  fmt.Println(sce1) // [1 3 5]
  fmt.Println(sce2) // [1 666 5 0 0]
   }

copy 函数在拷贝数据时永远以小容量为准

   package main
   import "fmt"
   func main() {
  // 容量为3
  var sce1 = []int{1, 3, 5}
  // 容量为5
  var sce2 = make([]int, 5)
  fmt.Println("拷贝前:", sce2) // [0 0 0 0 0]
  // sce2容量足够, 会将sce1所有内容拷贝到sce2
  copy(sce2, sce1)
  fmt.Println("拷贝后:", sce2) // [1 3 5 0 0]
   }
   package main
   import "fmt"
   func main() {
  // 容量为3
  var sce1 = []int{1, 3, 5}
  // 容量为2
  var sce2 = make([]int, 2)
  fmt.Println("拷贝前:", sce2) // [0 0]
  // sce2容量不够, 会将sce1前2个元素拷贝到sce2中
  copy(sce2, sce1)
  fmt.Println("拷贝后:", sce2) // [1 3]
   }

切片的注意点

  • 可以通过切片再次生成新的切片,两个切片底层指向同一数组
  package main
  import "fmt"
  func main() {
      arr := [5]int{1, 3, 5, 7, 9}
      sce1 := arr[0:4]
      sce2 := sce1[0:3]
      fmt.Println(sce1) // [1 3 5 7]
      fmt.Println(sce2) // [1 3 5]
      // 由于底层指向同一数组, 所以修改sce2会影响sce1
      sce2[1] = 666
      fmt.Println(sce1) // [1 666 5 7]
      fmt.Println(sce2) // [1 666 5]
   }
  • 和数组不同,切片只支持判断是否为 nil,不支持 ==、!= 判断
package main
import "fmt"
func main() {
    var arr1 [3]int = [3]int{1, 3, 5}
    var arr2 [3]int = [3]int{1, 3, 5}
    var arr3 [3]int = [3]int{2, 4, 6}
    // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, 
    // 如果所有元素都相同返回true,否则返回false
    fmt.Println(arr1 == arr2) // true
    fmt.Println(arr1 == arr3) // false

    sce1 := []int{1, 3, 5}
    sce2 := []int{1, 3, 5}
    //fmt.Println(sce1 == sce2) // 编译报错
    fmt.Println(sce1 != nil) // true
    fmt.Println(sce2 == nil) // false
 }
  • 只声明当没有被创建的切片是不能使用的
package main
import "fmt"
func main() {
  // 数组声明后就可以直接使用, 声明时就会开辟存储空间
  var arr [3]int
  arr[0] = 2
  arr[1] = 4
  arr[2] = 6
  fmt.Println(arr) // [2 4 6]

  // 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用
  var sce []int
  sce[0] = 2 // 编译报错
  sce[1] = 4
  sce[2] = 6
  fmt.Println(sce)
 }
  • 字符串的底层是 []byte 数组,所以字符也支持切片相关操作
package main
import "fmt"
func main() {
  str := "abcdefg"
  // 通过字符串生成切片
  sce1 := str[3:]
  fmt.Println(sce1) // defg

  sce2 := make([]byte, 10)
  // 第二个参数只能是slice或者是数组
  // 将字符串拷贝到切片中
  copy(sce2, str)
  fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0]
 }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

lorenzathorton8

文章 0 评论 0

Zero

文章 0 评论 0

萧瑟寒风

文章 0 评论 0

mylayout

文章 0 评论 0

tkewei

文章 0 评论 0

17818769742

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文