一、Go语言切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
1.1 定义切片
你可以声明一个未指定大小的数组来定义切片:
var identifier []type
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make([]type, len)
也可以简写为
slice1 := make([]type, len)
也可以指定容量,其中 capacity 为可选参数。
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度
1.2 len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
package main
import "fmt"
func main() {
var numbers = make([]int,3,5)
printSlice(numbers)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
//len=3 cap=5 slice=[0 0 0]
1.3 切片和数组
两者定义:
Go 切片:又称动态数组,它实际是基于数组类型做的一层封装。
Go 数组:数组是内置(build-in)类型,是一组同类型数据的集合,它是值类型,通过从 0 开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数 len(array)获取其长度。
两者区别:
1. Go 中的数组是值类型,换句话说,如果你将一个数组赋值给另外一个数组,那么,实际上就是将整个数组拷贝一份。因此,在 Go 中如果将数组作为函数的参数传递的话,那效率就肯定没有传递指针高了。
2. 数组的长度也是类型的一部分,这就说明[10]int和[20]int不是同一种数据类型。并且Go 语言中数组的长度是固定的,且不同长度的数组是不同类型,这样的限制带来不少局限性。
3. 而切片则不同,切片(slice)是一个拥有相同类型元素的可变长序列,可以方便地进行扩容和传递,实际使用时比数组更加灵活,这也正是切片存在的意义。而且切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。
1.4 切片的使用
定义方式:
var a []int //nil切片,和nil相等,一般用来表示一个不存在的切片
var b = []int{}||b := []int{} //空切片,和nil不相等,一般用来表示一个空的集合
var c = []int{1, 2, 3} //有3个元素的切片,len和cap都为3
var g = make([]int, 3) //创建一个切片,len和cap均为3
var h = make([]int, 3, 6) //创建一个切片,len为3,cap为6
var i = make([]int, 0, 3) //创建一个切片,len为0,cap为3
1.5 append()和copy()函数
package main
import "fmt"
func main() {
var numbers []int
printSlice(numbers)
/* 允许追加空切片 */
numbers = append(numbers, 0)
printSlice(numbers)
/* 向切片添加一个元素 */
numbers = append(numbers, 1)
printSlice(numbers)
/* 同时添加多个元素 */
numbers = append(numbers, 2,3,4)
printSlice(numbers)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1,numbers)
printSlice(numbers1)
}
func printSlice(x []int){
fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}
/*
len=0 cap=0 slice=[]
len=1 cap=1 slice=[0]
len=2 cap=2 slice=[0 1]
len=5 cap=6 slice=[0 1 2 3 4]
len=5 cap=12 slice=[0 1 2 3 4]
*/
当 numbers = [0, 1] 时,append(numbers, 2, 3, 4) 为什么 cap 从 2 变成 6 ?
经过实践得知,append(list, [params]),先判断 list 的 cap 长度是否大于等于 len(list) + len([params]),之后在进行变化。变化规则为
若len[list]+len[params]为偶数,则cap=len[list]+len[params]
若len[list]+len[params]为奇数,则cap=len[list]+len[params]+1
,所以当 append(numbers, 2, 3, 4) cap 从 2 变成 6。
1.6 切片的使用
1)合并多个数组
package main
import "fmt"
func main() {
var arr1 = []int{1,2,3}
var arr2 = []int{4,5,6}
var arr3 = []int{7,8,9}
var s1 = append(append(arr1, arr2...), arr3...) //这三个点很重要
fmt.Printf("s1: %v\n", s1)
}
// s1: [1 2 3 4 5 6 7 8 9]
2)在做函数调用时,slice 按引用传递(slice 的底层是数组指针),array 按值传递
package main
import "fmt"
func main(){
changeSliceTest()
}
func changeSliceTest() {
arr1 := []int{1, 2, 3}
arr2 := [3]int{1, 2, 3}
arr3 := [3]int{1, 2, 3}
fmt.Println("before change arr1, ", arr1)
changeSlice(arr1) // slice 按引用传递
fmt.Println("after change arr1, ", arr1)
fmt.Println("before change arr2, ", arr2)
changeArray(arr2) // array 按值传递
fmt.Println("after change arr2, ", arr2)
fmt.Println("before change arr3, ", arr3)
changeArrayByPointer(&arr3) // 可以显式取array的 指针
fmt.Println("after change arr3, ", arr3)
}
func changeSlice(arr []int) {
arr[0] = 9999
}
func changeArray(arr [3]int) {
arr[0] = 6666
}
func changeArrayByPointer(arr *[3]int) {
arr[0] = 6666
}
/*
before change arr1, [1 2 3]
after change arr1, [9999 2 3]
before change arr2, [1 2 3]
after change arr2, [1 2 3]
before change arr3, [1 2 3]
after change arr3, [6666 2 3]
*/
二、Go语言范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
以上代码中的 key 和 value 是可以省略。
如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
如果只想读取 value,格式如下:
for _, value := range oldMap
例子:
package main
import "fmt"
func main() {
//这是我们使用 range 去求一个 slice 的和。使用数组跟这个很类似
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
//在数组上使用 range 将传入索引和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
//range 也可以用在 map 的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//range也可以用来枚举 Unicode 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c)
}
}
/*
sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111
*/
三、Go语言Map(集合)
这个就是Python的字典,好理解一点。
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
3.1 定义Map
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:
如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
例子:
package main
import "fmt"
func main() {
//两种声明方式
/* 第一种
var countryCapitalMap map[string]string //创建集合
countryCapitalMap = make(map[string]string)
*/
//第二种
countryCapitalMap := make(map[string]string)
/* map插入key - value对,各个国家对应的首都 */
countryCapitalMap [ "France" ] = "巴黎"
countryCapitalMap [ "Italy" ] = "罗马"
countryCapitalMap [ "Japan" ] = "东京"
countryCapitalMap [ "India " ] = "新德里"
/*使用键输出地图值 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [country])
}
/*查看元素在集合中是否存在 */
capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
/*fmt.Println(capital) */
/*fmt.Println(ok) */
if (ok) {
fmt.Println("American 的首都是", capital)
} else {
fmt.Println("American 的首都不存在")
}
}
/*
France 首都是 巴黎
Italy 首都是 罗马
Japan 首都是 东京
India 首都是 新德里
American 的首都不存在
*/
注意:capital, ok := countryCapitalMap [ "American" ] /*如果确定是真实的,则存在,否则不存在 */
这里的ok返回的是bool值,如果存在则为true,反之false。
3.2 Delete()函数
delete(countryCapitalMap, "France")
delete(Map,key)
例子:
package main
import "fmt"
func main() {
/* 创建map */
countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
fmt.Println("原始地图")
/* 打印地图 */
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
/*删除元素*/ delete(countryCapitalMap, "France")
fmt.Println("法国条目被删除")
fmt.Println("删除元素后地图")
/*打印地图*/
for country := range countryCapitalMap {
fmt.Println(country, "首都是", countryCapitalMap [ country ])
}
}
基于 go 实现简单 HashMap,暂未做 key 值的校验。:
package main
import (
"fmt"
)
type HashMap struct {
key string
value string
hashCode int
next *HashMap
}
var table [16](*HashMap)
func initTable() {
for i := range table{
table[i] = &HashMap{"","",i,nil}
}
}
func getInstance() [16](*HashMap){
if(table[0] == nil){
initTable()
}
return table
}
func genHashCode(k string) int{
if len(k) == 0{
return 0
}
var hashCode int = 0
var lastIndex int = len(k) - 1
for i := range k {
if i == lastIndex {
hashCode += int(k[i])
break
}
hashCode += (hashCode + int(k[i]))*31
}
return hashCode
}
func indexTable(hashCode int) int{
return hashCode%16
}
func indexNode(hashCode int) int {
return hashCode>>4
}
func put(k string, v string) string {
var hashCode = genHashCode(k)
var thisNode = HashMap{k,v,hashCode,nil}
var tableIndex = indexTable(hashCode)
var nodeIndex = indexNode(hashCode)
var headPtr [16](*HashMap) = getInstance()
var headNode = headPtr[tableIndex]
if (*headNode).key == "" {
*headNode = thisNode
return ""
}
var lastNode *HashMap = headNode
var nextNode *HashMap = (*headNode).next
for nextNode != nil && (indexNode((*nextNode).hashCode) < nodeIndex){
lastNode = nextNode
nextNode = (*nextNode).next
}
if (*lastNode).hashCode == thisNode.hashCode {
var oldValue string = lastNode.value
lastNode.value = thisNode.value
return oldValue
}
if lastNode.hashCode < thisNode.hashCode {
lastNode.next = &thisNode
}
if nextNode != nil {
thisNode.next = nextNode
}
return ""
}
func get(k string) string {
var hashCode = genHashCode(k)
var tableIndex = indexTable(hashCode)
var headPtr [16](*HashMap) = getInstance()
var node *HashMap = headPtr[tableIndex]
if (*node).key == k{
return (*node).value
}
for (*node).next != nil {
if k == (*node).key {
return (*node).value
}
node = (*node).next
}
return ""
}
//examples
func main() {
getInstance()
put("a","a_put")
put("b","b_put")
fmt.Println(get("a"))
fmt.Println(get("b"))
put("p","p_put")
fmt.Println(get("p"))
}
/*
a_put
b_put
p_put
*/
四、Go语言递归函数
递归,就是在运行的过程中调用自己。
五、Go语言类型转换
类型转换用于将一种数据类型的变量转换为另外一种类型的变量。
go 不支持隐式转换类型。
例子:
package main
import "fmt"
func main() {
var a int64 = 3
var b int32
b = a
fmt.Printf("b 为 : %d", b)
}
此时会报错
cannot use a (type int64) as type int32 in assignment
cannot use b (type int32) as type string in argument to fmt.Printf
但是如果改成 b = int32(a) 就不会报错了:
package main
import "fmt"
func main() {
var a int64 = 3
var b int32
b = int32(a)
fmt.Printf("b 为 : %d", b)
}
六、Go语言接口
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
interface本质就是一个指针。
package main
import "fmt"
type Reader interface { // interface本质就是一个指针
ReadBook()
}
type Writer interface {
WriteBook()
}
type Book struct{
}
func (t *Book) ReadBook() {
fmt.Println("read a book")
}
func (t *Book) WriteBook() {
fmt.Println("write a book")
}
func main() {
// b : pair<type:Book, value:book{}地址>
b := &Book{}
// r: pair<type, value>
var r Reader
// r : pair<type:Book, value:book{}地址> pair是不变的
r = b // interface r 本质就是一个指针,所以b也要是指针类型
r.ReadBook()
r.WriteBook()
var w Writer
// w : pair<type:Book, value:book{}地址> pair是不变的
w = r.(Writer) // w和r的type一致,所以断言成功(断言就是强转?)
w.WriteBook()
}
实现办法:
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
例子:
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
/*
I am Nokia, I can call you!
I am iPhone, I can call you!
*/
例子2(便于理解):
package main
import (
"fmt"
)
type Phone interface {
call() string
}
type Android struct {
brand string
}
type IPhone struct {
version string
}
func (android Android) call() string {
return "I am Android " + android.brand
}
func (iPhone IPhone) call() string {
return "I am iPhone " + iPhone.version
}
func printCall(p Phone) {
fmt.Println(p.call() + ", I can call you!")
}
func main() {
var vivo = Android{brand:"Vivo"}
var hw = Android{"HuaWei"}
i7 := IPhone{"7 Plus"}
ix := IPhone{"X"}
printCall(vivo)
printCall(hw)
printCall(i7)
printCall(ix)
}
/*
I am Android Vivo, I can call you!
I am Android HuaWei, I can call you!
I am iPhone 7 Plus, I can call you!
I am iPhone X, I can call you!
*/
如果想要通过接口方法修改属性,需要在传入指针的结构体才行,具体代码入下的 [1][2] 处:
type fruit interface{
getName() string
setName(name string)
}
type apple struct{
name string
}
//[1]
func (a *apple) getName() string{
return a.name
}
//[2]
func (a *apple) setName(name string) {
a.name = name
}
func testInterface(){
a:=apple{"红富士"}
fmt.Print(a.getName())
a.setName("树顶红")
fmt.Print(a.getName())
}
七、错误处理
GO语言中err接口及defer延迟异常处理分析_Golang_脚本之家 (jb51.net)
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
例子:
package main
import (
"fmt"
)
// 定义一个 DivideError 结构
type DivideError struct {
dividee int
divider int
}
// 实现 `error` 接口
func (de *DivideError) Error() string {
strFormat := `
Cannot proceed, the divider is zero.
dividee: %d
divider: 0
`
return fmt.Sprintf(strFormat, de.dividee)
}
// 定义 `int` 类型除法运算的函数
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
if varDivider == 0 {
dData := DivideError{
dividee: varDividee,
divider: varDivider,
}
errorMsg = dData.Error()
return
} else {
return varDividee / varDivider, ""
}
}
func main() {
// 正常情况
if result, errorMsg := Divide(100, 10); errorMsg == "" {
fmt.Println("100/10 = ", result)
}
// 当除数为零的时候会返回错误信息
if _, errorMsg := Divide(100, 0); errorMsg != "" {
fmt.Println("errorMsg is: ", errorMsg)
}
}
/*
100/10 = 10
errorMsg is:
Cannot proceed, the divider is zero.
dividee: 100
divider: 0
*/
注意:
1.介绍一下 panic 与 recover,一个用于主动抛出错误,一个用于捕获panic抛出的错误。
概念:panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。
- 引发
panic
有两种情况,一是程序主动调用,二是程序产生运行时错误,由运行时检测并退出。 - 发生
panic
后,程序会从调用panic
的函数位置或发生panic
的地方立即返回,逐层向上执行函数的defer
语句,然后逐层打印函数调用堆栈,直到被recover
捕获或运行到最外层函数。 panic
不但可以在函数正常流程中抛出,在defer
逻辑里也可以再次调用panic
或抛出panic
。defer
里面的panic
能够被后续执行的defer
捕获。recover
用来捕获panic
,阻止panic
继续向上传递。recover()
和defer
一起使用,但是defer
只有在后面的函数体内直接被掉用才能捕获panic
来终止异常,否则返回nil
,异常继续向外传递。
八、并发
Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
goroutine 语法格式:
go 函数名( 参数列表 )
例子:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
/*
执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:
world
hello
hello
world
world
hello
hello
world
world
hello
*/
九、通道(channel)
9.1 通道定义
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
例子:通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
//-5 17 12
9.2 通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
例子:
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
/*
1
2
*/
9.3 Go 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。(收到数据ok则为true)格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
9.4 补充点
1.goroutine 是 golang 中在语言级别实现的轻量级线程,仅仅利用 go 就能立刻起一个新线程。多线程会引入线程之间的同步问题,在 golang 中可以使用 channel 作为同步的工具。通过 channel 可以实现两个 goroutine 之间的通信。创建一个 channel, make(chan TYPE {, NUM}) TYPE 指的是 channel 中传输的数据类型,第二个参数是可选的,指的是 channel 的容量大小。向 channel 传入数据, CHAN <- DATA , CHAN 指的是目的 channel 即收集数据的一方, DATA 则是要传的数据。从 channel 读取数据, DATA := <-CHAN ,和向 channel 传入数据相反,在数据输送箭头的右侧的是 channel,形象地展现了数据从隧道流出到变量里。
2.关闭通道并不会丢失里面的数据,只是让读取通道数据的时候不会读完之后一直阻塞等待新数据写入
3.Channel 是可以控制读写权限的 具体如下:
go func(c chan int) { //读写均可的channel c } (a)
go func(c <- chan int) { //只读的Channel } (a)
go func(c chan <- int) { //只写的Channel } (a)
4.演示带缓冲区的 channel 实现异步存取。
package main
import (
"fmt"
"time"
)
func put(c chan int) {
for i := 0; i < 10; i++ {
c <- i
time.Sleep(100 * time.Millisecond)
fmt.Println("->放入", i)
}
fmt.Println("=所有的都放进去了!关闭缓冲区,但是里面的数据不会丢失,还能取出。")
close(c)
}
func main() {
ch := make(chan int, 5)
go put(ch)
for {
time.Sleep(1000 * time.Millisecond)
data, ok := <-ch
if ok == true {
fmt.Println("<-取出", data)
} else {
break
}
}
}
/*
->放入 0
->放入 1
->放入 2
->放入 3
->放入 4
<-取出 0
->放入 5
<-取出 1
->放入 6
<-取出 2
->放入 7
<-取出 3
->放入 8
<-取出 4
->放入 9
=所有的都放进去了!关闭缓冲区,但是里面的数据不会丢失,还能取出。
<-取出 5
<-取出 6
<-取出 7
<-取出 8
<-取出 9
*/