Go 语言自学
- 前言 -- 数组和切片声明过程的比较
- 切片的声明及初始化
- 切片的二个属性 -- 长度和容量
- 切片的地址和扩容
- 切片截取
- 切片拷贝和排序
- 切片作为函数参数传递
以下原则仅个人娱乐使用:
& 租房原理:一个人来租房经常会订二个人的份,称为预留
& 二倍原则和四分之一原则:当切片的长度超过容量时,容量将变成原来的二倍;当容量大于 1024 时,每次将增加原来容量的 1/4
& 容量不超父亲原则:截取切片的容量索引值不能超过原始切片的容量值:cap(原始切片名)
& 切片截取子影响父原则:截取切片的改变将间接导致原始切片发生改变
& 切片拷贝不变原则:拷贝后的切片的改变不会导致原始切片发生改变
& 传递切片地址变不变原则:调用函数传递切片的地址,在函数内部使用赋值语句将不会改变形参的地址;在函数内部使用 append 函数追加将改变形参的地址
前言 – 数组和切片声明过程的比较
先看一个一维数组初始化的例子
语法:var 数组名 [数组长度]数组类型
package main
import "fmt"
func main() {
// 声明一个整型数组
var arr_int [5]int
}
再看切片声明的例子
语法:var 切片名 []切片类型
package main
import "fmt"
func main() {
// 声明一个整型空切片
var slice_int []int
}
切片的声明及初始化
1. 空切片
重点:利用 append 函数往切片中添加值
package main
import "fmt"
func main() {
// 声明一个空切片
var slice_int []int
// 利用 append 函数给切片中添加值
arr_int = append(slice_int, 1, 2, 3, 4, 5)
// 打印切片内的值
fmt.Println(slice_int) // [1 2 3 4 5]
}
2. 非空切片和自动推导
package main
import "fmt"
func main() {
// 初始化一个非空切片
slice_int := []int{1, 2, 3}
// 打印切片内的值
fmt.Println(slice_int) // [1 2 3]
}
切片的二个属性 – 长度和容量
1. 不指定长度和容量来初始化切片
package main
import "fmt"
func main() {
// 初始化一个非空切片
slice_int := []int{1, 2, 3}
// 打印长度和容量
fmt.Println(len(slice_int), cap(slice_int))
// output:3 3
}
2. 指定长度和容量来声明切片 – 及赋值操作
重点:在指定长度的空切片之后利用 append 进行追加数据将导致原本的长度和容量发生变化
package main
import "fmt"
func main() {
// 声明切片
slice_int := make([]int, 5, 10)
// 打印长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 5 10
slice_int = append(slice_int, 1, 2, 3, 4, 5, 6)
fmt.Println(slice_int) // [0 0 0 0 0 1 2 3 4 5 6]
// 打印长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 11 20
}
切片的赋值
重点:实质上利用了下标
package main
import "fmt"
func main() {
// 声明切片
slice_int := make([]int, 5, 10)
// 追加数据
slice_int = append(slice_int, 1, 2, 3, 4, 5, 6)
fmt.Println(slice_int) // [0 0 0 0 0 1 2 3 4 5 6]
// 赋值
slice_int[0] = -3
slice_int[1] = -2
slice_int[2] = -1
slice_int[3] = 0
fmt.Println(slice_int) // [-3 -2 -1 0 0 1 2 3 4 5 6]
}
切片的地址和扩容
切片:空切片
如下程序证明:切片名本身就是一个地址;空切片是指向内存地址编号为 0 的空间;空切片的长度和容量均为 0(也就是说不能进行赋值操作)
package main
import "fmt"
func main() {
// 声明空切片
var slice_int []int
// 打印空切片的地址
fmt.Printf("%p\n", slice_int) // 0x0
// 空切片的长度及容量
fmt.Println(len(slice_int), cap(slice_int)) // 0 0
}
如下程序证明:空切片只能用 append 函数进行追加操作,而且追加后切片的地址、长度和容量都将发生变化;此处长度和容量的变化不统一(容量常比长度多 1,类似租房原理)
package main
import "fmt"
func main() {
// 声明空切片
var slice_int []int
// append 函数追加数据
slice_int = append(slice_int, 1, 2, 3, 4, 5)
// 追加数据后切片的地址
fmt.Printf("%p\n", slice_int) // 0xc00000a36012345
// 追加数据后切片的长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 5 6
// 打印切片
fmt.Println(slice_int) // [1 2 3 4 5]
}
切片:指定了长度和容量的切片
如下程序证明:使用 append 给指定了长度和容量的切片追加数据后,切片的地址、长度和容量均会发生改变;区别在于容量的改变规则不同 (遵循二倍原则和四分之一原则)
package main
import "fmt"
func main() {
// 声明长度为 2 容量为 5 的切片
slice_int := make([]int, 2, 5)
// 1. 第一次 append 函数追加数据
slice_int = append(slice_int, 1, 2)
// 第一次追加数据后切片的地址
fmt.Printf("%p\n", slice_int) // 0xc0000d6030
// 第一次追加数据后切片的长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 4 5
// 第一次打印切片
fmt.Println(slice_int) // [0 0 1 2]
// 2. 第二次 append 函数追加数据
slice_int = append(slice_int, 3, 4)
// 第二次追加数据后切片的地址
fmt.Printf("%p\n", slice_int) // 00120xc00000c1e0
// 第二次追加数据后切片的长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 6 10
// 第二次打印切片
fmt.Println(slice_int) // [0 0 1 2 3 4]
}
切片截取
语法:截取切片 := 原始切片[起始索引:终止索引:容量索引]
对如上语法的解释:截取切片的长度取决于终止索引和起始索引的差;截取切片的容量取决于容量索引和起始索引的差;比较重要的是,容量索引不能大于原始切片的容量值(即容量不超父亲原则)
package main
import "fmt"
func main() {
// 初始化切片
slice_int := []int{1, 2, 3, 4, 5}
// 打印切片的长度和容量
fmt.Println(len(slice_int), cap(slice_int)) // 5 5
// 切片截取
slice2_int := slice_int[1:4:5]
// 打印截取后切片的长度和容量
fmt.Println(len(slice2_int), cap(slice2_int)) // 3 3
}
重点:利用赋值操作改变截取后的切片将影响到原始切片(即切片截取子影响父原则)
package main
import "fmt"
func main() {
// 初始化切片
slice_int := []int{1, 2, 3, 4, 5}
// 1. 切片截取
slice2_int := slice_int[1:4:5]
// 打印截取切片
fmt.Println(slice2_int) // [2 3 4]
// 打印原始切片
fmt.Println(slice_int) // [1 2 3 4 5]
// 2. 利用赋值操作修改截取切片
slice2_int[0] = 200
// 打印原始切片
fmt.Println(slice_int) // [1 200 3 4 5]
}
切片拷贝和排序
切片拷贝
重点:拷贝切片不会改变原切片的长度及容量;切拷贝后的切片发生改变将不会导致原始切片发生改变(即切片拷贝不变原则)
package main
import "fmt"
func main() {
// 初始化二个切片
slice1_int := []int{1, 2}
slice2_int := []int{3, 4, 5, 6, 7}
// 打印二个切片的长度和容量
fmt.Println(len(slice1_int), cap(slice1_int)) // 2 2
fmt.Println(len(slice2_int), cap(slice2_int)) // 5 5
// 1. copy 拷贝 -- 将切片 2 拷贝到切片 1 上
copy(slice1_int, slice2_int)
// 打印二个切片的长度和容量
fmt.Println(len(slice1_int), cap(slice1_int)) // 2 2
fmt.Println(len(slice2_int), cap(slice2_int)) // 5 5
// 打印切片 1 和切片 2
fmt.Println(slice1_int) // [3 4]
fmt.Println(slice2_int) // [3 4 5 6 7]
// 初始化二个切片
slice3_int := []int{1, 2}
slice4_int := []int{3, 4, 5, 6, 7}
// 打印二个切片的长度和容量
fmt.Println(len(slice3_int), cap(slice3_int)) // 2 2
fmt.Println(len(slice4_int), cap(slice4_int)) // 5 5
// 2. copy 拷贝 -- 将切片 3 拷贝到切片 4 上
copy(slice4_int, slice3_int)
// 打印二个切片的长度和容量
fmt.Println(len(slice3_int), cap(slice3_int)) // 2 2
fmt.Println(len(slice4_int), cap(slice4_int)) // 5 5
// 打印切片 3 和切片 4
fmt.Println(slice3_int) // [1 2]
fmt.Println(slice4_int) // [1 2 5 6 7]
for _, value := range slice4_int {
fmt.Print(value, " ") // outputs:1 2 5 6 7
}
}
切片排序
自定义场景:利用切片进行 int 类型数据的排序(冒泡排序)
package main
import "fmt"
func main() {
// 定义 9 个数据的乱序切片
slice_int := []int{1, 9, 2, 8, 3, 7, 4, 6, 5}
// 输出排序前的切片
fmt.Println(slice_int) // [1 9 2 8 3 7 4 6 5]
// 进行排序
for i := 0; i < len(slice_int)-1; i++ {
for j := 0; j < len(slice_int)-1-i; j++ {
if slice_int[j] > slice_int[j+1] {
slice_int[j], slice_int[j+1] = slice_int[j+1], slice_int[j]
}
}
}
// 输出排序后的切片
fmt.Println(slice_int) // [1 2 3 4 5 6 7 8 9]
}
切片作为函数参数传递
重点:函数中使用 append 进行数据追加,形参地址发生改变(即传递切片地址变不变原则)
package main
import "fmt"
func Change(slice_int []int) {
// 赋值改变
slice_int[0] = 100
}
func NoChange(slice_int []int) {
// append 函数追加数据
slice_int = append(slice_int, 4, 5, 6)
}
func main() {
// 初始化切片
slice1_int := []int{1, 2, 3}
slice2_int := []int{1, 2, 3}
// 第一次打印切片
fmt.Println(slice1_int) // [1 2 3]
fmt.Println(slice2_int) // [1 2 3]
// 调用 NoChange 和 Change 函数
NoChange(slice1_int)
Change(slice2_int)
// 第二次打印切片
fmt.Println(slice1_int) // [1 2 3]
fmt.Println(slice2_int) // [100 2 3]
}