• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Go-slice

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

slice (切片)

Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型。它是基于数组类型做的一层封装。

一个slice由三个部分构成:指针、长度和容量。

  • 指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。
  • 长度对应slice中元素的数目;长度不能超过容量
  • 容量一般是从slice的开始位置到底层数据的结尾位置。
  • 内置的len和cap函数分别返回slice的长度和容量。

结构

// %GOROOT%\src\runtime\silce.go

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

指向底层数组的指针、切片的元素数量和底层数组的容量

切片声明

// 切片声明
var varName []T

// varName:表示变量名
// T:表示切片中的元素类型

// 切片与数组声明对比,数组指定元素数量
// 数组声明语法
var 数组变量名 [元素数量]Type

切片初始化

slice的字面值也可以按顺序指定初始化值序列,或者是通过索引和元素值指定,或者的两种风格的混合语法初始化。

//声明一个整型切片并初始化
var a = []int{}

s1 := []int{0, 1, 2, 3, 4, 5}
s2 := []int{0:1, 1:2, 2:4}
s3 := []int{0:1, 1:2, 3}
package main

import "fmt"

// 反转
func reverse(s []int) {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}

func main() {
	// 初始化数组a
    a := [...]int{0, 1, 2, 3, 4, 5}
    s1 := a[:]
	reverse(a[:])
    fmt.Println(a) // 输出结果 [5 4 3 2 1 0]
    fmt.Println(s1) // [5 4 3 2 1 0]
    reverse(s1[:2])
    fmt.Println(s1) // [4 5 3 2 1 0]
    fmt.Println(a)  // [4 5 3 2 1 0] 底层数组元素也改变
    
	// 初始化切片s
	s := []int{0, 1, 2, 3, 4, 5}
	reverse(s[:2]) // [1 0 2 3 4 5]
	reverse(s[2:]) // [1 0 5 4 3 2]
	reverse(s)
	fmt.Println(s) // [2 3 4 5 0 1]
}

注意:slice类型的变量s和数组类型的变量a的初始化语法的差异。slice和数组的字面值语法很类似,它们都是用花括弧包含一系列的初始化元素,但是对于slice并没有指明序列的长度

切片的创建

由数组创建

array[b:e],其中,array表示数组名;b表示开始索引,可以不指定,默认是0;e表示结束索引,可以不指定,默认是len(array)。array[b:e]表示创建一个包含e-b个元素的切片,第一个元素是array[b],最后一个元素是array[e-1]

package main
import "fmt"
func main() {
    var array = [...]int{0,1,2,3,4,5,6} // 创建有7个 int 型元素的数组
    s1 := array[0:4]
    fmt.Printf("%v\n", s1) // [0 1 2 3]
}
内置函数make创建

内置的make函数动态创建一个指定元素类型、长度和容量的slice。切片各元素被默认初始化为切片元素类型的零值。

格式
make([]T, len)
make([]T, len, cap) 
例子
package main

import "fmt"

func main() {
    a := make([]int, 3)
    fmt.Println(a)
    fmt.Printf("len(a):%v cap(a):%v\n", len(a), cap(a))
    b := make([]int , 10, 15)
    fmt.Printf("len(b):%v cap(b):%v\n", len(b), cap(b))
}

切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

切片s2 := a[3:6]s2下标上限cap(a)=len(a)=8相应示意图如下:

切片的操作

slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。

package main

import "fmt"

func main() {
    // 初始化数组
	months := [...]string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}

	Q2 := months[4:7]
	summer := months[6:9]

	fmt.Println(Q2)     // ["April" "May" "June"]
	fmt.Println(summer) // ["June" "July" "August"]

	for _, s := range summer {
		for _, q := range Q2 {
			if s == q {
				fmt.Printf("%s appears in both\n", s)
			}
		}
	}
}
切片的长度和容量

切片拥有自己的长度和容量,可以使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

package main

import "fmt"

func main() {
    // 初始化切片
    var a = []int{1,2}
    fmt.Printf("len(s):%v cap(s):%v\n", len(a), cap(a))
}
判断切片是否为空

检查切片是否为空,要使用len(s) == 0来判断,而不应该使用s == nil来判断。

package main

import "fmt"

func main() {
    // 初始化切片
    var a = []int{1,2}
    if len(a) == 0 {
        fmt.Println("切片为空")
    } else {
        fmt.Println("切片元素非空")
    }
}
切片元素比较

​ 切片之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较

func equal(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    for i := range x {
        if x[i] != y[i] {
            return false
        }
    }
    return true
}
赋值拷贝
// 拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容
func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}
遍历
package main
import "fmt"

func main() {
	s := []int{1, 3, 5}

	for i := 0; i < len(s); i++ {
		fmt.Println(i, s[i])
	}

	for index, value := range s {
		fmt.Println(index, value)
	}
}
切片添加元素

​ 内建函数append()可以为切片动态添加元素。 可以一次添加一个元素或多个元素,也可以添加另一个切片中的元素(后面加…)。

package main
import "fmt"
func main(){
	var s []int        // []
	s = append(s, 1)        // [1]
    // 追加多个元素
	s = append(s, 2, 3, 4)  // [1 2 3 4]
    
	s2 := []int{5, 6, 7}  
    // 追加另一个切片s2到s
	s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化。

​ 每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”(每次扩容后都是扩容前的2倍的容量),此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

package main
import "fmt"

func main() {
	//append()添加元素和切片扩容
	var numSlice []int
	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}
删除切片的元素
func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除切片的索引为2的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

切片修改元素值
func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}
// 由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。
切片的复制

内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中

copy(destSlice, srcSlice []T)
// srcSlice: 数据来源切片
// destSlice: 目标切片

示例:

func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}
切片的扩容策略

通过查看$GOROOT/src/runtime/slice.go源码

成倍自动扩容切片容量数量


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
12 go实现几中基本排序算法发布时间:2022-07-10
下一篇:
Go语言标准库_输入/输出发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap