数组
- 和C语言一样,Go语言中也有数组的概念, Go语言中的数组也是用于保存一组
相同类型
的数据 - 和C语言一样,Go语言中的数组也分为
一维数组
和多维数组
一维数组
- 格式:
var arr [元素个数]数据类型
- 和C语言中数组不同, Go语言中数组定义之后就
有默认的初始值
- 默认初始值就是保存数据类型的默认值(零值)
package main import "fmt" func main() { // 1.定义一个数组 var arr [3]int // 2.打印数组 fmt.Println(arr) //[0 0 0] // 1.定义一个数组 var arr [3]bool // 2.打印数组 fmt.Println(arr) //[false false false] }
- 和C语言一样,Go语言中的数组也提供了好几种初始化方式
package main import "fmt" func main() { // 1.定义的同时完全初始化 var arr1 [3]int = [3]int{1, 3, 5} // 2.打印数组 fmt.Println(arr1) // [1 3 5] // 1.定义的同时部分初始化 var arr4 [3]int = [3]int{8, 9} // 2.打印数组 fmt.Println(arr4) // [8 9 0] // 1.定义的同时指定元素初始化 var arr5 [3]int = [3]int{0:8, 2:9} // 2.打印数组 fmt.Println(arr5) // [8 0 9] // 1.先定义再逐个初始化 var arr3 [3]int arr3[0] = 1 arr3[1] = 2 arr3[2] = 3 // 2.打印数组 fmt.Println(arr3) // [1 2 3] }
- 和C语言中的数组不同,Go语言中数组除了可以定义的同时初始化以外,还
可以先定义再一次性初始化
package main import "fmt" func main() { // 1.先定义再一次性初始化 var arr2 [3]int arr2 = [3]int{2, 4, 6} // 2.打印数组 fmt.Println(arr2) // [2 4 6] }
- 和C语言一样,Go语言中如果定义数组的同时初始化,那么元素个数可以省略,但是必须使用
...
来替代- ...会根据初始化元素个数自动确定数组长度
package main import "fmt" func main() { // 1.定义的同时完全初始化 var arr1 = [...]int{1, 3, 5} // 2.打印数组 fmt.Println(arr1) // [1 3 5] // 1.定义的同时指定元素初始化 var arr2 = [...]int{6:5} // 2.打印数组 fmt.Println(arr2) // [0 0 0 0 0 0 5] }
- 和C语言中数组不同, Go语言中数组定义之后就
- Go语言中数组的访问和使用方式和C语言一样都是通过
数组名称[索引]
的方式
package main
import "fmt"
func main() {
arr := [...]int{1, 3, 5}
// 使用数组, 往数组中存放数据
arr[1] = 666
// 访问数组, 从数组中获取数据
fmt.Println(arr[0])
fmt.Println(arr[1])
fmt.Println(arr[2])
}
- 遍历数组
- Go语言中提供了两种遍历数组的方式, 一种是通过传统for循环遍历, 一种是通过for...range循环遍历
package main
import "fmt"
func main() {
arr := [...]int{1, 3, 5}
// 传统for循环遍历
for i:=0; i<len(arr); i++{
fmt.Println(i, arr[i])
}
// for...range循环遍历
for i, v := range arr{
fmt.Println(i, v)
}
}
- 数组注意点
- Go语言中
数组长度
也是数据类型的一部分
package main import "fmt" func main() { var arr1 [3]int var arr2 [3]int //var arr3 [2]int fmt.Println(arr1 == arr2) // true //fmt.Println(arr1 == arr3) // 编译报错, 不是相同类型不能比较 }
- 如果元素类型支持==、!=操作时,那么数组也支持此操作
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = [3]int{1, 3, 5} var arr3 [3]int = [3]int{2, 4, 6} // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, // 如果所有元素都相同返回true,否则返回false fmt.Println(arr1 == arr2) // true fmt.Println(arr1 == arr3) // false }
- Go语言中的数组是值类型, 赋值和传参时会复制整个数组
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = arr1 arr2[0] = 666 fmt.Println(arr1) // [1 3 5] fmt.Println(arr2) // [666 3 5] }
- Go语言中
二维数组
- 用法和C语言数组一样, 只是创建的格式不同
- 格式:
[行数][列数]类型
package main
import "fmt"
func main() {
// 创建一个两行三列数组
arr := [2][3]int{
{1, 2, 3},
{4, 5, 6}, //注意: 数组换行需要以逗号结尾
}
fmt.Println(arr)// [[1 2 3] [4 5 6]]
}
- 创建多维数组时只允许第一维度使用...
- 格式:
[...][列数]类型
package main
import "fmt"
func main() {
// 创建一个两行三列数组
arr := [...][3]int{
{1, 2, 3},
{4, 5, 6},
}
fmt.Println(arr)// [[1 2 3] [4 5 6]]
}
切片
- 无论是C语言中的数组还是Go语言中的数组,数组的长度一旦确定就不能改变, 但在实际开发中我们可能事先不能确定数组的长度, 为了解决这类问题Go语言中推出了一种新的数据类型
切片
- 切片可以简单的理解为长度可以变化的数组, 但是Go语言中的切片本质上是一个结构体
- 切片源码
type slice struct{ array unsafe.Pointer // 指向底层数组指针 len int // 切片长度(保存了多少个元素) cap int // 切片容量(可以保存多少个元素) }
- 切片创建的三种方式
- 方式一: 通过数组创建切片
array[startIndex:endIndex]
package main import "fmt" func main() { var arr = [5]int{1, 3, 5, 7, 9} // 从数组0下标开始取,一直取到2下标前面一个索引 var sce = arr[0:2] fmt.Println(sce) // [1 3] // 切片len = 结束位置 - 开始位置 fmt.Println(len(sce)) // 2 - 0 = 2 fmt.Println(cap(sce)) // 5 - 0 = 5 // 数组地址就是数组首元素的地址 fmt.Printf("%p\n", &arr) // 0xc04200a330 fmt.Printf("%p\n", &arr[0]) // 0xc04200a330 // 切片地址就是数组中指定的开始元素的地址 // arr[0:2]开始地址为0, 所以就是arr[0]的地址 fmt.Printf("%p\n", sce) // 0xc04200a330 }
-
package main import "fmt" func main() { var arr = [5]int{1, 3, 5, 7, 9} // 根据数组的索引片段创建切片 var sce = arr[2:4] fmt.Println(sce) // [5 7] fmt.Println(len(sce)) // 4 - 2 = 2 fmt.Println(cap(sce)) // 5 - 2 = 3 fmt.Printf("%p\n", &arr[2]) // 0xc042076070 fmt.Printf("%p\n", sce) // 0xc042076070 }
- 指定起始位置时有三种方式可以指定
- 开始位置和结束位置都指定
- 只指定开始位置或结束位置
- 开始位置和结束位置都不指定
package main
import "fmt"
func main() {
var arr = [5]int{1, 3, 5, 7, 9}
// 同时指定开始位置和结束位置
var sce1 = arr[0:2]
fmt.Println(sce1) // [1 3]
// 只指定结束位置
var sce3 = arr[:2]
fmt.Println(sce3) // [1 3]
// 只指定开始位置
var sce2 = arr[0:]
fmt.Println(sce2) // [1 3 5 7 9]
// 都不指定
var sce4 = arr[:]
fmt.Println(sce4) // [1 3 5 7 9]
}
- 方式二: 通过make函数创建
make(类型, 长度, 容量)
- 内部会先创建一个数组, 然后让切片指向数组
- 如果没有指定容量,那么容量和长度一样
package main
import "fmt"
func main() {
// 第一个参数: 指定切片数据类型
// 第二个参数: 指定切片的长度
// 第三个参数: 指定切片的容量
var sce = make([]int, 3, 5)
fmt.Println(sce) // [0 0 0]
fmt.Println(len(sce)) // 3
fmt.Println(cap(sce)) // 5
/*
内部实现原理
var arr = [5]int{0, 0, 0}
var sce = arr[0:3]
*/
}
- 方式三:通过Go提供的语法糖快速创建
- 和创建数组一模一样, 但是
不能指定长度
- 通过该方式创建时, 切片的
长度和容量相等
- 和创建数组一模一样, 但是
package main
import "fmt"
func main() {
var sce = []int{1, 3, 5}
fmt.Println(sce) // [1 3 5]
fmt.Println(len(sce)) // 3
fmt.Println(cap(sce)) // 3
}
-
切片的使用
- 切片的基本使用方式和数组一样, 可以通过
切片名称[索引]
方式操作切片
package main import "fmt" func main() { var sce = []int{1, 3, 5} // 使用切片, 往切片中存放数据 sce[1] = 666 // 访问切片, 从切片中获取数据 fmt.Println(sce) // [1 666 5] }
- 和数组一样, 如果通过
切片名称[索引]
方式操作切片, 不能越界
package main import "fmt" func main() { var sce = []int{1, 3, 5} // 编译报错, 越界 sce[3] = 666 }
- 如果希望切片自动扩容,那么添加数据时必须使用append方法
- append函数会在切片
末尾
添加一个元素, 并返回一个追加数据之后的切片 - 利用append函数追加数据时,如果追加之后没有超出切片的容量,那么返回原来的切片, 如果追加之后超出了切片的容量,那么返回一个新的切片
- append函数每次给切片扩容都会按照原有切片容量*2的方式扩容
- append函数会在切片
package main import "fmt" func main() { var sce = []int{1, 3, 5} fmt.Println("追加数据前:", sce) // [1 3 5] fmt.Println("追加数据前:", len(sce)) // 3 fmt.Println("追加数据前:", cap(sce)) // 3 fmt.Printf("追加数据前: %p\n", sce) // 0xc0420600a0 // 第一个参数: 需要把数据追加到哪个切片中 // 第二个参数: 需要追加的数据, 可以是一个或多个 sce = append(sce, 666) fmt.Println("追加数据后:", sce) // [1 3 5 666] fmt.Println("追加数据后:", len(sce)) // 4 fmt.Println("追加数据后:", cap(sce)) // 6 fmt.Printf("追加数据前: %p\n", sce) // 0xc042076b60 }
- 除了append函数外,Go语言还提供了一个copy函数, 用于两个切片之间数据的快速拷贝
- 格式:
copy(目标切片, 源切片)
, 会将源切片中数据拷贝到目标切片中
- 格式:
package main import "fmt" func main() { var sce1 = []int{1, 3, 5} var sce2 = make([]int, 5) fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060 // 将sce2的指向修改为sce1, 此时sce1和sce2底层指向同一个数组 sce2 = sce1 fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值后:%p\n", sce2) // 0xc0420600a0 //copy(sce2, sce1) fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 3 5] sce2[1] = 666 fmt.Println(sce1) // [1 666 5] fmt.Println(sce2) // [1 666 5] }
package main import "fmt" func main() { var sce1 = []int{1, 3, 5} var sce2 = make([]int, 5) fmt.Printf("赋值前:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值前:%p\n", sce2) // 0xc042076060 // 将sce1中的数据拷贝到sce2中,, 此时sce1和sce2底层指向不同数组 copy(sce2, sce1) fmt.Printf("赋值后:%p\n", sce1) // 0xc0420600a0 fmt.Printf("赋值后:%p\n", sce2) // 0xc042076060 //copy(sce2, sce1) fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 3 5 0 0] sce2[1] = 666 fmt.Println(sce1) // [1 3 5] fmt.Println(sce2) // [1 666 5 0 0] }
- copy函数在拷贝数据时永远以小容量为准
package main import "fmt" func main() { // 容量为3 var sce1 = []int{1, 3, 5} // 容量为5 var sce2 = make([]int, 5) fmt.Println("拷贝前:", sce2) // [0 0 0 0 0] // sce2容量足够, 会将sce1所有内容拷贝到sce2 copy(sce2, sce1) fmt.Println("拷贝后:", sce2) // [1 3 5 0 0] }
package main import "fmt" func main() { // 容量为3 var sce1 = []int{1, 3, 5} // 容量为2 var sce2 = make([]int, 2) fmt.Println("拷贝前:", sce2) // [0 0] // sce2容量不够, 会将sce1前2个元素拷贝到sce2中 copy(sce2, sce1) fmt.Println("拷贝后:", sce2) // [1 3] }
- 切片的基本使用方式和数组一样, 可以通过
-
切片的注意点
- 可以通过切片再次生成新的切片, 两个切片底层指向同一数组
package main import "fmt" func main() { arr := [5]int{1, 3, 5, 7, 9} sce1 := arr[0:4] sce2 := sce1[0:3] fmt.Println(sce1) // [1 3 5 7] fmt.Println(sce2) // [1 3 5] // 由于底层指向同一数组, 所以修改sce2会影响sce1 sce2[1] = 666 fmt.Println(sce1) // [1 666 5 7] fmt.Println(sce2) // [1 666 5] }
- 和数组不同, 切片只支持判断是否为nil, 不支持==、!=判断
package main import "fmt" func main() { var arr1 [3]int = [3]int{1, 3, 5} var arr2 [3]int = [3]int{1, 3, 5} var arr3 [3]int = [3]int{2, 4, 6} // 首先会判断`数据类型`是否相同,如果相同会依次取出数组中`对应索引的元素`进行比较, // 如果所有元素都相同返回true,否则返回false fmt.Println(arr1 == arr2) // true fmt.Println(arr1 == arr3) // false sce1 := []int{1, 3, 5} sce2 := []int{1, 3, 5} //fmt.Println(sce1 == sce2) // 编译报错 fmt.Println(sce1 != nil) // true fmt.Println(sce2 == nil) // false }
- 只声明当没有被创建的切片是不能使用的
package main import "fmt" func main() { // 数组声明后就可以直接使用, 声明时就会开辟存储空间 var arr [3]int arr[0] = 2 arr[1] = 4 arr[2] = 6 fmt.Println(arr) // [2 4 6] // 切片声明后不能直接使用, 只有通过make或语法糖创建之后才会开辟空间,才能使用 var sce []int sce[0] = 2 // 编译报错 sce[1] = 4 sce[2] = 6 fmt.Println(sce) }
- 字符串的底层是[]byte数组, 所以字符也支持切片相关操作
package main import "fmt" func main() { str := "abcdefg" // 通过字符串生成切片 sce1 := str[3:] fmt.Println(sce1) // defg sce2 := make([]byte, 10) // 第二个参数只能是slice或者是数组 // 将字符串拷贝到切片中 copy(sce2, str) fmt.Println(sce2) //[97 98 99 100 101 102 103 0 0 0] }