转载:Go基础系列:7. 内置集合 - 数组 - 知乎 (zhihu.com)
学到什么
- 什么是数组?
- 如何获取数组长度?
- 如何操作数组元素?
- 如何比较两个数组?
- 如何拷贝数组?
- 如何创建多维数组?
- 如何省略变量(补充)?
概念
数组是一个长度固定和元素类型相同的集合,类型也可以自定义。如果想保存任意类型,定义一个接口类型数组。
数组每个元素的位置,称为索引。第一元素索引为0,第二个为1,以此类推。
声明
声明格式: var 数组名 [长度]类型
举例:声明一个长度为 4,元素类型为 int 的数组。
var nums [4]int
// 打印
[0 0 0 0]
声明后每个元素默认初始化为 0 ,如果是字符串数组,默认初始化为空字符串。
初始化
数组有多种初始化方式,一一列举说明,以下我用 :=
简写方式。
方式一:初始化每个元素,如果超过数组长度,编译时会提示越界错误。
nums := [4]int{3, 2, 1, 4}
方式二:初始化部分元素,只有索引 0 和 1 被赋值,后面的元素默认为 0 。
nums := [4]int{3, 2}
// 打印
[3 2 0 0]
方式三:使用 key/value 形式初始化指定索引元素,也可以和上面方式混合。
nums := [4]int{0: 3, 3: 4}
或
nums := [4]int{3, 3: 4}
// 打印
[3 0 0 4]
方式四:不指定数组长度,使用 ...
让编译器自己决定数组的长度。
// 数组长度为 4
nums := [...]int{3, 2, 1, 4}
获取数组的长度
使用内置的函数 len
获取数组的长度。
len(array)
还可以用于获取切片、map、字符串、通道的长度。
数组元素操作
1. 获取元素
使用索引获取数组的元素,后面要讲的切片获取方式也是如此。
nums := [...]int{3, 2, 1, 4}
fmt.Println(nums[1])
// 输出
2
2. 设置元素
当有了一个数组后,后面可以对此数组元素进行修改。
nums := [...]int{3, 2, 1, 4}
nums[1] = 3
fmt.Println(nums)
// 输出
[3 3 1 4]
遍历数组
使用“for 循环语句”遍历数组,上篇“流程控制”中有详细的“for 循环语句”讲解。
遍历数组有两种方式,第一种是“迭代计数”,第二种“for-range”,使用如下:
1. 迭代计数
nums := [...]int{3, 2, 1, 4}
for i := 0; i < len(nums); i++ {
fmt.Println(nums[i])
}
2. for-range
for i, v := range nums {
fmt.Println("索引:", i, " 值", v)
}
// 输出
索引: 0 值 3
索引: 1 值 2
索引: 2 值 1
索引: 3 值 4
数组比较
两个数组是否相等要考虑两方面:
- 数组必须是同一类型,这块指的类型不是元素类型,而是数组长度和元素类型决定数组的类型,例如:
[2]int{1, 2}
它的类型是[2]int
,不要误以为是array
。 - 数组元素相等
a := [2]int{1, 2}
b := [2]int{1, 2}
if a == b {
print("相等")
} else {
print("相等")
}
// 输出
相等
注:如果 b 的初始化方式为 [...]int{1, 2}
, a 和 b 依然是相等的,因为类型还是 [2]int
。
数组拷贝
在 Go 语言中,数组是值类型,也就是说在传递过程中会自动拷贝一份。
nums := [...]int{3, 2, 1, 4}
numsCopy := nums
numsCopy[1] = 3
fmt.Println("nums:", nums)
fmt.Println("numsCopy:", numsCopy)
// 输出
nums: [3 2 1 4]
numsCopy: [3 3 1 4]
多维数组
为了更好的理解多维数组,现在还原一个场景。
现在有间教室,里面有 4 行 3 列的座位,现在让你记录每个座位学生的状态,0表示旷课、1表示请假、2表示已到场。
1. 声明
先看看多维数组如何声明。
二维数组格式:
var 数组名 [长度][长度]类型
三维数组格式:
var 数组名 [长度][长度][长度]类型
依次类推可以继续声明四维数组、五维数组等等。
继续回到上面的场景,可以确定需要一个 4 行 3 列的二维数组。
var students [4][3]int
3. 初始化
设置每个学生的状态,可以看出 1 行 3 列学生旷课(值为0),3 行 2列学生请假(值为1)。
students := [4][3]int{
{2, 2, 0},
{2, 2, 2},
{2, 1, 2},
{2, 2, 2},
}
4. 遍历二维数组
使用“迭代计数”方式遍历 students
数组,输出旷课的学生座位。
// collection/mult-array.go
for i := 0; i < 4; i++ {
for j := 0; j < 3; j++ {
if students[i][j] == 0 {
fmt.Printf("%d行%d列学生旷课", i+1, j+1)
}
}
}
// 输出
1行3列学生旷课
省略变量
在 Go 语言中,使用变量有个要求:“在函数体内,声明了变量就必须使用”。如果不想使用,就使用“下划线(_)”去省略。
举两个例子,省略函数返回的 b 变量和数组循环的索引 i 。
a, b := fun1()
for i, v := range array {
...
}
// 省略后
a, _ := fun1()
for _, v := range array {
...
}
转载:Go基础系列:8. 内置集合 - 切片 - 知乎 (zhihu.com)
学到什么
- 什么是切片?
- 如何创建切片?
- 如何获取切片长度和容量?
- 切片和数组的关系?
- 操作切片具体元素?
- 切片元素如何追加和移除?
- 切片是引用类型还是值类型?
- 如何拷贝切片?
- 如何创建多维切片?
- 切片字符串是啥?
概念
切片使用起来类似长度可变的数组,不像数组长度是固定的。但切片的底层使用的还是数组,切片只是保存了对数组的引用,帮着管理数组,实现可变的效果。
声明
格式: var 切片名称 []数据类型
和数组声明的区别是,是否指明了长度,没有长度则为切片。
var nums []int
注:切片未初始化默认为 nil ,长度为 0 。如果清空切片可以赋值 nil,例: nums = nil
。
初始化
1. make 函数
使用 make 函数初始化切片,容量(马上有讲)参数可以省略,省略后长度和容量相等,格式如下:
切片名称 := make([]数据类型,长度,容量)
示例:
// 长度为 2,容量为 5
nums := make([]int, 2, 5)
下图是示例代码的原理图,下来深层了解切片。
蓝色区域为切片的结构,它包含数组的指针(ptr)、切片长度(len)和切片容量(cap)。
- ptr:数组指针,保存数组的内存地址,指向数组的具体索引。
- len:切片的长度,可以使用
len(nums)
函数获取,表示从指针对应的索引位置开始所使用的长度。 - cap:切片的容量,可以使用
cap(nums)
函数获取,表示引用数组的长度。
黄色区域为切片底层引用的数组,数组的长度就是切片的容量。
2. 初始化具体值
nums := []int{1, 2, 3}
初始化了一个长度为 3 的切片,此时容量也为3。
操作具体元素
切片中元素的具体操作和数组的方式是一样的。如果获取元素时超出切片长度,即使没有超出容量,编译器也会报错。
nums := []int{1, 2, 3}
// 设置索引 1 的元素为 4
nums[1] = 4
fmt.Println(nums[1])
// 输出
4
获取子集
定义了一个切片或数组后,可以获取其中的一部分,即子集。
格式: 切片或数组[开始索引:结束索引]
获取从“开始索引”到“结束索引”的子集,包含开始索引,但不包含结束索引。如果是数组获取子集后,类型会转化为切片类型。
// 切片
nums := []int{1, 2, 3, 4, 5}
// 获取切片子集
nums1 := nums[2:4] // []int{3, 4}
// 数组
arr := [5]int{1, 2, 3, 4, 5}
// nums2 为切片类型
nums2 := arr[2:4] // []int{3, 4}
省略索引
“开始索引”和“结束索引”都可以省略。
- 开始索引省略,表示子集从索引 0 开始到结束索引。
- 结束索引省略,表示子集从开始索引到最后结束。
- 都省略,如果是切片两者一样,如果是数组会转化为切片类型。
nums := []int{1, 2, 3, 4, 5}
// 开始索引省略
nums1 := nums[:3] // []int{1, 2, 3}
// 结束索引省略
nums2 := nums[2:] // []int{3, 4, 5}
// nums3 和 nums 相同
// 等价于:nums3 := nums
nums3 := nums[:]
如果代码中 nums
为数组,那 nums[:]
会将数组转化为切片。下来看看以上代码的原理图,这样会加深理解。
从图中可以看出所有的切片都指向同一个数组,这也说明了切片是一个引用类型,它在传递时不会进行拷贝。
追加和移除元素
往切片中追加元素,使用到 append
函数,此函数只能追加到切片末尾。
// collection/slice-append.go
nums := []int{1, 2, 3}
nums = append(nums, 2)
nums = append(nums, 4, 5)
fmt.Println(nums)
// 输出
[1 2 3 2 4 5]
如果想追加到切片开头,没有原生的函数,使用 append
变向的实现,这种方式其实就是合并两个切片。
// collection/slice-append-front.go
nums := []int{1, 2, 3}
// ... 三个点表示将切片元素展开传递给函数
nums = append([]int{4}, nums...)
fmt.Println(nums)
// 输出
[4 1 2 3]
如何移除某个元素呢,使用切片子集和 append
函数变向实现。
// collection/slice-append-remove.go
nums := []int{1, 2, 3, 4, 5}
// 移除索引为 2 的元素值 3
nums = append(nums[:2], nums[3:]...)
fmt.Println(nums)
// 输出
[1 2 4 5]
切片拷贝
1. copy 函数
从上面了解到切片是一个引用类型,因此不能像数组一样直接赋值给一个新变量就会产生拷贝。下来使用 copy
函数完成切片的拷贝。
// collection/slice-copy-1.go
// 将 nums 拷贝到 numsCopy
nums := []int{1, 2, 3}
numsCopy := make([]int, 3)
copy(numsCopy, nums)
// 修改了 numsCopy,不会对 nums 产生影响
numsCopy[0] = 2
fmt.Println("nums:", nums)
fmt.Println("numsCopy:", numsCopy)
// 输出
nums: [1 2 3]
numsCopy: [2 2 3]
numsCopy
长度可以小于或大于 nums
的长度,如果小于就会拷贝nums
前面一部分,大于会保留 numsCopy
后面一部分。
// collection/slice-copy-2.go
// numsCopy 长度小于 nums
nums := []int{1, 2, 3}
numsCopy := make([]int, 2)
// 前面两个元素 1 和 2 被复制
copy(numsCopy, nums)
fmt.Println("numsCopy(小于):", numsCopy)
// numsCopy 长度大于 nums
numsCopy = []int{4, 5, 6, 7}
// 4,5,6 会被覆盖,保留 7
copy(numsCopy, nums)
fmt.Println("numsCopy(大于):", numsCopy)
// 输出
numsCopy(小于): [1 2]
numsCopy(大于): [1 2 3 7]
2. “长度 > 容量”触发拷贝
使用 append
函数给切片追加元素时,如果追加的长度大于切片的容量时,切片的底层数组空间则重新开辟一块比原来大的地方,并把原来的数组值拷贝一份。
注解:
- 图中”新数组“两个位置就是切片长度大于容量的时刻,这两个时刻会自动开辟新数组,与原来的数组没有任何关联,只是把值拷贝了一份。
- 图中创建”新数组“时,容量的大小是原来的 2 倍,但这不是一成不变的,不同情况算法也会不一样,想要了解清楚我推荐一篇文章《深度解密Go语言之Slice》。
多维切片
这块和多维数组是类似的,唯一的不同点是切片没有指明长度,举个例子:
// 声明二维切片
var mult [][]int
// 初始化二维切片
students := [][]int{
{2, 2, 0},
{2, 2, 2},
{2, 1, 2},
{2, 2, 2},
}
注:如果想创建三维切片、四维切片,只要和多维数组类比就行。
切片字符串
这个是啥呢?是字符串可以使用上面的子集用法,来获取字符串中的一部分。
str := "I'm laomiao."
fmt.Println(str[4:7])
// 输出
lao
new() 和 make() 的区别
看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
new(T)
为每个新的类型T
分配一片内存,初始化为0
并且返回类型为*T
的内存地址:这种方法 返回一个指向类型为T
,值为0
的地址的指针,它适用于值类型如数组和结构体;它相当于&T{}
。make(T)
返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map
和channel
标签:nums,int,切片,numsCopy,数组,go,长度 From: https://www.cnblogs.com/dadishi/p/17057436.html