1、Go语言概述

  • Google开源
  • 编译型
  • 21世纪的C语言

Go和C的比较:https://hyperpolyglot.org/c

特性:

  • 语法简介
  • 开发效率高 - 自带垃圾回收
  • 执行性能好

2、Go语言开发环境搭建

2.1 下载

Go官方下载地址:https://golang.org/dl/

2.2 项目结构

Go语言的项目,需要特定的目录结构进行管理,不能随便写,一个标准的go工程需要三个目录:

  • src:存放我们自己的源代码
  • bin:编译之后的程序之后,使用标准命令go install之后存放的位置
  • pkg:缓存包

GOPATH:一般是我们的项目位置

GOROOT:类似JAVA_HOME

个人开发

Go - 图1

企业开发

Go - 图2

3.3 Go开发编辑器

VS Code下载地址:https://code.visualstudio.com/Download

3.4 编译 - go build

  1. 在项目目录下执行go build
  2. 其他路径下执行go build,需要在后面加上项目路径(src目录只有的路径开始写),将保存到当前目录下
  3. go build -o hello.exe,指定名称

3.5 go run

  • 像执行脚本一样执行go代码

3.6 go install

go install分为两步:

  1. go build
  2. 将编译后的文件移动到GOPATH/bin目录中

3.7 交叉编译

Go支持跨平台编译

老师网站

例如:在windows编译一个能在Linux下运行的文件

Go - 图3

Go - 图4

3、Go语言的编码基本结构

  1. package main
  2. import "fmt"
  3. //函数外只能放置标识符(变量/常量/函数/类型)的申明
  4. //fmt.Println("hell") //非法
  5. //程序入口
  6. func main() {
  7. fmt.Println("hello world")
  8. }

3.1 Go语言的25个关键字

基础数据类型:

  1. int int8 int16 int32 int64
  2. uint uint8 ... uint64
  3. float32 float64

3.2 变量和常量

  • 先申明再使用
  • Go语言中非全局变量申明了必须使用(不使用无法编译)

Go语言的变量申明格式:

<font style="background-color:#FADB14;">var 变量名 变量类型</font>

批量申明:

  1. var (
  2. name string
  3. age int
  4. isOk bool
  5. )

申明变量同时赋值:

var name string = "zhangsan" 不推荐

类型推导:

var age = 21

间短变量声明:(只能在函数中使用)

s3 := "李四"

匿名变量:_表示

常量:const

iota:类似枚举

  • 在const关键字出现时将会被置为0,const常量中每新增一行,计数加1
  1. const (
  2. a1 = iota //a1=0
  3. a2 //a2=1
  4. _
  5. a3 //a3=3
  6. )

插队

  1. const (
  2. a1 = iota //0
  3. a2 = 100 //100
  4. a3 = iota //2
  5. a4 //3
  6. )

多个常量申明在一行

  1. const (
  2. d1, d2 = iota + 1, iota + 2 //d1=1,d2=2
  3. d3, d4 = iota + 1, iota + 2 //d3=2,d4=3
  4. )

定义数量级

  1. const (
  2. _ = iota
  3. KB = 1 << (10 * iota) //1左移10位 10000000000(二进制) --> 1024(十进制)
  4. MB = 1 << (10 * iota) //2左移10位
  5. GB = 1 << (10 * iota) //3左移10位
  6. TB = 1 << (10 * iota) //4左移10位
  7. PB = 1 << (10 * iota) //5左移10位
  8. )

4、基本数据类型

4.1 整型

  1. func main() {
  2. // 十进制
  3. var a int = 10
  4. fmt.Printf("%d \n", a) //10
  5. fmt.Printf("%d \n", a) //1010,%d表示二进制,转换成了二进制
  6. fmt.Printf("%o \n", a) //转换成八进制
  7. // 八进制,以0x开头
  8. i := 077
  9. fmt.Printf("%d \n", i)
  10. //十六进制
  11. i1 := 0x1234567
  12. fmt.Printf("%d \n", i1)
  13. //查看变量的类型
  14. fmt.Printf("%T \n", i1)
  15. //申明int8类型的变量
  16. i3 := int8(9)
  17. fmt.Printf("%T \n", i3)
  18. }

4.2 浮点数

  • Go语言中默认的浮点数是float64类型

4.3 复数

  • complex64和complex128
  • 复数有实部和虚部,complex64实部和虚部是32,complex128实部和虚部是64

4.4 布尔值

  • true和false(默认值是false)
  • Go语言中不允许将整形和强制转换为布尔型
  • 无法参数运算,无法进行数据类型装换

