目录
数据类型
/*数据类型*/
func mDataType() {
//数据类型
//数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存
//布尔型 var b bool = true
//数字类型 [u]int16 float32 float64 并且支持复数
//字符串类型 UTF-8
//派生类型: 指针类型(Pointer) 数组类型 结构化类型(struct) Channel 类型 函数类型 切片类型 接口类型(interface) Map 类型
//整数类型有无符号和带符号两种
//Go同时支持int和uint
//rune, int8, int16, int32, int64和byte, uint8, uint16, uint32,uint64
//其中rune是int32的别称,byte是uint8的别称。尽管int的长度是32 bit, 但int 与 int32并不可以互用
//这些类型的变量之间不允许互相赋值或操作,不然会在编译时引起编译器报错。
//var a int8
//var b int32
//c:=a + b // invalid operation: a + b (mismatched types int8 and int32)
}
变量
/*变量*/
func mVariable() {
//1. 变量 var identifier1, identifier2 type
//var vname1, vname2, vname3 type= v1, v2, v3
//2. 省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误 只能在函数体中出现
//vname1, vname2, vname3 := v1, v2, v3
//3. 这种因式分解关键字的写法一般用于声明全局变量
//var (
// vname1 v_type1
// vname2 v_type2
//)
//4. 单纯地给 var 赋值也是不够的,这个值必须被使用 否则编译错误
//5. 定义变量并初始化值
//var variableName type = value
//6.Go对于已声明但未使用的变量会在编译阶段报错
//7. 特殊的变量名 _(下划线) 任何赋予它的值都会被丢弃
fmt.Printf("\n变量\n")
//声明多个变量
var name1, name2 int = 10, 20
fmt.Println(name1, name2)
//指定变量类型,如果没有初始化,则变量默认为零值
var name3 int
var name4 bool
var name5 string = "dd"
fmt.Println(name3, name4, name5) //将信息显示在命令窗口中,输出光标换行定位在下一行开头
//忽略类型声明
var vname3, vname4, vname5 = "v1", "v2", 3
fmt.Println(vname3, vname4, vname5)
//继续简化
//简短声明 它只能用在函数内部
vname1, vname2, vname0 := "v1", "v2", 3
fmt.Println(vname1, vname2, vname0)
//特殊的变量名 _(下划线)
//任何赋予它的值都会被丢弃
_, b := 34, 35
fmt.Println(b)
}
常量
/*常量*/
func mConst() {
//程序编译阶段就确定下来的值,而程序在运行时无法改变该值
//不会被修改的量
//只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型
//const identifier [type] = value
//const c_name1, c_name2 = value1, value2
fmt.Printf("\n常量\n")
const LENGTH int = 10
const a, b = 1, "hello"
const (
d = "abc"
e = len(d)
f = unsafe.Sizeof(d)
)
fmt.Println(LENGTH, a, b, d, e, f)
//10 1 hello abc 3 16
}
运算符
/*运算符*/
func mOperator() {
//算术运算符 + - * / % ++ --
fmt.Printf("\n算术运算符 + - * / %% ++ --\n")
var a, b int = 21, 10
//将信息进行格式化显示在bai命令窗口中,输出光标定位在最后一个字符之后。
fmt.Printf("a=%d b=%d\n", a, b)
fmt.Printf("a+b=%d\n", a+b)
fmt.Printf("a-b=%d\n", a-b)
fmt.Printf("a*b=%d\n", a*b)
fmt.Printf("a/b=%d 相除\n", a/b)
fmt.Printf("a%%b=%d 求余\n", a%b)
a++
fmt.Printf("a++=%d\n", a)
a--
fmt.Printf("a--=%d\n", a)
//a=21 b=10
//a+b=31
//a-b=11
//a*b=210
//a/b=2 相除%d
//a%b=1 求余
//a++=22
//a--=21
//关系运算符 == != > < >= <=
fmt.Printf("\n关系运算符 == != > < >= <=\n")
a = 21
b = 10
fmt.Printf("a=%d b=%d\n", a, b)
fmt.Printf("a == b %s\n", mBoolString(a == b))
fmt.Printf("a != b %s\n", mBoolString(a != b))
fmt.Printf("a > b %s\n", mBoolString(a > b))
fmt.Printf("a < b %s\n", mBoolString(a < b))
fmt.Printf("a >= b %s\n", mBoolString(a >= b))
fmt.Printf("a <= b %s\n", mBoolString(a <= b))
//a=21 b=10
//a == b false
//a != b true
//a > b true
//a < b false
//a >= b true
//a <= b false
//逻辑运算符 && || !
fmt.Printf("\n逻辑运算符 && || !\n")
var c = true
var d = false
fmt.Printf("c=%s d=%s\n", mBoolString(c), mBoolString(d))
fmt.Printf("c && d %s\n", mBoolString(c && d))
fmt.Printf("c || d %s\n", mBoolString(c || d))
fmt.Printf("!c %s\n", mBoolString(!c))
//c=true d=false
//c && d false
//c || d true
//!c false
//var c2 = 1
//var d2 = 0
//fmt.Printf("!c2 %s\n", mBoolString(!c2)) //invalid operation: ! int
//operator && not defined on int
//if c2 && d2 {
// fmt.Printf("c2 && d2 %s\n", "true")
//} else {
// fmt.Printf("c2 && d2 %s\n", "false")
//}
fmt.Printf("\n位运算符 & | ^ << >>\n")
fmt.Printf("\n赋值运算符 = += -= /= %%= <<= >>= &= ^= |=\n")
fmt.Printf("\n其他运算符 &a返回变量存储地址 *a指针变量\n")
}
/*布尔字符串*/
func mBoolString(bData bool) string {
var result string = "false"
if bData {
result = "true"
}
return result
}
条件语句
/*条件语句*/
func mCondition() {
fmt.Printf("\n条件语句\n")
//if...else 布尔表达式 条件必须是bool值
fmt.Printf("if\n")
var a int = 10
if a < 20 {
fmt.Printf("a=%d a小于20\n", a)
}
//switch语句 默认情况下 case 最后自带 break 语句 匹配项后面不需要再加break 如果我们需要执行后面的 case,可以使用 fallthrough
fmt.Printf("switch语句\n")
//select语句
//select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
//select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
fmt.Printf("select语句\n")
var grade string = "B"
var marks int = 90
//marks 相同的类型;或者最终结果为相同类型的表达式
switch marks {
case 90:
grade = "A"
case 80:
grade = "B"
case 50, 60, 70:
grade = "C"
default:
grade = "D"
}
fmt.Printf("grade=%s\n", grade)
switch {
case grade == "A":
fmt.Printf("优秀!\n")
case grade == "B", grade == "C":
fmt.Printf("良好\n")
case grade == "D":
fmt.Printf("及格\n")
case grade == "F":
fmt.Printf("不及格\n")
default:
fmt.Printf("差\n")
}
//使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true:
fmt.Println("2、case 条件语句为 true")
fallthrough
case false:
fmt.Println("3、case 条件语句为 false")
fallthrough
case true:
fmt.Println("4、case 条件语句为 true")
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
循环语句
/*循环语句*/
func mLoop() {
/*****************************************/
fmt.Printf("\nfor语句\n")
sum := 10
for i := 0; i < 10; i++ {
sum++
if sum > 15 {
/* 使用 break 语句跳出循环 */
break;
//continue;
//continue re
}
}
fmt.Println(sum)
//不使用标记
sum = 10
var sum2 = 10
for i := 0; i < 10; i++ {
sum++
for ii := 0; ii < 10; ii++ {
if sum2 > 15 {
break;
}
sum2++
}
}
fmt.Println(sum)
fmt.Println(sum2)
//使用标记
sum = 10
sum2 = 10
ree:
for i := 0; i < 10; i++ {
sum++
for ii := 0; ii < 10; ii++ {
if sum2 > 15 {
break ree;
}
sum2++
}
}
fmt.Println(sum)
fmt.Println(sum2)
//For-each range 循环
//这种格式的循环可以对字符串、数组、切片等进行迭代输出元素
fmt.Printf("\nFor-each range 循环\n")
strings := []string{"google", "runoob"}
numbers := [6]int{1, 2, 3, 5}
for i, s := range strings {
fmt.Println(i, s)
}
for i, s := range numbers {
fmt.Println(i, s)
}
//break 语句 经常用于中断当前 for 循环或跳出 switch 语句
//continue 语句 跳过当前循环的剩余语句,然后继续进行下一轮循环。
//goto 语句 将控制转移到被标记的语句。
//for true {} for {}无线循环
}
函数
/*函数*/
func mFunc() {
fmt.Printf("\n函数\n")
//函数返回多个值 return y, x a, b := swap()
//函数参数 值传递 引用传递
//值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
//引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
//函数参数 没有默认值 追求显式的表达,避免隐含
var a int = 100
var b int = 200
swap := func(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
swap(&a, &b)
fmt.Println(a, b)
//函数用法
//函数作为另外一个函数的实参 函数定义后可作为另外一个函数的实参数传入 swap
//闭包 闭包是匿名函数,可在动态编程中使用
//方法 方法就是一个包含了接受者的函数
}
数组
/*数组*/
func mArray() {
fmt.Printf("\n数组\n")
//var variable_name [SIZE] variable_type
//var balance [10] float32
//初始化数组
//var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
//如果数组长度不确定,可以使用 ... 代替数组的长度
//初始化数组中 {} 中的元素个数不能大于 [] 中的数字
//如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小
var balance = [6]int{1, 2, 3}
balance[5] = 10
fmt.Println(balance[0], balance[5])
//指定下标来初始化元素
balances := [5]float32{1:2.0, 3:7.0}
fmt.Println(balances[1], balances[0])
fmt.Printf("\n多维数组\n")
//var threedim [5][10][4]int
//void myFunction(param [10]int)
threedim := [5][10][4]int{
{
{1, 2, 3, 4},
{11, 21, 31, 41},
{111, 211, 311, 411},
},
{
{91, 92, 93, 94},
{911, 921, 931, 941},
{9111, 9211, 9311, 9411},
}, //,必须
}
fmt.Println(threedim[0][2][2], threedim[1][2][2], threedim[2][2][2])
}
指针
/*指针*/
func mPointer() {
fmt.Printf("\n指针 默认nil\n")
pname := 10
fmt.Println(pname, &pname)
//var var_name *var-type * 号用于指定变量是作为一个指针
//*var_name 使用指针访问值
//一个指针变量指向了一个值的内存地址。
var ip *int //默认 空指针nil if(ptr == nil)
ip = &pname
fmt.Println(&pname, ip, *ip, *&pname)
//指针数组
//var ptr [10]*int
//ptr[i] = &a[i]
//*ptr[i]
//指向指针的指针
//如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量
//第一个指针存放第二个指针的地址,第二个指针存放变量的地址
//var pptr **int;
//**pptr
pptr := &ip
fmt.Println(pptr, *pptr, **pptr)
//指针作为函数参数
//swap(&a, &b)
//func swap(x *int, y *int) {}
}
结构体
/*结构体*/
func mStruct() {
fmt.Printf("\n结构体\n")
//数组可以存储同一类型的数据
//结构体中我们可以为不同项定义不同的数据类型
//结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
//结构体定义需要使用 type 和 struct 语句。
//struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。
//struct就相当于class
//type struct_variable_type struct {}
//变量的声明
//variable_name := structure_variable_type {value1, value2...valuen}
//variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
type Books struct {
title string
author string
subject string
book_id int
nums int
arrs [3]int
ptr *int
}
//Books{"title", "author", "subject", 12}
//Books{title: "title", author: "author", subject: "subject", book_id: 12}
fmt.Println(Books{title: "title", book_id: 12}) //{title 12 0 [0 0 0] <nil>} 忽略的字段为 0 或 空
var Book1 Books
Book1.author = "Book1.author"
Book1.book_id = 13
fmt.Println(Book1.title, Book1.author, Book1.subject, Book1.book_id) // Book1.author 13
//结构体指针
var ptr *Books
ptr = &Book1
fmt.Println(ptr.author) //问结构体成员,使用 "." 操作符
fmt.Println(ptr) //&{ Book1.author 13 0 [0 0 0] <nil>}
Book11 := func(books *Books) {
fmt.Println(books.author)
}
Book11(ptr)
booksDefault := Books{title: "title11", author: "author11", subject: "subject11", book_id: 1211}
fmt.Println(booksDefault.author)
booksDefault.author += "rr"
fmt.Println(booksDefault.author)
}
切片(Slice) 动态数组
/*切片(Slice) 动态数组*/
func mSlice() {
fmt.Printf("\n切片(Slice nil) 动态数组\n")
//切片是对数组的抽象
//Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),
//与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
//定义切片
//var identifier []type 未指定大小的数组来定义切片 默认nil var slice20 []int
//var slice1 []type = make([]type, len) //len 是数组的长度并且也是切片的初始长度
//slice1 := make([]type, len)
//make([]T, length, capacity) // 也可以指定容量,其中capacity为可选参数 切片最长可以达到多少
arr := [5]int{10, 11, 12, 13, 14}
slice1 := []int{1, 2, 3} //[]表示是切片类型.其cap=len=3
slice2 := arr[:] //数组arr的引用
slice3 := arr[1:2] //arr[startIndex:endIndex] startIndex到endIndex-1
slice4 := arr[1:] //arr[startIndex:] 一直到arr的最后一个元素
slice5 := arr[:2]
fmt.Println(slice1, slice2, slice3, slice4, slice5) //[1 2 3] [10 11 12 13 14] [11] [11 12 13 14] [10 11]
//len() 和 cap() 函数
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice5), cap(slice5), slice5) //len=2 cap=5 slice=[10 11]
slice10 := make([]int, 2, 5)
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice10), cap(slice10), slice10)
//append() 和 copy() 函数
//如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
var slice20 []int
slice20 = append(slice20, 111)
slice20 = append(slice20, 112, 113, 114)
slice21 := make([]int, len(slice20), (cap(slice20))*2)
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice20), cap(slice20), slice20) //len=4 cap=4 slice=[111 112 113 114]
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice21), cap(slice21), slice21)
copy(slice21, slice20)
fmt.Printf("len=%d cap=%d slice=%v\n", len(slice21), cap(slice21), slice21)
}
范围
/*范围(Range)*/
func mRange() {
fmt.Printf("\n范围(Range)\n")
// range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
//在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对
num := [5]int{1, 3, 5, 7, 9}
for _, v := range num { //使用空白符"_"省略
fmt.Println(v)
}
for k := range num {
fmt.Println(k)
}
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//枚举Unicode字符串 第一个参数是字符的索引,第二个是字符(Unicode的值)本身
for i, c := range "go" {
fmt.Println(i, c)
}
}
Map(集合 nil)
/*Map(集合 nil)*/
func mMap() {
fmt.Printf("\nMap(集合 nil)\n")
//索引数组
//Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
//Map 是无序的 Map 是使用 hash 表来实现的
//var map_variable map[key_data_type]value_data_type 默认 map 是 nil
//map_variable := make(map[key_data_type]value_data_type)
//如果不初始化 map,那么就会创建一个 nil map。
//nil map 不能用来存放键值对
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
//1
var kvs2 map[string]string
kvs2 = make(map[string]string)
kvs2["str1"] = "str11"
capital, kvs2v1 := kvs2["str1"] //查看元素在集合中是否存在 kvs2v1布尔值
fmt.Println(capital, mBoolString(kvs2v1)) //str11 true
capital2, kvs2v2 := kvs2["str2"]
fmt.Println(capital2, mBoolString(kvs2v2)) // false
//2
kvs3 := make(map[string]string)
kvs3["str1"] = "str11"
kvs3["str2"] = "str12"
//delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key
delete(kvs3, "str2")
fmt.Println(kvs3) //map[str1:str11]
}
类型转换
/*类型转换*/
func mTypeChange() {
fmt.Printf("\n类型转换\n")
//类型转换用于将一种数据类型的变量转换为另外一种类型的变量
//type_name(expression) type_name 为类型,expression 为表达式。
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum) / float32(count)
fmt.Println(mean, float32(sum), float32(count), sum/count) //3.4 17 5 3
}
接口
package main
import (
"fmt"
)
/* 定义接口 */
type Phone interface {
//method_name1 [return_type]
//method_name2 [return_type]
call() (int, string)
call2()
Error() string
Error2(str string) string
}
/* 定义结构体 */
type NokiaPhone struct {
/* variables */
num1 int
}
/* 实现接口方法 */
func (nokiaPhone NokiaPhone) call()(num1 int, err string){
num1 = nokiaPhone.num1
err = nokiaPhone.Error2("I am Nokia, I can call you!")
return
}
func (nokiaPhone NokiaPhone) call2() {
fmt.Println("I am Nokia, I can call2 you!")
}
func (nokiaPhone NokiaPhone) Error() string {
return fmt.Sprintf("I am Nokia, error! %d", nokiaPhone.num1)
}
func (nokiaPhone NokiaPhone) Error2(str string) string {
return fmt.Sprintf("I am Nokia, error! %d %s", nokiaPhone.num1, str)
}
type IPhone struct {
num1 int
}
func (iPhone IPhone) call() (int, string) {
return iPhone.num1, fmt.Sprintf("I am iPhone, I can call you!")
}
func (iPhone IPhone) call2() {
fmt.Println("I am iPhone, I can call2 you!")
}
func (iPhone IPhone) Error() string {
return fmt.Sprintf("I am Nokia, error! %d", iPhone.num1)
}
func (iPhone IPhone) Error2(str string) string {
return fmt.Sprintf("I am Nokia, error! %d %s", iPhone.num1, str)
}
func main() {
var phone Phone
phone = new(NokiaPhone)
num1, err := phone.call()
fmt.Println(num1, err)
phone.call2()
//phone.errors() //phone.errors undefined
phone = new(IPhone)
phone.call()
phone.call2()
var nokiaPhone NokiaPhone = NokiaPhone{101}
fmt.Println(nokiaPhone.num1)
fmt.Println(nokiaPhone.Error())
fmt.Println(nokiaPhone.Error2("fdf"))
iPhone := IPhone{103}
fmt.Println(iPhone.num1)
}
goroutine和通道
var wg sync.WaitGroup //sync.WaitGroup来实现并发任务的同步
/*并发*/
func mRoutine() {
fmt.Printf("\n并发\n")
//并发,我们只需要通过 go 关键字来开启 goroutine 即可
//goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的
//go 函数名( 参数列表 )
//Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。
//同一个程序中的所有 goroutine 共享同一个地址空间
fun1 := func(s string) string {
defer wg.Done() //计数器-1
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println("fun1 " + s + " " + strconv.Itoa(i))
}
return "fun1 end " + s
}
//Go1.5版本之前,默认使用的是单核心执行。Go1.5版本之后,默认使用全部的CPU逻辑核心数。
//runtime.GOMAXPROCS(2) 将任务分配到不同的CPU逻辑核心上实现并行的效果
//核心是就是最大并行数
//并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。
//并行:同一时刻执行多个任务(你和你朋友都在用微信和女朋友聊天)。
//创建新的goroutine的时候需要花费一些时间,而此时mian函数所在的goroutine是继续执行的
wg.Add(1) //计数器+delta
go fun1("test12") //test11先执行
//fmt.Println(fun1("test11")) //panic: sync: negative WaitGroup counter 负于计数器
//mRoutineSay("world00")
wg.Add(1)
go mRoutineSay("world11")
wg.Add(1)
go mRoutineSay("world22")
/*
在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。
当main()函数返回的时候该goroutine就结束了,
所有在main()函数中启动的goroutine会一同结束,
所以我们要想办法让main函数等一等其他goroutine函数,
最简单粗暴的方式就是Sleep
但是不友好,不适用 -> sync.WaitGroup add done wait
*/
//time.Sleep(time.Second) //等待所有goroutine执行完
wg.Wait() //sync.WaitGroup是一个结构体,传递的时候要传递指针
//通道(channel)
//通道(channel)是用来传递数据的一个数据结构。
//通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
//操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
//ch <- v // 把 v 发送到通道 ch
//v := <-ch // 从 ch 接收数据 并把值赋给 v
//ch := make(chan int) 声明
//默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
//通道缓冲区
//Go 遍历通道与关闭通道
}
func mRoutineSay(s string) {
defer wg.Done()
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
/*通道(channel)*/
func mChannel() {
fmt.Printf("\n通道(channel)\n")
fun1 := func(s []int, c chan int) {
//defer wg.Done() //计数器-1
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c, 通道不带缓冲,阻塞等待直到接收参数,通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内
}
//通道(channel)
//通道(channel)是用来传递数据的一个数据结构。
//通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
//操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
//ch <- v // 把 v 发送到通道 ch
//v := <-ch // 从 ch 接收数据 并把值赋给 v
//ch := make(chan int) 声明
//默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
s1 := []int{1, 2, 3}
s2 := []int{7, 8, 9}
c := make(chan int)
go fun1(s1, c)
go fun1(s2, c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y) //返回无序
//通道缓冲区
//通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小
//ch := make(chan int, 100)
//带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,
//可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
//不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
//如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;
//如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
//带缓冲通道 缓冲区大小为2
ch := make(chan int, 2)
ch <- 1
ch <- 2
// 获取这两个数据 1 2
fmt.Println(<-ch)
fmt.Println(<-ch)
go fun1(s1, ch)
go fun1(s2, ch)
x1, y1 := <-ch, <-ch // 从通道 ch 中接收, 接收方在有值可以接收之前会一直阻塞
fmt.Println(x1, y1, x1+y1) //返回无序
ch <- 11
ch <- 22
//ch <- 3 //all goroutines are asleep - deadlock!
//所有的 协程(goroutines)都处于休眠(阻塞)状态
//当所有协程都处于阻塞状态的时候,那所有的协程都等不来解锁的那一天了,出现死锁,所以golang调度直接把这个给kill掉了。
close(ch)
//Go 遍历通道与关闭通道 v, ok := <-ch
if value, ok := <-ch; ok == true {
//如果写端没有写数据,也没有关闭。<-ch; 会阻塞 ---【重点】
//如果写端写数据, value 保存 <-ch 读到的数据。 ok 被设置为 true
//如果写端关闭。 value 为数据类型默认值。ok 被设置为 false
fmt.Println(value, ok)
}
//for data:=range ch{
// fmt.Print(data,"\t")
// 输出后会一直阻塞当前协程,如果在其他协程中调用了close(ch),那么就会跳出for range循环。这也就是for range的特别之处
//}
//close(ch) //这里关闭会阻塞
//go fun1(s1, ch) 在fun1关闭,关闭后for不再阻塞
}