3.切片类型 (slice)💥
切片的声明与数组类似,只不过是没有长度,var s []int,因为切片是对数组的引用,所以只声明而未初始化时切片的值为nil。
在go语言中,切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型,这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集;切片的内存分布是连续的,所以可以把切片当做一个大小不固定的数组。切片有三个字段的数据结构:指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。
1. 获取全部 slice[:] 2.获取指定区域 左闭右开 slice[start:end] 3.从指定区域到尾部 slice[:end] 4.从头部到指定区域 silce[start:] 5.切片的三种定义: var strList []string //与数组的最大区别就是不要写参数,而数组需要写参数 //生命整形切片 var numList []int //与数组的最大区别就是不要写参数,而数组需要写参数 //声明一个空的切片 var numListEmpty = []int{} //与数组的最大区别就是不要写参数且左边不用编写类型,而数组需要写参数
package main // 这里的包一定要写成 main. import "fmt" /** 切片单独使用几乎没有什么用处,需要配合数组使用 */ func main() { // 主函数 //声明字符串切片 var strList []string //与数组的最大区别就是不要写参数,而数组需要写参数 //生命整形切片 var numList []int //与数组的最大区别就是不要写参数,而数组需要写参数 //声明一个空的切片 var numListEmpty = []int{} //与数组的最大区别就是不要写参数且左边不用编写类型,而数组需要写参数 // 输出三个切片 fmt.Println(strList, " ", numList, " ", numListEmpty) fmt.Println("以上是输出三个切片") // 输出三个切片的大小 fmt.Println(len(strList), " ", len(numList), " ", len(numListEmpty)) fmt.Println("以上是输出三个切片的大小") //判定输出结果是否位不为空 fmt.Println(strList == nil) fmt.Println(numList == nil) fmt.Println(numListEmpty == nil) fmt.Println("以上是判断切片是否为空") fmt.Println("以下是数组的指向") // 数组的第一中定义 var a [3]int = [3]int{1, 2, 3} // 数组的第二种定义 var b [3]int b[0] = 1 // 数组的第三种定义方式 c := [3]int{1, 2, 3} fmt.Println(a, " ", b, " ", c) fmt.Println("以下是切片指向数组") // 已经定义的切片指向数组 numList = a[:] fmt.Println(numList) // 未定义的切片指向数组 strlist1 := a[:] fmt.Println(strlist1) fmt.Println("以下是替换和追加") // 切片在指定索引处进行替换值 numList[0] = 5 fmt.Println(numList) // 切片在尾部追加 numList = append(numList, 6) fmt.Println(numList) }
4.函数类型 (func)💥
Go语言中的函数也是一种类型。
第一个小括号是入参的小括号,第二个小括号是返回的类型。如果为nil就可以省略
package main // 参数为两个,且参数是返回一个值 func functionName1(a int, b int) (int) { return b + a } // 假如方法是空的话第二个参数可以省略 func functionName2(a int, b int) { println("第二个方法得数是: ", a, b) } func functionName3(a int, b int) (int, int) { return a, b } func main() { println("第一个方法得数是: ", functionName1(1, 3)) functionName2(1, 2) var ( sum1 int sum2 int ) sum1, sum2 = functionName3(1, 2) // 假如有两个或者两个以上的返回值我们需要先接受,也可以使用匿名函数进行接受 println("第三个方法得数是: ", sum1, " ", sum2) }
5.管道类型 (channel)
Channel 是 Go 语言中被用来实现并行计算方程之间通信的类型。其功能是允许线程间通过发送和接收来传输指定类型的数据。其初始值是 nil。
var c1 chan 类型 c1 = make(chan 类型, 容量) var c1 chan [value type] c1 = make([channel type] [value type], [capacity])
- [value type] 定义的是 Channel 中所传输数据的类型。
[channel type] 定义的是 Channel 的类型,其类型有以下三种:
- “chan” 可读可写——“chan int” 则表示可读写 int 数据的 channel
- “chan<-” 仅可写——“chan<- float64” 则表示仅可写64位 float 数据的 channel
- “<-chan” 仅可读——“<-chan int” 则表示仅可读 int 数据的 channel
[capacity] 是一个可选参数,其定义的是 channel 中的缓存区 (buffer)。如果不填则默认该 channel 没有缓冲区
(unbuffered)。对于没有缓冲区的 channel,消息的发送和收取必须能同时完成,否则会造成阻塞并提示死锁错误。对于
channel 的阻塞和非阻塞将在后面详细介绍。
(1).正常读和写
接受消息和发送消息
package main func main() { var c1 chan int //定义一个channel类型的变量 var jsxs int c1 = make(chan int, 100) //对这个channel的变量进行初始化 // 我们向c1发送一个消息 (写入) c1 <- 20 // 我们从c1中进行接受数据 (读取) jsxs = <-c1 // 将我们接受的信息进行打印 println(jsxs) }
(2).发生死锁
只读不写或者只写不读会发生死锁或者读和写不同步都会导致死锁
读和写时间不一致,造成死锁反应
package main import ( "fmt" "time" ) func main() { var c1 chan string c1 = make(chan string) //管道读和写 func() { time.Sleep(time.Second * 2) //睡眠一会 c1 <- "result 1" //传入数据 }() fmt.Println("received: '", <-c1, "' from c1") //读出数据 }
(3).如何解决死锁
第一种解决方法:
使用 go 语句进行并行计算
package main import ( "fmt" "time" ) func main() { var c1 chan string c1 = make(chan string) go func() { //使用go语句 time.Sleep(time.Second * 2) c1 <- "result 1" }() fmt.Println("received: '", <-c1, "' from c1") }
通过 go 语句定义发送操作的方程在另一个线程并行运行,这样发送和接收操作就可以同时发生,从而能够解决死锁问题。
第二种解决方法:
package main import ( "fmt" "time" ) func main() { var c1 chan string c1 = make(chan string, 1) //这里我们设置了一个长度为 1 的 buffer func() { time.Sleep(time.Second * 2) c1 <- "result 1" }() fmt.Println("received: '", <-c1, "' from c1") }
为 channel 添加一个缓冲区(buffer),这样只要 buffer 没有用尽,阻塞就不会发生,死锁也不会发生。
“不要用共享来完成通信,而是用通信来完成共享” 说的就是推荐使用channel来完成协程之间的同步和通信等需求,而不是使用加锁,虽然GO也提供了完善的锁机制。