环境搭建
-
下载源码包
-
配置环境变量
-
检查安装情况
go version // 或者 go --help
Go语言分析
- 优点:容易部署、静态语言、支持并发
- 缺点:包管理、不支持泛型、异常处理
- 使用场景:云计算、微服务、区块连
初识go
package main // 程序的包名
/*
导包
import "fmt"
import "time"
*/
import (
"fmt"
"time"
)
func main() {
fmt.Print("Hello ")
time.Sleep(1 * time.Second)
fmt.Print("Go!")
}
变量的声明方式
- 变量声明
- 第一种:只声明,不赋值,默认值为0
- 第二种:声明并赋值
- 第三种:初始化时省去数据类型,通过自动匹配当前变量的数据类型
- 第四种(最常用):省去var关键字和数据类型
- 声明全局变量:除了第四种
- 多变量声明
- 单行写法
- 多行写法
package main
import (
"fmt"
)
/*
四种变量声明方式
*/
// 声明全局变量(方法4不能用来声明全局变量)
var g01 int
var g02 string = "tongtianzi"
func main() {
// 第一种:只声明,不赋值,默认值为0
var a int
fmt.Println("a = ", a)
fmt.Printf("a type is: %T\n", a)
// 第二种:声明并赋值
var b int = 100
fmt.Println("b = ", b)
fmt.Printf("b type is: %T\n", b)
// 第三种:初始化时省去数据类型,通过自动匹配当前变量的数据类型
var c = "haha"
fmt.Println("c = ", c)
fmt.Printf("c type is: %T\n", c)
// 第四种(最常用):省去var关键字和数据类型
d := 200
fmt.Println("d = ", d)
fmt.Printf("d type is: %T\n", d)
// 访问全局变量
fmt.Println("g01 = ", g01, "g02 = ", g02)
// 声明多个变量
var e, f int = 101, 102
fmt.Println("e = ", e)
fmt.Println("f = ", f)
var g, h = 201, 202
fmt.Println("g = ", g, "h = ", h)
var (
j int = 1001
k string = "TongTianZi"
)
fmt.Println("j = ", j)
fmt.Println("k = ", k)
}
const和iota
-
const定义常量
// const定义常量 const height int = 100
-
iota和const定义枚举
// const来定义枚举 const ( // 在const()中通过关键字iota来实现自增枚举,每行iota加1,iota默认为0 a = iota // iota = 0 b // iota = 1 ) const ( c, d = iota + 1, iota + 2 // iota = 0, c = 1, d = 2 e, f // iota = 1, e = 2, f = 3 g, h // iota = 2, g = 3, h = 4 j, k = 10 * iota, 20 * iota // iota = 3, j = 30, k = 60 l, m // iota = 4, l = 40, m = 80 )
函数的多返回值
-
一个返回值
func f01(name string, age int) int { // 一个返回值 fmt.Println("name = ", name) fmt.Println("age = ", age) return 100 }
-
多返回值,匿名
func f02(a string, b int) (int, int) { // 多个匿名返回值 fmt.Println("a = ", a) fmt.Println("b = ", b) return 10, 20 }
-
多返回值,有名称
func f03(a int, b int) (ret01 int, ret02 int) { // 多个有名返回值 fmt.Println("a = ", a) fmt.Println("b = ", b) // ret01, ret02相当于f03的形参,默认值为0 // 作用域为f03{}中的空间 ret01 = 10 ret02 = 20 //return return ret01, ret02 }
import导包和init函数
init函数
- import导包的时候会先执行该包中的init()函数
- 包中函数名首字母小写表示该接口只有在该包中能使用
- 包中函数名首字母大写表示该接口可以对外提供服务
import导包
-
import _ “fmt”:给包取别名,匿名。
go中有严格的导包规定,导包之后不适用包中的内容,将会报错!
import _ "fmt"相当于给包取了一个别名,匿名,无法使用保重的内容,但会执行保重的init函数;
-
import aa “fmt”:给包取别名,aa,就可以通过aa.Println()调用包中的接口;
-
import . “fmt”:把fmt包中的内容全部导入到当前包中,可以直接使用包中的api,Println(),不需要通过fmt.Println()来调用,但不安全,容易引起歧义,不建议这样用。
指针
代码示例
package main
import "fmt"
func swap(pa *int, pb *int){ // 表示pa和pb都是指针类型
temp := *pa // 用temp保存pa的地址值
*pa = *pb // pa的地址值等于pb的地址值
*pb = temp // pb的地址值等于pa的地址值
}
func main() {
var a = 10
var b = 20
// 交换a , b的值
swap(&a, &b) // 传递的是a和b的地址值
fmt.Println("a = ", a, "b = ", b)
}
defer
-
defer作用:在函数执行的过程中遇到defer + 表达式,先进栈,当函数中所有逻辑执行完,在函数结束出栈一次执行。
代码示例
package main import "fmt" func test01_defer() { fmt.Println("test01_defer ...") } func test02_defer() { fmt.Println("test02_defer ...") } func main() { defer test01_defer() // test01_defer进栈 defer test02_defer() // test01_defer进栈 fmt.Println("main ...") // 在函数结束前出栈 } // 结果 main ... test02_defer ... test01_defer ...
-
defer在return后执行(defer在函数所有代码执行完,结束前执行,return仍然是函数中的一部分)
代码示例
package main import "fmt" func returnFunc() int { fmt.Println("return 执行了...") return 0 } func deferFunc() { fmt.Println("defer 执行了...") } func return_defer() int { defer deferFunc() return returnFunc() } func main() { return_defer() } // 执行结果 return 执行了... defer 执行了...
defer
-
defer作用:在函数执行的过程中遇到defer + 表达式,先进栈,当函数中所有逻辑执行完,在函数结束出栈一次执行。
代码示例
package main import "fmt" func test01_defer() { fmt.Println("test01_defer ...") } func test02_defer() { fmt.Println("test02_defer ...") } func main() { defer test01_defer() // test01_defer进栈 defer test02_defer() // test01_defer进栈 fmt.Println("main ...") // 在函数结束前出栈 } // 结果 main ... test02_defer ... test01_defer ...
-
defer在return后执行(defer在函数所有代码执行完,结束前执行,return仍然是函数中的一部分)
代码示例
package main import "fmt" func returnFunc() int { fmt.Println("return 执行了...") return 0 } func deferFunc() { fmt.Println("defer 执行了...") } func return_defer() int { defer deferFunc() return returnFunc() } func main() { return_defer() } // 执行结果 return 执行了... defer 执行了...
array(数组)
静态数组
-
静态数组就是长度固定的数组;
-
代码示例
package main import ( "fmt" ) func array(){ // 定义一个长度为10的静态数组,默认值为0 var array01 [10]int for i:=0; i < len(array01); i++ { fmt.Println(array01[i]) } fmt.Println("*****************") // 定义一个静态数组 array02 := [10]int{1, 2, 3} for idx, val := range array02 { fmt.Println(idx, val) } fmt.Println("+++++++++++++++++++") } func printArray(array [10]int){ array[0] = 110 // 静态数组作为参数,为值传递,且参数类型一致 for _, val := range array{ // _:表示匿名变量,只用来占位,不参与程序运行 fmt.Println(val) } fmt.Println("++++++++++++++++++++") } func main(){ array() // 静态数组作为参数 array03 := [10]int{1,2,3,4,5} printArray(array03) fmt.Println("------------------------") for _, val := range array03{ fmt.Println(val) } }
-
注意事项
- 静态数组的默认值为0;
- 静态数组作为参数传递时,传递的是值;
- 静态数组作为参数传递时,实参类型和形参类型一致(即长度一致);
动态数组(slice切片)
-
动态数组就是长度可变的数组;
-
代码示例
package main import "fmt" // 遍历(动态)数组 func test01_slice(array []int) { array[0] = 110 for _, val := range array{ fmt.Println(val) } fmt.Println("++++++++++++++++++") } func main() { // 定义一个动态数组 array01 := []int{1,2,3,4} test01_slice(array01) // 动态数组作为参数传递时传递的是引用 for _, val := range array01{ fmt.Println(val) } }
-
注意事项
- 动态数组作为参数传递时,传递的是引用;
- 动态数组作为参数时不需要考虑类型(长度)。
切片声明的四种方式
package main
import "fmt"
func main() {
// 切片的四种声明方式
// 方式一:声明slice1是一个切片,并且初始化,默认值是1,2,3。 长度len是3
slice01 := []int{1, 2, 3}
fmt.Println(slice01)
fmt.Println("+++++++++++++++++++++")
// 方式二:
var slice02 []int // 声明slice02是一个切片,并没有给slice02分配空间
slice02 = make([]int, 3) // 给slice02开辟3个空间,默认值为0
fmt.Println(slice02)
fmt.Println("+++++++++++++++++++++")
// 方式三:声明slice03是一个切片,并且开辟3个空间,默认值为0
var slice03 []int = make([]int, 3)
fmt.Println(slice03)
fmt.Println("+++++++++++++++++++++")
// 方式四(常用):声明slice1是一个切片,同时给slice分配空间,3个空间,初始化值是0, 通过:=推导出slice是一个切片
slice04 := make([]int, 3)
fmt.Println(slice04)
fmt.Println("+++++++++++++++++++++")
}
切片的容量
-
切片的长度(len)表示左指针和右指针之间的距离(切片实际的元素的数量)
-
切片的容量(cap)表示左指针到底层数组末尾的距离(切片所能容纳元素的数量)
切片的扩容机制
切片在append时,如果长度超过了容量,容量将增减为原来的2倍
代码示例
package main
import fmt "fmt"
func main() {
// 声明一个切片,长度为3,容量为5
var slice01 = make([]int, 3, 5)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice01), cap(slice01), slice01)
// 追加一个元素
slice01 = append(slice01, 10)
// 追加第二个元素
slice01 = append(slice01, 20)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice01), cap(slice01), slice01)
// 追加第三个元素
slice01 = append(slice01, 30)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice01), cap(slice01), slice01)
fmt.Println("++++++++++++++++++++++++++++")
var slice02 = make([]int, 2) // 如果不指定容量,容量默认和长度相同
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice02), cap(slice02), slice02)
slice02 = append(slice02, 100)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice02), cap(slice02), slice02)
slice02 = append(slice02, 101)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice02), cap(slice02), slice02)
slice02 = append(slice02, 102)
fmt.Printf("length = %d, cap = %d, slice = %v\n", len(slice02), cap(slice02), slice02)
}
// 打印结果
length = 3, cap = 5, slice = [0 0 0]
length = 5, cap = 5, slice = [0 0 0 10 20]
length = 6, cap = 10, slice = [0 0 0 10 20 30]
++++++++++++++++++++++++++++
length = 2, cap = 2, slice = [0 0]
length = 3, cap = 4, slice = [0 0 100]
length = 4, cap = 4, slice = [0 0 100 101]
length = 5, cap = 8, slice = [0 0 100 101 102]
切片的截取
- 截取是一种引用
- copy(s1, s0):将s0中的元素一次拷贝到s1中
代码示例
package main
import "fmt"
func main() {
// 截取(即python中的切片):切片是一种引用
s0 := []int{1, 2, 3} // [1, 2, 3]
fmt.Println(s0)
s1 := s0[0:2] // [1, 2]
fmt.Println(s1)
// 修改
s0[0] = 100 // [100, 2, 3]
fmt.Println(s0) // [100, 2, 3]
fmt.Println(s1) // [100, 2]
fmt.Println("===========================")
// copy
s3 := make([]int, 3) // [0, 0, 0]
copy(s3, s0) // 将s0中的值 依次拷贝到s3中
fmt.Println(s3) // [100, 2, 3]
}
map(字典)
map的定义方式
-
map的三种声明方式如下
package main import "fmt" func main() { // 声明map有三种方式 // 方式一: var map01 map[string]string // 声明一个map01是一种map类型,key为string类型,value也是string类型,默认为map为空 if map01 == nil { fmt.Println("map01 为空map") } map01 = make(map[string]string, 10) // 给map01开辟容量为10的空间(扩容机制和array相同) map01["name"] = "TongTianZi" // 给map01添加元素 map01["age"] = "18" map01["gender"] = "women" fmt.Println(map01) fmt.Println("====================") // 方式二:不指定容量,表示容量为任意个 map02 := make(map[string]string) map02["first"] = "python" map02["second"] = "go" map02["third"] = "java" fmt.Println(map02) fmt.Println("====================") // 方式三:声明一个map,并进行初始化 map03 := map[int]string{ 1: "python", 2: "go", 3: "java", } fmt.Println(map03) }
-
注意事项
- map的扩容机制和array相同;
- 一般不知道map的内容时使用第二种方式,知道map内容的情况下使用第三种方式。
map的使用
-
代码示例(增删改查)
package main import "fmt" func main() { // 声明一个map map0 := make(map[string]string) // 添加数据 map0["陕西"] = "西安" map0["湖南"] = "长沙" map0["江西"] = "南昌" // 遍历 for key, value := range map0{ fmt.Printf("%s = %s\n", key, value) } fmt.Println("===================") // 修改 map0["陕西"] = "长安" // 函数遍历 forMap(map0) // 删除 delete(map0, "陕西") forMap(map0) } func forMap(myMay map[string]string) { // map作为参数,传递的是引用(指针/内存地址值) for key, value := range myMay{ fmt.Printf("%s = %s\n", key, value) } fmt.Println("===================") }
-
注意事项
- map作为参数,传递的是引用(指针/内存地址值)
面向对象
结构体
-
结构体:自定义的数据类型;
-
代码示例
package main import "fmt" // 声明一种新的数据类型myInt,是int的别名 type myInt int // 定义一个结构体 type Book struct { title string author string } func changeBook0(book Book){ book.title = "活着" book.author = "余华" fmt.Printf("书名:%s, 作者:%s\n", book.title, book.author) fmt.Println("==========================") } func changeBook1(book *Book){ book.title = "白鹿原" book.author = "陈忠实" fmt.Printf("书名:%s, 作者:%s\n", book.title, book.author) fmt.Println("==========================") } func main() { // myInt var num01 myInt = 100 fmt.Println(num01) fmt.Printf("%T\n", num01) fmt.Println("==========================") // 结构体的使用 var book0 Book book0.title = "平凡的世界" book0.author = "路遥" fmt.Printf("书名:%s, 作者:%s\n", book0.title, book0.author) fmt.Println("==========================") // 结构体作为参数传递的一个副本(值传递) changeBook0(book0) fmt.Printf("书名:%s, 作者:%s\n", book0.title, book0.author) fmt.Println("==========================") // 如果要把结构体的地址值作为参数传递,需要指明形参为指针类型,实参传递地址值 changeBook1(&book0) fmt.Printf("书名:%s, 作者:%s\n", book0.title, book0.author) fmt.Println("==========================") }
-
注意事项
- 结构体作为参数传递的一个副本(值传递)
- 如果要把结构体的地址值作为参数传递,需要指明形参为指针类型,实参传递地址值
封装
-
go中类的实现其实是给结构体绑定方法。
-
示例代码
package main import ( "fmt" ) // 定义一个Person的结构体 //如果类名首字母大写,表示其他包也能够访问 type Person struct { //如果说类的属性首字母大写, 表示该属性是对外能够访问的,否则的话只能够类的内部访问 Name string age int Gender string } // 给结构体绑定方法 /* func (this Person) Show() { fmt.Printf("name = %s, age = %d, gender = %s\n", this.Name, this.age, this.Gender) fmt.Println("==================") } func (this Person) GetInfo() (string, int, string) { return this.Name, this.age, this.Gender } func (this Person) SetInfo(name string, age int, gender string) { this.Name = name this.age = age this.Gender = gender } */ // 方法名首字母大写表示对外开放(即其他包中也能调用),否则仅限于本包中使用 func (this *Person) Show() { // this表示调用该方法的对象的副本(拷贝) fmt.Printf("name = %s, age = %d, gender = %s\n", this.Name, this.age, this.Gender) fmt.Println("==================") } func (this *Person) GetInfo() (string, int, string) { return this.Name, this.age, this.Gender } func (this *Person) SetInfo(name string, age int, gender string) { this.Name = name this.age = age this.Gender = gender } func main() { // 创建一个对象 p := Person{Name: "TongTianZi", age: 18, Gender: "women"} p.Show() // 修改属性 p.SetInfo("高圆圆", 20, "女") p.Show() }
-
注意事项
- 方法参数传递仅仅是值(副本)传递,要想传递地址值需要指明参数为指针类型;
- 类、方法名、属性首字母大写表示对外(其他包)可以访问,否则只能在本包中访问。
继承
-
go中继承是通过结构体嵌套实现的;
-
代码示例
package main import ( "fmt" ) // 定义一个Animal类 type Animal struct { Color string } func (this *Animal) Eat(){ fmt.Println("eating...") } func (this *Animal) walk(){ fmt.Println("walking...") } type Dog struct { Animal // 表示Dog类继承了Animal(结构体嵌套) Age int Nick string } func (this *Dog) Eat(){ // 重写父类的方法 fmt.Println("dog is eating ...") } func (this *Dog) Shout() { // 子类新增方法 fmt.Println("Dog wang wang ...") } func main() { // 创建父类对象 a := Animal{Color: "red"} fmt.Println(a.Color) a.Eat() fmt.Println("========================") // 创建子类对象 d := Dog{Animal{Color: "black"}, 18, "XiaoHei"} d.Shout() fmt.Println("========================") // 或者 var d1 Dog d1.Color = "black" d1.Age = 2 d1.Nick = "XiaoHei" d1.Eat() }
-
注意事项
- 通过子类创建对象时,父类可以指定属性名,子类不需要指定属性名
多态
-
多态:同一个方法的不同表现形式(例如:同样eat(),dog对象调用时表示dog吃东西,cat调用时表示cat吃东西)
-
go实现多态的方式
-
有一个父类(接口:即抽象方法的集合)
-
子类(继承)实现父类中所有的抽象方法
-
父类数据类型标量指向(引用)子类数据类型变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-05bcd8I6-1617288175781)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210328231932028.png)]
-
-
代码示例
package main import ( "fmt" ) // 父类(接口),本质是一个指针 type AnimalIF interface { // 接口就是抽象方法的集合 // 声明抽象方法 Sleep() GetColor() string GetType() string } // 声明一个Dog子类 type Pig struct { // 继承Animal类(可省略不写) Color string } // 实现父类的所有抽象方法 func (this *Pig) Sleep() { fmt.Println("Pig is sleeping ...") } func (this *Pig) GetColor() string { return this.Color } func (this *Pig) GetType() string { return "Dog" } // 声明一个Cat子类 type Cat struct { // 继承Animal类(可省略不写) Color string } // 实现父类的所有抽象方法 func (this *Cat) Sleep() { fmt.Println("Dog is sleeping ...") } func (this *Cat) GetColor() string { return this.Color } func (this *Cat) GetType() string { return "Cat" } func Show(animal AnimalIF) { // 接口本质是一个指针 animal.Sleep() fmt.Println(animal.GetColor()) fmt.Println(animal.GetType()) fmt.Println("========================") } // main方法 func main() { // 声明一个父类类型对象 var animal AnimalIF animal = &Pig{Color: "Black"} // 接口本质是指针,故需要传递地址值 animal.Sleep() fmt.Println(animal.GetColor()) fmt.Println(animal.GetType()) fmt.Println("========================") animal = &Cat{Color: "yellow"} animal.Sleep() fmt.Println(animal.GetColor()) fmt.Println(animal.GetType()) fmt.Println("========================") // 或者 Show(&Cat{Color: "yellow"}) // 需要传递引用 Show(&Pig{Color: "black"}) }
万能类型
-
interface{}空接口,也是go中的通用类型,int, string, float32, float64等都实现了interface{}的方法,因此就可以用interface{}类型引用任意的数据类型。
-
类型断言:interface{}提供了类型断言,用于判断是否是指定的类型。
-
代码示例
package main import ( "fmt" ) // interface{}:通用万能类型 func test_interface(args interface{}){ fmt.Println(args) // 通过断言判断args到底是什么类型 val, isString:= args.(string) // 判断args是否是string类型 if isString { fmt.Printf("%s type is %T\n", val, val) }else { fmt.Printf("%s type is %T\n", val, val) } fmt.Println("==================") } type Book01 struct { BookName string Author string } func main() { // 万能类型测试 test_interface(10) test_interface("haha") b := Book01{BookName: "傻吊英雄传", Author: "金庸"} test_interface(b) }
reflect(反射)
变量的结构
变量的结构:type + value
反射
获取封装好的变量的结构(类型和值)
- reflect.Valueof(val interface{}:获取接口参数的数据值,如果接口为空,返回0
- reflect.Typeof(val interface{}):获取接口参数的类型,如果接口为空,返回nil
结构体标签
- 结构体标签:标签就是对结构体属性的补充说明;
- 结构体标签的定义方式
package main
import (
"fmt"
"reflect"
)
type Person struct {
// 标签就是对结构体属性的补充说明
Name string `info:"姓名" nick:"Yang"`
Age int `info:"year"`
}
func FindTag(args interface{}){
els := reflect.TypeOf(args).Elem() // 获取结构体所有元素
for i := 0; i < els.NumField(); i++ {
j := els.Field(i).Tag.Get("info") // 获取第i行属性的标签
k := els.Field(i).Tag.Get("nick")
fmt.Println("info = ", j, "nick = ", k)
}
}
func main(){
var p Person
FindTag(&p)
}
json编解码(序列化和反序列化)
- 编码(序列化):结构体——>json
- 解码(反序列化):json——>结构体
- 代码示例
package main
import (
"encoding/json"
"fmt"
)
type Movie struct{
Title string `json:"电影名字"` // 该属性在json格式的字符串中显示"电影名字"
TicketPrice int `json:"票价"`
Actors []string `json:"演员表"`
}
func main(){
// 声明一个对象
movie := Movie{"泰坦尼克号", 20, []string{"Jack", "Rose"}}
// 编码 结构体——>json (序列化)
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("序列化出错!")
return
}
fmt.Printf("jsonStr = %s\n", jsonStr)
// 解码 json——>结构体 (反序列化)
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("反序列化错误!")
}
fmt.Printf("myMovie = %s\n", myMovie)
}
ORM映射关系
goroutine(协成)
- 协成:又叫微线程,相比于线程消耗的资源更少,效率更高。
- 使用go关键字开启一个协成
代码示例
package main
import (
"fmt"
"time"
)
func GoroutineTest(){
for { // 不写控制条件就是死循环
fmt.Println("Goroutine running...")
time.Sleep(1 * time.Second)
}
}
func main(){
// 协成(goroutine):又名微线程,相比于线程,消耗资源更少
go GoroutineTest() // 使用go关键字开启一个协成
for {
fmt.Println("main running...")
time.Sleep(1 * time.Second)
}
}
# 测试效果
main running...
Goroutine running...
main running...
Goroutine running...
main running...
Goroutine running...
......
- 协成依赖于主线程,主线程结束协成也会结束,可以使用runtime.Goexit()退出当前的协成。
代码示例
package main
import (
"fmt"
"runtime"
)
func main(){
// go开启一个协程序
go func(){ // 定义一个匿名函数
defer fmt.Println("defer A...")
func(){
defer fmt.Println("defer B...")
// return "B"不会被打印,"A"还是会被打印 相当于continue
runtime.Goexit() // 退出当前goroutine,"B"和"A"都不会被打印 相当于break
fmt.Println("B")
}()
fmt.Println("A ")
}() // 调用匿名函数
fmt.Println("main running ...")
}
channel(管道)
-
channel:中文叫管道,用于协程之间的通信。
-
channel的使用
package main
import "fmt"
func main(){
// 声明一个channel
c01 := make(chan int)
// goroutine1
go func(){
defer fmt.Println("goroutine1 end ...")
fmt.Println("goroutine1 start ...")
c01 <- 666 // 将666发送到c01中
}()
// goroutine2
go func(){
defer fmt.Println("goroutine2 end ...")
fmt.Println("goroutine2 start ...")
c02 := <- c01 // 从c01中读取666
fmt.Println(c02)
}()
fmt.Println("main end ...")
}
// 测试结果
goroutine2 start ...
goroutine1 start ...
goroutine1 end ...
main end ...
666
goroutine2 end ...
- channel在goroutine中的通信过程
由于main go和sub go的执行顺序是随机的,因此我们并不知道是会先执行”读取“还是先执行”写入“
- 假如先执行写入操作
sub go执行写入操作后会阻塞,直到main go执行读取操作后sub go才会解阻塞,继续往下执行。 - 假如先执行读取操作
main go执行读取操作时发现channel中没有内容,会阻塞,直到sub go往channel中写入一个值后,main go才会解阻塞,执行读取操作。
无缓冲的channel
有缓冲的channel
- 示意图
- 特点:
- 当channel已经满,在向里面写数据,就会阻塞
- 当channel为空,从里面取数据也会阻塞
- 代码示例
package main
import "fmt"
func main(){
c01 := make(chan int, 3) // 初始化带有缓冲的channel
fmt.Println("c01 = ", c01, ", len() = ", len(c01), ", cap() = ", cap(c01))
// write
go func() {
defer fmt.Println("write end")
for i:=0; i < cap(c01); i++ {
c01 <- i
fmt.Println("write in ", i)
}
}()
// read
go func() {
defer fmt.Println("read end")
for i:=0; i<cap(c01); i++ {
c02 := <-c01
fmt.Println("read in ", c02)
}
}()
fmt.Println("main end")
}
关闭channel
- 代码示例
package main
import "fmt"
func main(){
// 声明一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine end")
for i:=0; i<5; i++{
c <- i
}
close(c) // 关闭channel
}()
for {
// ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
if data, ok := <-c; ok{
fmt.Println(data)
} else {
break
}
}
fmt.Println("main end")
}
- 注意事项
(1)channel不像⽂件⼀样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
(2)关闭channel后,⽆法向channel 再发送数据(引发 panic 错误后导致接收⽴即返回零值);
(3)关闭channel后,可以继续从channel接收数据;
(4)对于nil channel,⽆论收发都会被阻塞。
channel与range
可以使用range不断从channel中取数据
package main
import "fmt"
func main(){
// 声明一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine end")
for i:=0; i<5; i++{
c <- i
}
close(c) // 关闭channel
}()
/*for {
// ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
if data, ok := <-c; ok{
fmt.Println(data)
} else {
break
}
}*/
// 可以使用range不断迭代从channel中取出数据
for data := range c {
fmt.Println(data)
}
fmt.Println("main end")
}
channel与select
单流程下⼀个go只能监控⼀个channel的状态,select可以实现多路channel的状态监控。
代码示例
package main
import (
"fmt"
"time"
)
func WriteInto(c01, c02 chan int) {
fmt.Println("开始写入数据...")
for i:=0; i<10; i++{
c01 <- i
}
time.Sleep(1 * time.Second)
c02 <- 10
}
func main(){
// 声明两个channel
c01 := make(chan int)
c02 := make(chan int)
// 开启一个协成,往c01中写数据
go WriteInto(c01, c02)
for {
select {
case <-c01: // 监控c01中是否有数据,如果有数据,就从c01中取出来,添加到c02中
fmt.Println(<-c01)
case <-c02:
return
}
}
fmt.Println("main end ...")
}
p33?