golang-study/notes/4.数组.md
2021-09-11 18:30:18 +08:00

26 KiB
Raw Permalink Blame History

本章目录:

0X00 Go语言基础之数组

1.数组定义

2.数组初始化

3.数组的遍历

4.多维数组

二维数组的定义

二维数组的遍历

5.数组是值类型

0X01 Go语言基础之切片

1.切片定义

2.切片长度与容量

3.切片表达式

简单切片表达式

完整切片表达式

4.切片遍历

5.切片的本质

6.make() 方法构造切片

7.append() 方法切片添加元素

8.copy()方法复制切片

9.从切片中删除元素

10.切片相关操作

判断切片是否为空

切片不能直接比较

切片的赋值拷贝

切片的扩容策略

0X00 Go语言基础之数组

描述: 本文主要介绍Go语言中数组array及它的基本使用。

Q: 什么是Array(数组)?

答: 学过C语言的同学都知道数组是同一种数据类型元素的集合。 在Go语言中数组从声明时就确定使用时可以修改数组成员但是数组大小不可变化。

1.数组定义

基本语法:

var 数组变量名 [元素数量]T

// 定义一个长度为3元素类型为int的数组a
var a [3]int

比如:var a [5]int 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。注意: [5]int[10]int是不同的类型。

var 数组变量名 [元素数量]T

// 定义一个长度为3元素类型为int的数组a
var a [3]int

Tips :数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1访问越界下标在合法范围之外则触发访问越界会panic。

2.数组初始化

数组的初始化也有很多方式。

1.方法一

初始化数组时可以使用初始化列表来设置数组元素的值。

func main() {
	var testArray [3]int                        //数组会初始化为int类型的零值
	var numArray = [3]int{1, 2}                 //使用指定的初始值完成初始化
	var cityArray = [3]string{"北京", "上海", "深圳"} //使用指定的初始值完成初始化
	fmt.Println(testArray)                      //[0 0 0]
	fmt.Println(numArray)                       //[1 2 0]
	fmt.Println(cityArray)                      //[北京 上海 深圳]
}

2.方法二

按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,例如:

func main() {
	var testArray [3]int
	var numArray = [...]int{1, 2}
	var cityArray = [...]string{"北京", "上海", "深圳"}
	fmt.Println(testArray)                          //[0 0 0]
	fmt.Println(numArray)                           //[1 2]
	fmt.Printf("type of numArray:%T\n", numArray)   //type of numArray:[2]int
	fmt.Println(cityArray)                          //[北京 上海 深圳]
	fmt.Printf("type of cityArray:%T\n", cityArray) //type of cityArray:[3]string
}

3.方法三(非常值得学习)

我们还可以使用指定索引值的方式来初始化数组,例如:

func main() {
	a := [...]int{1: 1, 3: 5}
  b := [...]int{1:100,9:200}      // [0 100 0 0 0 0 0 0 200 ]
	fmt.Println(a)                  // [0 1 0 5]
	fmt.Printf("type of a:%T\n", a) // type of a:[4]int
}

3.数组的遍历

遍历数组a有以下两种方法

func main() {
	var a = [...]string{"北京", "上海", "深圳"}
	// 方法1for循环遍历
	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}

	// 方法2for range遍历
	for index, value := range a {
		fmt.Println(index, value)
	}
}

4.多维数组

Go语言是支持多维数组的我们这里以二维数组为例数组中又嵌套数组

二维数组的定义

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}
	fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
	fmt.Println(a[2][1]) //支持索引取值:重庆
}

注意: 多维数组只有第一层可以使用...来让编译器推导数组长度。例如:

//支持的写法
a := [...][2]string{
	{"北京", "上海"},
	{"广州", "深圳"},
	{"成都", "重庆"},
}
//不支持多维数组的内层使用...
b := [3][...]string{
	{"北京", "上海"},
	{"广州", "深圳"},
	{"成都", "重庆"},
}

二维数组的遍历

func main() {
	a := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}

	// 方式1. for range 方式
	for _, v1 := range a {
		for _, v2 := range v1 {
			fmt.Printf("%s\t", v2)
		}
		fmt.Println()
	}
}

输出:

北京 上海

广州 深圳

成都 重庆

5.数组是值类型

