数组是一个由固定长度的特定类型元素组成的序列,属于值类型(所以传参数组的时候一般使用指针,避免值类型传参复制导致内存消耗),不可以与nil
比较,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Go语言中很少直接使用数组。
var variable [length]Type
// 声明
var arr [3]int
// 声明并赋值
var arr [3]int = [3]int{1,2,3}
// 自动类型推导
arr := [3]int{1,2,3}
// 自动类型推导并自动计算长度
arr := [...]int{1,2,3}
// 初始化并指定索引的值
arr := [5]string{2: "lucy", 4: "tom"}
数组的每个元素都可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置,内置函数 len() 可以返回数组中元素的个数。
//main包
package main
import (
"fmt"
)
func main() {
var arr [3]int
fmt.Println(len(arr)) //数组长度:3
fmt.Println(arr[0]) //数组索引为0的元素值为:0
}
如果两个数组类型相同(包括数组的长度,数组中元素的类型)的情况下,我们可以直接通过较运算符(==
和!=
)来判断两个数组是否相等,只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型、长度不同的数组,否则程序将无法完成编译。
//main包
package main
import (
"fmt"
)
func main() {
a1 := [...]int{1, 2, 3}
a2 := [...]int{1, 2, 3}
a3 := [...]int{2, 3, 4}
a4 := [...]int{1, 2, 3, 4}
fmt.Println(a1 == a2) //true
fmt.Println(a1 == a3) //false
fmt.Println(a1 == a4) //编译错误,[3]int不能和[4]int进行比较
}
//main包
package main
import "fmt"
func main() {
names := [...]string{"lucy", "tom", "lily"}
//使用for,根据数据长度遍历
for i := 0; i < len(names); i++ {
fmt.Printf("第%d个姓名为:%s\n", i, names[i])
}
//使用for range
for index, value := range names {
fmt.Printf("第%d个姓名为:%s\n", index, value)
}
}
var array_name [size1][size2] array_type
//声明
var ab [2][3]int
//声明并赋值
var ab [2][3]int = [2][3]int{{1, 2, 3}, {4, 5, 6}}
//自动类型推导
ab := [2][3]int{{1, 2, 3}, {4, 5, 6}}
//初始化并指定索引的值
ab := [5][3]int{2:{1, 2, 3}, 4:{1:66}}
//获取指定索引的值
a := ab[0][2]
注意:多维数组只有最外层可以使用[...]
来表示数组长度,内层数组不可以使用[...]
来表示数组长度
切片(slice)本质就是对一个底层数组的一个连续片段的引用(封装),所以切片是一个引用类型(因此更类似于 C/C++中的数组类型,或者Python中的 list 类型),这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集,需要注意的是,终止索引标识的项不包括在切片内,也就是切片不含结束索引的元素。切片一般用于快速地操作一块数据集合。
切片的长度(通过len(s)
获取)就是它所包含的元素个数,这些元素是会被初始化的。
切片的容量(通过cap(s)
获取)是从它的第一个元素开始数,到其底层数组元素末尾的个数。
也就是说,对于s := make([]int, 3, 6)
,该切片创建了一个能够容纳6个元素(容量)的数组。同时,因为长度length
被设置成了3,所以,Go仅仅初始化前3个元素。因为slice的元素是[]int
类型,所以前3个元素用int
的零值0
来初始化。剩余的元素空间只被分配,但没有使用。
切片默认指向一段连续内存区域,可以是数组,也可以是切片本身。
s := target[开始位置 : 结束位置]
package main
import "fmt"
func main() {
a := [5]int{1, 2, 3, 4, 5}
slice := a[1:3]
fmt.Println(slice) // [2 3]
fmt.Println(len(slice), cap(slice)) // 2 4
a[1] = 10
fmt.Println(slice) // [10 3]
}
从数组或切片生成新的切片拥有如下特性:
结束位置 - 开始位置
;slice[len(slice - 1)]
获取;target[:2]
等价于target[0:2]
;target[2:]
等价于target[2:len(target)]
;target
本身等效,例如target[:]
等价于target[0:len(target)]
;注意:[]type
声明的是切片,是引用类型;而[...]type
或[length]type
声明的是数组,是值类型
var name []Type
//声明一个切片,此时slice没有分配内存,是nil,len和cap都是0
var slice []string
//声明切片并赋值
var slice []string = []string{"lucy", "tom", "lily"}
//自动类型推导
slice := []string{"lucy", "tom", "lily"}
//声明一个空切片,此时slice已经分配内存,不是nil
slice := []string
make( []Type, len, cap )
cap(slice)
限制,就会重新分配底层数组,即便原数组并未填满。len <= cap
package main
import "fmt"
func main() {
a := make([]int, 2) // len = 2;cap = 2
b := make([]int, 2, 10) // len = 2;cap = 10
fmt.Println(len(a), len(b)) // 2 2
//cap函数可以获取切片的容量
fmt.Println(cap(a), cap(b)) // 2 10
}
注意:使用 make()
函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作,也就是说如果修改切片元素,那么相当于修改被切片的数组或切片的元素。
package main
import "fmt"
func main() {
a := [...]int{1, 2, 3, 4, 5, 6, 7}
slice := a[1:6]
slice[1] = 100
fmt.Println(a) //[1 2 100 4 5 6 7]
}
Go语言的内建函数append()
可以为切片动态添加元素,切片的扩容也只能通过append()
方法进行
注意:append()
函数的第一个参数必须为slice;而第二位参数为可变参数,如果是切片则需要使用...
进行解包,如果是数组就需要先转成切片再解包,例如append(a,arr[:]...)
package main
import "fmt"
func main() {
var a []int
//在切片尾部添加
//在切片a的基础上,追加一个元素,并返回新的切片
a = append(a, 1)
//在切片a的基础上,追加三个元素,并返回新的切片
a = append(a, 2, 3, 4)
//在切片a的基础上,追加一个切片(这个切片需要使用省略号...进行解包),并返回新的切片
a = append(a, []int{5, 6, 7}...)
//在切片头部添加
//在切片a的前面加一个元素0,并返回新的切片
a = append([]int{0}, a...)
//在切片a的前面加一个切片,并返回新的切片
a = append([]int{-3, -2, -1}, a...)
fmt.Println(a) // [-3 -2 -1 0 1 2 3 4 5 6 7]
}
注意:在使用append()
函数为切片动态添加元素时,如果空间不足以容纳足够多的元素,切片就会进行扩容,此时新切片的长度会发生改变,切片在扩容时,容量的扩展规律是按现在容量(cap)的 2 倍进行扩充。
每次扩容时,创建一个新的2倍容量的数组,并将之前数组内容copy过来,所以发生扩容的时候slice的内存地址会发生变化。
package main
import "fmt"
func main() {
s := []int{100, 200, 300, 400}
fmt.Println(len(s), cap(s)) // 4 4
s = append(s, 500)
fmt.Println(len(s), cap(s)) // 5 8
}
因为 append 函数返回新切片的特性,所以切片也支持链式操作,我们可以将多个 append 操作组合起来,实现在切片中间插入元素
// 在第i个位置插入x
a = append(a[:i], append(x, a[i:]...)...)
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
insert(&slice, 1, 100, 200)
fmt.Println(arr) // [1 100 200 2 3]
}
// 在切片slice的第index位插入num
func insert(slice *[]int, index int, num ...int) {
*slice = append((*slice)[:index], append(num, (*slice)[index:]...)...)
}
Go语言的内置函数 copy()
可以将一个切片复制到另一个切片中,如果加入的两个数组切片不一样大,就会按照其中len()
较小的那个数组切片的元素个数进行复制。
copy( destSlice, srcSlice )
srcSlice
复制到 destSlice
注意:目标切片必须和原切片类型一致
package main
import "fmt"
func main() {
srcSlice := []int{100, 200, 300, 400}
targetSlice := make([]int, 2, 5)
i := copy(targetSlice, srcSlice)
fmt.Println(i, targetSlice) // 2 [100 200]
}
Go语言并没有对删除切片元素提供专用的语法或者接口,需要使用切片本身的特性来删除元素,根据要删除元素的位置有三种情况,分别是从开头位置删除、从中间位置删除和从尾部删除,其中删除切片尾部的元素速度最快。
a = a[n:] //从开头位置删除n个元素
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
//从开头位置删除2个元素
a = a[2:]
fmt.Println(a) //[3 4 5]
}
a = append(a[:n], a[m:]...) //从索引为n的位置,删除count=m-n个元素,即m=count+n
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
//从索引为1的位置开始删除2个元素
a = append(a[:1], a[2:]...)
fmt.Println(a) //[1 3 4 5]
}
a = a[:len(a)-n] //删除尾部n个元素
package main
import "fmt"
func main() {
a := []int{1, 2, 3, 4, 5}
//删除尾部的2个元素
a = a[:len(a)-2]
fmt.Println(a) //[1 2 3]
}
注意:连续容器的元素删除无论在任何语言中,都要将删除点前后的元素移动到新的位置,随着元素的增加,这个过程将会变得极为耗时,因此,当业务需要大量、频繁地从一个切片中删除元素时,如果对性能要求较高的话,就需要考虑更换其他的容器了(如双链表等能快速从删除点删除元素)。
Golang中除了自己手写排序算法实现排序外,还提供了sort
包
升序排序
package main
import (
"fmt"
"sort"
)
func main() {
s1 := []int{4, 1, 6, 2, 10}
sort.Ints(s1)
fmt.Println(s1) // [1 2 4 6 10]
s2 := []float64{3.1, 1.1, 2.5, 10.10}
sort.Float64s(s2)
fmt.Println(s2) // [1.1 2.5 3.1 10.1]
s3 := []string{"a", "c", "f", "b", "e"}
sort.Strings(s3)
fmt.Println(s3) // [a b c e f]
}
降序排序
package main
import (
"fmt"
"sort"
)
func main() {
s1 := []int{4, 1, 6, 2, 10}
sort.Sort(sort.Reverse(sort.IntSlice(s1)))
fmt.Println(s1) // [1 2 4 6 10]
s2 := []float64{3.1, 1.1, 2.5, 10.10}
sort.Sort(sort.Reverse(sort.Float64Slice(s2)))
fmt.Println(s2) // [10.1 3.1 2.5 1.1]
s3 := []string{"a", "c", "f", "b", "e"}
sort.Sort(sort.Reverse(sort.StringSlice(s3)))
fmt.Println(s3) // [f e c b a]
}
Go语言中 map 是一种特殊的数据结构,一种元素对(Pair)的无序集合,Pair 对应一个 key(索引)和一个 value(值),所以这个结构也称为关联数组或字典,这是一种能够快速寻找值的理想结构,给定 key,就可以迅速找到对应的 value。
var mapName map[keyType]valueType // [keyType]和valueType中间可以有空格
// 声明一个map
var m map[int]string
// 声明map并初始化
var m map[int]string = map[int]string{1: "lucy", 2: "tom"}
// 自动类型推导
m := map[int]string{1: "lucy", 2: "tom"}
// 换行初始化时最后一个元素后要加逗号
m := map[int]string{
1: "lucy",
2: "tom",
3: "lily",
}
// 使用make进行初始化
m := make(map[int]string)
// 使用make进行初始化,并且初始容量100
m := make(map[int]string, 100)
+1
)的,未初始化的 map 的值是 nil
(声明的map必须初始化才可以进行添加元素),使用函数len()
可以获取 map 中 Pair(键值对)的数目。new()
来构造 map,如果错误的使用 new()
分配了一个引用对象,会获得一个空引用的指针package main
import "fmt"
func main() {
a := make(map[int]string)
// 添加元素
a[1] = "lucy"
a[2] = "tom"
a[3] = "lily"
// 如果key已存在,那么会修改value
a[1] = "james"
// 获取值和是否存在,存在返回value,true
val, exist := a[1]
fmt.Println(val, exist) // james true
// 使用一个变量接收,value
value := a[1]
fmt.Println(value) // james
// 遍历map
for k, v := range a {
println(k, v)
}
}
使用 delete()
内建函数从 map 中删除一组键值对,delete()
函数的格式如下:
delete(map, key)
package main
import "fmt"
func main() {
a := map[string]string{
"A1": "tom",
"A2": "lily",
"A3": "james",
}
// 删除编号为A2的键值对
delete(a, "A2")
fmt.Println(a) // map[A1:tom A3:james]
}
package main
import "fmt"
func main() {
// map切片:切片中元素为map
a1 := []map[string]string{
{
"id": "1",
"name": "tom",
},
{
"id": "2",
"name": "lucy",
},
}
fmt.Println(a1) // [map[id:1 name:tom] map[id:2 name:lucy]]
// 切片作为map的value
a2 := map[string][]string{
"A1": {"tom", "lucy"},
"A2": {"lily", "james"},
}
fmt.Println(a2) // map[A1:[tom lucy] A2:[lily james]]
}
Go 语言中map如果在并发读的情况下是线程安全的,如果是在并发写的情况下,则是线程不安全的。Golang 为我们提供了一个sync.Map 是并发写安全的。
Golang 中的 map 的 key 和 value 的类型必须是一致的,但 sync.Map 的 key 和 value 不一定是要相同的类型,不同的类型也是支持的。
sync.Map是一个结构体,所以无须初始化,直接声明即可使用,获取使用new()
。
var smap sync.Map
smap = new(sync.Map)
//对一个Map添加any类型的key和value
func (m *Map) Store(key, value interface{})
//根据key获取value,ok表示是否存在
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
//根据key删除
func (m *Map) Delete(key interface{})
//根据key删除,如果存在,就返回value,true;如果不存在返回nil,false
func (m *Map) LoadAndDelete(key any) (value any, loaded bool)
//接受一个参数为any类型的key和value、返回值为bool类型的函数
//如果返回true表示继续遍历,如果false表示停止遍历
func (m *Map) Range(f func(key, value interface{}) bool)
a := new(sync.Map)
a.Store("lucy", "stu001")
a.Store("tom", "stu002")
a.Store("lily", "stu003")
a.Range(func(key, value any) bool {
fmt.Printf("name:%s,id:%s\n", key, value)
return true
})
//这个函数就是如果key存在那么返回value,true;如果key不存在就添加该key-value
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。
在Go语言中,列表使用 container/list
包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。
该包下有两个结构体,Element
(代表双向链表中的节点)和List
(代表双向链表)
list 的初始化有两种方法:分别是使用 list.New() 函数(func New() *List
)和 var 关键字声明,两种方法的初始化效果都是一致的。
li := list.New()
var li list.List
列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,但是最好放一样的类型
成员 | 描述 |
---|---|
Value | 在节点中存储的值 |
func (e *Element) Next() *Element | 返回该元素的下一个元素指针,如果没有则返回nil |
func (e *Element) Prev() *Element | 返回该元素的前一个元素指针,如果没有则返回nil |
成员 | 描述 |
---|---|
func (l *List) Back() *Element | 获取list l的最后一个元素 |
func (l *List) Front() *Element | 获取list l的第一个元素 |
func (l *List) Init() *List | list l初始化或者清空 |
func (l *List) InsertAfter(v interface{}, mark *Element) *Element | 在list l中元素mark之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变 |
func (l *List) InsertBefore(v interface{}, mark *Element) *Element | 在list l中元素mark之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。 |
func (l *List) Len() int | 获取list l的长度 |
func (l *List) MoveAfter(e, mark *Element) | 将元素e移动到元素mark之后,如果元素e或者mark不属于list l,或者e==mark,则list l不改变 |
func (l *List) MoveBefore(e, mark *Element) | 将元素e移动到元素mark之前,如果元素e或者mark不属于list l,或者e==mark,则list l不改变 |
func (l *List) MoveToBack(e *Element) | 将元素e移动到list l的末尾,如果e不属于list l,则list不改变 |
func (l *List) MoveToFront(e *Element) | 将元素e移动到list l的首部,如果e不属于list l,则list不改变 |
func (l *List) PushBack(v interface{}) *Element | 在list l的末尾插入值为v的元素,并返回该元素 |
func (l *List) PushBackList(other *List) | /在list l的尾部插入另外一个list,其中l和other可以相等 |
func (l *List) PushFront(v interface{}) *Element | 在list l的首部插入值为v的元素,并返回该元素 |
func (l *List) PushFrontList(other *List) | 在list l的首部插入另外一个list,其中l和other可以相等 |
unc (l *List) Remove(e *Element) interface{} | 如果元素e属于list l,将其从list中删除,并返回元素e的值 |
//main包
package main
import (
"container/list"
"fmt"
)
func main() {
li := list.New()
li.PushBack("lucy")
li.PushBack("tom")
li.PushBack("lily")
fmt.Printf("列表的长度:%d\n", li.Len())
//遍历li
for e := li.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
/*
列表的长度:3
lucy
tom
lily
*/
}
range
关键字在 go
语言中是相当常用好用的语法糖,可以用在 for
循环中迭代 array
、slice
、map
、channel
、字符串
所有涉及到遍历输出的东西。
注意:range迭代出来的value(v)
只是每个元素的副本,也就是说range每迭代一次,就会出现一次拷贝,所以,修改v
并不会对原有数据进行修改!
如果不需要k或v,建议使用匿名变量_
,这样减少赋值所带来的损耗
k:该字符开始位置的字节索引
v:单个字符输出的是ASCII码,实际类型是 rune 类型
package main
import "fmt"
func main() {
a := "你好呀"
for k, v := range a {
fmt.Printf("第%d个字节后是:%c\n", k, v)
}
/*
第0个字符是:你
第3个字符是:好
第6个字符是:呀
*/
}
k:元素的索引
v:元素的值
package main
import "fmt"
func main() {
a := [...]string{"lucy", "lily", "tom"}
for k, v := range a {
fmt.Printf("第%d个姓名是:%s\n", k, v)
}
/*
第0个姓名是:lucy
第1个姓名是:lily
第2个姓名是:tom
*/
}
k:元素的索引
v:元素的值
package main
import "fmt"
func main() {
a := []string{"lucy", "lily", "tom"}
for k, v := range a {
fmt.Printf("第%d个姓名是:%s\n", k, v)
}
/*
第0个姓名是:lucy
第1个姓名是:lily
第2个姓名是:tom
*/
}
k:Pair的键(key)
v:Pair的值(value)
//main包
package main
import "fmt"
func main() {
a := map[string]string{"A1": "tom", "A2": "lily", "A3": "james"}
for k, v := range a {
fmt.Printf("编号:%s,姓名:%s\n", k, v)
}
/*
编号:A1,姓名:tom
编号:A2,姓名:lily
编号:A3,姓名:james
*/
}
v:从管道中接收的数据
ch := make(chan string)
for v := range ch {
fmt.Println("accept ok:", v)
}
相同点:二者都是用来分配内存的
new
func new(Type) *Type
make
func make(t Type, size ...IntegerType) Type
一般情况下,我们不经常使用new,但是make非常常用,因为slice、map、chan必须初始化才可以进行操作,二者内存都是分配在堆上