指针 Go 语言中和 C/C++ 一样,直接提供了指针类型 ,但是不同的是 Go 语言的指针无法进行位移和运算 的,它只是一个存储了其它变量地址的变量 而已。它在 Go 语言中最大的作用就是在值传递 的过程中减少内存消耗,毕竟一个地址的存储开销是十分少的。
1. 指针的定义和使用 在 Go 语言中指针类型的声明和 C/C++ 类似,都是采用 *
符号来区分正常类型变量和指针变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { str := "Hello Golang!" var strP *string strP = &str fmt.Println("未修改前变量str的值为" , str) fmt.Println("未修改前变量str的地址为" , &str) *strP = "Hello Go!" fmt.Println("======通过指针修改后======" ) fmt.Println("指针strP的地址为" , strP) fmt.Println("指针strP的指向地址为" , *strP) fmt.Println("变量str的地址为" , &str) fmt.Println("变量str的值为" , str) }
输出结果
1 2 3 4 5 6 7 未修改前变量str的值为 Hello Golang! 未修改前变量str的地址为 0x14000096230 ======通过指针修改后====== 指针strP的地址为 0x14000096230 指针strP的指向地址为 Hello Go! 变量str的地址为 0x14000096230 变量str的值为 Hello Go!
变量 strP 存储的值就是变量 str 的地址 ,可以通过使用 *strP 操作符改变其存储的地址所存储的值 。但地址 是没有改变的,都是0x14000096230
2. 指针作为函数的参数 Go 语言中只有值传递,没有引用传递 ,因为引用传递设计之初是为了解决函数想要有多个返回值的问题,但是 Go 语言的函数自带多返回值的返回方式。所以想要指针作为参数传入函数中,只能传递地址进入函数进行修改,并不能直接引用指针参数,这一特点就大大降低了 Go 语言指针的使用难度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { a := 10 b := 20 fmt.Println("交换前的a=" , a, "b=" , b) swap(&a, &b) fmt.Println("交换后的a=" , a, "b=" , b) }func swap (a, b *int ) { *a, *b = *b, *a }
输出结果
1 2 交换前的a= 10 b= 20 交换后的a= 20 b= 10
3. 总结
Go 语言的指针不可以做偏移 ;
Go 语言的指针可以看作一个存储地址的特殊变量类型 。
数组 1. 数组的形式 数组的声明形式形如var 数组名 [数组长度]数组类型
,其中数组类型可以是数组本身,也就是数组类型的数组,这样就构成了多维数组。和变量的声明相同,数组在声明时会初始化变量类型的零值 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a [2 ]int var b [2 ][2 ]int var c = [2 ]int {1 , 2 } var d = [...]int {3 , 4 } fmt.Println("a的零值" , a) fmt.Println("b的零值" , b) fmt.Println("c的值" , c) fmt.Println("d的值" , d) }
输出结果
1 2 3 4 a的零值 [0 0] b的零值 [[0 0] [0 0]] c的值 [1 2] d的值 [3 4]
2. 数组的比较 在 Go 语言中判断数组是否相等需要比较两个部分。一个是数组的长度是否相等,另一个是数组中存放的值是否顺序和大小完全相同。只要这两个部分相等,则 Go 语言中的两个数组就是相等的。
⚠️:Go 语言中只有类型相同的数组才可以互相比较,且数组没有大小的比较,只能比较是否相等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport ( "fmt" )func main () { var a [2 ]int var c = [2 ]int {1 , 2 } var d = [...]int {1 , 2 } fmt.Println("a == c ? " , a == c) fmt.Println("c == d ?" , c == d) }
输出结果
1 2 a == c ? false c == d ? true
切片 1. 切片的创建 切片的声明方式和数组类似,写法上看就是声明一个没有长度的数组:var 切片名 []切片类型
。其中切片类型可以是切片本身,也就是切片的切片,就构成了多维 的切片。
切片在使用之前必须要初始化 ,它没有零值。声明后它的值是 nil ,这是因为它的底层实现是一个指向数组的指针,在你给它存入一个数组的地址之前,它只能是 nil 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a []int fmt.Println("初始化前: " , a) a = make ([]int , 5 , 10 ) fmt.Println("初始化后:" , a) a[4 ] = 5 fmt.Println("赋值后: " , a) a[5 ] = 6 fmt.Println("赋值后: " , a) }
结果如下:
1 2 3 4 5 6 7 8 初始化前: [] 初始化后: [0 0 0 0 0] 赋值后: [0 0 0 0 5] panic: runtime error: index out of range [5] with length 5 goroutine 1 [running]: main.main() /Users/GolandProjects/awesomeProject/hello.go:12 +0x160
2. 切片的截取 切片可以从任意长度开始切,切到任意长度为止,然后这一段拿出来就是一个新的切片。切割形式为
切片名(s)[起始下标(begin):结束下标(end):最大容量(max)]
⚠️:截取到的切片包含起始下标(begin),不包含结束下标(end)。
切片截取形式表:
操作
含义
s[begin?max]
截取切片s从begin到end的数据,构成一个容量为max-begin,长度为begin-end的切片。
s[begin:end]
截取切片s从begin到end的数据,构成一个容量和长度均为begin-end的切片。
s[begin:]
截取切片s从begin到最后的数据,构成一个容量和长度均为len(s)-end的切片。
s[:end]
截取切片s从0到最后的数据,构成一个容量和长度均为end-0的切片。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var a = []int {1 , 2 , 3 , 4 , 5 } fmt.Println("a[1:3]=" , a[1 :3 ]) fmt.Println("a[1:]=" , a[1 :]) fmt.Println("a[:3]=" , a[:3 ]) }
输出结果
1 2 3 a[1:3]= [2 3] a[1:]= [2 3 4 5] a[:3]= [1 2 3]
3. 切片的追加 切片使用一个 Go 语言的内置函数append(切片,待添加的值)
,来进行切片末尾元素的追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport "fmt" func main () { var a = []int {1 , 2 , 3 , 4 , 5 } a = append (a, 6 ) fmt.Println(a) a = append (a, 7 , 8 ) fmt.Println(a) b := []int {9 , 10 } a = append (a, b...) fmt.Println(a) }
输出结果
1 2 3 [1 2 3 4 5 6] [1 2 3 4 5 6 7 8] [1 2 3 4 5 6 7 8 9 10]
4. 切片的长度和容量 在切片中可以使用len()
获取切片中元素的数量 ,也就是切片的长度。使用cap()
可以获取切片引用的数组的长度 ,也就切片的容量。切片的容量一般大于等于长度,容量会随着长度的增长而增长。
在初始化一个切片的时候其实时给切片引用了一个数组,然后容量就是这个数组的长度,然后如果切片的长度超过了切片的容量,它就会让切片引用一个容量更大数组来存放这些元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var a = []int {1 , 2 , 3 , 4 , 5 } fmt.Printf("a的地址%p, a的长度%d, a的容量%d\n" , a, len (a), cap (a)) a = append (a, 6 ) fmt.Printf("a的地址%p, a的长度%d, a的容量%d\n" , a, len (a), cap (a)) a = append (a, 7 , 8 ) fmt.Printf("a的地址%p, a的长度%d, a的容量%d\n" , a, len (a), cap (a)) b := []int {9 , 10 , 11 } a = append (a, b...) fmt.Printf("a的地址%p, a的长度%d, a的容量%d\n" , a, len (a), cap (a)) }
输出结果
1 2 3 4 a的地址0x140000161b0, a的长度5, a的容量5 a的地址0x140000181e0, a的长度6, a的容量10 a的地址0x140000181e0, a的长度8, a的容量10 a的地址0x14000102000, a的长度11, a的容量20
在切片a每次添加的元素要超过它的容量 时,它的地址就会发生改变 ,其实就是让它引用了一个新的容量更大的数组。
Go语言中的Map 一种元素对的无序集合,每一个**索引(key)对应一个值(value)**,这种数据结构在 Go 语言中被称之为 map 。
map 是一种能够通过索引(key)迅速找到值(value)的数据结构,所以也被称为 字典 。在 Go 语言中因为线程安全问题,一共实现了两种类型的 map
1. 无锁的map 这种类型的 map 是线程不安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,会有读写冲突,会导致系统奔溃。所以一般在单线程程序中使用的较多。
1.1 map的创建 map 的底层结构也是一个指针,所以和变量不同,并不是声明后立刻能够使用。和切片相同,需要使用make()函数进行初始化。在初始化之前为空,没有零值。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { var m map [string ]string fmt.Println(m == nil ) m = make (map [string ]string ) fmt.Println(m == nil ) }
输出结果
1.2 map的赋值 map 的赋值有两种方式:
使用:=
使map在定义的时候直接赋值;
使用map[key]=value
的形式对map进行赋值。
在明确知道 map 的值的时候就可以使用第一种方式进行赋值,比如说在建立中英文对应关系的时候。在未知 map 的取值时,一般建议使用后者进行赋值。
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package mainimport "fmt" func main () { m1 := map [string ]string {"Apple" : "苹果" , "Orange" : "橘子" , "Bannana" : "香蕉" } fmt.Println(m1["Apple" ]) m2 := make (map [string ]string ) m2["Apple" ] = "苹果" m2["Orange" ] = "橘子" m2["Banana" ] = "香蕉" fmt.Println(m2["Apple" ]) }
输出结果
1.3 map的遍历 map 是字典结构,如果不清楚所有 key 的值,是无法对 map 进行遍历的,所以 Go 语言中使用了一个叫做range 的关键字,配合for循环结构 来对map结构进行遍历。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { m := map [string ]string {"Apple" : "苹果" , "Orange" : "橘子" , "Bannana" : "香蕉" } for k, v := range m { fmt.Println("key:" , k, ", value:" , v) } }
⚠️:map 是无序的,所以每次输出的顺序可能会不一样。
输出结果
1 2 3 key: Apple , value: 苹果 key: Orange , value: 橘子 key: Bannana , value: 香蕉
1.4 map的删除 map 在普通的用法中是无法移除只可以增加 key 和 value 的,所以 Go 语言中使用了一个内置函数delete(map,key)
来移除 map 中的 key 和 value。
1 2 3 4 5 6 7 8 9 10 11 package mainimport "fmt" func main () { m := map [string ]string {"Apple" : "苹果" , "Orange" : "橘子" , "Banana" : "香蕉" } fmt.Println(m) delete (m, "Apple" ) fmt.Println(m) }
输出结果
1 2 map[Apple:苹果 Banana:香蕉 Orange:橘子] map[Banana:香蕉 Orange:橘子]
2. 自带锁的 sync.Map 这种类型的 map 是线程安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,不会有读写冲突,因为它自带原子锁,保障了多线程的数据安全。
2.1 sync.Map 的创建 这种类型的 map 创建不需要make ,直接声明就可以使用,而且不需要声明 map 的 key 和 value 的类型。因为它底层的实现并不是指针,是一种多个变量的聚合类型,叫做结构体 。
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" "sync" )func main () { var m sync.Map fmt.Println(m) }
输出结果
1 {{0 0} {[] {} <nil>} map[] 0}
2.2 sync.Map 的操作 这个类型关于 map 的所有操作都是使用它自带的方法来实现的。包括range 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport ( "fmt" "sync" )func main () { var m sync.Map m.Store("Apple" , "苹果" ) m.Store("Orange" , "橘子" ) m.Store("Banana" , "香蕉" ) tmp, exist := m.Load("Orange" ) fmt.Println(tmp, exist) m.Delete("Banana" ) m.Range(func (k, v interface {}) bool { fmt.Println("key:" , k, ", value:" , v) return true }) }
输出结果
1 2 3 橘子 true key: Apple , value: 苹果 key: Orange , value: 橘子