描述: 数组是值类型,赋值和传参会复制整个数组。因此改变副本的值,不会改变本身的值。

// go 语言中默认传参是值传递(拷贝原变量的值即 Ctrl+c 、Ctrl+v 
func modifyArray(x [3]int) {
	x[0] = 100
}

func modifyArray2(x [3][2]int) {
	x[2][0] = 100
}
func main() {
	a := [3]int{10, 20, 30}
	modifyArray(a) //在modify中修改的是a的副本x不会更改数组a的元素
	fmt.Println(a) //[10 20 30]

	b := [3][2]int{
		{1, 1},
		{1, 1},
		{1, 1},
	}
	modifyArray2(b) //在modify中修改的是b的副本x不会更改数组b的元素
	fmt.Println(b)  //[[1 1] [1 1] [1 1]]
}

注意:

  1. 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的。
  2. [n]*T表示指针数组,*[n]T表示数组指针 。

示例演示:

package main

import "fmt"

func main() {
	// 定义一个长度为3元素类型为int的数组a
	var a [2]int      // 默认为0
	var a1 [2]string  // 默认为空
	var a2 [2]bool    // 默认为false
	var a3 [2]float64 // 默认为0

	fmt.Printf("a 数组类型 %T , 元素: %v\n", a, a)
	fmt.Printf("a1 数组类型 %T , 元素: %v\n", a1, a1)
	fmt.Printf("a2 数组类型 %T , 元素: %v\n", a2, a2)
	fmt.Printf("a3 数组类型 %T , 元素: %v\n", a3, a3)

	// 数组初始化
	// 方式1.使用初始化列表来设置数组元素的值
	var b = [3]int{1, 2} // 三个元素,未指定下标元素的其值为 0
	var c = [3]string{"Let's", "Go", "语言"}
	// 方式2.根据初始值的个数自行推断数组的长度
	var d = [...]float32{1.0, 2.0}
	e := [...]bool{true, false, false}
	// 方式3.使用指定索引值的方式来初始化数组
	var f = [...]int{1: 1, 3: 8} // 只有 下标为1的其值为1下标为3的其值为8初开之外都为0
	g := [...]string{"Weiyi", "Geek"}

	fmt.Printf("b 数组类型 %T , 元素: %v\n", b, b)
	fmt.Printf("c 数组类型 %T , 元素: %v\n", c, c)
	fmt.Printf("d 数组类型 %T , 元素: %v\n", d, d)
	fmt.Printf("e 数组类型 %T , 元素: %v\n", e, e)
	fmt.Printf("f 数组类型 %T , 元素: %v\n", f, f)
	fmt.Printf("f 数组类型 %T , 元素: %v\n", g, g)

	// 数组指定元素获取
	fmt.Println("c[1] 元素获取 : ", c[1])
	// 数组遍历
	// 方式1
	alen := len(c)
	for i := 0; i < alen; i++ {
		fmt.Printf("c[%d]: %s ", i, c[i])
	}
	fmt.Println()
	// 方式2
	for i, v := range c {
		fmt.Printf("c[%d]: %s ", i, v) // 注意如果是切片类型需要强转为string
	}
	fmt.Println()

	// 多维数组
	// 方式1
	s1 := [3][2]string{
		{"北京", "上海"},
		{"广州", "深圳"},
		{"成都", "重庆"},
	}

	// 方式2
	s2 := [...][2]string{
		{"Go", "C"},
		{"PHP", "Python"},
		{"Shell", "Groovy"},
	}
	fmt.Println(s1[2][1])    //支持索引取值:重庆
	fmt.Println(len(s1), s1) //[[北京 上海] [广州 深圳] [成都 重庆]]
	fmt.Println(len(s2), s2)

	// 多维数组遍历
	// 方式1
	s1len := len(s1)
	for i := 0; i < s1len; i++ {
		s1length := len(s1[i])
		for j := 0; j < s1length; j++ {
			fmt.Printf("s1[%d][%d] = %v ", i, j, s1[i][j])
		}
	}
	fmt.Println()

	// 方式2 (推荐方式)
	for i, v1 := range s2 {
		for j, v2 := range v1 {
			fmt.Printf("s2[%d][%d] = %v ", i, j, v2)
		}
	}
	fmt.Println()

	// 多维数组元素更改
	s1[1][0] = "Test"
	s1[1][1] = "Change"
	fmt.Println(s1)
}