4.5 占位符

  1. //占位符
  2. func main() {
  3. var n = 100
  4. //查看类型
  5. fmt.Printf("%T /n", n)
  6. fmt.Printf("%v /n", n)
  7. fmt.Printf("%b /n", n)
  8. fmt.Printf("%d /n", n)
  9. fmt.Printf("%o /n", n)
  10. fmt.Printf("%x /n", n)
  11. var s = "你好"
  12. fmt.Printf("字符串:%s /n", s)
  13. fmt.Printf("字符串:%v /n", s)
  14. fmt.Printf("字符串:%#v /n", s)
  15. }

4.6 字符串

Go - 图5

4.7 rune

  1. func main() {
  2. s := "白萝卜"
  3. s1 := []rune(s) //转换
  4. s1[0] = '红'
  5. fmt.Println(string(s1))
  6. }

4.8 类型转换

4.9 定长数组 - 切片

4.10 不定长数组

4.11 字典 - map

4.12 枚举 - iota

  • Go语言中没有枚举类型,但是我们可以使用const(常量关键字)+iota(常量累加器)来进行模拟
  1. //模拟一个一周的枚举
  2. const (
  3. MONDAY = iota //0
  4. TUESDAY //1
  5. WEDNEDAY //2
  6. THURSDAY //...
  7. FRIDAY
  8. SATUDAY
  9. SUNDAY
  10. M, N = iota, iota // 7,7
  11. X, Y = iota + 1, iota // 9,8
  12. )
  13. /*
  14. 1. iota是常量组计数器
  15. 2. iota从0开始,没换行递增1
  16. 3. 常量组不赋值,默认与上一行表达式相同
  17. 4. 如果同一行出现两个iota,那么两个iota的值相同
  18. 5. 每个常量组的iota是独立的,遇到const会清0
  19. */

4.13 结构体 - type + struct

  • 概念类似java中的对象
  1. type 结构体名称 struct{}

4.14 init()函数

  • init()函数没有参数没有返回值
  • 一个包中包含多个init时,调用顺序不确定
  • init()函数不允许被用户显示调用
  • 有时候引用一个包,可能只想使用这个包中的inti函数(mysql的init对驱动进行初始化)
    • 可以使用_在import里处理

4.15 defer关键字 - 延迟

延迟、关键字,可以用于修饰语句,函数,确保这条语句可以在当前栈退出时执行

4.16 结构体(类)- 方法绑定 —> 封装

  1. //Go语言中的结构体就好比java/C中的类
  2. //结构体的定义为:type 结构体名 struct{}
  3. //Go语言中的方法不是定义在类里面
  4. //Go语言是面向接口的不是面向类的
  5. //Go语言的方法通过绑定类实现的(定义在类的外面)
  6. //定义结构体(类)
  7. type Person struct {
  8. name string
  9. age int
  10. gender string
  11. score float64
  12. }
  13. //定义方法绑定person类
  14. func (p *Person) Eat() {
  15. //尽量使用指针传递
  16. fmt.Println(p.name + "吃的真香!")
  17. }

4.17 继承

  1. package main
  2. import "fmt"
  3. // 继承
  4. //定义一个Human(人)类
  5. type Human struct {
  6. name string
  7. age int
  8. gender string
  9. scode float64
  10. }
  11. //定义Human的方法
  12. func (h *Human) Eat() {
  13. fmt.Println(h.name + "吃的吧唧香....")
  14. }
  15. //定义一个学生类 -- 类的嵌套
  16. type Student struct {
  17. //包含Human类型的变量(类的嵌套)
  18. hum Human
  19. school string
  20. }
  21. //定义一个老师类 -- 继承Human
  22. type Teacher struct {
  23. //继承的时候虽然没有定义字段名字,但是会自动创建一个默认的同名字段
  24. //这是为了在子类中依然可以操作父类,因为子类父类中可能存在同名的字段
  25. Human //直接写父类名称,没有字段名字
  26. subject string
  27. }
  28. func main() {
  29. stu1 := Student{
  30. hum: Human{
  31. "狗娃",
  32. 19,
  33. "男",
  34. 89.0,
  35. },
  36. school: "人民一中",
  37. }
  38. fmt.Println(stu1)
  39. //类的继承
  40. t1 := Teacher{}
  41. t1.name = "强国"
  42. t1.age = 19
  43. t1.subject = "语文"
  44. t1.Eat()
  45. fmt.Println(t1)
  46. }

4.18 Go中的权限

  • Go语言中权限都是通过首字母大小写控制
    • import —> 如果包名不同,那么只有大写字母开头的才是public的
    • 类成员、方法 —> 只有首写字母大写才能在其他包中使用

