数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在Go语言中很少直接使用数组。和数组对应的类型是Slice,它是可以增长和收缩的动态序列,slice功能也更加灵活,但是要先理解slice工作原理的话需要先理解数组。
var a [3]int64 // 初始化长度为3的int64类型的数组 因为没有为每个元素分配值,所有都为零值,注意此处的零值不一定是0主要是根据数组的元素类型来确定
fmt.Println(a[0]) // 0
a[2] = 5
fmt.Println(a[len(a)-1]) // 5
for i, v := range a {
fmt.Printf("a[%d] value is %d \n", i, v)
}
q := [...]string{"a","b","c"} //使用... 来表示数组的长度等于{}中元素的个数
关于range
关键字:
range
用于迭代各种数据结构,包括切片,数组,映射,字符串和通道,它有两个返回值,index(下标),value(对应下标的元素值)。
这里需要特别提到的是,当使range
遍历字符串的时候,他会按照Unicode码点(rune)进行迭代,
str := "你好"
for index, char := range str {
fmt.Printf("Index: %d, Char: %c\n", index, char)
}
/*
Index: 0, Char: 你
Index: 3, Char: 好
*/
此处会发现 index并不是 0 1 2 3 递增的,而是直接从0跳到了3,我们之前提到过,string的本质就是字节数组,但是在Unicode编码中,一个字符可能需要一个或者多个字节才能表示,当我们迭代每一个字符时,则会读取到多个字节,导致index的跳跃。
Slice(切片)
Slice代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很相似,只是没有固定长度而已。
var month []string = []string{"January", "February", "March", "April", "May", "June"}
fmt.Println(month[0]) // January
//make创建slice(类型,长度,容量) 长度表示这个slice中有多少个元素(即这些元素会被赋予零值),容量表示的是slice可容纳多少个元素,
//循环只会打印其长度的值,而非其容量
//关于容量参数可以忽略,在使用append等函数对slice进行追加操作时,如果其长度超过了容量Go 语言会创建一个新的底层数组,并将原始数据复制到新数组中,同时切片会引用新的底层数组。
//所以,是否显式指定容量取决于你对切片的操作需求。如果你事先知道切片的容量需求,指定容量可以避免频繁地进行数组扩容操作,提高性能。如果容量不确定或不重要,可以省略容量参数。
var numbers []int = make([]int, 5, 12)
for i, v := range numbers {
fmt.Printf("numbers[%d] is %d \n", i, v)
/*
numbers[0] is 0
numbers[1] is 0
numbers[2] is 0
numbers[3] is 0
numbers[4] is 0
*/
}
数组和slice之间有着紧密的联系。一个slice是一个轻量的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用了一个数组对象。一个silce由三个部分构成:指针,长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,需要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一边是从slice的开始位置到底层数据的结尾位置,内置的len
和cap
函数分别返回slice的长度和容量。
多个slice之间可以共享底层数据,并且引用的数组部分区间可能重叠。如下代码所示
var sort1 []int8 = []int8{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var sort2 []int8 = sort1[0:5]
var sort3 []int8 = make([]int8, 5, 5)
copy(sort3, sort1[0:5])
sort2[0] = 11
fmt.Println(sort1[0]) //11
fmt.Println(sort2[0]) //11
fmt.Println(sort3[0]) //1
sort2和sort1的部分数组引用区间相同(因为sort2是从sort1中截取得到的),所以当我们修改他们相同引用区间的部分时,两个数组都会受到影响,而使用copy
函数拷贝出来的数组是重新拷贝出一个新的数组,则不会被影响到。
golang中的参数传递都是值传递,但是需要注意一点,因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。如下示例:
package main
import "fmt"
func main() {
num := 55
rpNum(num)
fmt.Println(num) //55
fmt.Println("===============================")
nums := make([]int, 5)
fmt.Println(nums) // [0 0 0 0 0]
fmt.Println("===============================")
rpSlice(nums)
fmt.Println(nums) //[1 0 0 0 0]
fmt.Println("===============================")
bob := obj{name: "Bob", age: 15}
rpStruct(bob)
fmt.Println(bob.name) // Bob
fmt.Println("===============================")
array := [5]int{0, 1}
rpArray(array)
fmt.Println(array) // [0 1 0 0 0]
hMap := map[string]int{
"id": 1,
}
rpMap(hMap)
fmt.Println(hMap["id"]) //5
}
type obj struct {
name string
age int
}
func rpNum(num int) {
num = 53
}
func rpSlice(slice []int) {
slice[0] = 1
}
func rpStruct(object obj) {
object.name = "spider"
}
func rpArray(array [5]int) {
array[0] = 5
}
func rpMap(hMap map[string]int) {
hMap["id"] = 5
}
可以看到,当传递 int
,struct
,数组
对象作为参数时,我们的修改并没有影响原值,而当我们使用slice
和map
时,会对原值产生影响。对于引用类型(如 slices
和 map
),虽然它们是通过值传递的,但传递的是指向底层数据的指针。因此,对这些数据的修改会影响原始数据
append函数
内置的append函数用于向slice
追加元素:
appendList := make([]string, 0)
//关于此处为什么要重新给appendList赋值,上面我们讲过,当append时slice的长度超过了slice的容量,则会创建一个新的底层数组
//如果没有创建新的底层数组,那么此时的slice指向的地址和append函数返回的对象指向的底层数组的地址是一致的,如果创建了则不一样
//很多情况下我们无法得知slice是否进行了扩容(创建了新的数组) 所以直接重新赋值,避免slice并没有被更新内存地址
appendList = append(appendList, "first")
fmt.Println(appendList) //[first]
copy函数
copy
函数用于将一个切片的元素复制到另一个切片。在上面的示例中我们已经提到过了但是我们需要注意以下两点
- dst 切片必须足够大以容纳要复制的元素;如果不够大,则只会复制到 dst 的长度。
- 如果 dst 和 src 指向同一个切片,copy 操作是安全的,Go 会处理好内部的复制逻辑。
删除元素
// 移除某个元素
func remove(slice []int, i int) []int {
//第一种方法,截取i位前的数据,然后再后面附加上i+1位以后的内容
//slice = append(slice[:i], slice[i+1:]...)
//return slice
//第二种方法,通过将第i+1位的元素附加到i位开始的后面,返回时需要删除最后一位
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
Map
哈希表是一种巧妙并且实用的数据结构。它是一个无序的key/value对的集合,其中所有的key都是不用的,然后通过给定的key可以在常数时间复杂度内检索、更新或删除对应的value。
在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V
,其中K和V分别对应key和value。map中所有的key都有相同的类型,所有的value也有相同的类型,但是key和value可以是不同的数据类型。其中key必须是支持==
比较运算符的数据类型,所以map可以通过测试key是否相等来判断已经存在。虽然浮点数类型也是支持相等运算符比较的,但是将浮点数用作key类型则是一个坏的想法,因为浮点数的精度问题是不适合使用==
比较的。对于value数据类型则没有任何的限制。
//map的创建方式 make(map[keyType]valueType)
ages := make(map[string]int16)
//或者使用map[keyType]valueType{key:value}初始化并赋值
//但是key必须支持==运算符的数据类型 比如以下类型就不支持 float slice map本身 function等
ages = map[string]int16{} //也可以是空的 不赋值
ages = map[string]int16{
"Bob": 15,
"Charlie": 20,
}
fmt.Println(ages)
Map中的元素通过key访问,也可以使用range
迭代Map,需要注意的是,Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。
bob := ages["Bob"]
alice := ages["Alice"]
fmt.Printf("Bob:%d \n", bob) //15
fmt.Printf("Alice:%d \n", alice) //0 虽然map中没有名为 alice 的 key 但是会返回零值
// //_ = &ages["Bob"] //map中的元素不是变量,无法对map的元素进行取址操作。 这样是无法通过编译的
//也可以通过range迭代 range返回两个值 分别是key和value
for name, age := range ages {
fmt.Printf("%s:%d \n", name, age)
}
使用内置的delete
函数可以删除元素
personMap := map[string]string{
"name": "Bob",
"gender": "male",
"address": "USA",
}
delete(personMap, "address")
fmt.Println(personMap) //map[gender:male name:Bob]
delete(personMap, "age") //移除不存在的key也不会报错
map上的大部分操作,包括查找、删除、leng
和range
循环都可以安全工作在nil
值的map上,他们的行为和一个空的map类似但是向一个nil值的map存入元素是不被允许的。在向map存数据前必须先创建map
var nilMap map[string]int64
fmt.Println(nilMap == nil) //true
fmt.Println(len(nilMap) == 0) //true
nilMap["age"] = 18 //会报错,因为nilMap并没有指向一个内存地址
通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在则返回零值。这个规则很实用,但是有时候可能需要知道对应的元素是否真的是在map之中。例如,如果一个元素类型是一个数字,你可能需要区分一个已经存在的0,和不存在而返回的零值的0。我们可以通过使用多个返回值的方式,在这种场景下,map的下标语法将产生两个值;第二个是布尔值,用于报告元素是否真正的存在。 如下所示
zeroMapTest := map[string]int16{
"Michele": 19,
}
age, ok := zeroMapTest["Michele"]
fmt.Printf("Michele's age is %d,the value is %t \n", age, ok) //Michele's age is 19,the value is true
age, ok = zeroMapTest["Bob"]
fmt.Printf("Bob's age is %d,the value is %t \n", age, ok) //Bob's age is 0,the value is false
和slice一样,map之间也不能进行相等比较;唯一能比较的就是nil
。要判断两个map是否包含相同的key和value,我们必须通过一个循环实现。
golang中没有set类型,可以用map实现类似set的功能。