执行结果:

a 数组类型 [2]int , 元素: [0 0]
a1 数组类型 [2]string , 元素: [ ]
a2 数组类型 [2]bool , 元素: [false false]
a3 数组类型 [2]float64 , 元素: [0 0]
b 数组类型 [3]int , 元素: [1 2 0]
c 数组类型 [3]string , 元素: [Let's Go 语言]
d 数组类型 [2]float32 , 元素: [1 2]
e 数组类型 [3]bool , 元素: [true false false]
f 数组类型 [4]int , 元素: [0 1 0 8]
f 数组类型 [2]string , 元素: [Weiyi Geek]
c[1] 元素获取 :  Go
c[0]: Let's c[1]: Go c[2]: 语言
c[0]: Let's c[1]: Go c[2]: 语言
s1[2][1] 元素获取 : 重庆
3 [[北京 上海] [广州 深圳] [成都 重庆]]
3 [[Go C] [PHP Python] [Shell Groovy]]
s1[0][0] = 北京 s1[0][1] = 上海 s1[1][0] = 广州 s1[1][1] = 深圳 s1[2][0] = 成都 s1[2][1] = 重庆
s2[0][0] = Go s2[0][1] = C s2[1][0] = PHP s2[1][1] = Python s2[2][0] = Shell s2[2][1] = Groovy
[[北京 上海] [Test Change] [成都 重庆]]

0X01 Go语言基础之切片

描述: 本文主要介绍Go语言中切片slice及它的基本使用。

Q: 为什么要引入切片这个特性? 描述: 因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。

例如:

func arraySum(x [3]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}

这个求和函数只能接受[3]int类型,其他的都不支持。 再比如,

a := [3]int{1, 2, 3}

数组a中已经有三个元素了我们不能再继续往数组a中添加新元素了, 所以为了解决上述问题我们引入了Python一样切片的编程语言特性。

1.切片定义

描述: 切片Slice是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装

特点:

  • 切片它非常灵活,支持自动扩容。
  • 切片是一个引用类型,它的内部结构包含地址长度容量。切片一般用于快速地操作一块数据集合。

声明切片类型的基本语法如下:

var name []T

// 关键字解析
- name:表示变量名
- T:表示切片中的元素类型

Tips : 在定义时可看出与数组定义var array [number]T间的区别,其不需要设置元素个数。

举个例子:

func main() {
	// 声明切片类型
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
	fmt.Println(c == nil)       //false
	// fmt.Println(c == d)      //切片是引用类型不支持直接比较只能和nil比较
}

2.切片长度与容量

描述: 切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

// 切片长度与容量
var lth = []int{}
var lth64 = []float64{1, 2, 3}
fmt.Println("切片长度", len(lth), ",切片容量", cap(lth))      // 切片长度 0 ,切片容量 0
fmt.Println("切片长度", len(lth64), ",切片容量", cap(lth64))  // 切片长度 3 ,切片容量 3

3.切片表达式

描述: 切片表达式从字符串、数组、指向数组或切片的指针构造子字符串或切片。

它有两种变体一种指定low和high两个索引界限值的简单的形式另一种是除了low和high索引界限值外还指定容量的完整的形式。

简单切片表达式

描述: 切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的lowhigh表示一个索引范围(左包含,右不包含)也就是下面代码中从数组a中选出1<=索引值<4的元素组成切片s得到的切片长度=high-low,容量等于得到的切片的底层数组的容量。

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s := a[1:3]  // s := a[low:high]
	fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) // 5 - 1
}

输出:

s:[2 3] len(s):2 cap(s):4

为了方便起见,可以省略切片表达式中的任何索引。省略了low则默认为0省略了high则默认为切片操作数的长度:

a[2:]  // 等同于 a[2:len(a)]
a[:3]  // 等同于 a[0:3]
a[:]   // 等同于 a[0:len(a)]

**注意:**对于数组或字符串,如果0 <= low <= high <= len(a)则索引合法否则就会索引越界out of range