4.19 接口

  • Go语言中使用interface关键字创建接口
  • interface 不仅仅是用来处理多态的,他可以接收任何类型的数据类型,有点类似于void
  1. func main() {
  2. //定义三个接口类型
  3. var i, j, k interface{} //空接口
  4. names := []string{"duke", "lily"}
  5. i = names
  6. fmt.Println("i代表切片数组:", i)
  7. age := 20
  8. j = age
  9. fmt.Println("j代表数字:", j)
  10. s := "hello"
  11. k = s
  12. fmt.Println("k代表字符串:", k)
  13. //现在我们只知道k是interface类型,但是我们不知道具体代表什么类型?可以进行判断
  14. kvalue, ok := k.(int)
  15. if !ok {
  16. fmt.Println("k不是int")
  17. fmt.Printf("k的类型是:%T\n", k) //k的类型是:string
  18. fmt.Printf("i的类型是:%T\n", i) //i的类型是:[]string
  19. fmt.Printf("j的类型是:%T\n", j) //j的类型是:int
  20. } else {
  21. fmt.Println("k是int,k的值:", kvalue)
  22. }
  23. }
  • 常用的场景:把interface当做一个函数的参数,(类似于print),使用switch来判断用户输入的不同类型,在根据不同的类型,做相应的逻辑处理
  1. //创建具有三个数据类型的切片
  2. array := make([]interface{}, 3)
  3. array[0] = 1
  4. array[1] = "hello"
  5. array[2] = true
  6. for _, value := range array {
  7. switch v := value.(type) {
  8. case int:
  9. fmt.Printf("当前类型为%T,内容为:%d\n", v, v)
  10. case string:
  11. fmt.Printf("当前类型为%T,内容为:%s\n", v, value)
  12. case bool:
  13. fmt.Printf("当前类型为%T,内容为:%v\n", v, value)
  14. default:
  15. fmt.Printf("其他数据类型,当前类型为%T,内容为:%v\n", v, value)
  16. }
  17. }

4.20 多态

  • Go语言的多态不需要继承,只需要实现相同的接口即可
  1. //Go语言中实现多态,需要实现定义的接口
  2. //人类发起攻击,不同等级子弹效果不同
  3. //创建接口 , 注意类型是interface
  4. type IAttack interface {
  5. //接口的函数可以有多个,但是只能有函数原型,不可以实现
  6. Attack()
  7. }
  8. //玩家
  9. type Human struct {
  10. name string
  11. level int
  12. hurt float64
  13. }
  14. //定义玩家方法绑定到HumanLowLevel类
  15. func (hum *Human) Attack() {
  16. fmt.Println("我是:", hum.name, ",我的等级是:", hum.level, ",我造成了", hum.hurt, "点伤害...")
  17. }
  18. //定义一个多态的通用接口,传入不同的对象,调用同样的方法,实现不同效果 ---> 多态
  19. func doIAttack(i IAttack) {
  20. i.Attack()
  21. }
  22. func main() {
  23. //定义一个包含IAttack接口变量
  24. var player IAttack
  25. //低等级玩家
  26. lowLevel := Human{
  27. name: "David",
  28. level: 1,
  29. hurt: 100,
  30. }
  31. // lowLevel.Attack()
  32. //给player赋值为lowLevel,接口一定要使用指针来赋值
  33. player = &lowLevel
  34. player.Attack()
  35. //高等级玩家
  36. highLevl := Human{
  37. name: "狗蛋",
  38. level: 6,
  39. hurt: 10000,
  40. }
  41. player = &highLevl
  42. player.Attack()
  43. fmt.Println("-----多态--------")
  44. doIAttack(&lowLevel)
  45. doIAttack(&highLevl)
  46. }

5、Go语言基础之流程控制

5.1 if else

  1. age := 21
  2. if age > 35 {
  3. fmt.Println("人到中年")
  4. } else if age > 18 {
  5. fmt.Println("好青年")
  6. } else {
  7. fmt.Println("小盆友")
  8. }
  9. # 特殊写法
  10. 减少程序内存的占用
  11. if age := 19; age > 35 { //作用域,age的作用范围仅限于if条件判断语句中
  12. fmt.Println("人到中年")
  13. } else if age > 18 {
  14. fmt.Println("好青年")
  15. } else {
  16. fmt.Println("小盆友")
  17. }

5.2 for

  • Go语言中只有一种循环(for循环)

格式:

  1. for 初始语句;条件表达式;结束语句{
  2. 循环体语句
  3. }
  4. // 变种1
  5. i:= 5
  6. for ;i<10;i++{
  7. fmt.Println(i)
  8. }
  9. // 变种2
  10. j:=5
  11. for j<10{
  12. fmt.Println(j)
  13. j++
  14. }
  15. //无限循环
  16. for {
  17. fmt.Println("11")
  18. }
  19. //for range循环
  20. s := "hello沙河"
  21. for i, v := range s {
  22. fmt.Printf("%d %c \n", i, v)
  23. }

