Go语言学习之路-7-切片(slice)

聊一聊切片

数组存在的问题

  • 数组长度在定义的时候就已经定义好了,且不可以修改

  • 数组长度属于类型的一部分,所以数组有很多的局限性

package main

import (
	"fmt"
	"reflect"
)

func main() {
	a := [3]int{1, 2, 3}

	fmt.Println(reflect.TypeOf(a))
}
// [3]int  就是a的类型

什么是切片

  • 切片(Slice)是一个拥有相同类型元素的可变长度的序列

  • 切片是基于数组类型做的一层封装

  • 切片非常灵活: 支持自动扩容

切片的声明

// var names []T
* names是变量名
* T是切片中元素的类型

实际例子

package main

import "fmt"

func main() {
	// 定义一个名称为a,元素类型为string的切片
	var a []string
	// 定义一个名称为b,元素类型为int的切片,并初始化赋值
	b := []int{1, 2, 3, 4, 5}
	// 定义一个名称为c,元素类型为bool的切片,并初始化赋值
	c := []bool{true, false}
	// 通过make函数构造切片
	d := make([]string, 0, 10)

	fmt.Println(a, b, c, d)
}

切片 slice是引用类型,变量不能直接判断两个变量是否相等,只有: string、bool、int相关类型、array、struct可以直接判断

上面的切片申明有什么区别,如何选择?

如果可以对切片的容量大小有个概念的话建议使用make,因为他可以指定容量,目的就是提高性能(因为一旦容量满了就需要扩容影响性能)

make([]T, size, cap)

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量
package main

import "fmt"

func main() {
	d := make([]string, 0, 100)
	d = append(d, "alex", "eson")
	fmt.Println(len(d), cap(d))
}

在就是使用初始化赋值的方式了更直观一些

package main

import "fmt"

func main() {
	s := []string{"alex", "eson", "eric"}
	fmt.Println(s)
}

切片的本质

新创建切片

从数组创建切片

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

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

现在我有一个数组,[8]int{0,1,2,3,4,5,6,7}, 那么新创建一个切片

package main

import "fmt"

func main() {
	// s是一个数组
	s := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
	// 切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
	s1 := s[0:5]

	fmt.Printf("s-type:%T, s1-type:%T\n", s, s1)
	fmt.Println(s, s1)
}

切片的操作

新、增、删、改、复制、循环、注意事项

切片声明

package main

import "fmt"

func main() {
	// 定义一个名称为a,元素类型为string的切片
	var a []string
	// 定义一个名称为b,元素类型为int的切片,并初始化赋值
	b := []int{1, 2, 3, 4, 5}
	// 定义一个名称为c,元素类型为bool的切片,并初始化赋值
	c := []bool{true, false}
	// 通过make函数构造切片
	d := make([]string, 0, 10)

	fmt.Println(a, b, c, d)
}

切片增加元素append

package main

import "fmt"

func main() {
	// 创建一个长度为0,容量为1的切片
	nums := make([]int, 0, 1)
	fmt.Printf("nums长度:%d, nums容量:%d, nums内存地址:%p\n", len(nums), cap(nums), nums)
	for i := 0; i < 10; i++ {
		nums = append(nums, 1)
		fmt.Printf("nums长度:%d, nums容量:%d, nums内存地址:%p\n", len(nums), cap(nums), nums)
	}
	// 添加多个元素
	nums = append(nums, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3)
	fmt.Printf("nums长度:%d, nums容量:%d, nums内存地址:%p\n", len(nums), cap(nums), nums)
	fmt.Println(nums)
}

输出结果:

nums长度:0, nums容量:1, nums内存地址:0xc0000bc008
nums长度:1, nums容量:1, nums内存地址:0xc0000bc008
nums长度:2, nums容量:2, nums内存地址:0xc0000bc040
nums长度:3, nums容量:4, nums内存地址:0xc0000be040
nums长度:4, nums容量:4, nums内存地址:0xc0000be040
nums长度:5, nums容量:8, nums内存地址:0xc0000b4080
nums长度:6, nums容量:8, nums内存地址:0xc0000b4080
nums长度:7, nums容量:8, nums内存地址:0xc0000b4080
nums长度:8, nums容量:8, nums内存地址:0xc0000b4080
nums长度:9, nums容量:16, nums内存地址:0xc0000c2000
nums长度:10, nums容量:16, nums内存地址:0xc0000c2000
nums长度:23, nums容量:32, nums内存地址:0xc0000c4000
[1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3]

