mirror of
https://gitee.com/shikong-sk/golang-study
synced 2025-02-23 23:42:15 +08:00
966 lines
26 KiB
Markdown
966 lines
26 KiB
Markdown
|
**本章目录:**
|
|||
|
|
|||
|
**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.数组定义
|
|||
|
|
|||
|
基本语法:
|
|||
|
|
|||
|
```go
|
|||
|
var 数组变量名 [元素数量]T
|
|||
|
|
|||
|
// 定义一个长度为3元素类型为int的数组a
|
|||
|
var a [3]int
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
比如:`var a [5]int`, 数组的长度必须是常量,并且长度是数组类型的一部分。一旦定义,长度不能变。注意: `[5]int`和`[10]int`是不同的类型。
|
|||
|
|
|||
|
```go
|
|||
|
var 数组变量名 [元素数量]T
|
|||
|
|
|||
|
// 定义一个长度为3元素类型为int的数组a
|
|||
|
var a [3]int
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Tips :数组可以通过下标进行访问,下标是从`0`开始,最后一个元素下标是:`len-1`,访问越界(下标在合法范围之外),则触发访问越界,会panic。
|
|||
|
|
|||
|
# 2.数组初始化
|
|||
|
|
|||
|
数组的初始化也有很多方式。
|
|||
|
|
|||
|
**1.方法一**
|
|||
|
|
|||
|
初始化数组时可以使用初始化列表来设置数组元素的值。
|
|||
|
|
|||
|
```go
|
|||
|
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.方法二**
|
|||
|
|
|||
|
按照上面的方法每次都要确保提供的初始值和数组长度一致,一般情况下我们可以让编译器根据初始值的个数自行推断数组的长度,例如:
|
|||
|
|
|||
|
```go
|
|||
|
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.方法三(非常值得学习)**
|
|||
|
|
|||
|
我们还可以使用指定索引值的方式来初始化数组,例如:
|
|||
|
|
|||
|
```go
|
|||
|
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有以下两种方法:
|
|||
|
|
|||
|
```go
|
|||
|
func main() {
|
|||
|
var a = [...]string{"北京", "上海", "深圳"}
|
|||
|
// 方法1:for循环遍历
|
|||
|
for i := 0; i < len(a); i++ {
|
|||
|
fmt.Println(a[i])
|
|||
|
}
|
|||
|
|
|||
|
// 方法2:for range遍历
|
|||
|
for index, value := range a {
|
|||
|
fmt.Println(index, value)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 4.多维数组
|
|||
|
|
|||
|
Go语言是支持多维数组的,我们这里以二维数组为例(数组中又嵌套数组)。
|
|||
|
|
|||
|
# 二维数组的定义
|
|||
|
|
|||
|
```go
|
|||
|
func main() {
|
|||
|
a := [3][2]string{
|
|||
|
{"北京", "上海"},
|
|||
|
{"广州", "深圳"},
|
|||
|
{"成都", "重庆"},
|
|||
|
}
|
|||
|
fmt.Println(a) //[[北京 上海] [广州 深圳] [成都 重庆]]
|
|||
|
fmt.Println(a[2][1]) //支持索引取值:重庆
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
**注意:** 多维数组**只有第一层**可以使用`...`来让编译器推导数组长度。例如:
|
|||
|
|
|||
|
```go
|
|||
|
//支持的写法
|
|||
|
a := [...][2]string{
|
|||
|
{"北京", "上海"},
|
|||
|
{"广州", "深圳"},
|
|||
|
{"成都", "重庆"},
|
|||
|
}
|
|||
|
//不支持多维数组的内层使用...
|
|||
|
b := [3][...]string{
|
|||
|
{"北京", "上海"},
|
|||
|
{"广州", "深圳"},
|
|||
|
{"成都", "重庆"},
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 二维数组的遍历
|
|||
|
|
|||
|
```go
|
|||
|
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
|
|||
|
// 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`表示数组指针 。
|
|||
|
|
|||
|
示例演示:
|
|||
|
|
|||
|
```go
|
|||
|
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)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
执行结果:
|
|||
|
|
|||
|
```go
|
|||
|
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: 为什么要引入切片这个特性?**
|
|||
|
描述: 因为`数组的长度是固定的并且数组长度属于类型的`一部分,所以数组有很多的局限性。
|
|||
|
|
|||
|
例如:
|
|||
|
|
|||
|
```go
|
|||
|
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)`是一个拥有相同类型元素的可变长度的序列。它是`基于数组类型做的一层封装`。
|
|||
|
|
|||
|
特点:
|
|||
|
|
|||
|
- 切片它非常灵活,支持自动扩容。
|
|||
|
- 切片是一个引用类型,它的内部结构包含`地址`、`长度`和`容量`。切片一般用于快速地操作一块数据集合。
|
|||
|
|
|||
|
声明切片类型的基本语法如下:
|
|||
|
|
|||
|
```go
|
|||
|
var name []T
|
|||
|
|
|||
|
// 关键字解析
|
|||
|
- name:表示变量名
|
|||
|
- T:表示切片中的元素类型
|
|||
|
```
|
|||
|
|
|||
|
Tips : 在定义时可看出与数组定义`var array [number]T`间的区别,其不需要设置元素个数。
|
|||
|
|
|||
|
举个例子:
|
|||
|
|
|||
|
```go
|
|||
|
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()`函数求切片的容量。
|
|||
|
|
|||
|
```go
|
|||
|
// 切片长度与容量
|
|||
|
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索引界限值外还指定容量的完整的形式。
|
|||
|
|
|||
|
# 简单切片表达式
|
|||
|
|
|||
|
描述: 切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片。 切片表达式中的`low`和`high`表示一个索引范围`(左包含,右不包含)`,也就是下面代码中从数组a中选出`1<=索引值<4`的元素组成切片s,得到的切片`长度=high-low`,容量等于得到的切片的底层数组的容量。
|
|||
|
|
|||
|
```go
|
|||
|
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`则默认为切片操作数的长度:
|
|||
|
|
|||
|
```go
|
|||
|
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类型的值表示;对于数组或常量字符串,常量索引也必须在有效范围内。如果`low`和`high`两个指标都是常数,它们必须满足`low <= high`。如果索引在运行时超出范围,就会发生运行时`panic`。
|
|||
|
|
|||
|
```go
|
|||
|
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))
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
输出:
|
|||
|
|
|||
|
```go
|
|||
|
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。
|
|||
|
|
|||
|
```go
|
|||
|
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))
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
输出结果:
|
|||
|
|
|||
|
```go
|
|||
|
t:[2 3] len(t):2 cap(t):4
|
|||
|
```
|
|||
|
|
|||
|
Tips : 完整切片表达式需要满足的条件是`0 <= low <= high <= max <= cap(a)`,其他条件和简单切片表达式相同。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 4.切片遍历
|
|||
|
|
|||
|
描述: 切片的遍历方式和数组是一致的,支持索引遍历和`for range`遍历。
|
|||
|
|
|||
|
```go
|
|||
|
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]`,相应示意图如下。
|
|||
|
|
|||
|

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

|
|||
|
|
|||
|
Tips : 由上面两图可知切片的容量是`数组长度 - 切片数组起始索引下标`,例如 a[1:] = 8 - 1 其容量为7
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 6.make() 方法构造切片
|
|||
|
|
|||
|
描述: 我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的`make()`函数,格式如下:
|
|||
|
|
|||
|
格式说明:
|
|||
|
|
|||
|
```go
|
|||
|
make([]T, size, cap)
|
|||
|
|
|||
|
# 参数说明
|
|||
|
- T:切片的元素类型
|
|||
|
- size:切片中元素的数量
|
|||
|
- cap:切片的容量
|
|||
|
```
|
|||
|
|
|||
|
举个例子:
|
|||
|
|
|||
|
```go
|
|||
|
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)`返回2,`cap(a)`则返回该切片的容量。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 7.append() 方法切片添加元素
|
|||
|
|
|||
|
描述: Go语言的内建函数`append()`可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。
|
|||
|
|
|||
|
```go
|
|||
|
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()`函数直接使用,无需初始化。
|
|||
|
|
|||
|
```go
|
|||
|
var s []int
|
|||
|
s = append(s, 1, 2, 3)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
**注意:** 没有必要像下面的代码一样初始化一个切片再传入`append()`函数使用,
|
|||
|
|
|||
|
```go
|
|||
|
s := []int{} // 没有必要初始化
|
|||
|
s = append(s, 1, 2, 3)
|
|||
|
|
|||
|
var s = make([]int) // 没有必要初始化
|
|||
|
s = append(s, 1, 2, 3)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
描述: 每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在`append()`函数调用时,所以我们通常都需要用原变量接收append函数的返回值。
|
|||
|
|
|||
|
举个例子:
|
|||
|
|
|||
|
```go
|
|||
|
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)
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
输出情况:
|
|||
|
|
|||
|
```go
|
|||
|
[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的容量按照`1,2,4,8,16`这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
append() 函数还支持一次性追加多个元素,例如:
|
|||
|
|
|||
|
```go
|
|||
|
var citySlice []string
|
|||
|
// 追加一个元素
|
|||
|
citySlice = append(citySlice, "北京")
|
|||
|
// 追加多个元素
|
|||
|
citySlice = append(citySlice, "上海", "广州", "深圳")
|
|||
|
// 追加切片
|
|||
|
a := []string{"成都", "重庆"}
|
|||
|
citySlice = append(citySlice, a...)
|
|||
|
fmt.Println(citySlice) // [北京 上海 广州 深圳 成都 重庆]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 8.copy()方法复制切片
|
|||
|
|
|||
|
描述: 首先我们来看一个问题
|
|||
|
|
|||
|
```go
|
|||
|
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()`函数的使用格式如下:
|
|||
|
|
|||
|
```go
|
|||
|
copy(destSlice, srcSlice []T)
|
|||
|
|
|||
|
# 参数:
|
|||
|
- srcSlice: 数据来源切片
|
|||
|
- destSlice: 目标切片
|
|||
|
```
|
|||
|
|
|||
|
举个例子:
|
|||
|
|
|||
|
```go
|
|||
|
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语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:
|
|||
|
|
|||
|
```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`来判断。
|
|||
|
|
|||
|
```go
|
|||
|
d := [5]int{1, 2, 3, 4, 5}
|
|||
|
// 判断切片是否为空
|
|||
|
if len(d) != 0 {
|
|||
|
fmt.Println("变量 d 切片不为空: ", d)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
# 切片不能直接比较
|
|||
|
|
|||
|
描述: 切片之间是不能比较的,我们不能使用`==`操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和`nil`比较。 一个`nil`值的切片并没有底层数组,一个`nil`值的切片的长度和容量都是0。
|
|||
|
|
|||
|
但是我们不能说一个长度和容量都是0的切片一定是`nil`,例如下面的示例:
|
|||
|
|
|||
|
```go
|
|||
|
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`来判断。
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 切片的赋值拷贝
|
|||
|
|
|||
|
描述: 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。
|
|||
|
|
|||
|
```go
|
|||
|
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]
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 切片的扩容策略
|
|||
|
|
|||
|
描述: 可以通过查看`$GOROOT/src/runtime/slice.go`源码,其中扩容相关代码如下:
|
|||
|
|
|||
|
```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/4,即`(newcap=old.cap,for {newcap += newcap/4})`直到最终容量(newcap)大于等于新申请的容量(cap),即`(newcap >= cap)`
|
|||
|
- 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
|
|||
|
|
|||
|
Tips : 需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如`int`和`string`类型的处理方式就不一样。
|
|||
|
|
|||
|
**示例演示:**
|
|||
|
|
|||
|
```javascript
|
|||
|
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)
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
**执行结果:**
|
|||
|
|
|||
|
```javascript
|
|||
|
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 总结: 数组是值类型,且包含元素的类型和元素个数,需注意元素的个数`(数组长度)`属于数组类型的一部分。
|