6、数据类型

6.1 切片

  • 切片指向了一个底层的数组(切片不存值)
  • 切片的长度是它的元素个数
  • 切片的容量是底层数组从切片的第一个元素到数组的最后一个元素
  • 切片是引用类型,都指向了一个底层数组(修改底层数组的值,切片也将被修改)

6.2 make()函数

make([]T, len, cap)

  • 用来创建切片
  • 函数的指针返回存在内存逃逸

6.3 append()

  • 为切片追加元素
  • 必须用原来的切片变量接收返回值
  • 追加时容量不足,将底层数组换一个比原来数组大(策略:根据类型策略不同)的新数组

6.4 copy()

  • 复制切片

6.5 sort

  • 对切片进行排序

6.6 指针

  • Go语言中不存在指针操作,只需记住两个符号
    • &:取地址
    • *:根据地址取值

6.7 make和new

  1. 都是用来申请内存的
  2. new很少用,一般用来给基本数据类型申请内存,string/int…,返回的是对应类型的指针(string、int)
  3. make用来给slice、map、chan申请内存,make函数返回的对应的这三个类型本身

6.8 map

map[keyType]valueType

7、结构体(类似java的类)

  1. type 类型名 结构体类型{
  2. 字段 数据类型
  3. 字段 数据类型
  4. }

例如:

  1. package main
  2. import "fmt"
  3. //结构体
  4. type person struct {
  5. name string
  6. age int
  7. gender string
  8. hobby []string
  9. }
  10. func main() {
  11. //申明person类型变量
  12. var p person
  13. //赋值
  14. p.name = "zhoulin"
  15. p.age = 21
  16. p.gender = "女"
  17. p.hobby = []string{"篮球", "足球", "乒乓球"}
  18. //访问变量
  19. fmt.Println(p) //{zhoulin 21 女 [篮球 足球 乒乓球]}
  20. fmt.Println(p.name) //zhoulin
  21. }

7.1 匿名结构体

  • 多用域临时场景
  1. var 变量名 struct{
  2. 变量 数据类型
  3. ...
  4. }

7.2 结构体指针和结构体初始化

  • 结构体是值类型
  • Go语言中函数传参永远是拷贝
  • 结构体占用一块连续的内存空间
  1. type person struct {
  2. name string
  3. age int
  4. gender string
  5. }
  6. func f(x person) {
  7. x.gender = "男" //修改的是副本的gender
  8. }
  9. func f2(x *person) {
  10. // (*x).gender = "男" //根据内存地址找到原来的变量,修改的是原来的变量的值
  11. x.gender = "男" //指针的语法糖,与上面相同找到原来的变量修改
  12. }
  13. func main() {
  14. var p person
  15. p.name = "zhoulin"
  16. p.age = 17
  17. p.gender = "女"
  18. f(p)
  19. fmt.Println(p) //{zhoulin 17 女}
  20. f2(&p) //传递的是p的内存地址
  21. fmt.Println(p) //{zhoulin 17 男}
  22. }

Go - 图6

7.3 构造函数

  • 返回一个结构体变量的函数
  1. //构造函数
  2. type person struct {
  3. name string
  4. age int
  5. }
  6. //构造函数返回的是结构体还是结构体指针?
  7. //当结构体比较大的时候尽量使用结构体指针,减少程序的内存开销
  8. func newPerson(name string, age int) *person {
  9. return &person{
  10. name: name,
  11. age: age,
  12. }
  13. }
  14. func main() {
  15. p1 := newPerson("元帅", 18)
  16. p2 := newPerson("周林", 21)
  17. fmt.Println(p1)
  18. fmt.Println(p2)
  19. }

8、结构体

  1. //方法
  2. type dog struct {
  3. name string
  4. }
  5. //构造函数
  6. func newDog(name string) dog {
  7. return dog{
  8. name: name,
  9. }
  10. }
  11. //方法是作用与特定类型的函数
  12. //接收者表示的是调用方法的具体变量,多用类型名首字母小写表示
  13. func (d dog) wang() {
  14. fmt.Printf("%s: 汪汪汪~", d.name)
  15. }
  16. func main() {
  17. d1 := newDog("大黄")
  18. d1.wang()
  19. }
  • Go语言中如果标识符的首字母大写,表示对外部包可见(暴露的,共有的)

何时使用指针接收者:

  • 值接收者,需要修改接收者中的值时需要指针接收者
  • 尽量使用指针接收者

