# 1.5 数据类型:数组与切片
## 1. 数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
声明数组,并给该数组里的每个元素赋值(索引值的最小有效值和其他大多数语言一样是 0,不是1)
```go
// [3] 里的3 表示该数组的元素个数及容量
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
```
声明并直接初始化数组
```go
// 第一种方法
var arr [3]int = [3]int{1,2,3}
// 第二种方法
arr := [3]int{1,2,3}
```
上面的 3 表示数组的元素个数 ,万一你哪天想往该数组中增加元素,你得对应修改这个数字,为了避免这种硬编码,你可以这样写,使用 `...` 让Go语言自己根据实际情况来分配空间。
```go
arr := [...]int{1,2,3}
```
`[3]int` 和 `[4]int` 虽然都是数组,但他们却是不同的类型,使用 fmt 的 `%T` 可以查得。
```go
import (
"fmt"
)
func main() {
arr01 := [...]int{1, 2, 3}
arr02 := [...]int{1, 2, 3, 4}
fmt.Printf("%d 的类型是: %T\n", arr01, arr01)
fmt.Printf("%d 的类型是: %T", arr02, arr02)
}
```
输出 如下
```
[1 2 3] 的类型是: [3]int
[1 2 3 4] 的类型是: [4]int
```
如果你觉得每次写 `[3]int` 有点麻烦,你可以为 `[3]int` 定义一个类型字面量,也就是别名类型。
使用 `type` 关键字可以定义一个类型字面量,后面只要你想定义一个容器大小为3,元素类型为int的数组 ,都可以使用这个别名类型。
```go
import (
"fmt"
)
func main() {
type arr3 [3]int
myarr := arr3{1,2,3}
fmt.Printf("%d 的类型是: %T", myarr, myarr)
}
```
输出 如下
```
[1 2 3] 的类型是: main.arr3
```
其实定义数组还有一种偷懒的方法,比如下面这行代码
```go
arr:=[4]int{2:3}
```
打印 arr,会是
```go
[0 0 3 0]
```
可以看出`[4]int{2:3}`,4表示数组有4个元素,2 和 3 分别表示该数组索引为2(初始索引为0)的值为3,而其他没有指定值的,就是 int 类型的零值,即0。
## 2. 切片
切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度。每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。
切片是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内(意思是这是个左闭右开的区间)
```go
import (
"fmt"
)
func main() {
myarr := [...]int{1, 2, 3}
fmt.Printf("%d 的类型是: %T", myarr[0:2], myarr[0:2])
}
```
输出 如下
```
[1 2] 的类型是: []int
```
切片的构造,有四种方式
1. 对数组进行片段截取,主要有如下两种写法
```go
// 定义一个数组
myarr := [5]int{1,2,3,4,5}
// 【第一种】
// 1 表示从索引1开始,直到到索引为 2 (3-1)的元素
mysli1 := myarr[1:3]
// 【第二种】
// 1 表示从索引1开始,直到到索引为 2 (3-1)的元素
mysli2 := myarr[1:3:4]
```
如果你把上面的 `mysli1` 和 `mysli2 ` 打印出来,会发现他们居然是一样的。那第二种的 `myarr[1:3:4] ` 的 4有什么用呢?
在切片时,若不指定第三个数,那么切片终止索引会一直到原数组的最后一个数。而如果指定了第三个数,那么切片终止索引只会到原数组的该索引值。
用下面这段代码来验证一下
```go
package main
import "fmt"
func main(){
myarr := [5]int{1,2,3,4,5}
fmt.Printf("myarr 的长度为:%d,容量为:%d\n", len(myarr), cap(myarr))
mysli1 := myarr[1:3]
fmt.Printf("mysli1 的长度为:%d,容量为:%d\n", len(mysli1), cap(mysli1))
fmt.Println(mysli1)
mysli2 := myarr[1:3:4]
fmt.Printf("mysli2 的长度为:%d,容量为:%d\n", len(mysli2), cap(mysli2))
fmt.Println(mysli2)
}
```
输出如下,说明切片的第三个数,影响的只是切片的容量,而不会影响长度
```
myarr 的长度为:5,容量为:5
mysli1 的长度为:2,容量为:4
[2 3]
mysli2 的长度为:2,容量为:3
[2 3]
```
2. 从头声明赋值(例子如下)
```go
// 声明字符串切片
var strList []string
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
```
3. 使用 make 函数构造,make 函数的格式:`make( []Type, size, cap )`
这个函数刚好指出了,一个切片具备的三个要素:类型(Type),长度(size),容量(cap)
```go
import (
"fmt"
)
func main() {
a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))
}
```
输出 如下
```
[0 0] [0 0]
2 2
2 10
```
4. 使用和数组一样,偷懒的方法
```go
import (
"fmt"
)
func main() {
a := []int{4:2}
fmt.Println(a)
fmt.Println(len(a), cap(a))
}
```
输出如下
```
[0 0 0 0 2]
5 5
```
关于 len 和 cap 的概念,可能不好理解 ,这里举个例子:
- 公司名,相当于字面量,也就是变量名。
- 公司里的所有工位,相当于已分配到的内存空间
- 公司里的员工,相当于元素。
- cap 代表你这个公司最多可以容纳多少员工
- len 代表你这个公司当前有多少个员工
由于 切片是引用类型,所以你不对它进行赋值的话,它的零值(默认值)是 nil
```go
var myarr []int
fmt.Println(myarr == nil)
// true
```
数组 与 切片 有相同点,它们都是可以容纳若干类型相同的元素的容器
也有不同点,数组的容器大小固定,而切片本身是引用类型,它更像是 Python 中的 list ,我们可以对它 append 进行元素的添加。
```go
import (
"fmt"
)
func main() {
myarr := []int{1}
// 追加一个元素
myarr = append(myarr, 2)
// 追加多个元素
myarr = append(myarr, 3, 4)
// 追加一个切片, ... 表示解包,不能省略
myarr = append(myarr, []int{7, 8}...)
// 在第一个位置插入元素
myarr = append([]int{0}, myarr...)
// 在中间插入一个切片(两个元素)
myarr = append(myarr[:5], append([]int{5,6}, myarr[5:]...)...)
fmt.Println(myarr)
}
```
输出 如下
```
[0 1 2 3 4 5 6 7 8]
```
## 3. 思考题
最后,给你留道思考题。
```go
package main
import (
"fmt"
)
func main() {
var numbers4 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
myslice := numbers4[4:6:8]
fmt.Printf("myslice为 %d, 其长度为: %d\n", myslice, len(myslice))
myslice = myslice[:cap(myslice)]
fmt.Printf("myslice的第四个元素为: %d", myslice[3])
}
```
为什么 myslice 的长度为2,却能访问到第四个元素
```
myslice为 [5 6], 其长度为: 2
myslice的第四个元素为: 8
```
---
- 第一章:基础知识
- 1.1 一文搞定开发环境的搭建
- 1.2 五种变量创建的方法
- 1.3 数据类型:整型与浮点型
- 1.4 数据类型:byte、rune与字符串
- 1.5 数据类型:数组与切片
- 1.6 数据类型:字典与布尔类型
- 1.7 数据类型:指针
- 1.8 流程控制:if-else
- 1.9 流程控制:switch-case
- 1.10 流程控制:for 循环
- 1.11 流程控制:goto 无条件跳转
- 1.12 流程控制:defer 延迟语句
- 1.13 流程控制:理解 select 用法
- 1.14 异常机制:panic 和 recover
- 1.15 语法规则:理解语句块与作用域
- 第二章:面向对象
- 2.1 面向对象:结构体与继承
- 2.2 面向对象:接口与多态
- 2.3 面向对象:结构体里的 Tag 用法
- 2.4 学习接口:详解类型断言
- 2.5 学习接口:Go 语言中的空接口
- 2.6 学习接口:接口的三个"潜规则"
- 2.7 学习反射:反射三定律
- 2.8 学习反射:全面学习反射的函数
- 2.9 详细图解:静态类型与动态类型
- 2.10 关键字:make 和 new 的区别?
- 第三章:项目管理
- 3.1 依赖管理:包导入很重要的 8 个知识点
- 3.2 依赖管理:超详细解读 Go Modules 应用
- 3.3 开源发布:如何开源自己写的包给别人用?
- 3.4 代码规范:Go语言中编码规范
- 第四章:并发编程
- 4.1 学习 Go 函数:理解 Go 里的函数
- 4.2 学习 Go 协程:goroutine
- 4.3 学习 Go 协程:详解信道/通道
- 4.4 学习 Go 协程:WaitGroup
- 4.5 学习 Go 协程:互斥锁和读写锁
- 4.7 学习 Go 协程: 信道死锁经典错误案例
- 4.7 学习 Go 协程:如何实现一个协程池?
- 4.8 理解 Go 语言中的 Context
- 4.9 学习一些常见的并发模型
- 第五章:学标准库
- 5.1 fmt.Printf 方法详解
- 5.2 os/exec 执行命令的五种姿势
- 第六章:开发技能
- 6.1 Go 命令:go test 工具详解
- 6.2 单元测试:如何进行单元测试?
- 6.3 调试技巧:使用 GDB 调试 Go 程序
- 6.4 Go 命令: Go 命令指南
- 第七章:暂未分类
- 7.1 20 个学习 Go 语言的精品网站
