GO基础-数组与切片与Map
数组与切片与MAP
数组
数组(Array)是一种非常常见的数据类型,几乎所有的计算机编程语言中都会用到。在GO语言中,数组的特点:
数组里的元素必须全部为同一类型,要么全部是字符串,要么全部是整数
声明数组时,必须指定其长度或者大小(英文叫做length或者size),所谓长度就是该数组能包含的元素的最大个数。如果你不确定数组里具体有多少个元素时,可以使用[...]替代具体的长度,Go的编译器会自动帮你算出该数组的长度。
数组一旦被创建过后,其长度就再也不能被更改,不管是改大还是改小
如果将数组作为参数传入一个函数,Go会为该数组创建一个副本,实际传入函数中的是数组的副本而不是源本,因此在函数下面对该数组进行的任何操作都会在函数返回后丢失。
package main
import "fmt"
func main() {
var array1 = [5]int{1, 2, 3, 4}
array2 := [...]int{1, 2, 3}
var array3 [6]int
fmt.Println(array1, array2, array3) // [1 2 3 4 0] [1 2 3] [0 0 0 0 0 0]
fmt.Println("数组array1的长度:", len(array1)) // 数组array1的长度: 5
fmt.Println("数组array2的长度:", len(array2)) // 数组array2的长度: 3
fmt.Println("数组array3的长度:", len(array3)) // 数组array3的长度: 6
}
切片
切片(Slice)相较于数组更灵活,因为在声明切片后其长度是可变的,将切片作为参数传入一个函数后,对该切片内元素进行的任何操作都会在函数返回时体现在该切片的源本上,不会丢失。同时它有一个很重要的概念:切片头部
切片头部
切片不等同于数组,切片的本质是用来描述(或指向)一个底层数组的数据结构,该数据结构又被叫做切片头部(Slice Header),描述切片头部的结构体(struct)如下:
type sliceHeader struct {
Length int
Capacity int
ZerothElement *int
}
长度
切片头部里的长度指的就是切片本身的长度,也就是切片里当前拥有多少个元素。
容量
切片头部里的容量(capacity)指的是该切片所指向的底层数组的长度(length),也是该切片其本身所能达到的最大长度,在Go中我们使用cap()函数来得到一个切片的容量,cap()函数的返回值为整数
ZerothElement
所谓ZerothElement指的是切片所指向(描述)的底层数组的数据里的“第零个元素”:假如 slice := array[3:7],包含四个数{3,4,5,6},那么ZerothElement代表的就是{3,4,5,6}里的“第零个元素”
package main
import "fmt"
func main() {
array := [5]int{} //声明一个数组变量array,其大小为5。
slice := array[:0] //声明一个指向数组变量array的切片变量slice,该切片为空切片,长度为0
fmt.Println("数组长度: ", len(array)) // 数组长度: 5
fmt.Println("切片:", slice) // 切片: []
fmt.Println("切片容量: ", cap(slice)) // 切片容量: 5
fmt.Println("第一次切片长度: ", len(slice)) // 第一次切片长度: 0
slice = array[:3]
fmt.Println("第二次切片长度: ", len(slice)) // 第一次切片长度: 3
slice = array[:5]
fmt.Println("第三次切片长度: ", len(slice)) // 第一次切片长度: 5
slice = array[:6] // 这里报错
fmt.Println("第三次切片长度: ", len(slice)) // invalid slice index 6 (out of bounds for 5-element array)
}
其他
修改切片里的元素会同时修改其指向的底层数组里的元素
package main
import "fmt"
func main() {
array := [6]int{1, 2, 3, 4, 5, 6}
slice := array[2:5]
fmt.Println("\n修改切片元素之前的切片: ", slice) // 修改切片元素之前的切片: [3 4 5]
fmt.Println("修改切片元素之前的数组: ", array) // 修改切片元素之前的数组: [1 2 3 4 5 6]
slice[0] = 6
fmt.Println("\n修改切片元素之后的切片: ", slice) // 修改切片元素之后的切片: [6 4 5]
fmt.Println("修改切片元素之后的数组: ", array) // 修改切片元素之后的数组: [1 2 6 4 5 6]
}
修改切片的容量会直接切断该切片和其指向的底层数组之间的联系。
package main
import "fmt"
func main() {
array := [...]string{"我", "不", "是", "小", "丑"}
slice := array[:]
fmt.Println("修改前的数组: ", array) // 修改前的数组: [我 不 是 小 丑]
fmt.Println("修改前的数组长度: ", len(slice)) // 修改前的数组长度: 5
fmt.Println("修改前的数组容量: ", cap(slice)) // 修改前的数组容量: 5
fmt.Println("修改前的切片: ", slice) // 修改前的切片: [我 不 是 小 丑]
fmt.Println("修改前的切片长度: ", len(slice)) // 修改前的切片长度: 5
fmt.Println("修改前的切片容量: ", cap(slice)) // 修改前的切片容量: 5
slice = append(slice, "。")
fmt.Println("修改后的数组: ", array) // 修改后的数组: [我 不 是 小 丑]
fmt.Println("修改后的数组长度: ", len(array)) // 修改后的数组长度: 5
fmt.Println("修改后的数组容量: ", cap(array)) // 修改后的数组容量: 5
fmt.Println("修改后的切片: ", slice) // 修改后的切片: [我 不 是 小 丑 。]
fmt.Println("修改后的切片长度: ", len(slice)) // 修改后的切片长度: 6
fmt.Println("修改后的切片容量: ", cap(slice)) // 修改后的切片容量: 10
}
切片可独立于数组存在
package main
import "fmt"
func main() {
var slice = []string{"一", "二", "三", "四"}
fmt.Println(slice) // [一 二 三 四]
fmt.Println(len(slice)) // 4
fmt.Println(cap(slice)) // 4
slice = append(slice, "五")
fmt.Println(slice) // [一 二 三 四 五]
fmt.Println(len(slice)) // 5
fmt.Println(cap(slice)) // 8
}
make()函数创建切片
make([]type, length, capacity)
make()函数和[]type{}的区别在于:
你只能通过make()创建一个所有元素都为零值的切片,用[]type{}声明的切片无法自定义切片的长度和容量
值得注意的是,我们通过make()函数创建了一个字符串型的切片slice,其长度为2,容量为3。因为用make()函数创建的切片无法给出切片具体的元素,所以这时该切片里有两个值为空的字符串(字符串的零值为空,整数类型还是0)。
MAP
Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key-value对的集合。Go语言中并没有提供set类型,但是map中的key也是不相同的,可以用map实现类似set的功能。
创建map
package main
import "fmt"
func main() {
m1 := map[int]string{}
m2 := make(map[int]string)
m3 := make(map[int]string, 10) // 第二个参数指定容量
fmt.Println(m1, m2, m3)
}
值得注意的是,map 的自动扩容无需想切片一样使用append函数,直接赋值即可。如果key重复,则会覆盖value值,但是map能用len()函数,不能用cap()函数。
初始化map
package main
import "fmt"
func main() {
//1、定义同时初始化
var m1 map[int]string = map[int]string{1: "小丑", 2: "joker"}
fmt.Println(m1) //map[1:小丑 2:joker]
//2、自动推导类型
m2 := map[int]string{1: "小丑", 2: "joker"}
fmt.Println(m2) //map[1:小丑 2:joker]
}
遍历map
package main
import "fmt"
func main() {
m1 := map[int]string{1: "小丑", 2: "joker"}
//遍历1,第一个返回值是key,第二个返回值是value
for k, v := range m1 {
fmt.Printf("%d ----> %s\n", k, v)// 1 ----> 小丑 //2 ----> joker
}
//遍历2,第一个返回值是key,省略第二个返回值
for k := range m1 {
fmt.Printf("%d ----> %s\n", k, m1[k])// 1 ----> 小丑 //2 ----> joker
}
}
查找map中的值
有时候可能需要知道对应的元素是否真的是在map之中。可以使用下标语法判断某个key是否存在。map的下标语法将产生两个值,其中第二个是一个布尔值,用于报告元素是否真的存在。
package main
import "fmt"
func main() {
m1 := map[int]string{1: "小丑", 2: "joker"}
value1, ok1 := m1[1]
fmt.Println("value1 = ", value1, ", ok1 = ", ok1) //value1 = 小丑 , ok1 = true
value2, ok2 := m1[3]
fmt.Println("value2 = ", value2, ", ok2 = ", ok2) //value2 = , ok2 = false
}
删除map中的值
package main
import "fmt"
func main() {
m1 := map[int]string{1: "小丑", 2: "joker", 3: "clown"}
//遍历1,第一个返回值是key,第二个返回值是value
for k, v := range m1 {
fmt.Printf("%d ----> %s\n", k, v) // 2 ----> joker 3 ----> clown 1 ----> 小丑
}
delete(m1, 2) // 删除key为2的数据
//遍历2,第一个返回值是key,第二个返回值是value(可省略)
for k := range m1 {
fmt.Printf("%d ----> %s\n", k, m1[k]) // 1 ----> 小丑 3 ----> clown
}
}
map的传递类型
map不论是做函数参数还是做函数返回值都是引用传递