Go语言学习之4 递归&闭包&数组切片&map&锁
主要内容:
1. 内置函数、递归函数、闭包
2. 数组与切片
3. map数据结构
4. package介绍
5. 排序相关
1. 内置函数、递归函数、闭包
1)内置函数
(1). close:主要用来关闭channel
1). close函数是一个内建函数,用来关闭channel,这个channel要么是双向的, 要么是只写的(chan<- Type)。
2). 这个方法应该只由发送者调用, 而不是接收者。
3). 当最后一个发送的值都被接收者从关闭的channel(下简称为c)中接收时,接下来所有接收的值都会非阻塞直接成功,返回channel元素的零值。
channel(Go中channel可以是只读、只写、同时可读写的)
//定义只读的channel
read_only := make(<-chan int)
//定义只写的channel
write_only := make(chan<- int)
//可同时读写
read_write := make(chan int)
定义只读和只写的channel意义不大,一般用于在参数传递中,见代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func main() { 9 c := make(chan int) 10 go send(c) 11 go recv(c) 12 time.Sleep(3 * time.Second) //等待上面执行结束 13 } 14 //只能向chan里写数据 15 func send(c chan<- int) { 16 fmt.Println("write data to chan") 17 for i := 0; i < 10; i++ { 18 c <- i 19 } 20 } 21 //只能取channel中的数据 22 func recv(c <-chan int) { 23 fmt.Println("read data from chan") 24 for i := range c { 25 fmt.Println(i) 26 } 27 }
close函数简介:
1) close函数是一个内建函数, 用来关闭channel,这个channel要么是双向的, 要么是只写的(chan<- Type)。 2) 这个方法应该只由发送者调用, 而不是接收者。 3) 当最后一个发送的值都被接收者从关闭的channel(下简称为c)中接收时, 接下来所有接收的值都会非阻塞直接成功,返回channel元素的零值。
例如如下的代码:
如果c已经关闭(c中所有值都被接收), x, ok := <- c, 读取ok将会得到false。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func main() { 6 ch := make(chan int, 5) 7 8 for i := 0; i < 5; i++ { 9 ch <- i 10 } 11 12 close(ch) // 关闭ch 13 for i := 0; i < 10; i++ { 14 e, ok := <-ch //如果c已经关闭(c中所有值都被接收),再次读取 x, ok := <- c, 读取ok将会得到false 15 fmt.Printf("%v, %v\n", e, ok) 16 17 if !ok { 18 break 19 } 20 } 21 } 22 23 // 输出结果: 24 // 0, true 25 // 1, true 26 // 2, true 27 // 3, true 28 // 4, true 29 // 0, false
close函数使用注意事项:
对于值为nil的channel或者对同一个channel重复close,都会panic,关闭只读channel会报编译错误。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 1) 关闭值为nil的通道 2 var c4 chan int 3 // 运行时错误:panic: close of nil channel 4 close(c4) 5 6 2) 重复关闭同一个通道 7 c3 := make(chan int, 1) 8 close(c3) 9 // 运行时错误: 10 // panic: close of closed channel 11 close(c3) 12 13 14 3) 关闭只读通道 15 c3 := make(<-chan int, 1) 16 // 编译错误: 17 // invalid operation: close(c3) (cannot close receive-only channel) 18 close(c3) 19 20 //正确的用法 21 c1 := make(chan int, 1) // 双向通道 (bidirectional) 22 c2 := make(chan<- int, 1) // 只写的 (send-only) 23 close(c1) 24 close(c2)
close的详细使用见链接: https://www.jianshu.com/p/d24dfbb33781
(2). len:用来求长度,比如string、array、slice、map、channel
(3). new:用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
(4). make:用来分配内存,主要用来分配引用类型,比如chan、map、slice
(5). append:用来追加元素到数组、slice中
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func main() { 6 var a []int 7 a = append(a, 10, 20, 30) 8 a = append(a, a...) 9 fmt.Println(a) //[10 20 30 10 20 30] 10 11 var b []int 12 b = make([]int, 5) //存放5个int型数 13 b = append(b, 10, 20, 30) 14 b = append(b, b...) 15 fmt.Println(b) //b扩容了,[0 0 0 0 0 10 20 30 0 0 0 0 0 10 20 30] 16 }
(6). panic和recover:用来做错误处理
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 panic: 2 1、内建函数 3 2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行 4 3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行,这里的defer 有点类似 try-catch-finally 中的 finally 5 4、直到goroutine整个退出,并报告错误 6 7 recover: 8 1、内建函数 9 2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为 10 3、一般的调用建议 11 a). 在defer函数中,通过recever来终止一个gojroutine的panicking过程,从而恢复正常代码的执行 12 b). 可以获取通过panic传递的error
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 import "time" 5 import "errors" 6 7 func initConfig() (err error) { 8 return errors.New("init config failed") 9 } 10 11 func test() { 12 defer func() { 13 if err := recover(); err != nil { 14 fmt.Println(err) 15 } 16 }() //执行匿名函数 17 18 err := initConfig() 19 if err != nil { 20 panic(err) //在该处抛出一个panic的异常,然后在上面的defer中通过recover捕获这个异常,然后正常处理。 21 } 22 23 fmt.Println("can not excute") //该行及以下的代码不会被执行 24 return 25 } 26 27 func main() { 28 for { 29 test() 30 time.Sleep(time.Second) 31 } 32 }
总结:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
(7). new和make的区别
new 的作用是初始化一个指向类型的指针(*T),make 的作用是为 slice,map 或 chan 初始化并返回引用(T)。
例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func test() { 6 7 s1 := new([]int) 8 fmt.Println(s1) //&[] 9 10 s2 := make([]int, 10) 11 fmt.Println(s2) // [0 0 0 0 0 0 0 0 0 0] 12 13 *s1 = make([]int, 5) 14 (*s1)[0] = 100 15 s2[0] = 100 16 fmt.Println(s1) // &[100 0 0 0 0] 17 return 18 } 19 20 func main() { 21 test() 22 }
更详细的区别见链接: https://www.jb51.net/article/126703.htm
2)递归函数
递归定义:一个函数调用自己,就叫做递归。
例1:计算一个数的阶乘
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func calcRecur(n int) int { 6 if n == 1 { 7 return 1 8 } 9 10 return calcRecur(n-1)*n 11 } 12 13 func main() { 14 res := calcRecur(5) 15 fmt.Println(res) //120 16 }
例2:计算斐波那契数列
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func calcFib(n int) int { 6 if n <= 1 { 7 return 1 8 } 9 10 return calcFib(n - 1) + calcFib(n - 2) 11 } 12 13 func main() { 14 for i := 0; i <= 10; i++ { 15 fmt.Println(calcFib(i)) 16 } 17 18 }
递归的设计原则:
1)一个大的问题能够分解成相似的小问题
2)定义好出口条件
3)闭包
闭包:一个函数和与其相关的引用环境组合而成的实体。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func Adder() func(int) int { 6 var x int 7 return func(delta int) int { 8 x += delta 9 return x 10 } 11 } 12 13 func main() { 14 var f = Adder() //x的值,只要Adder()还在调用,则x就在内存中 15 fmt.Println(f(1)) //1 16 fmt.Println(f(20)) //21 17 fmt.Println(f(300)) //321 18 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 //该函数作用检查name是否以suffix结尾,如果不是则追加 9 func makeSuffixFunc(suffix string) func(string) string { 10 return func(name string) string { 11 if !strings.HasSuffix(name, suffix) { 12 return name + suffix 13 } 14 return name 15 } 16 } 17 18 func main() { 19 func1 := makeSuffixFunc(".bmp") 20 func2 := makeSuffixFunc(".jpg") 21 fmt.Println(func1("test")) //test.bmp 22 fmt.Println(func2("test")) //test.jpg 23 }
闭包更多了解详见链接:https://www.cnblogs.com/cxying93/p/6103375.html 和 https://www.cnblogs.com/hzhuxin/p/9199332.html
2. 数组与切片
1.数组
(1). 数组:是同一种数据类型的固定长度的序列。
(2). 数组定义:var a [len]int,比如:var a[5]int,一旦定义,长度不能变
(3). 长度是数组类型的一部分,因此,var a[5] int和var a[10]int是不同的类型
(4). 数组可以通过下标进行访问,下标是从0开始,最后一个元素下标是:len-1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 方法1: 2 for i := 0; i < len(a); i++ { 3 } 4 5 方法2: 6 for index, v := range a { 7 }
(5). 访问越界,如果下标在数组合法范围之外,则触发访问越界,会panic
(6). 数组是值类型,因此改变副本的值,不会改变本身的值
arr2 := arr1
arr2[2] = 100 //arr1的值不会发生改变
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(arr [5]int) { //arr是a的副本 8 arr[0] = 100 9 return 10 } 11 12 func main() { 13 var a [5]int 14 15 //var a []int 16 //a = make([]int, 5) // cannot use a (type []int) as type [5]int in argument to modify 17 18 //modify(a[:]) //cannot use a[:] (type []int) as type [5]int in argument to modify 19 modify(a) //a的值不会发生改变 20 for i := 0; i < len(a); i++ { 21 fmt.Println(a[i]) 22 } 23 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(arr *[5]int) { 8 //fmt.Printf("%p\n", arr) //0xc0420481b0 9 (*arr)[0] = 100 10 (*arr)[1] = 200 11 return 12 } 13 14 func main() { 15 var a [5]int 16 //fmt.Printf("%p\n", &a) //0xc0420481b0 17 modify(&a) //a的值改变了 18 for i := 0; i < len(a); i++ { 19 fmt.Println(a[i]) // a[0]=100 a[1]=200 20 } 21 }
注意下面例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(arr [5]int) { //arr是a的副本 8 arr[0] = 100 9 } 10 11 func test() { 12 var b [5]int = [5]int{1, 2, 3, 4, 5} 13 //fmt.Printf("%p\n", &b) 14 modify(b) 15 fmt.Println(b) //[1 2 3 4 5] 未改变数组b的值 16 } 17 18 func modifySlice(a []int) { 19 fmt.Printf("%p\n", a) //0xc042042440 20 a[1] = 1000 21 } 22 23 func testSlice() { 24 var b []int = []int{1, 2, 3, 4} 25 fmt.Printf("%p\n", b) //0xc042042440 26 modifySlice(b) //地址的传递 27 fmt.Println(b) //[1 1000 3 4] 改变了b的值 28 } 29 30 func main() { 31 test() 32 testSlice() 33 }
练习:使用非递归的方式实现斐波那契数列,打印前10个数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func fab(n int) { 6 var a []uint64 7 a = make([]uint64, n) //初始化数组 a 8 9 a[0] = 1 10 a[1] = 1 11 12 for i := 2; i < n; i++ { 13 a[i] = a[i-1] + a[i-2] 14 } 15 16 for _, v := range a { 17 fmt.Println(v) 18 } 19 } 20 21 func main() { 22 fab(10) 23 }
1). 数组初始化
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 a. var age0 [5]int = [5]int{1,2,3} 2 b. var age1 = [5]int{1,2,3,4,5} 3 c. var age2 = […]int{1,2,3,4,5,6} 4 d. var str = [5]string{3:”hello world”, 4:”tom”}
2). 多维数组
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 a. var age [5][3]int 2 b. var f [2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}}
3). 多维数组遍历
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 9 var f[2][3]int = [...][3]int{{1, 2, 3}, {7, 8, 9}} 10 11 for k1, v1 := range f { 12 for k2, v2 := range v1 { 13 fmt.Printf("(%d,%d)=%d ", k1, k2, v2) 14 } 15 fmt.Println() 16 } 17 }
2. 数组切片
(1). 切片:切片是数组的一个引用,因此切片是引用类型
(2). 切片的长度可以改变,因此,切片是一个可变的数组
(3). 切片遍历方式和数组一样,可以用len()求长度
(4). cap可以求出slice最大的容量,0 <= len(slice) <= cap(array),其中array是slice引用的数组
(5). 切片的定义:var 变量名 []类型,比如 var str []string, var arr []int
切片引用:
(1). 切片初始化:var slice []int = arr[start:end]包含start到end之间的元素,但不包含end
(2). var slice []int = arr[0:end]可以简写为 var slice []int=arr[:end]
(3). var slice []int = arr[start:len(arr)] 可以简写为 var slice[]int = arr[start:]
(4). var slice []int = arr[0, len(arr)] 可以简写为 var slice[]int = arr[:]
(5). 如果要切片最后一个元素去掉,可以这么写: Slice = slice[:len(slice)-1]
练习:写一个程序,演示切片的各个用法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 var slice []int 9 var arr [5]int = [...]int{1, 2, 3, 4, 5} 10 11 slice = arr[:] 12 fmt.Println(slice) //[1 2 3 4 5] 13 slice = slice[1:] 14 fmt.Println(len(slice)) //4 15 slice = slice[:len(slice)-1] 16 fmt.Println(cap(slice)) //4 17 18 slice = slice[0:1] 19 fmt.Println(len(slice)) //1 20 fmt.Println(cap(slice)) //4 21 }
切片的内存布局:
练习: 写一个程序,演示切片的内存布局(注意和上图的联系)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 //自定义切片类型 6 type slice struct { 7 ptr *[10]int //为了测试方便,定义ptr指向具有固定大小10个字节内存地址 8 len int //数组长度 9 cap int //数组容量 10 } 11 12 //初始化切片 13 func make1(s slice, cap int) slice { 14 s.ptr = new([10]int) 15 s.cap = cap 16 s.len = 0 17 return s 18 } 19 20 //修改切片内的数组值 21 func modify(s slice) { 22 s.ptr[1] = 1000 23 } 24 25 func testSlice1() { 26 var s1 slice 27 s1 = make1(s1, 10) 28 29 s1.ptr[0] = 100 30 modify(s1) 31 32 fmt.Println(s1.ptr) //&[100 1000 0 0 0 0 0 0 0 0] 33 } 34 35 func modify1(a []int) { 36 a[1] = 1000 37 } 38 39 func testSlice2() { 40 var b []int = []int{1, 2, 3, 4} 41 modify1(b) 42 fmt.Println(b) //[1 1000 3 4] 43 } 44 45 func testSlice3() { 46 var a = [10]int{1, 2, 3, 4} 47 48 b := a[1:5] 49 fmt.Printf("%p\n", b) //0xc042058058 50 fmt.Printf("%p\n", &a[1]) //0xc042058058 51 } 52 53 func main() { 54 //testSlice1() 55 //testSlice2() 56 testSlice3() 57 }
(6). 通过make来创建切片
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var slice []type = make([]type, len) 2 slice := make([]type, len) 3 slice := make([]type, len, cap)
(7). 用append内置函数操作切片
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 slice = append(slice, 10) 2 var a = []int{1,2,3} 3 var b = []int{4,5,6} 4 a = append(a, b…)
(8). For range 遍历切片
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 for index, val := range slice {}
(9). 切片resize
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var a = []int {1,3,4,5} 2 b := a[1:2] 3 b = b[0:3]
(10). 切片拷贝
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 s1 := []int{1,2,3,4,5} 2 s2 := make([]int, 10) 3 copy(s2, s1) 4 5 s3 := []int{1,2,3} 6 s3 = append(s3, s2…) 7 s3 = append(s3, 4, 5, 6)
(11). string与slice
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 string底层就是一个byte的数组,因此,也可以进行切片操作 2 str := "hello world" 3 s1 := str[0:5] 4 fmt.Println(s1) 5 s2 := str[5:] 6 fmt.Println(s2)
(12). string的底层布局
(13). 如何改变string中的字符值?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 string本身是不可变的,因此要改变string中字符,需要如下操作: 2 str := "hello world" 3 s := []byte(str) 4 s[0] = 'O' 5 str = string(s)
(14). 排序和查找操作
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 排序操作主要都在 sort包中,导入就可以使用了 2 import("sort") 3 sort.Ints对整数进行排序,sort.Strings对字符串进行排序, sort.Float64s对 4 浮点数进行排序. 5 sort.SearchInts(a []int, b int) 从数组a中查找b,前提是a必须有序 6 sort.SearchFloats(a []float64, b float64) 从数组a中查找b,前提是a必须有序 7 sort.SearchStrings(a []string, b string) 从数组a中查找b,前提是a必须有序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 "sort" 6 ) 7 8 func testIntSort() { 9 var a = [...]int{1, 8, 38, 2, 348, 484} 10 sort.Ints(a[:]) 11 12 fmt.Println(a) //[1 2 8 38 348 484] 13 } 14 15 func testStrings() { 16 var a = [...]string{"abc", "efg", "b", "A", "eeee"} 17 sort.Strings(a[:]) 18 19 fmt.Println(a) //[A abc b eeee efg] 20 } 21 22 func testFloat() { 23 var a = [...]float64{2.3, 0.8, 28.2, 392342.2, 0.6} 24 sort.Float64s(a[:]) 25 26 fmt.Println(a) //[0.6 0.8 2.3 28.2 392342.2] 27 } 28 29 func testIntSearch() { 30 var a = [...]int{1, 8, 38, 2, 348, 484} 31 //sort.Ints(a[:]) 32 index := sort.SearchInts(a[:], 348) //SearchInts该函数内部会先排序然后查找 33 fmt.Println(index) //4 34 fmt.Println(a) //[1 8 38 2 348 484] 未改变a的值 35 } 36 37 func main() { 38 testIntSort() 39 testStrings() 40 testFloat() 41 testIntSearch() 42 }
注意:sort.Ints,sort.Strings,sort.Float64s会改变源数组的值。
切片,copy及字符串修改测试:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func testSlice() { 6 var a [5]int = [...]int{1, 2, 3, 4, 5} 7 s := a[1:] 8 9 fmt.Printf("before len[%d] cap[%d]\n", len(s), cap(s)) //before len[4] cap[4] 10 s[1] = 100 //改变s[1](a[1])值 11 fmt.Printf("s=%p a[1]=%p\n", s, &a[1]) //s=0xc0420481b8 a[1]=0xc0420481b8 12 fmt.Println("before a:", a) //before a: [1 2 100 4 5] 13 14 s = append(s, 10) 15 s = append(s, 10) 16 fmt.Printf("after len[%d] cap[%d]\n", len(s), cap(s)) //after len[6] cap[8] 17 s = append(s, 10) 18 s = append(s, 10) 19 s = append(s, 10) 20 21 s[1] = 1000 //s扩容之后s和a[1]的地址不同,因此改变s[1]的值a[1]的值不发生变化 22 fmt.Println("after a:", a) //after a: [1 2 100 4 5] 23 fmt.Println(s) //[2 1000 4 5 10 10 10 10 10] 24 fmt.Printf("s=%p a[1]=%p\n", s, &a[1]) //s=0xc042014200 a[1]=0xc0420481b8 25 } 26 27 func testCopy() { 28 var a []int = []int{1, 2, 3, 4, 5, 6} 29 b := make([]int, 1) 30 31 copy(b, a) //拷贝的大小以b为准 32 33 fmt.Println(b) //[1] 34 } 35 36 func testString() { 37 s := "hello world" 38 s1 := s[0:5] 39 s2 := s[6:] 40 41 fmt.Println(s1) //hello 42 fmt.Println(s2) //world 43 } 44 45 func testModifyString() { 46 s := "我hello world" 47 s1 := []rune(s) 48 49 s1[0] = '你' 50 s1[1] = 'z' 51 s1[2] = 'z' 52 str := string(s1) 53 fmt.Println(str) //你zzllo world 54 } 55 56 func main() { 57 //testSlice() 58 //testCopy() 59 //testString() 60 testModifyString() 61 }
3. map数据结构
(1). map简介
key-value的数据结构,又叫字典或关联数组
声明:
var map1 map[keytype]valuetype
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string
注意:声明是不会分配内存的,初始化需要make
(2). map相关操作
var a map[string]string = map[string]string{"hello": "world"} 定义并初始化map a
a := make(map[string]string, 10)
a["hello"] = "world" 插入和更新
val, ok := a["hello"] 查找
for k, v := range a {
fmt.Println(k,v) 遍历
}
delete(a, "hello") 删除
len(a) 长度
(3). map是引用类型
func modify(a map[string]int) {
a["one"] = 134
}
(4). slice of map
items := make([]map[int][int], 5)
for i := 0; I < 5; i++ {
items[i] = make(map[int][int])
}
(5). map排序
a. 先获取所有key,把key进行排序
b. 按照排序好的key,进行遍历
(6). Map反转
初始化另外一个map,把key、value互换即可
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func testMap() { 6 var a map[string]string = map[string]string { 7 "key": "value", 8 } 9 //a := make(map[string]string, 10) 10 a["abc"] = "efg" 11 a["abc"] = "efg" 12 a["abc1"] = "efg" 13 14 fmt.Println(a) //map[key:value abc:efg abc1:efg] 15 } 16 17 func testMap2() { 18 19 a := make(map[string]map[string]string, 100) //value是一个map 20 a["key1"] = make(map[string]string) 21 a["key1"]["key2"] = "abc" 22 a["key1"]["key3"] = "abc" 23 a["key1"]["key4"] = "abc" 24 a["key1"]["key5"] = "abc" 25 fmt.Println(a) //map[key1:map[key2:abc key3:abc key4:abc key5:abc]] 26 } 27 28 func modify(a map[string]map[string]string) { 29 _, ok := a["zhangsan"] 30 if !ok { 31 a["zhangsan"] = make(map[string]string) 32 } 33 34 a["zhangsan"]["passwd"] = "123456" 35 a["zhangsan"]["nickname"] = "pangpang" 36 37 return 38 } 39 40 func testMap3() { 41 42 a := make(map[string]map[string]string, 100) 43 44 modify(a) 45 46 fmt.Println(a) //map[zhangsan:map[passwd:123456 nickname:pangpang]] 47 } 48 49 func trans(a map[string]map[string]string) { 50 for k, v := range a { 51 fmt.Println(k) 52 for k1, v1 := range v { 53 fmt.Println("\t", k1, v1) 54 } 55 } 56 } 57 58 func testMap4() { 59 60 a := make(map[string]map[string]string, 100) 61 a["key1"] = make(map[string]string) 62 a["key1"]["key2"] = "abc" 63 a["key1"]["key3"] = "abc" 64 a["key1"]["key4"] = "abc" 65 a["key1"]["key5"] = "abc" 66 67 a["key2"] = make(map[string]string) 68 a["key2"]["key2"] = "abc" 69 a["key2"]["key3"] = "abc" 70 71 trans(a) 72 delete(a, "key1") //删除"key1" 73 fmt.Println() 74 trans(a) 75 76 fmt.Println(len(a)) 77 } 78 79 func testMap5() { 80 var a []map[int]int 81 a = make([]map[int]int, 5) 82 83 if a[0] == nil { 84 a[0] = make(map[int]int) 85 } 86 a[0][10] = 10 87 fmt.Println(a) //[map[10:10] map[] map[] map[] map[]] 88 } 89 90 func main() { 91 //testMap() 92 //testMap2() 93 //testMap3() 94 //testMap4() 95 testMap5() 96 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 "sort" 6 ) 7 8 func testMapSort() { 9 var a map[int]int 10 a = make(map[int]int, 5) 11 12 a[8] = 10 13 a[3] = 10 14 a[2] = 10 15 a[1] = 10 16 a[18] = 10 17 18 var keys []int 19 for k, _ := range a { 20 keys = append(keys, k) 21 //fmt.Println(k, v) 22 } 23 24 sort.Ints(keys) 25 26 for _, v := range keys { 27 fmt.Println(v, a[v]) 28 } 29 // 1 10 30 // 2 10 31 // 3 10 32 // 8 10 33 // 18 10 34 } 35 36 func testMapSort1() { 37 var a map[string]int 38 var b map[int]string 39 40 a = make(map[string]int, 5) 41 b = make(map[int]string, 5) 42 43 a["abc"] = 101 44 a["efg"] = 10 45 46 for k, v := range a { 47 b[v] = k 48 } 49 50 fmt.Println(b) 51 } 52 53 func main() { 54 //testMapSort() 55 testMapSort1() 56 }
4. 包
(1). golang中的包
a. golang目前有150个标准的包,覆盖了几乎所有的基础库
b. golang.org有所有包的文档,没事都翻翻
(2). 线程同步
a. import("sync")
b. 互斥锁, var mu sync.Mutex
c. 读写锁, var mu sync.RWMutex
1)锁的概念:
什么是锁呢?就是某个协程(线程)在访问某个资源时先锁住,防止其它协程的访问,等访问完毕解锁后其他协程再来加锁进行访问。
2)互斥锁:
每个资源都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,只能有一个协程(线程)访问该资源。其它的协程只能等待。
注意:在使用互斥锁时,一定要注意:对资源操作完成后,一定要解锁,否则会出现流程执行异常,死锁等问题。通常借助defer。锁定后,立即使用defer语句保证互斥锁及时解锁。如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 var mutex sync.Mutex // 定义互斥锁变量 mutex 2 3 func write() { 4 mutex.Lock( ) 5 defer mutex.Unlock( ) 6 }
3)读写锁:
互斥锁的问题:互斥锁的本质是当一个goroutine访问的时候,其他goroutine都不能访问。这样在资源同步,避免竞争的同时也降低了程序的并发性能。程序由原来的并行执行变成了串行执行。真正的互斥应该是读取和修改、修改和修改之间,读和读是没有互斥操作的必要的。因此,衍生出另外一种锁,叫做读写锁。因此,读写锁是针对于读写操作的互斥锁。
基本遵循两大原则:
1、可以随便读。多个goroutin同时读。
2、写的时候,啥都不能干。不能读,也不能写。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sync" 7 "sync/atomic" 8 "time" 9 ) 10 11 var lock sync.Mutex 12 var rwLock sync.RWMutex 13 14 func testMap() { 15 var a map[int]int 16 a = make(map[int]int, 5) 17 18 a[8] = 10 19 a[3] = 10 20 a[2] = 10 21 a[1] = 10 22 a[18] = 10 23 24 for i := 0; i < 2; i++ { 25 go func(b map[int]int) { 26 lock.Lock() 27 b[8] = rand.Intn(100) 28 lock.Unlock() 29 }(a) 30 } 31 32 lock.Lock() 33 fmt.Println(a) 34 lock.Unlock() 35 36 time.Sleep(time.Second) 37 } 38 39 func testRWLock() { 40 var a map[int]int 41 a = make(map[int]int, 5) 42 var count int32 43 a[8] = 10 44 a[3] = 10 45 a[2] = 10 46 a[1] = 10 47 a[18] = 10 48 49 for i := 0; i < 2; i++ { 50 go func(b map[int]int) { 51 //rwLock.Lock() 52 lock.Lock() 53 b[8] = rand.Intn(100) 54 time.Sleep(10 * time.Millisecond) 55 lock.Unlock() 56 //rwLock.Unlock() 57 }(a) 58 } 59 60 for i := 0; i < 100; i++ { 61 go func(b map[int]int) { 62 for { 63 lock.Lock() 64 //rwLock.RLock() 65 time.Sleep(time.Millisecond) 66 //fmt.Println(a) 67 //rwLock.RUnlock() 68 lock.Unlock() 69 atomic.AddInt32(&count, 1) 70 } 71 }(a) 72 } 73 time.Sleep(time.Second * 3) 74 fmt.Println(atomic.LoadInt32(&count)) 75 } 76 77 func main() { 78 //testMap() 79 testRWLock() 80 }
(3). go get安装第三方包
go get 可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。整个过程就像安装一个 App 一样简单。
使用 go get 前,需要安装与远程包匹配的代码管理工具,如 Git、SVN、HG 等,参数中需要提供一个包名。
go get+远程包
默认情况下,go get 可以直接使用。例如,想获取 go 的源码并编译,使用下面的命令行即可:
$ go get github.com/davyxu/cellnet
注意:获取前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹下。
5. 排序相关
1). 实现一个冒泡排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func bsort(a []int) { 6 7 for i := 0; i < len(a); i++ { 8 for j := 1; j < len(a)-i; j++ { 9 if a[j] < a[j-1] { 10 a[j], a[j-1] = a[j-1], a[j] 11 } 12 } 13 } 14 } 15 16 func main() { 17 b := [...]int{8, 7, 5, 4, 3, 10, 15} 18 bsort(b[:]) 19 fmt.Println(b) 20 }
2). 实现一个选择排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func ssort(a []int) { 6 7 for i := 0; i < len(a); i++ { 8 var min int = i 9 for j := i + 1; j < len(a); j++ { 10 if a[min] > a[j] { 11 min = j 12 } 13 } 14 a[i], a[min] = a[min], a[i] 15 } 16 } 17 18 func main() { 19 b := [...]int{8, 7, 5, 4, 3, 10, 15} 20 ssort(b[:]) 21 fmt.Println(b) 22 }
3). 实现一个插入排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func isort(a []int) { 6 7 for i := 1; i < len(a); i++ { 8 for j := i; j > 0; j-- { 9 if a[j] > a[j-1] { 10 break 11 } 12 a[j], a[j-1] = a[j-1], a[j] 13 } 14 } 15 } 16 17 func main() { 18 b := [...]int{8, 7, 5, 4, 3, 10, 15} 19 isort(b[:]) 20 fmt.Println(b) 21 }
4). 实现一个快速排序
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package main 2 3 import "fmt" 4 5 func qsort(a []int, left, right int) { 6 if left >= right { 7 return 8 } 9 10 val := a[left] 11 k := left 12 //确定val所在的位置 13 for i := left + 1; i <= right; i++ { 14 if a[i] < val { 15 a[k] = a[i] 16 a[i] = a[k+1] 17 k++ 18 } 19 } 20 21 a[k] = val 22 qsort(a, left, k-1) 23 qsort(a, k+1, right) 24 } 25 26 func main() { 27 b := [...]int{8, 7, 5, 4, 3, 10, 15} 28 qsort(b[:], 0, len(b)-1) 29 fmt.Println(b) 30 }
参考文献:
- https://www.jianshu.com/p/0cbc97bd33fb(Go语言 异常panic和恢复recover用法)
- https://blog.csdn.net/weixin_42927934/article/details/82533940(读写锁、互斥锁)
- http://c.biancheng.net/view/123.html (go get命令——一键获取代码、编译并安装)