8、基础语法

8.1 switch

8.2 标签

8.3 枚举const-iota

8.4 结构体

8.5 init函数

8.6 defer(延迟)

9、类的相关操作

9.1 封装 — 方法绑定

9.2 类继承

9.3 interface(接口)

9.4 多态

10、并发相关

10.1 基础

  • Go语言中不是线程,而是go程 ==> goroutine,go程是go语言原生支持的
  • 每一个go程占用系统资源远远小于线程,一个go程大约需要4~5k的内存资源
  • 一个程序可以启动大量的go程:
    • 线程 ==> 几十个
    • go程可以启动成百上千个,===>实现高并发,性能非常好
    • 只需要在函数前面加上go关键字即可
  1. //用于子go程使用
  2. func display() {
  3. count := 1
  4. for {
  5. fmt.Println("这是子go程:", count)
  6. count++
  7. // time.Sleep(500) //默认为微秒
  8. time.Sleep(1 * time.Second)
  9. }
  10. }
  11. func main() {
  12. //启动子go程
  13. go display()
  14. //主go程
  15. count := 1
  16. for {
  17. fmt.Println("============>这是主go程:", count)
  18. count++
  19. time.Sleep(1 * time.Second)
  20. }
  21. }

启动多个子go程,竞争资源

  1. //用于子go程使用
  2. func display(num int) {
  3. count := 1
  4. for {
  5. fmt.Println("这是子go程:", num, "当前执行的次数:", count)
  6. count++
  7. // time.Sleep(500) //默认为微秒
  8. time.Sleep(1 * time.Second)
  9. }
  10. }
  11. func main() {
  12. //启动子go程
  13. for i := 1; i <= 3; i++ {
  14. go display(i)
  15. }
  16. //主go程
  17. count := 1
  18. for {
  19. fmt.Println("============>这是主go程:", count)
  20. count++
  21. time.Sleep(1 * time.Second)
  22. }
  23. }

10.2 提前退出go程序

  1. /*
  2. GOEXIT:提前退出当前go程序
  3. return:返回当前函数
  4. exit:退出整个程序(进程)
  5. */
  6. func main() {
  7. //匿名函数创建go程
  8. go func() {
  9. func() {
  10. fmt.Println("这是子go程内部的函数...")
  11. // return //返回当前函数
  12. // os.Exit(1) //退出程序
  13. runtime.Goexit() //退出当前go程
  14. }()
  15. fmt.Println("子go程结束!")
  16. }()
  17. fmt.Println("这是主go程!")
  18. time.Sleep(3 * time.Second)
  19. fmt.Println("OVER!!")
  20. }

10.3 无缓冲管道channel

  1. func main() {
  2. /*
  3. 当涉及多个go程时,c语言使用互斥量,上锁来保证资源同步,避免资源竞争
  4. Go语言也支持这种方式,但是Go语言更好的解决方案是使用管道(通道:channel)
  5. 使用管道不需要我们去加锁
  6. 例如:读写数据的同步
  7. */
  8. //创建装数字的管道,创建管道一定要用make,同map一样,否则是nil
  9. //不指定长度,此时是无缓存的管道
  10. // numChan := make(chan int) //五缓冲
  11. numChan := make(chan int, 10) //有缓冲
  12. //子进程读数据
  13. go func() {
  14. for i := 0; i < 50; i++ {
  15. //numChan写入数据向data
  16. data := <-numChan
  17. fmt.Println("子进程1,读取数据:", data)
  18. }
  19. }()
  20. //子进程写入数据到管道numChan中
  21. go func() {
  22. for i := 0; i < 20; i++ {
  23. numChan <- i
  24. fmt.Println("子进程2写书数据:", i)
  25. }
  26. }()
  27. //主进程写数据
  28. for i := 20; i < 50; i++ {
  29. //i写入数据到管道numChan
  30. numChan <- i
  31. fmt.Println("主进程写入数据:", i)
  32. }
  33. }

10.4 有缓冲管道

死锁

  1. func main() {
  2. //1.当缓冲写满后,写阻塞,等到读取后,再恢复写入
  3. //2.当缓冲区空时,读阻塞,直到有数据写入
  4. //3.如果管道没有使用make分配空间,那么管道默认是nil的,读写都会阻塞
  5. // numChan := make(chan int, 10)
  6. var names chan string //默认是nil的
  7. //读数据
  8. go func() {
  9. fmt.Println("数据是:", names)
  10. }()
  11. //写数据
  12. names <- "hello"
  13. }

