Go語言學習之路-7-切片(slice)

聊一聊切片

數組存在的問題

  • 數組長度在定義的時候就已經定義好了,且不能夠修改數組

  • 數組長度屬於類型的一部分,因此數組有不少的侷限性app

package main

import (
	"fmt"
	"reflect"
)

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

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

什麼是切片

  • 切片(Slice)是一個擁有相同類型元素的可變長度的序列函數

  • 切片是基於數組類型作的一層封裝oop

  • 切片很是靈活: 支持自動擴容性能

切片的聲明

// 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能夠直接判斷code

上面的切片申明有什麼區別,如何選擇?

若是能夠對切片的容量大小有個概念的話建議使用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))
}

在就是使用初始化賦值的方式了更直觀一些string

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)
	}
}
相關文章
相關標籤/搜索