go20/day02/slice/README.md
yumaojun03 54415d45ba ```
feat(day02): 添加Go语言基础数据类型教程

新增map和slice详细教程,完善array和struct内容

- 新增map章节,详细介绍Go中map的概念、创建、操作和底层原理
- 新增slice章节,涵盖切片的创建、操作、引用特性和实际应用
- 完善array章节,添加数组作为函数参数、多维数组等内容
- 更新struct章节,修正标题错误并补充结构体定义说明
- 为各章节添加实践作业题目,增强学习效果
```
2026-01-11 13:56:26 +08:00

206 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 切片
![alt text](image.png)
Go中的slice依赖于数组它的底层就是数组所以数组具有的优点, slice都有。 且slice支持可以通过append向slice中追加元素长度不够时会动态扩展通过再次slice切片可以得到得到更小的slice结构可以迭代、遍历等
// runtime/slice.go
type slice struct {
array unsafe.Pointer // 数组指针
len int // 长度
cap int // 容量
}
每一个slice结构都由3部分组成
+ 容量(capacity): 即底层数组的长度表示这个slice目前最多能扩展到这么长
+ 长度(length)表示slice当前的长度即当前容纳的元素个数
+ 数组指针(array): 指向底层数组的指针
比如创建一个长度为3容量为5int类型的切片
```go
s := make([]int, 3, 4)
fmt.Println(a, len(s), cap(s)) // [0 0 0] 3 5
```
![alt text](image-1.png)
## 创建和初始化
**原理**:切片是动态数组,底层基于固定大小的数组,可以根据需要动态扩容。创建时可以指定长度和容量。
```go
// 使用 make 创建切片make([]类型, 长度, 容量)
s1 := make([]int, 3, 5) // 长度3容量5
fmt.Println(s1, len(s1), cap(s1)) // [0 0 0] 3 5
// 直接初始化
s2 := []int{1, 2, 3} // 长度和容量都是3
fmt.Println(s2, len(s2), cap(s2)) // [1 2 3] 3 3
// 从数组创建切片
arr := [5]int{1, 2, 3, 4, 5}
s3 := arr[1:4] // 从索引1到3不包括4
fmt.Println(s3, len(s3), cap(s3)) // [2 3 4] 3 4
```
## 切片访问
**原理**切片通过索引访问元素索引从0开始。与数组类似但更灵活。
```go
s := []int{10, 20, 30, 40, 50}
// 通过索引访问元素
fmt.Println(s[0]) // 10
fmt.Println(s[2]) // 30
// 修改元素
s[1] = 25
fmt.Println(s) // [10 25 30 40 50]
```
## nil和空切片
**原理**nil切片表示未初始化的切片长度和容量都为0。空切片是已初始化的切片但不包含元素。两者在行为上略有不同。
```go
// nil 切片:未初始化的切片
var s1 []int
fmt.Println(s1 == nil) // true
fmt.Println(len(s1), cap(s1)) // 0 0
// 空切片:已初始化的空切片
s2 := []int{}
s3 := make([]int, 0)
fmt.Println(len(s2), cap(s2)) // 0 0
fmt.Println(len(s3), cap(s3)) // 0 0
```
## 往切片中添加元素
**原理**使用append函数添加元素。如果容量不足会自动扩容底层数组通常容量翻倍增长。
```go
s := []int{1, 2}
fmt.Println(s, len(s), cap(s)) // [1 2] 2 2
// 使用 append 添加元素
s = append(s, 3)
fmt.Println(s, len(s), cap(s)) // [1 2 3] 3 4 (容量自动扩容)
// 添加多个元素
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]
```
## 通过切片创建新的切片
**原理**切片操作创建新的切片视图共享底层数组。新切片的长度是high-low容量是原容量减去low。
```go
s := []int{0, 1, 2, 3, 4, 5}
// 创建子切片s[low:high] (不包括high)
sub1 := s[1:4] // [1, 2, 3]
fmt.Println(sub1)
// 省略索引
sub2 := s[:3] // [0, 1, 2]
sub3 := s[2:] // [2, 3, 4, 5]
sub4 := s[:] // [0, 1, 2, 3, 4, 5] 复制整个切片
```
## 遍历切片
**原理**可以使用传统for循环或range关键字遍历。range返回索引和值更简洁。
```go
s := []string{"apple", "banana", "cherry"}
// 方法1使用索引
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
// 方法2使用 range推荐
for index, value := range s {
fmt.Println(index, value)
}
```
## slice是引用类型
**原理**:切片是引用类型,赋值时复制切片头,但共享底层数组。修改一个会影响另一个。
```go
s1 := []int{1, 2, 3}
s2 := s1 // s2 指向同一个底层数组
s2[0] = 99
fmt.Println(s1) // [99 2 3] s1 也被修改了
fmt.Println(s2) // [99 2 3]
```
## 切片拷贝
**原理**使用copy函数进行深拷贝创建独立的数据副本。修改拷贝后的切片不会影响原切片。
```go
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // 拷贝元素到 s2
s2[0] = 99
fmt.Println(s1) // [1 2 3] s1 不变
fmt.Println(s2) // [99 2 3]
```
## 切片作为函数参数
**原理**:切片作为参数传递时,是引用传递,函数内修改会影响调用者。高效但需注意副作用。
```go
func sum(nums []int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
func main() {
s := []int{1, 2, 3, 4, 5}
result := sum(s)
fmt.Println(result) // 15
}
```
## 总结
切片是 Golang 中比较有特色的一种数据类型,既为我们操作集合类型的数据提供了便利的方式,是又能够高效的在函数间进行传递,因此在代码中切片类型被使用的相当广泛
## 作业
请同学们完成以下切片练习题以巩固对Go语言切片的理解
1. **创建和初始化切片**
创建一个字符串切片,包含"Go", "Python", "Java",并打印长度和容量。
2. **切片操作**
给定切片[]int{1,2,3,4,5}创建子切片包含第2到第4个元素不包括第4个并打印结果。
3. **添加元素**
从空切片开始使用append添加5个整数观察长度和容量的变化。
4. **遍历切片**
遍历一个整数切片,计算所有元素的和。
5. **切片拷贝**
创建一个切片,拷贝到另一个切片,修改原切片,验证拷贝是否独立。
6. **函数参数**
编写一个函数,接受字符串切片作为参数,将所有字符串转换为大写并返回新切片。
请将代码写在 `main.go` 文件中,并运行测试。