初始化空间

  1. func main() {
  2. //1.当缓冲写满后,写阻塞,等到读取后,再恢复写入
  3. //2.当缓冲区空时,读阻塞,直到有数据写入
  4. //3.如果管道没有使用make分配空间,那么管道默认是nil的,读写都会阻塞
  5. //4.对于管道,读写次数必须对等
  6. //不对等:一、主程序读的数大于写的,程序奔溃死锁;二、子程序读大于写,不会奔溃,内存泄露
  7. // numChan := make(chan int, 10)
  8. // var names chan string //默认是nil的
  9. // names = make(chan string, 10) //分配空间
  10. //合并上面
  11. names := make(chan string, 10)
  12. //读数据
  13. go func() {
  14. fmt.Println("数据是:", <-names)
  15. }()
  16. //写数据
  17. names <- "hello" //由于这里是names是nil,写操作会阻塞在这里
  18. time.Sleep(1 * time.Second)
  19. }

解决读写不对等 - for-range

  • close()
  1. func main() {
  2. numChan2 := make(chan int, 10)
  3. //写
  4. go func() {
  5. for i := 0; i < 50; i++ {
  6. numChan2 <- i
  7. fmt.Println("写入数据:", i)
  8. }
  9. close(numChan2)
  10. }()
  11. //读,遍历管道时,只返回一个参数
  12. //问题:for-range不知道管道是否已经写完了,所以会一直等待(奔溃)--->写时,显示关闭管道close
  13. //在写入端,将管道关闭,for-range遍历时关闭管道,会退出
  14. for v := range numChan2 {
  15. fmt.Println("读数据:", v)
  16. }
  17. fmt.Println("over!")
  18. }

10.5 for range遍历

  1. func main() {
  2. numChan2 := make(chan int, 10)
  3. //写
  4. go func() {
  5. for i := 0; i < 50; i++ {
  6. numChan2 <- i
  7. fmt.Println("写入数据:", i)
  8. }
  9. close(numChan2)
  10. }()
  11. //读,遍历管道时,只返回一个参数
  12. //问题:for-range不知道管道是否已经写完了,所以会一直等待(奔溃)--->写时,显示关闭管道close
  13. //在写入端,将管道关闭,for-range遍历时关闭管道,会退出
  14. for v := range numChan2 {
  15. fmt.Println("读数据:", v)
  16. }
  17. fmt.Println("over!")
  18. }

10.6 管道总结

  • 当管道写满了,写阻塞
  • 当缓冲区读完了,读阻塞
  • 如果管道没有使用make分配空间,管道默认是nil
  • 从nil的管道读取数据、写入数据、都会阻塞(不会奔溃)
  • 从一个已经close的管道读数据时,会反馈零值(不会奔溃)
  • 向一个已经close的管道写数据是,会奔溃
  • 关闭一个已经关闭的管道,程序会奔溃
  • 关闭管道的动作一定要在写管道的一端,不应该放在读端,否则写端继续写,会奔溃
  • 读写次数一定要对等,否则:
    • 在多个go程中:资源泄露
    • 在主go程中,程序奔溃(deadlock)

10.7 多go程序读写

略(上面已经实现)

10.8 判断管道是否已关闭

  • 需要知道一个管道的状态,如果已经close了,读不怕(返回0),如果写(奔溃)

判断和map中是否有值很类似

  • map:v,ok := m1[0]
  • chan:v,ok := <- numchan
  1. func main() {
  2. numChan := make(chan int, 10)
  3. //写
  4. go func() {
  5. for i := 0; i < 10; i++ {
  6. numChan <- i
  7. fmt.Println("写数据:", i)
  8. }
  9. close(numChan)
  10. }()
  11. //读
  12. // for v := range numChan {
  13. // fmt.Println("读数据:", v)
  14. // }
  15. for {
  16. v, ok := <-numChan //ok-idom判断模式
  17. if !ok {
  18. fmt.Println("管道已经关闭了,准备退出...")
  19. break
  20. }
  21. fmt.Println(v)
  22. }
  23. }