从结果可以看出:

  • append每次把元素添加切片中
  • 每次切片容量满的时候切片会自动扩容,容量会扩容为当前容量的2倍

切片的扩容策略 $GOROOT/src/runtime/slice.go中

	newcap := old.cap
	doublecap := newcap + newcap
	if cap > doublecap {
		newcap = cap
	} else {
		if old.len < 1024 {
			newcap = doublecap
		} else {
			// Check 0 < newcap to detect overflow
			// and prevent an infinite loop.
			for 0 < newcap && newcap < cap {
				newcap += newcap / 4
			}
			// Set newcap to the requested cap when
			// the newcap calculation overflowed.
			if newcap <= 0 {
				newcap = cap
			}
		}
	}
  • 如果原切片长度小于1024,那么容量是当前的两倍
  • 否则长度是:原长度+原长度的4分之一

删除元素

package main

import "fmt"

func main() {
	nums := []int{11, 12, 13, 14, 15}
	// 切片没有给删除元素单独指定方法,但是可以通过append以及切片特性来实现:
	// 删除索引为2的元素(索引是0开始的)
	nums = append(nums[:2], nums[3:]...)
	fmt.Println(nums) // 输出结果:[11 12 14 15]
}

修改元素

package main

import "fmt"

func main() {
	nums := []int{11, 12, 13, 14, 15}
	// 修改下标为1的元素
	nums[1] = 111
	fmt.Println(nums) // 输出结果:[11 111 13 14 15]
}

复制copy

因为切片类型的特性,它是一个引用类型,变量指向的并不是实际的数据,所以当我复制的时候其实相当于把指针复制了一遍
他们指向了相同的内存

package main

import "fmt"

func main() {
	n1 := []int{11, 12, 13, 14, 15}
	n2 := n1

	fmt.Printf("n1的内存地址:%p, n2的内存地址:%p", n1, n2)
	// 输出结果: n1的内存地址:0xc000138000, n2的内存地址:0xc000138000
        // 同理所以n1和n2是同一个内存指向,修改任意一个都会影响另外一个
}

所以需要一个函数来解决:Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中

package main

import "fmt"

func main() {
	n1 := []int{11, 12, 13, 14, 15}
	n2 := make([]int, 5, 5)
	// copy接收两个参数目标和源
	copy(n2, n1)

	fmt.Printf("n1的内存地址:%p, n2的内存地址:%p\n", n1, n2)
	// 输出结果: n1的内存地址:0xc00001c0f0, n2的内存地址:0xc00001c120
	// 两个不同的内存
	// 现在修改n1和n2就不会互相影响了
	n1[0] = 123
	n2[0] = 321

	fmt.Printf("n1的值:%v  n1的内存地址:%p\n", n1, n1)
	fmt.Printf("n2的值:%v  n2的内存地址:%p\n", n2, n2)
	// 输出结果: 
	// n1的值:[123 12 13 14 15]  n1的内存地址:0xc00001c0f0
	// n2的值:[321 12 13 14 15]  n2的内存地址:0xc00001c120
}

循环切片

package main

import "fmt"

func main() {
	n1 := []int{11, 12, 13, 14, 15}

	// 第一种循环通过切片长度
	for i := 0; i < len(n1); i++ {
		fmt.Printf("n1的当前下标是:%d, n1当前下标元素值是: %d\n", i, n1[i])
	}

	// 第二种通过range
	for index, value := range n1 {
		fmt.Printf("n1的当前下标是:%d, n1当前下标元素值是: %d\n", index, value)
	}
}
posted @ 2021-01-04 23:32  天帅  阅读(162)  评论(0编辑  收藏  举报