在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
数组数组是值类型,因此改变副本的值,不会影响到本身 数组的定义:var 变量名 [元素数量] T
定义一个字符串数组,然后赋值: var team [3]string team[0] = "hammer" team[1] = "soldier" team[2] = "mum" fmt.Println(team) 输出结果: [hammer soldier mum]用 也可以在声明时进行元素赋值: var team [3]string = [3]string{"hammer", "soldier", "mum"} var team = [3]string{"hammer", "soldier", "mum"}
遍历数组: 打印索引值(访问越界,会触发panic) var arrayA = [3]string{"hammer", "soldier", "mum"} for index, value := range arrayA { fmt.Println(index, value) } 运行结果: 0 hammer 1 soldier 2 mum 用匿名标识符忽略 index var arrayA = [3]string{"hammer", "soldier", "mum"} for _, value := range arrayA { fmt.Println(value) } 运行结果: hammer soldier mum
多维数组 func main(){ var manyArray [2][5]int = [2][5]int{{1,2,3,4,5}, {6,7,8,9,10}} for rowIndex, rowValue := range manyArray{ for columnIndex, columnValue := range rowValue{ fmt.Printf("(%d %d)→%d ", rowIndex, columnIndex, columnValue) } } } 运行结果: (0 0)→1 (0 1)→2 (0 2)→3 (0 3)→4 (0 4)→5 (1 0)→6 (1 1)→7 (1 2)→8 (1 3)→9 (1 4)→10
数组排序 不能对数组进行排序
切片(Slice)是一个拥有相同类型元素的可变长度的序列,是数组的引用(引用类型),包含地址,大小和容量,如下图,切片结构和内存分配:
从数组或切片生成新的切片 slice [start:end] start:开始位置索引,end:结束位置索引 (顾头不顾尾) 两个位置索引都缺省时,与数组或切片本身等效 两个位置索引都为0时,等效于空切片(用于清空元素) var arrayA = [3]string{"a", "b", "c"} fmt.Println("arrayA[:2]:", arrayA[:2]) 运行结果: arrayA[:2]: [a b]
创建切片的两种方式(make 和 声明) 使用 make() 函数构造切片(推荐) make([]T, size, cap)
var arrayA []int = make([]int, 2, 10) fmt.Println(arrayA) // [0 0] fmt.Println(len(arrayA)) // 2 使用 make() 函数生成的切片一定发生了内存分配操作。但从数组或切片中获取新切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作
声明新的切片 // 声明字符串切片 var strList []string // 声明整型切片 var numList []int // 声明一个空切片 var numListEmpty = []int{} 空切片不为 nil 声明新的切片后,可以使用 append() 函数来添加元素 (数组不行)
使用 append() 函数为切片添加元素 内建函数 append() 函数可以为切片添加元素,每个切片会指向一片内存空间,这片空间可以容纳一定数量的元素,当空间不足时,切片就会进行“扩容”,扩容操作往往发生在 append() 调用时 切片在扩容时,容量的扩展规律按容量的 2 倍扩充,例如 1,2,4,8,16... var numSlice []int for i:=0; i<10; i++{ numSlice = append(numSlice, i) fmt.Printf("len:%d, cap:%d, pointer:%p\n", len(numSlice), cap(numSlice), numSlice) } 运行结果: len:1, cap:1, pointer:0xc00001c090 len:2, cap:2, pointer:0xc00001c0c0 len:3, cap:4, pointer:0xc0000162c0 len:4, cap:4, pointer:0xc0000162c0 len:5, cap:8, pointer:0xc000018200 len:6, cap:8, pointer:0xc000018200 len:7, cap:8, pointer:0xc000018200 len:8, cap:8, pointer:0xc000018200 len:9, cap:16, pointer:0xc00008e080 len:10, cap:16, pointer:0xc00008e080 len() 返回切片内元素数量,cap() 函数返回切片容量大小 往一个切片中不断添加元素的过程,类似于公司搬家。公司发展初期,资金紧张,人员很少,所以只需要很小的房间即可容纳所有的员工。随着业务的拓展和收入的增加就需要扩充工位,但是办公地的大小是固定的,无法改变。因此公司选择搬家,每次搬家就需要将所有的人员转移到新的办公点
添加多个元素 numSlice = append(numSlice, 1, 2)
添加切片 var sliceA []string var sliceB []string sliceA = append(sliceA, "a") sliceB = append(sliceB, "b", "c", "d") sliceC := append(sliceA, sliceB...) fmt.Println(sliceA, sliceB, sliceC) 输出结果: [a] [b c d] [a b c d]
copy切片 内建函数 copy() ,可以将一个切片的数据复制到另外一个空间 使用格式:copy(destSlice, srcSlice) package main import( "fmt" ) func main(){ const elementCount int = 10 var srcSlice []int = make([]int, elementCount) for i:=0;i<elementCount;i++{ srcSlice[i] = i } // 引用切片数据 refSlice := srcSlice // 制造切片,copy 0到5,返回发生copy元素的数量 var copySlice []int = make([]int, elementCount) num := copy(copySlice, srcSlice[:6]) fmt.Println("copy count:", num) fmt.Println(srcSlice) fmt.Println(copySlice) // 修改srcSlice的值,观察 refSlice 和 copySlice 值的变化 srcSlice[0]= 666 fmt.Printf("srcSlice[0]:%d, refSlice[0]:%d, copySlice[0]:%d \n", srcSlice[0], refSlice[0], copySlice[0]) } 运行结果: copy count: 6 copy元素的数量 [0 1 2 3 4 5 6 7 8 9] srcSlice内容 [0 1 2 3 4 5 0 0 0 0] copySlice内容 srcSlice[0]:666, refSlice[0]:666, copySlice[0]:0
从切片中删除数据 并没有什么函数来提供删除切片元素,需要切片本身的特性来删除元素 例子:删除 Slice 中的 "c" 元素 package main import( "fmt" ) func main(){ var seq []string = []string{"a", "b", "c", "d", "e"} fmt.Printf("seq pointer:%p\n", seq) subSeq1 := seq[:2] fmt.Printf("subSeq1 pointer:%p\n", subSeq1) subSeq2 := seq[3:] fmt.Printf("subSeq2 pointer:%p\n", subSeq2) // 将截取的Slice连接起来,达到删除元素的效果 newSeq := append(subSeq1, subSeq2...) fmt.Printf("newSeq:%v, pointer:%p\n", newSeq, newSeq) } 运行结果: seq pointer:0xc000090000 subSeq1 pointer:0xc000090000 subSeq2 pointer:0xc000090030 newSeq:[a b d e], pointer:0xc000090000 代码的删除过程,本质是以被删除元素为分界点,将前后两个部分的内存重新连接起来,如下图:
切片排序 导入 sort 包
func main(){ var sliceInt []int = []int{4,6,2,7,8,1} var sliceString []string = []string{"abc", "ab", "de", "d"} var sliceFloat64 []float64 = []float64{2.34, 5.6, 1.23} sort.Ints(sliceInt) sort.Strings(sliceString) sort.Float64s(sliceFloat64) fmt.Printf("%v\n%v\n%v\n", sliceInt, sliceString, sliceFloat64) } 运行结果: [1 2 4 6 7 8] [ab abc d de] [1.23 2.34 5.6]
stringstring 是值类型,底层是一个 byte 数组,因此,可以对 string 进行切片操作 Go 语言中,string本身是不可变的,如何改变 string 中的字符? func main(){ var str string = "hello, world" // 将字符串转换成字符切片 var slice []byte = []byte(str) // 改变切片的值 slice[0] = 'o' // 再将切片转换成 string 类型 str = string(slice) fmt.Println(str) } 运行结果: oello, world
映射(map)map 使用散列表(hash)实现,和 python 的 dictionary 一样,是无序的 创建 map 的两种方式(make 和 声明) 使用 make 创建一个 map(推荐) func main(){ var scene map[string]int = make(map[string]int) scene["route"] = 1 fmt.Println(scene["route"]) fmt.Println(scene["route2"]) } 运行结果: 1 0
声明 map 并填充内容(字面量方式) 就像 json 格式一样,冒号的左边是 key,右边是值,键值对之间使用逗号分隔(最后一个值后面的冒号也不能少) func main(){ var mapA map[string]string mapA = map[string]string{ "W": "forward", "A": "left", "D": "right", "S": "backward", } fmt.Println(mapA) fmt.Println(mapA["W"]) } 运行结果: map[A:left D:right S:backward W:forward] forward 注意:map 声明后是 nil 值,必须要初始化,直接使用触发 panic: assignment to entry in nil map var mapA map[string]string mapA["name"]="johny"
查询某个 key 是否在 map 中存在 与 python 里面不同,如果 key 不存在会得到 value 类型的默认值 func main(){ var scene map[string]int = make(map[string]int) scene["route"] = 1 value, ok := scene["route1"] fmt.Println(value, ok) } 运行结果: 0 false
遍历 map map 的遍历过程也是使用 for range 完成 func main(){ var mapA map[string]string mapA = map[string]string{ "W": "forward", "A": "left", "D": "right", "S": "backward", } for key, value := range mapA{ fmt.Println(key, value) } } 运行结果: W forward A left D right S backward
只需要遍历键时: for key := range mapA{ fmt.Println(key) }
也可以使用匿名变量选择忽略键或值: for key, _ := range mapA{ fmt.Println(key) }
需要注意的是,遍历输出元素的顺序与填充顺序无关,不能期望 map 在遍历时返回某种期望顺序的结果,如果你得到了顺序的结果,也不要庆幸,也许是当前的数据量不够大 如果需要特定顺序的遍历结果,正确的做法是排序:把字典的 key 或 value 放入到切片中,然后进行排序 func main(){ var mapA map[string]string mapA = map[string]string{ "W": "forward", "A": "left", "D": "right", "S": "backward", } var sliceA []string for key, _ := range mapA{ sliceA = append(sliceA, key) } // 对切片进行排序 sort.Strings(sliceA) fmt.Println(sliceA) } 运行结果: [A D S W]
map 的删除与清空 使用内建函数 delete() 从 map 中删除一组键值对(delete 没有返回值): func main(){ var mapA map[string]string mapA = map[string]string{ "W": "forward", "A": "left", "D": "right", "S": "backward", } delete(mapA, "W") fmt.Println(mapA) } 运行结果: map[A:left D:right S:backward]
清空 map but it didn't 唯一的方法就是重新 make 一个新 map,不用担心垃圾回收的效率,Go 语言中的并行垃圾回收效率比写一个清空函数高效多了
sync.Map同时读写 map 的问题 map 在并发情况下,只读线程是安全的,同时读写线程不安全 下面来看下并发情况下读写 map 时会出现的问题: package main // 不停的对map进行写入 func write(mapA map[int]int){ for { mapA[1] = 1 } } func main(){ var mapA map[int]int = make(map[int]int) go write(mapA) // 不停的对map进行读取 for { _ = mapA[1] } } 运行结果: fatal error: concurrent map read and map write 运行时输出提示:并发的 map 读写,也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题,map 内部会对这种并发操作进行检查并提前发现
引入 sync.Map 并发读写时,一般的做法是加锁,但这样性能并不高,Go 在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,与 map 不同,它不是以语言原生形态提供,而是在 sync 包下的特殊结构 声明一个 sync.Map(不能使用 make 创建) var scene sync.Map sync.Map 特性:
并发安全的 sync.Map 演示代码如下: func main(){ var syncMap sync.Map // 设置值 syncMap.Store("hebei", "beijing") syncMap.Store("hubei", "wuhan") syncMap.Store(1, "shenzhen") // 取值 value1, err1 := syncMap.Load("hubei") value2, err2 := syncMap.Load("err") fmt.Println(value1, err1) fmt.Println(value2, err2) // 删除值(没有返回值) syncMap.Delete("hubei") // 遍历 sync.Map 中的键值对(只取第一对) syncMap.Range(func(key, value interface{}) bool { fmt.Println("iterate:", key, value) if key == "hebei"{ return false } else { return true } }) } 运行结果: wuhan true <nil> false iterate: hebei beijing
列表(list)和 python 的列表很类似,它可以存放任意类型,可以插入数据,可以移除数据,可以获取数据(不能通过索引取值) 有两种方式初始化列表 通过 container/list 包的 New 方法: listA := list.New() 直接声明一个 list: var listA list.List
列表插入数据 双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack func main(){ var listA list.List // 返回一个元素句柄 (*list.Element 结构) element1 := listA.PushFront("first") element2 := listA.PushBack(666) fmt.Println(element1) fmt.Println(element2) } 运行结果: &{0xc0000701b0 0xc000070150 0xc000070150 first} &{0xc000070150 0xc000070180 0xc000070150 666}
列表删除元素 列表的插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值及和其他节点之间的关系等信息。从列表中删除元素时,需要用到这个结构进行快速删除 func main(){ var listA list.List // 添加元素,返回一个元素句柄 (*list.Element 结构) element := listA.PushFront("first") fmt.Println(element) // 删除元素,返回这个元素的值 ret := listA.Remove(element) fmt.Println(ret) } 运行结果: &{0xc000084150 0xc000084150 0xc000084150 first} first
遍历列表 遍历双链表需要配合 Front() 函数获取头元素句柄,Value 属性可以获取句柄的值,遍历时只要元素不为空就可以继续进行。每一次遍历调用元素的 Next func main(){ var listA list.List // 取句柄的值 element := listA.PushFront("first") listA.PushBack(666) fmt.Println(element.Value) // 遍历列表 for i:=listA.Front(); i!=nil; i=i.Next(){ fmt.Println(i.Value) } } 运行结果: first first 666
end~
|
请发表评论