Tips : 对切片再执行切片表达式时(切片再切片high的上限边界是切片的容量cap(a),而不是长度。

常量索引必须是非负的并且可以用int类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果lowhigh两个指标都是常数,它们必须满足low <= high。如果索引在运行时超出范围,就会发生运行时panic

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	s1 := a[1:3]  // s1 := a[low:high]
	fmt.Printf("s1:%v len(s1):%v cap(s1):%v\n", s1, len(s1), cap(s1))
	s2 := s[3:4]  // 索引的上限是cap(s)而不是len(s)
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2))
}

输出:

s:[2 3] len(s):2 cap(s):4
s2:[5]  len(s2):1 cap(s2):1

完整切片表达式

描述: 对于数组指向数组的指针或切片a(注意不能是字符串)支持完整切片表达式:

a[low : high : max]

描述: 上面的代码会构造与简单切片表达式a[low: high]相同类型、相同长度和元素的切片。另外它会将得到的结果切片的容量设置为max-low。在完整切片表达式中只有第一个索引值low可以省它默认为0。

func main() {
	a := [5]int{1, 2, 3, 4, 5}
	t := a[1:3:5]
	fmt.Printf("t:%v len(t):%v cap(t):%v\n", t, len(t), cap(t))
}

输出结果:

t:[2 3] len(t):2 cap(t):4

Tips : 完整切片表达式需要满足的条件是0 <= low <= high <= max <= cap(a),其他条件和简单切片表达式相同。

4.切片遍历

描述: 切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

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)
	}
}

5.切片的本质

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

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

WeiyiGeek.slice_01

切片s2 := a[3:6],相应示意图如下:

WeiyiGeek.slice_02

Tips 由上面两图可知切片的容量是数组长度 - 切片数组起始索引下标,例如 a[1:] = 8 - 1 其容量为7

6.make() 方法构造切片

描述: 我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

格式说明:

make([]T, size, cap)

# 参数说明
- T:切片的元素类型
- size:切片中元素的数量
- cap:切片的容量

举个例子:

func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}

上面代码中a的内部存储空间已经分配了10个但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2cap(a)则返回该切片的容量。

7.append() 方法切片添加元素

描述: Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

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}
	s = append(s, s2...)    // [1 2 3 4 5 6 7]
}

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

var s []int
s = append(s, 1, 2, 3)

注意: 没有必要像下面的代码一样初始化一个切片再传入append()函数使用,

s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)

var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)

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

举个例子:

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)
	}
}

输出情况:

[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

从上面的结果可以看出:

  1. append()函数将元素追加到切片的最后并返回该切片。
  2. 切片numSlice的容量按照124816这样的规则自动进行扩容每次扩容后都是扩容前的2倍。

append() 函数还支持一次性追加多个元素,例如:

var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice)       //  [北京 上海 广州 深圳 成都 重庆]

8.copy()方法复制切片

描述: 首先我们来看一个问题

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]
}

Tips : 由于切片是引用类型所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,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]
}

9.从切片中删除元素

描述: Go语言中并没有删除切片元素的专用方法我们可以使用切片本身的特性来删除元素。 代码如下:

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:]...)

10.切片相关操作

判断切片是否为空

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

d := [5]int{1, 2, 3, 4, 5}
// 判断切片是否为空
if len(d) != 0 {
  fmt.Println("变量 d 切片不为空: ", d)
}

切片不能直接比较

描述: 切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。

但是我们不能说一个长度和容量都是0的切片一定是nil,例如下面的示例:

var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

切片的赋值拷贝

描述: 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
	s1 := make([]int, 3) //[0 0 0]
	s2 := s1             //将s1直接赋值给s2s1和s2共用一个底层数组
	s2[0] = 100
	fmt.Println(s1) //[100 0 0]
	fmt.Println(s2) //[100 0 0]
}

切片的扩容策略

描述: 可以通过查看$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
		}
	}
}

从上面的代码可以看出以下内容:

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

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

示例演示:

package main

import "fmt"

