06 数组和切片

本文详细介绍了Go语言中数组、多维数组和切片的使用方法,包括定义、初始化、遍历以及它们之间的区别。通过示例展示了如何创建、操作和遍历这些数据结构,同时解释了切片的长度、容量以及如何进行元素的追加和拷贝。

一、数组

1.定义

//数组的长度是类型的一部分
//如果不初始化,默认值为零值(布尔值为false)
var a1 [4]bool
var a2 [3]int

2.初始化

//初始化1
var a1 [4]bool
var a2 [3]int

a1 = [4]bool{true, true, true, true}
a2 = [3]int{12, 23, 34}

//初始化2
a3 := [4]bool{true, true, true, true}
a4 := [3]int{12, 23, 34}
a5 := [3]int{1223}		//后边自动补0

//初始化3,根据初始值自动推断长度
a6 := [...]int{1, 2, 2, 3, 3, 4}

//初始化4,根据索引初始化
a7 := [5]int{0: 12, 3: 23}

3.遍历

citys := [5]string{"beijing", "shanghai", "taiyuan", "datong", "nanjing"}

//方法1
for i := 0; i < len(citys); i++ {
	fmt.Println(citys[i])
}

//方法2
for i, v := range citys {
	fmt.Println(i, v)
}

二、多维数组

1.定义

//方法1:多维数组定义
var a1 [3][2]int
a1 = [3][2]int{
	[2]int{1, 2},
	[2]int{3, 4},
	[2]int{5, 6},
}

fmt.Println(a1)

//方法2:多维数组定义
var a1 [3][2]int = [3][2]int{
	[2]int{1, 2},
	[2]int{3, 4},
	[2]int{5, 6},
}

2.遍历

var a1 [3][2]int = [3][2]int{
	[2]int{1, 2},
	[2]int{3, 4},
	[2]int{5, 6},
}

for _, v := range a1 {
	fmt.Println(v)
}

三、切片

1.定义

//切片定义
var a1 []int = []int{1, 2, 3}
var a2 []string = []string{"hello", "world"}
a2 := []string{"hello", "world"}

2.由数组得到切片

//切片定义
a := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := a[2:4] //[3 4]
s2 := a[:4]  //[1 2 3 4]
s3 := a[4:]  //[5 6 7]
s4 := a[:]   //[1 2 3 4 5 6 7]

3.切片的长度和容量

a := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := a[2:4] //[3 4]
s2 := a[:4]  //[1 2 3 4]
s3 := a[4:]  //[5 6 7]

//切片的长度等于切片的元素数量,切片的容量等于底层数组从切片第一个元素到最后的元素数量
fmt.Println(len(a), cap(a))   //7 7
fmt.Println(len(s1), cap(s1)) //2 5
fmt.Println(len(s2), cap(s2)) //4 7
fmt.Println(len(s3), cap(s3)) //3 3

4.切片再切片

a := [...]int{1, 2, 3, 4, 5, 6, 7}
s1 := a[2:]   //[3 4 5 6 7]
s2 := s1[2:4] //[5 6]

fmt.Println(a, s1, s2) 
fmt.Println(len(s2), cap(s2))	//len=2 cap=3

//数组为值类型,切片为引用类型
//修改切片就是修改底层数组
a := [...]int{1, 2, 3, 4, 5, 6, 7}
s := a[:] //[1 2 3 4 5 6 7]

fmt.Println(a) //[1 2 3 4 5 6 7]
s[2] = 100
fmt.Println(a) //[1 2 100 4 5 6 7]

5.切片追加元素append

s1 := []string{"北京", "上海", "深圳"}

//方法1
s1 = append(s1, "广州", "杭州")

//方法2
s2 := []string{"西安", "成都", "南京"}
s1 = append(s1, s2...) //...表示拆开

6.切片拷贝copy

s1 := []string{"北京", "上海", "深圳"}
s2 := []string{"广州", "杭州"}

copy(s1, s2)

