01、切片的定义、操作、嵌套切片、引用类型证明
--数组申明之后长度就不会再改变,但往往我们需要一个动态数组,长度固定的数组一般不使用
--切片:拥有相同元素的可变长度存储容器
--定义和操作的实例:
// 切片定义的时[]中没有数字
var s1 []int
var s2 []string
fmt.Println(s1, s2)
fmt.Println(s1 == nil) // 为空表示没有开辟内存空间
fmt.Println(s2 == nil)
// 切片初始化赋值
s1 = []int{1, 2, 3}
s2 = []string{"nanjing", "huawei", "wuxi"}
fmt.Println(s1, s2)
fmt.Println(s1 == nil)
fmt.Println(s2 == nil)
// 求切片长度和容量 -- len()求长度 cap()求容量
fmt.Printf("len(s1): %d cap(s1): %d\n", len(s1), cap(s1))
fmt.Printf("len(s2): %d cap(s2): %d\n", len(s2), cap(s2))
// 2、通过数组切片的方式获取切片,和python非常类似
a1 := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 数字切片的多种方式
s3 := a1[0:4]
fmt.Println(s3) // [1 2 3 4]
s4 := a1[1:6]
fmt.Println(s4) // [2 3 4 5 6]
s5 := a1[:5]
fmt.Println(s5) // [1 2 3 4 5]
s6 := a1[3:]
fmt.Println(s6) // [4, 5, 6, 7, 8, 9, 10]
s7 := a1[:]
fmt.Println(s7) // [1 2 3 4 5 6 7 8 9 10]
// 3、切片的容量和大小:数组切片后看切片容量,一般和切片指针位置及数组最大长度有关
fmt.Printf("len(s5): %d cap(s5): %d\n", len(s5), cap(s5)) // len(s5): 5 cap(s5): 10
fmt.Printf("len(s6): %d cap(s6): %d\n", len(s6), cap(s6)) // len(s6): 7 cap(s6): 7
fmt.Printf("len(s7): %d cap(s7): %d\n", len(s7), cap(s7)) // len(s7): 10 cap(s7): 10
// 4、切片再切片,并印证切片是引用类型会随着原始数组的值改变而改变
s8 := s6[1:3]
fmt.Printf("len(s8): %d cap(s8): %d\n", len(s8), cap(s8)) // len(s8): 2 cap(s8): 6
// 印证s6是引用类型
fmt.Println(s6) // [4, 5, 6, 7, 8, 9, 10]
a1[3] = 15
fmt.Println(s6) // [15 5 6 7 8 9 10]
02、更加高级的切片定义:指定长度和容量
--make函数生成切片
--make([]T, size, cap)
--make()函数使用示例:
a := make([]int, 5, 10)
fmt.Printf("value: %v len(a): %d cap(a): %d \n", a, len(a), cap(a))
**************** 输出结果 *******************
value: [0 0 0 0 0] len(a): 5 cap(a): 10
--切片之间是不能比较的
--不能使用==操作符来判断两个切片是否含有全部相等元素
--切片唯一合法的比较操作是和nil比较
--一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0
--但我们不能说一个长度和容量都是0的切片一定是nil
--所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == 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
03、切片的赋值拷贝
--也就是说明是引用类型
// 引用的赋值拷贝
s3 := []int{1, 3, 5}
s4 := s3
fmt.Println(s3, s4)
s3[0] = 1001
fmt.Println(s3, s4)
**************** 输出 ******************
[1 3 5] [1 3 5]
[1001 3 5] [1001 3 5]
04、切片的遍历
--两种遍历方式:
// 引用的赋值拷贝
s3 := []int{1, 3, 5}
// 01、索引方式遍历
for i := 0; i < len(s3); i++ {
fmt.Println(s3[i])
}
// 02、for range方式遍历
for j, v := range s3 {
fmt.Println(j, v)
}
05、append 和
--append使用示例:
--记住一定要使用原来的切片变量对其进行接收
--代码示例:
// 不同于python,直接s1[3]="guangzhou",是没有办法将元素插入的,因为容量只有3不能通过这种方式进行扩容
// 如果是数组切片,造成实际长度小于容量是可以用上述的方法进行元素新增的,此时不涉及到扩容
s2 := make([]string, 1, 10)
fmt.Println(s2)
s2[0] = "guangzhou"
fmt.Println(s2)
// append 使用:单个添加
s1 := []string{"北京", "上海", "深圳"}
fmt.Printf("s1= %v len(s1): %d cap(s1): %d \n", s1, len(s1), cap(s1)) // s1 value: [北京 上海 深圳] len(s1): 3 cap(s1): 3
// 通过append进行扩容,必须使用原来的变量接收返回值
// 这里可以看到容量直接翻倍,说明扩容的时候不会按照新增的+原有的来计算,会直接分配多余的空间以免造成不停扩容的现象
// 扩容策略:如果新申请的容量大于原来的二倍
s1 = append(s1, "广州")
fmt.Printf("s1= %v len(s1): %d cap(s1): %d \n", s1, len(s1), cap(s1)) // s1 value: [北京 上海 深圳 广州] len(s1): 4 cap(s1): 6
// append 使用:一次添加多个
ss := []string{"南京", "成都", "苏州"}
// 这里直接 s1 = append(s1, ss) 是错误的必须拆开添加
// 在golang语言中...表示拆开元素逐个操作
s1 = append(s1, ss...)
fmt.Printf("s1= %v len(s1): %d cap(s1): %d \n", s1, len(s1), cap(s1)) // s1 value: [北京 上海 深圳 广州] len(s1): 4 cap(s1): 6
--append扩容策略:
--具体代码见:$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/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
--如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。
--需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
06、copy
--copy: 复制粘贴
--copy(新变量, 原始变量) // 新变量必须使用make初始化,并分配长度和容量
--copy不是引用复制,而是值赋值,因此原始值变化不影响当前值
--代码示例:
a1 := []int{1, 2, 3}
a2 := a1
var a3 = make([]int, 3, 3)
copy(a3, a1)
fmt.Println(a1, a2, a3) // [1 2 3] [1 2 3] [1 2 3]
a1[0] = 100
fmt.Println(a1, a2, a3) // [100 2 3] [100 2 3] [1 2 3]
07、其他操作切片元素定义: 删除 插入
--删除指定为元素:
a1 := []int{12, 34, 56, 78, 90, 21, 43, 65, 87, 99}
// 删除位置3的数据
a1 = append(a1[:3], a1[4:]...)
fmt.Println(a1) // [12 34 56 90 21 43 65 87 99]
08、切片 和 数组的一点小变化注意
--注意这里x1的值的变化:
x1 := [...]int{1, 3, 5} // [1, 3, 5]
s1 := x1[:] // [1, 3, 5]
// 1、切片不保存具体的值
// 2、切片对应的是一个底层数组
// 3、底层数组都是一块连续的内存
fmt.Println(s1, len(s1), cap(s1)) // [1, 3, 5] 3 3
s1 = append(s1[:1], s1[2:]...) // [1, 5] == s1的起始位置还是x1的起始位置,所以当s1变为[1, 5]时则说明x1[0] == 1 x1[1] == 5 x1[2] == 5
fmt.Println(s1, len(s1), cap(s1)) // [1, 5] 3 3
// 这里x1的值是重要的
fmt.Println(x1) // [1, 5, 5]
--这一段代码非常有意思,总算找到了怎么转换int到string类型
a := make([]string, 0, 10)
for i := 0; i < 10; i++ {
a = append(a, fmt.Sprintf("%v", i))
}
fmt.Println(a) // [0 1 2 3 4 5 6 7 8 9]