10.9 单向通道

  • numChan := make(chan int,10) ==>双向通道,即可写,也可读
  • 单向通道:为了明确语义,一般用于函数参数
    • 单向读通道:var numChanReadOnly <- chan int
    • 单向写通道:var numChanWriteOnly chan <- int
  1. // consumer:消费者 --->只读
  2. func consumer(in <-chan int) {
  3. for v := range in {
  4. // in <- v //读通道不允许写操作
  5. fmt.Println("消费者消费:", v)
  6. }
  7. }
  8. // producer:生产者 --->只写
  9. func producer(out chan<- int) {
  10. for i := 0; i < 10; i++ {
  11. out <- i
  12. fmt.Println("生产者生产:", i)
  13. }
  14. }
  15. func main() {
  16. //单向读通道
  17. // var numChanReadOnly <-chan int
  18. //单向写通道
  19. // var numChanWriteOnly chan<- int
  20. //生产者消费者模型
  21. //C语言:数组+锁 thread1:写,thread2:读
  22. //Go语言:goroutine + channel
  23. //1.在主函数中创建一个双向channel:numChan
  24. numChan := make(chan int, 5)
  25. //2.将numChan传递给生产者,生产
  26. go producer(numChan) // 双向通道可以赋值给同类型的单向通道
  27. //3.将numChan传递给消费者,消费
  28. go consumer(numChan)
  29. time.Sleep(2 * time.Second)
  30. fmt.Println("OVER!")
  31. }

10.10 select

当程序总有多个channel协同工作时,ch1,chan2,在某时刻,ch1和chan2触发了,程序要做响应的处理

使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)

select语法和Switch case很像,但是所有的分支条件都必须是通道io
  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. // 当程序总有多个channel协同工作时,ch1,chan2,在某时刻,ch1和chan2触发了,程序要做响应的处理
  7. // 使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)
  8. // select语法和Switch case很像,但是所有的分支条件都必须是通道io
  9. func main() {
  10. // var chan1, chan2 chan int
  11. chan1 := make(chan int)
  12. chan2 := make(chan int)
  13. //启动1个go程,负责监听两个chan
  14. go func() {
  15. for {
  16. fmt.Println("监听中...")
  17. select {
  18. case data1 := <-chan1:
  19. fmt.Println("从chan1读取成功,data1:", data1)
  20. case data2 := <-chan2:
  21. fmt.Println("从chan2读取成功,data2:", data2)
  22. default:
  23. fmt.Println("....")
  24. time.Sleep(1 * time.Second)
  25. }
  26. }
  27. }()
  28. //启动go1,写chan1
  29. go func() {
  30. for i := 0; i < 10; i++ {
  31. chan1 <- i
  32. // fmt.Println("写入chan1:", i)
  33. time.Sleep(1 * time.Second / 2)
  34. }
  35. }()
  36. //启动go2,写chan2
  37. go func() {
  38. for i := 0; i < 10; i++ {
  39. chan2 <- i
  40. // fmt.Println("写入chan1:", i)
  41. time.Sleep(1 * time.Second)
  42. }
  43. }()
  44. for {
  45. time.Sleep(3 * time.Second)
  46. fmt.Println("OVER!")
  47. }
  48. }

11、网络

1. 分层

Go - 图7

OIS七层 TCP/IP四层 对应的网络协议

2. socket

Go - 图8

Server Demo

  • 只能接收一个连接,发送一个数据
  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "strings"
  6. )
  7. func Server() {
  8. //1.创建监听
  9. ip := "127.0.0.1"
  10. port := 8848
  11. address := fmt.Sprintf("%s:%d", ip, port)
  12. // net.Listen("tcp", ":8848") // 简写,冒号前面默认是ip
  13. listenner, err := net.Listen("tcp", address)
  14. if err != nil {
  15. fmt.Println("net.Listen err:", err)
  16. return
  17. }
  18. //2.建立连接
  19. //err不会重新创建,而是复用
  20. conn, err := listenner.Accept()
  21. if err != nil {
  22. fmt.Println("listenner.Accept err:", err)
  23. return
  24. }
  25. fmt.Println("连接建立成功!")
  26. //3.创建一个容器,用于接收读取到的资源
  27. buf := make([]byte, 1024)
  28. //cnt真正读取client发来的数据长度
  29. cnt, err := conn.Read(buf)
  30. if err != nil {
  31. fmt.Println("conn.Read err:", err)
  32. return
  33. }
  34. fmt.Println("Client ===> Server,长度:", cnt, ",数据:", string(buf[:cnt]))
  35. //4.将数据转成大写:"hello" ==> "HELLO"
  36. //服务器对客户端进行响应
  37. upperData := strings.ToUpper(string(buf))
  38. cnt, err = conn.Write([]byte(upperData))
  39. if err != nil {
  40. fmt.Println("conn.Write err:", err)
  41. return
  42. }
  43. fmt.Println("Client <=== Server,长度:", cnt, ",数据:", upperData)
  44. // 5.关闭连接
  45. conn.Close()
  46. }
  47. func main() {
  48. Server()
  49. }