fmt.Println(s1) //[广州 杭州 深圳]
<think>我们正在讨论Go语言中数组切片的区别,以及使用`range`遍历时对它们的影响。 根据用户的问题,我们需要重点区分数组切片在存储方式、赋值行为上的不同,以及这些不同如何影响`range`操作。 关键点: 1. 数组是值类型,切片是引用类型(但注意:切片本身是一个结构体,包含指向底层数组的指针、长度容量)。 2. 使用`range`遍历数组时,会复制数组的每个元素到临时变量(副本),因此修改副本不会影响原数组3. 遍历切片时,同样会复制每个元素到临时变量(副本),但由于切片是引用类型,其底层数组是共享的。然而,这里需要注意: - 直接修改临时变量(元素副本)不会改变原切片中的元素(因为元素本身被复制了)。 - 但是,如果元素是指针或引用类型(如切片、映射、通道、接口、函数等),那么复制的副本原元素指向同一个底层数据,因此通过副本修改指针指向的数据会影响原切片。 4. 要修改原数组切片中的元素,必须通过索引(如`arr[i]`或`slice[i]`)来赋值。 我们将从以下几个方面展开: 一、数组切片的定义与区别 二、`range`遍历数组的行为 三、`range`遍历切片的行为 四、如何正确修改数组切片中的元素 五、性能考虑 引用说明: [^1]:介绍了数组切片的定义,以及数组是值类型,切片是引用类型。 [^2]:强调数组是固定长度,切片是可变长度;数组是值类型,切片是引用类型。 [^3]:切片底层基于数组,当容量不足时会分配新的数组(扩容)。 [^5]:切片的底层结构(指针、长度、容量)。 注意:引用[4]关于删除切片元素的方法,虽然与当前问题不直接相关,但可以说明切片操作的灵活性。 下面开始回答:</think>### Go语言中数组切片的区别及`range`操作的影响 #### 一、核心区别:值类型 vs 引用类型 | **特性** | **数组** | **切片** | |----------------|------------------------------|------------------------------| | **类型** | 值类型(直接存储数据) | 引用类型(存储底层数组指针)[^2] | | **长度** | 固定(声明时确定) | 动态可变(可自动扩容)[^3] | | **赋值行为** | 复制整个数组(深拷贝) | 复制切片头(浅拷贝)[^5] | | **零值** | 元素类型零值(如`[3]int{}`→`[0,0,0]`) | `nil`(未初始化时)[^2] | | **内存模型** | ![数组内存图](https://via.placeholder.com/300x100?text=连续内存块) | ![切片结构](https://via.placeholder.com/400x120?text=指针+len+cap)[^5] | #### 二、`range`遍历对原数据的影响 1. **数组遍历(值类型)** ```go arr := [3]int{1, 2, 3} for i, v := range arr { v = 10 // 修改副本,不影响原数组 arr[i] *= 2 // 通过索引修改原数组 } fmt.Println(arr) // 输出: [2, 4, 6] ``` - ✅ **可修改**:必须通过索引`arr[i]`修改 - ❌ **不修改**:直接修改`v`无效(元素是副本)[^1] 2. **切片遍历(引用类型)** ```go slice := []int{1, 2, 3} for i, v := range slice { v = 10 // 修改副本,不影响元素值 slice[i] *= 2 // 修改原切片元素 } fmt.Println(slice) // 输出: [2, 4, 6] ``` - ✅ **可修改**:通过索引`slice[i]`直接修改底层数组 - ⚠️ **特殊场景**:若元素是指针/结构体,修改字段会影响原数据 ```go users := []*User{{"Alice"}, {"Bob"}} for _, u := range users { u.Name = "Charlie" // 修改指针指向的数据(影响原切片) } ``` #### 三、关键行为对比表 | **操作** | **数组** | **切片** | |------------------|-----------------------------|------------------------------| | `range`元素副本 | 创建完整元素副本 | 创建元素值副本(非底层数据) | | 修改元素值 | 必须用索引`arr[i]` | 必须用索引`slice[i]` | | 扩容能力 | ❌ 固定长度 | ✅ `append()`自动扩容[^3] | | 传参到函数 | 复制整个数组(内存开销大) | 复制切片头(仅24字节)[^5] | | 底层数据共享 | ❌ 独立存储 | ✅ 多个切片可共享同一数组[^3] | #### 四、性能与最佳实践 1. **优先使用切片** - 动态扩容更灵活,避免固定长度限制 - 函数传参效率高(仅复制切片头) 2. **避免遍历中修改结构** ```go // 错误:遍历中追加元素可能导致底层数组重建 for _, v := range slice { slice = append(slice, v*2) // 危险! } // 正确:先计算新长度再操作 newSlice := make([]int, 0, len(slice)*2) for _, v := range slice { newSlice = append(newSlice, v*2) } ``` 3. **大数组切片传递** ```go func process(data []int) { ... } // 推荐:传切片 // vs func process(data [100000]int) { ... } // 内存复制开销大 ``` > **底层原理**:切片扩容时(如`append`超过`cap`),Go会创建新数组并复制数据,此时原切片仍指向旧数组[^3]。因此多个切片共享底层数组时,需注意修改的相互影响。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值