func main() {
	// 切片声明与定义
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化

	// - 切片 a 变量值为空/零值。
	if a == nil {
		fmt.Println("a 切片元素:", a)
	}
	fmt.Println("b 切片元素:", b)
	fmt.Println("c 切片元素:", c)

	// 切片长度与容量
	var lth = []int{}
	var lth64 = []float64{1, 2, 3}
	fmt.Println("切片长度", len(lth), ",切片容量", cap(lth))
	fmt.Println("切片长度", len(lth64), ",切片容量", cap(lth64))

	// 切片表达式
	d := [5]int{1, 2, 3, 4, 5}
	s := [5]string{"Let", "'s", "Go", "语言", "学习"}
	s1 := d[1:3]   // s := d[low(包含):high(不包含)] == d[1] d[2]
	s2 := d[2:]    // 等同于 a[2:5]  == d[2] d[3] d[4]
	s3 := d[:3]    // 等同于 a[0:3]  == d[0] d[1] d[2]
	s4 := d[:]     // 等同于 a[0:5]  == d[0] d[1] d[2] d[3] d[4]
	s5 := s[1:4:5] // 等同于 s[1:4] == s[1] s[2] s[3]

	fmt.Printf("s1:%v len(s1):%v cap(s1):%v\n", s1, len(s1), cap(s1)) // 注意此种情况 { 2 .. 5 容量为 4 }
	fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) // { 3 .. 5 容量为 3 }
	fmt.Printf("s3:%v len(s3):%v cap(s3):%v\n", s3, len(s3), cap(s3)) // 注意此种情况 { 1 .. 5 容量为 5 }
	fmt.Printf("s4:%v len(s4):%v cap(s4):%v\n", s4, len(s4), cap(s4)) // { 1 .. 5 容量为 5}
	fmt.Printf("s5:%v len(s5):%v cap(s5):%v\n", s5, len(s5), cap(s5)) // s5:['s Go 语言] len(s5):3 cap(s5):4

	// 判断切片是否为空
	if len(d) != 0 {
		fmt.Println("变量 d 切片不为空: ", d)
	}

	// 切片遍历
	for i, v := range s {
		fmt.Printf("i: %d, v: %v , 切片指针地址: %p \n", i, v, &v)
	}
	fmt.Println()

	// make() 构造切片
	e := make([]int, 2, 10)
	fmt.Printf("e:%v len(e):%d cap(e):%d \n", e, len(e), cap(e)) // 长度 2容量为 10

	// append() 添加元素 {7,8,9}
	f := append(e, 7, 8, 9)                                      // f:[0 0 7 8 9] len(f):5 cap(f):10
	f = append(f, e...)                                          // 追加切片
	fmt.Printf("f:%v len(f):%d cap(f):%d \n", f, len(f), cap(f)) // 长度 7容量为 10

	// copy() 复制切片
	slice1 := []int{1, 2, 3, 4, 5}
	slice2 := make([]int, 7, 7)
	copy(slice2, slice1)
	slice2[6] = 2048
	fmt.Println("slice1 : ", slice1, "\t slice2 :", slice2)

	// 切片赋值拷贝
	slice3 := make([]int, 3)
	slice4 := slice3
	slice4[0] = 1024
	slice4[2] = 4096
	fmt.Printf("slice3 : %v, ptr : %p \n", slice3, slice3)
	fmt.Printf("slice4 : %v, ptr : %p \n", slice4, slice4)
}

执行结果:

a 切片元素: []
b 切片元素: []
c 切片元素: [false true]
切片长度 0 ,切片容量 0
切片长度 3 ,切片容量 3
s1:[2 3] len(s1):2 cap(s1):4
s2:[3 4 5] len(s2):3 cap(s2):3
s3:[1 2 3] len(s3):3 cap(s3):5
s4:[1 2 3 4 5] len(s4):5 cap(s4):5
s5:['s Go 语言] len(s5):3 cap(s5):4
变量 d 切片不为空:  [1 2 3 4 5]
i: 0, v: Let , 切片指针地址: 0xc000010270
i: 1, v: 's , 切片指针地址: 0xc000010270
i: 2, v: Go , 切片指针地址: 0xc000010270
i: 3, v: 语言 , 切片指针地址: 0xc000010270
i: 4, v: 学习 , 切片指针地址: 0xc000010270

e:[0 0] len(e):2 cap(e):10
f:[0 0 7 8 9 0 0] len(f):7 cap(f):10
slice1 :  [1 2 3 4 5] 	 slice2 : [1 2 3 4 5 0 2048]
slice3 : [1024 0 4096], ptr : 0xc000018288
slice4 : [1024 0 4096], ptr : 0xc000018288

Tips 总结: 数组是值类型,且包含元素的类型和元素个数,需注意元素的个数(数组长度)属于数组类型的一部分。