Client Demo

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. )
  6. func Client() {
  7. //1. 建立连接
  8. conn, err := net.Dial("tcp", ":8848")
  9. if err != nil {
  10. fmt.Println("net.Dial err:", err)
  11. return
  12. }
  13. fmt.Println("client与server连接建立成功!")
  14. //2. 写数据 --> Server
  15. sendData := []byte("helloworld")
  16. cnt, err := conn.Write(sendData)
  17. if err != nil {
  18. fmt.Println("Client ===> Server cnt:", cnt, ",data:", string(sendData))
  19. }
  20. //3. 接收Server返回数据
  21. buf := make([]byte, 1024)
  22. cnt, err = conn.Read(buf[:cnt])
  23. if err != nil {
  24. fmt.Println("conn.Read err:", err)
  25. }
  26. fmt.Println("Client <=== Server cnt:", cnt, ",data:", buf[:cnt])
  27. // 4. 关闭连接
  28. conn.Close()
  29. }
  30. func main() {
  31. Client()
  32. }

多连接

client

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "time"
  6. )
  7. func Client() {
  8. //1. 建立连接
  9. conn, err := net.Dial("tcp", ":8848")
  10. if err != nil {
  11. fmt.Println("net.Dial err:", err)
  12. return
  13. }
  14. fmt.Println("client与server连接建立成功!")
  15. for {
  16. //2. 写数据 --> Server
  17. sendData := []byte("helloworld")
  18. cnt, err := conn.Write(sendData)
  19. if err != nil {
  20. fmt.Println("Client ===> Server cnt:", cnt, ",data:", string(sendData))
  21. }
  22. //3. 接收Server返回数据
  23. buf := make([]byte, 1024)
  24. cnt, err = conn.Read(buf[:cnt])
  25. if err != nil {
  26. fmt.Println("conn.Read err:", err)
  27. }
  28. fmt.Println("Client <=== Server cnt:", cnt, ",data:", buf[:cnt])
  29. time.Sleep(1 * time.Second)
  30. }
  31. // 4. 关闭连接
  32. // _ = conn.Close()
  33. }
  34. func main() {
  35. Client()
  36. }

server

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "strings"
  6. )
  7. //处理具体业务的逻辑,需要将conn传递进来,每一个新连接,conn是不同的
  8. func handleFunc(conn net.Conn) {
  9. //3.创建一个容器,用于接收读取到的资源
  10. buf := make([]byte, 1024)
  11. for {
  12. fmt.Println("准备读取数据....")
  13. //cnt真正读取client发来的数据长度
  14. cnt, err := conn.Read(buf)
  15. if err != nil {
  16. fmt.Println("conn.Read err:", err)
  17. return
  18. }
  19. fmt.Println("Client ===> Server,长度:", cnt, ",数据:", string(buf[:cnt]))
  20. //4.将数据转成大写:"hello" ==> "HELLO"
  21. //服务器对客户端进行响应
  22. upperData := strings.ToUpper(string(buf))
  23. cnt, err = conn.Write([]byte(upperData))
  24. if err != nil {
  25. fmt.Println("conn.Write err:", err)
  26. return
  27. }
  28. fmt.Println("Client <=== Server,长度:", cnt, ",数据:", upperData)
  29. }
  30. // 5.关闭连接
  31. // _ = conn.Close()
  32. }
  33. func Server() {
  34. //1.创建监听
  35. ip := "127.0.0.1"
  36. port := 8848
  37. address := fmt.Sprintf("%s:%d", ip, port)
  38. // net.Listen("tcp", ":8848") // 简写,冒号前面默认是ip
  39. listenner, err := net.Listen("tcp", address)
  40. if err != nil {
  41. fmt.Println("net.Listen err:", err)
  42. return
  43. }
  44. //需求:server可以接收多个连接,每个连接可以处理多个请求
  45. //解:主go程负责监听,子go程负责处理
  46. //主
  47. for {
  48. fmt.Println("监听中...")
  49. //2.建立连接
  50. //err不会重新创建,而是复用
  51. conn, err := listenner.Accept()
  52. if err != nil {
  53. fmt.Println("listenner.Accept err:", err)
  54. return
  55. }
  56. fmt.Println("连接建立成功!")
  57. go handleFunc(conn)
  58. }
  59. }
  60. func main() {
  61. Server()
  62. }

3. http

编写web的语言:

  1. java
  2. php ==> 现在用go重写
  3. python ,豆瓣
  4. go ===> beego,gin主流的web框架

https协议:浏览器发送的就是http请求

  1. http是应用层的协议,底层还是依赖传输层:tcp(短连接),网络层(ip)
  2. 无状态的,每次请求都是独立的,下次请求需要重新建立连接
  3. https:
    1. http是标准协议,明文传输,不安全
    2. https不是标准协议,https:http + ssl(非对称加密,数字证书)
    3. 现在所有网站都会尽量要求使用https开发:安全