Golang秒懂----golang语法要点(语法+案例+说明)

一 GO语言简介

  • 全称:Golang
  • 起源:Google
  • 三大作者:罗伯派克、肯汤普森、罗伯特
  • 运行速度快、开发速度快、学习速度快
  • 2009年被开源,2012年1.0正式版本发布,2015年的1.5版本移除了最后的C语言代码,目前更新到1.19
  • https://studygolang.com/ 资源
  • http://www.golang.ltd/ 官方文档
  • 主要特点:
    • 继承了C语言的一些理念,如指针、数据类型、语法等
    • 函数可以有多个返回值,切片、延时执行、包管理
    • 并发编程、网络编程支持性好
    • 独特的内存分配及回收机制
    • 编译型语言
  • VS Code / Goland
  • hello world
    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("hello world")
    	return
    }
    
  • 编译/运行
    > go build (-o myhello) hello.go 
    > go build (-o myhello.exe) hello.go 
    > go run hello.go
    hello world
    

二 包

  • 管理项目

    • 1.11版本之前 -> GOPATH 目录
    • 1.11版本之后 -> go mod管理
      • 使用go mod 需要配置 -> go env查看 -> 将GO111MODULE配置改成auto
        go env -w GO111MODULE=auto
        
      • 使用mod
        • go mod init Gone
        • go get xxx
        • go mod tidy
            module Gone
            
            go 1.18
            
            require (
            	github.com/Masterminds/sprig v2.22.0+incompatible
            	github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
            )
          
  • package xxximport "Gone/xxx"

     > Test
     	> C
     	> S
     	go.mod
     	go.sum 
    
  • import

    import (
    	"fmt"
    	_ "strings"	//会执行包里的init()函数,其他隐藏
    	l "log"		//取别名,取了别名则原包名不可用,使用包只能用别名
    )
    

三 基本语法

3.1 书写规范
  • 大小写敏感,首字母大小写表示公私有
    type public_st struct {
    	Test int32
    }
    
    type private_st struct {
    	test int32
    }
    
    func main() {
    	var st1 public_st
    	res := st1.Test
    	st1.Test = 2
    	fmt.Println(res, " ", st1.Test)
    
    	//var st2 private_st
    	//res1 := st2.test 		//err  test字段私有,对于外部隐藏
    }
    
    0 2
    
  • 一行一个语句
    var str string = "aaaa" + "aaaa" + "aaaa" + "aaaa" + "aaaa" +
    	"aaaa" + "aaaa" + "aaaa" + "aaaa"
    
  • 语句后不加分号
  • 大括号严格在上
  • 声明的变量不使用,编译时报错type xxx is unused
  • 注释
    • //注释
    • /*注释*/
  • hs[i],hs[j] = hs[j],hs[i]
3.2 函数
  • 定义
    func funcName(param) (returnparam){
    }
    
  • 程序的起点是main包的main函数 func init() {}
    func main() {
    	fmt.Println("ddd")
    }
    func init() {
    	fmt.Println("ccc")
    }
    
    • 仅做介绍,不要使用init()函数,会造成很多不必要的麻烦
3.3 关键字
3.3.1 type
  • type TypeDef_Int int
  • type TypeDef_St struct{}
  • type TypeDef_Inter interface{}
3.3.2 const
  • 使用const可以达到枚举的效果,所以golang没有enum字段
	const (		
		Def_a TypeDef_Int = iota + 1	//固定写法iota = 0,iota可以省略
		Def_b                			//在前者基础上+1
	)
3.3.3 var
  • var name type–>var a int
    var (
    	a int32
    	b int32
    )
    
  • 声明变量还可以用:=a := 1
3.3.4 defer
  • 延迟执行

  • 案例1:延迟执行关于锁的关闭操作(mutex、db、log等)

    func Unlock(lock *sync.RWMutex) {
    	fmt.Println("unlock")
    	lock.RUnlock()
    }
    
    func main() {
    	var (
    		test int = 10
    		lock sync.RWMutex
    	)
    	lock.RLock()		//加锁,加锁成功再修改test的值,否则阻塞
    	defer Unlock(&lock)	//延迟解锁
    
    	test = 5
    	fmt.Println(test)
    }
    
      5
      unlock
    
  • 案例2:关于defer内的变量值

    func deferTest(){
        num := 0
        defer fmt.Println(num)
        num++
    }
    
    func main(){
        deferTest()
    }
    
    defer语句在调用时候的值已经被确定了,本质上是拷贝了一份。仍然打印“0”。
    
  • 案例3:多个defer的执行顺序

    func main() {
    	defer fmt.Println("1111111")
    	defer fmt.Println("2222222")
    }
    
    2222222
    1111111
    
  • 闭包概念在面向对象补充

3.3.5 if
	...
	//写法1
	_, err := db.Exec(s, 3)
	if err == nil {
	}
	
	//写法2
	if _, err := db.Exec(s, 3); err == nil {
	} else {
	}
  • 两种写法err作用域不同,写法1和if同级的作用域,写法2是仅在if内部
  • 写法2定义的变量不一定要在if判断时候就用
  • 在golang中,常用于错误判断
3.3.6 switch
  • 不用break,需要穿透case可以用fallthrough,只透传一层
  • 匹配到一个case之后不会进行后续case判断
  • case后直接写相同值会编译不过,但是变量值相同可以编过
  • switch可以不带表达式,case里面用作和if一样的操作
  • switch i:= x.(type)可以判断接口变量x的实际类型,case里写类型,在接口章节中补充
    func main() {
    	var num int = 2
    	switch num {
    	case 1:
    		fmt.Println("one")
    	case 2:
    		fallthrough
    	case 3:
    		fmt.Println("three")
    	default:
    		fmt.Println("err")
    	}
    
    	switch {
    	case num == 1:
    		fmt.Println(1)
    	case num == 2:
    		fmt.Println(2)
    	}
    }
    
3.3.7 for
  • 基本用法
    func main() {
    	for n := 1; n <= 3; n++ {
    		fmt.Println(n)
    	}
    
    	str := "abcde"
    	for k, v := range str {  
    		fmt.Printf("%d,%c\n", k, v)
    	}
    }
    
    1
    2
    3
    0,a
    1,b
    2,c
    3,d
    4,e
    
  • 坑:for...range中的k,v是值拷贝,若修改要通过数组下标修改值
    func main() {
    	arr := [...]int{1, 2, 3}
    	fmt.Println(arr)
    	for k, v := range arr {
    		fmt.Println(k, " ", v)
    		if k == 2 {
    			//v = 9		//无法修改,v为临时变量
    			arr[k] = 9
    		}
    	}
    
    	fmt.Println(arr)
    }
    
    [1 2 3]
    0   1
    1   2
    2   3
    [1 2 9]
    
3.4 数据类型
3.4.1 指针
  • &取地址、*根据地址取值。
    请添加图片描述
  • 使用
type st struct {
	Test int32
}
...
var s st
var ps *st = &s
res := s.Test
res1 := ps.Test		//偷懒
res2 := (*ps).Test
3.4.2 string
  • 支持库:strconvstrings
  • golang没有char类型
  • 底层结构
    type stringStruct struct {
    	str unsafe.Pointer		//指向一个数组的指针
    	len int					//实际长度
    }
    
  • 常用操作
    str1 := "hello world"
    
    // 输出 str1[3] = 108  l的ascii码
    fmt.Println(string(str1[3]))
    
    // 错误,unsafe.Pointer对于数组是只读的
    //str1[3] = 'l'
    
    //获取字符串长度
    fmt.Println(len(str1)) //输出:11
    
    // 对于中文,可以使用中文的切片[]rune进行中文文字拆分
    strChinese := "你好,世界"
    fmt.Println(len(strChinese))         //输出13,英文占1个字节,中文占3个字节
    fmt.Println(len([]rune(strChinese))) //输出5
    
    /***********************strings库常用方法***********************/
    //匹配函数
    //cmp = -1  str1字典序小于strChinese
    //cmp = 0  str1和strChinese相等
    //cmp = 1,表示str1字典序大于strChinese
    fmt.Println(strings.Compare(str1, strChinese)) //输出:-1
    
    //查找位置
    fmt.Println(strings.Index(str1, "o"))     //输出:4
    fmt.Println(strings.LastIndex(str1, "o")) //输出:7
    
    //出现次数
    fmt.Println(strings.Count(str1, "o")) //输出:2
    
    //重复多次
    fmt.Println(strings.Repeat(str1, 3)) //输出:hello worldhello worldhello world
    
    //字符串替换   第四个参数是替换的次数,小于0为全部替换
    fmt.Println(strings.Replace(str1, "o", "a", 1))  //输出:hella world
    fmt.Println(strings.Replace(str1, "o", "a", -1)) //输出:hella warld
    
    //字符串拆分,返回一个切片
    fmt.Println(strings.Split(str1, ",")) //输出:[hello world]   <--这是一个切片
    
    //字符串拼接
    sli := []string{"hello", "world", "tt"}
    fmt.Println(strings.Join(sli, ",")) //输出:hello,world,tt
    
    /***********************strconv库常用方法***********************/
    //字符串解析
    //字符串转整型    还有转别的类型不再举例
    //参数:(字符串,进制,字符串的bit的大小)   bit大小举例:8就是int8,16就是int16,具体用法查询文档
    //返回类型:int64, error
    vParseInt, err := strconv.ParseInt("12345", 10, 0)
    if err != nil {
    	fmt.Println("ParseInt error")
    }
    fmt.Println(vParseInt)
    //另一种写法,简单一些,返回int型
    vAtoi, err := strconv.Atoi("12345")
    if err != nil {
    	fmt.Println("error happens")
    }
    fmt.Println(vAtoi)
    
    //整型转字符串   同样用int举例
    fmt.Println(strconv.FormatInt(int64(12345), 10)) //第二参数代表进制
    fmt.Println(strconv.Itoa(12345))
    
3.4.3 数组
  • 声明数组变量并初始化
    • var arr [4]int = [4]int{1,2,3,4}
    • arr := [...]int{1, 2, 3}
  • 数组不会自动扩容
  • 下标越界会崩溃
    func main() {
    
    	var arr [4]int = [4]int{1, 2, 3, 4}
    	fmt.Println(arr)
    
    	var arr2 [4]int = [4]int{1, 2, 3}
    	fmt.Println(arr2)
    
    	arr3 := [...]int{1, 2, 3}
    	fmt.Println(arr3)
    }
    
    [1 2 3 4]
    [1 2 3 0]
    [1 2 3]
    
3.4.4 切片
  • 本质是对底层数组的封装:底层数组的指针、长度(len)、 容量(cap)

  • 声明

    • var a []string
    • var b = []int{1,2,3}
    • sc := make([]int,2,10)
  • 使用

    • 添加
       var s1 []int  
       s1 = append(s1)  
       s1 = append(s1,1)  
       s1 = append(s1,2,3,4) 
    
    使用append可以对切片进行扩容:
    1.len未超过cap,直接赋值
    2.len超过cap,扩容
    
    发生扩容的情况表示:
    

    请添加图片描述

    • 删、改、查
    	//删除
    	a := []int{11, 22, 33, 44, 55}
    	a = append(a[:2], a[3:]...)
    	
    	//修改
    	s1[1] = 1000
    	
        //查询
    	num:=[3]int{0,1,2}
    	sc1:=num[:] 		//[0.1.2]   
    	sc2:=num[0:2] 		//[0,1]  --左闭右开
    	sc3:=num[1:] 		//[2]
    	sc4:=num[:1] 		//[0]
    
3.4.5 可变参数
  • 使用案例
    func myfunc(args ...int) {
        for _, arg := range args {
            fmt.Println(arg)
        }
    }
    ...
    myfunc(2, 3, 4)
    myfunc(1, 3, 7, 13)
    tmp := []int{1,2,3}	//针对切片的写法
    myfunc(tmp...)
    
  • 有确定的参数,必须写在可变参数前面
    func myfunc(str string, args ...int) {
    }
    
    myfunc("test", 1, 2, 3)
    
  • 针对不同类型的可变参数,可以使用空接口类型的可变参数,在函数内部进行判断,在接口章节补充
    func myfunc(args ...interface{}) {
    }
    
    myfunc(1, 3.1, "test", false)
    
3.4.6 map
  • key-value的键值对,key值无序
  • 声明:map[key类型]value类型
  • 初始化要makem := make(map[int]int)
  • 添加/修改:mapTalkRuleCfg[1] = 1
    • 键不存在,则会创建键,自动扩容
    • 键存在:更新值
  • 删除:delete(mapNpcInfo, iNpcId)
  • 查找:
    info, ok := mapTalkRuleCfg[iRuleId]
    if ok{
    	//具体操作
    }
    
  • 坑1:修改。cannot assign to struct field m[1].iLv in map
    func main() {
    	m := map[int]User{
    		1: User{101, 10},
    		2: User{102, 20},
    	}
    
    	m[1].iLv = 11   //error
    }
    
    type User struct {
    	iId int
    	iLv int
    }
    
    func main() {
    	m := map[int]*User{
    		1: &User{101, 10},
    		2: &User{102, 20},
    	}
    
    	m[1].iLv = 11 
    }
    
    type User struct {
    	iId int
    	iLv int
    }
    
  • 坑2:将切片数据存入map操作不当,会导致数据丢失。
    func main() {
    	sliceUsers := []User{
    		User{101, 10},
    		User{102, 20},
    	}
    	mu := make(map[int]*User)
    	for _, u := range sliceUsers {
    		mu[u.iId] = &u   //	mu[u.iId] = &sliceUsers[i]
    	}
    	fmt.Println(mu)
    }
    
    type User struct {
    	iId int
    	iLv int
    }
    
    func (u *User) String() string {
    	return fmt.Sprintf("{Id: %v, Lv: %v}", u.iId, u.iLv)
    }
    
  • map的底层原理:hashmap
    • hmap底层结构:

        type hmap struct {
        	count     int 		//已存储多少结构
        	flags     uint8		//map状态旗帜
        	B         uint8  	//buckets数量是(2^B)个
        	noverflow uint16 	//溢出桶的数量
        	hash0     uint32 	//哈希种子
        	
        	buckets    unsafe.Pointer //一个数组,数量是(2^B)个,哈希桶
        	oldbuckets unsafe.Pointer //迁移的时候的旧地址,平时都是nil
        	nevacuate  uintptr       //迁移进度
        	
        	extra *mapextra 	//溢出桶的情况
        }
      
    • 只关注Bbuckets字段简单的结构图:在这里插入图片描述 - 关于添加:对key进行hash算法处理,根据得出的哈希值选择对应的桶bucket存入

      • 值已使用(哈希冲突):拉链法(Golang采用此方案,上图虚线表示)、开放寻址法
      • 值未使用:
        • 负载因子LF=已存键值count/桶数量2^B,用于判断扩容依据
        • 不需要扩容,直接存入
        • 需要扩容,B++,采用渐进式扩容,分配新的buckets并发生数据迁移
          在这里插入图片描述
    • buckets指向的bucket,是一个bmap结构。现版本中源代码结构中只列出了一个字段,此字段包含此buckets中每个key的哈希值的最高8位(1字节)。

      type bmap struct {
          tophash [bucketCnt]uint8
      }
      
      bucketCntBits = 3
      bucketCnt     = 1 << bucketCntBits		//0000 0001 -> 0000 1000  = 8
      
    • 但是,map 在编译时已经确定了 map 中 key、value 及桶的大小,通过指针还会操作后续的内存空间,所以其实运行起来的bmap长这样。

        type bmap struct {
            tophash  [8]uint8
            keys     [8]keytype
            values   [8]valuetype
            overflow *bmap
        }
      

      在这里插入图片描述

    • overflow字段连接着一个溢出桶,桶满了之后的数据就会放在这儿

    • B>4时,认为使用到溢出桶的概率较大,会分配2*(B-4)个溢出桶,内存布局和常规桶是连续的

  • map引出一个golang-GC的引用计数原理
    • 每个内存数据单元都拥有一个引用数量,当引用数量为 0 时,将其回收。
    • 以刚刚的例子做一个修改
    var mu map[int]*User
    
    func doMap() {
    	sliceUsers := []User{
    		User{101, 10},
    		User{102, 20},
    	}
    
    	fmt.Printf("%p\n", sliceUsers) //0xc000010200
    
    	mu = make(map[int]*User)
    	for i, u := range sliceUsers {
    		mu[u.iId] = &sliceUsers[i]
    	}
    }
    
    func main() {
    	doMap()
    
    	fmt.Println("执行这段话的时候,doMap函数中的sliceUsers地址,还没有被释放")
    	fmt.Printf("%p\n", mu[101]) //0xc000010200
    }
    
    0xc000010200
    执行这段话的时候,doMap函数中的sliceUsers地址,还没有被释放
    0xc000010200
    
    • sliceUsers是在doMap()函数中被声明的,但是他的地址已经被mu所引用,在doMap()函数结束之后没有释放。
3.5 面向对象
3.5.1 方法
  • 定义
    func (s structName)funcName(param) (returnparam){
    }
    
    func (s *structName)funcName(param) (returnparam){
    }
    
  • 使用案例
    type st struct {
    	Param1 int
    }
    
    func (s *st) stCallPoint() {
    	s.Param1 = 100
    	fmt.Println("stCallPoint()")
    }
    
    func (s st) stCall() {
    	s.Param1 = 999
    	fmt.Println("stCall()")
    }
    
    func main() {
    	var myst st
    	fmt.Println("myst.Param1 = ", myst.Param1)
    	myst.stCallPoint()
    	fmt.Println("myst.Param1 = ", myst.Param1)
    	myst.stCall()
    	fmt.Println("myst.Param1 = ", myst.Param1)
    }
    
    myst.Param1 =  0
    stCallPoint()
    myst.Param1 =  100
    stCall()
    myst.Param1 =  100
    
    • 通过在函数名前加上( s structName)或者( s *structName)声明结构体方法
    • 使用指针可以修改相应结构的值
    • (s *structName)相当于一个显式的this指针
    • 不建议写成(this *structName)或者(self *structName)
3.5.2 接口
  • 接口基础
    • 只含方法名、参数、返回值的方法集合,是抽象类型
    • 声明
      type 接口名 interface{
        funcName1( param1 ) paramreturn1
        funcName2( param2 ) paramreturn2
        …
      }
      
    • 实现一个接口,只需要用结构实现接口内相应的方法就行,不需要显式声明实现哪个接口
    • 案例演示
       type Sayer interface {
       	say()
       }
       
       type dog struct{}
       
       type cat struct{}
       
       func (d dog) say() {
       	fmt.Println("doooog")
       }
       
       func (c *cat) say() {
       	fmt.Println("caaaat")
       }
       
       func main() {
       	var x Sayer
       	d1 := dog{}
       	x = d1 
       	x.say()
       	d2 := &dog{}
       	x = d2 
       	x.say()		//(*x).say()     d = x    or   d = *x
       
       	//c1 := cat{}
       	//x = c1 		//error    c = c1
       	//x.say()
       	c2 := &cat{}
       	x = c2 
       	x.say()
       }
       
       var _ Sayer = &cat{}
       var _ Sayer = dog{}
       var _ Sayer = &dog{}
      
      doooog
      doooog
      caaaat
      
    • 接口嵌套,将各个被包含的的接口函数进行结合,成为一个新的接口
  • 空接口 (any)
    • 定义
      type 接口名 interface{
      }
      
    • 因为里面没有方法,所以可以认为所有结构都已经实现了空接口
    • 可以用来处理未知数据
    • 使用案例
      type inter interface {
      }
        
      func myfunc(args ...inter) {
        for k, v := range args {
        	fmt.Println(k, " ", v)
        }
      }
        
      func main() {
        myfunc("lalala", 1.1, 666, false)
      }
      
      0   lalala
      1   1.1
      2   666
      3   false
      
    • 空接口一般用更简便的写法:
      func myfunc(args ...interface{}) {
        for k, v := range args {
        	fmt.Println(k, " ", v)
        }
      }
      
      func main() {
        myfunc("lalala", 1.1, 666, false)
      }
      
  • 空接口类型
    • 类型断言

      • b := a.(structName)
      • 使用案例
        func main() {
        	var inter interface{} = "hello"
        
        	str, ok := inter.(string)
        	if ok {
        		fmt.Println(str)
        	} else {
        		fmt.Println("error")
        	}
        
        	i, ok := inter.(int)
        	if ok {
        		fmt.Println(i)
        	} else {
        		fmt.Println("error2")
        	}
        }
        
        hello
        error2
        
      • 类型断言返回值,可以是一个参数,也可以是两个参数
      • 使用一个参数接收返回值,必须思路清晰知道要转成什么,类型不匹配会崩溃
      • 使用两个参数接收返回值,必须校验成功与否,否则类型不匹配会崩溃
    • switch

      func main() {
      	var inter interface{} = "hello"
      
      	switch inter.(type) {
      	case int:
      		fmt.Println("is int")
      	case float64:
      		fmt.Println("is float")
      	case string:
      		fmt.Println("is string")
      	}
      }
      
      is string
      
    • 反射

      func main() {
         	var inter interface{} = "hello"
         	
         	reflectType := reflect.TypeOf(inter)	//var reflectType reflect.Type
         	reflectValue := reflect.ValueOf(inter)	//var reflectValue reflect.Value
         
         	fmt.Println("type: ", reflectType)
         	fmt.Println("value: ", reflectValue)
      }
      
      type:  string
      value:  hello
      
      • import "reflect"
      • 自动化把空接口转化成想要的对象
      • 注意反射返回值的类型分别是reflect.Typereflect.Value
      • 简单使用
        type st struct {
        	a int
        	b int
        }
        
        func main() {
        	var inter interface{} = st{}
        
        	fmt.Println(reflect.TypeOf(inter).Name(), reflect.TypeOf(inter).NumField())
        }
        
        
        st 2
        
      • 有兴趣可以以TypeOfValueOfTypeValue为核心进行拓展,深入学习
3.5.3 继承
  • 单继承
    type Father struct {
    	FName string
    }
    
    func (this *Father) Say() string {
    	return "父类 " + this.FName
    }
    
    type Child struct {
    	Father
    }
    
    func main() {
    	c := new(Child)
    	c.FName = "AAA"
    	fmt.Println(c.Say())
    }
    
  • 多继承
    type Father struct {
    	FName string
    }
    
    func (this *Father) Say() string {
    	return "父类 " + this.FName
    }
    
    type Mother struct {
    	MName string
    }
    
    func (this *Mother) Say() string {
    	return "母类 " + this.MName
    }
    
    type Child struct {
    	Father
    	Mother
    }
    
    func main() {
    	c := new(Child)
    	c.FName = "AAA"
    	c.MName = "BBB"
    	fmt.Println(c.Father.Say())
    	fmt.Println(c.Mother.Say())
    }
    
  • 结构体中字段是接口名
    type Inter interface {
    	interTest()
    }
    
    func (this *Chlid) interTest() {
    }
    
    type Child struct {
    	Father
    	Mother
    	Inter
    }
    
    => c.Inter = &Child{}
3.6 闭包
  • 闭包 = 函数 + 引用环境
  • 基础一些 – 匿名函数
    func tast() {
    	func() {
    		fmt.Println("里")
    	}()
    	fmt.Println("外")
    }
    
  • 复杂一些
    • 案例1 – 匿名函数做回调
      func call(i int, f func(int) int) {
      	fmt.Println("call()  i = ", i)
      	res := f(i)
      	fmt.Println("call() over res = ", res)
      }
      
      func main() {
      	call(1, func(i int) int {
      		fmt.Printf("it is a callback func!(i = %v)\n", i)
      		i++
      		return i
      	})
      }
      
      call()  i =  1
      it is a callback func!(i = 1)
      call() over res =  2
      
    • 案例2 – 闭包
      func add() func(int) int {
      	var x int
      	return func(y int) int {
      		x += y
      		return x
      	}
      }
      
      func main() {
      	var f = add()
      	fmt.Println(f(1))
      	fmt.Println(f(1))
      	fmt.Println(f(1))
      	f = add()
      	fmt.Println(f(1))
      	fmt.Println(f(1))
      	fmt.Println(f(1))
      }
      
      1
      2
      3
      1
      2
      3
      
3.7 多线程
3.7.1 多线程原理
  • 进程、线程、协程
  • 线程和协程区别:调度机制不同,线程需要内核调度,协程由程序调度(GMP)
    • G-goroutine,go程
    • M-machine,go程调度器
    • P-processer,处理器
    • 资源消耗:协程比线程更加轻量级
  • Golang提倡 ---- “不通过共享内存来交流,要通过交流来共享内存”
3.7.2 chan管道
  • 先进先出、线程安全、引用类型

  • 初始化要用make

    • 不带缓冲区:intChan = make(chan int, 0)
      • 写入后阻塞,直到被读取
      • 读取后阻塞,直到被写入
    • 带缓冲区:intChan = make(chan int, 3)
      • 写入阻塞:缓冲区满
      • 读取阻塞:缓冲区没有数据
  • 遍历时要close(c)关闭管道,使管道不再写入,且要用for range

    func main() {
    	var intChan chan int
    	intChan = make(chan int, 3)
    	intChan <- 10
    	intChan <- 20
    	num := <-intChan
    	fmt.Println("out num:", num)
    	close(intChan) 
    	for v := range intChan {
    		fmt.Println("num in chan:", v)
    	}
    	//num2 := <-intChan
    	//fmt.Println("out num2:", num2)  --遍历完之后管道数据没了
    }
    
      out num: 10
      num in chan: 20
    
  • 死锁deadlock:由channel引起的程序永久阻塞。fatal error: all goroutines are asleep - deadlock!

    • 具体情况在goroutine章节后分析
  • 可以声明为只读或只写,限制操作。var ch chan<- intvar ch <-chan int

    func chanIn(In chan<- int) {
    	In <- 10
    	In <- 20
    	In <- 30
    	//num := <-In
    }
    
    func chanOut(Out <-chan int) {
    	num := <-Out
    	fmt.Println("out num:", num)
    }
    
    func main() {
    	var intChan chan int
    	intChan = make(chan int, 3)
    
    	chanIn(intChan)
    	chanOut(intChan)
    
    	close(intChan) //关闭管道  不能再推入数据
    	for v := range intChan {
    		fmt.Println("num in chan:", v)
    	}
    }
    
    out num: 10
    num in chan: 20
    num in chan: 30	
    
  • select

    • 监听和 channel 有关的 IO 操作
    • 使用
      select {
      	case <- chan1:
      		// 处理语句
      	case chan2 <- num:
      		// 处理语句
      	default:
      		// 处理语句
      }
      
      • 匹配到case执行case内的语句,否则执行default内语句
      • 没有default且没匹配到case,永久阻塞
      • 实际使用一般嵌套for循环,结合协程并加上流程控制
3.7.3 goroutine
  • 3.7.1已经作出铺垫,直接看使用

    func main() {
        go say("Hello World")
    }
     
    func say(s string) {
        fmt.Println(s)
    }
    
    • 这个例子,不会有输出 ---- 主协程优先于子协程结束
      在这里插入图片描述
  • sync.WaitGroup

    • import "sync"
    • 解决主协程等待子协程问题
    • 如果要传递参数要使用wg *sync.WaitGroup
      var wg sync.WaitGroup
      
      func main() {
      	wg = sync.WaitGroup{}
      	wg.Add(1)	//设置计数
      
      	go say("Hello World")
      
      	wg.Wait()	//等待计数变成0
      }
      
      func say(s string) {
      	fmt.Println(s)
      	wg.Done()	//计数减一
      }
      
      Hello World
      
  • 案例

    func add(ya int, ch chan<- int) {
    	func(y int) {
    		ch <- y
    	}(ya)
    }
    
    func opadd(ch chan<- int) {
    	add(1, ch)
    	time.Sleep(2 * time.Second)
    	add(2, ch)
    	time.Sleep(2 * time.Second)
    	add(3, ch)
    }
    
    func main() {
    	var ch chan int = make(chan int, 5)
    
    	go opadd(ch)
    
    	for {
    		time.Sleep(time.Second)
    		select {
    		case v := <-ch:
    			fmt.Println(v)
    		default:
    			fmt.Println("wait")
    		}
    	}
    }
    
    1
    wait
    2
    wait
    3
    wait
    wait
    wait
    ...
    
  • chan死锁的情况

    • 无缓存管道
      • 自导自演
        • 案例
           func main() {
               ch := make(chan int, 0)
           ​
               ch <- 10
               num := <- ch
               fmt.Println(num)
           }
          
        • 解决方案:读写分离
          func in(in chan<- int) {
          	for {
          		time.Sleep(1 * time.Second)
          		in <- 1
          	}
          }
          	
          func out(out <-chan int) {
          	for {
          		time.Sleep(1 * time.Second)
          		num := <-out
          		fmt.Println(num)
          	}
          }
          	
          func main() {
          	ch := make(chan int, 0)
          	go in(ch)
          	
          	go out(ch)
          	
          	select {}
          }
          
      • 没有先开读线程,再开写线程
        • 案例
          func main() {
              ch := make(chan int, 0)
            	ch <- 10
            	go func() {
            		<-ch
            }()
          }
          
        • 解决方案 ---- 先开读线程,再开写线程
          func main() {
              ch := make(chan int, 0)
            	go func() {
            		<-ch
            }()
            ch <- 10
          }
          
      • 嵌套依赖
        • 案例
            func main() {
            	chA := make(chan int, 0)
            	chB := make(chan int, 0)
            
            	go func() {
            		select {
            		case <-chA:
            			chB <- 1
            		}
            	}()
            
            	select {
            	case <-chB:
            		chA <- 2
            	}
            }
          
    • 有缓冲区的管道越界
      func main() {
      	chA := make(chan int, 2)
      
      	chA <- 10
      	chA <- 20
      	chA <- 30
      	close(chA)
      	for i := 0; i < 3; i++ {
      		<-chA
      	}
      }
      
3.8 Context
  • import "context"
  • 一个接口:context.Context
  • 四种实现:emptyCtvcancelCtxtimerCtxvalueCtx
  • 六个函数:BackgroundTODOWithCancelWithDeadlineWithTimoutWithValue
  • 使用案例1:
    func myfunc(ctx context.Context) {
    	fmt.Print(ctx.Value("begin"))
    	fmt.Println("你是猪")
    }
    
    func main() {
    	ctx := context.WithValue(context.Background(), "begin", "这部剧里,女主对男主说:")
    	go myfunc(ctx)
    	time.Sleep(time.Second)
    }
    
    这部剧里,女主对男主说:你是猪
    
  • 使用案例2:
    func myfunc2(ctx context.Context) {
    	fmt.Print(ctx.Value("end"))
    	//fmt.Print(ctx.Value("begin"))	//也能访问到值的
    }
    
    func myfunc(ctx context.Context) {
    	fmt.Print(ctx.Value("begin"))
    	fmt.Println("你是猪")
    	go myfunc2(context.WithValue(ctx, "end", "男主很开心"))
    }
    
    func main() {
    	ctx := context.WithValue(context.Background(), "begin", "这部剧里,女主对男主说:")
    	go myfunc(ctx)
    	time.Sleep(time.Second)
    }
    
    这部剧里,女主对男主说:你是猪
    男主很开心
    
  • 大部分业务函数参数需要带上context,并作为第一个参数
3.9 异常处理
  • error,定义在errors包中
    type error interface {
    	Error() string
    }
    
    func Operate() error{
    	return errors.New("errrrrrr")
    }
    
    func main(){
    	err := Operate()
    	if err != nil{}
    }
    
  • 没有try-catch
  • defer + recover 可以让进入宕机流程中的 goroutine 恢复过来,防止崩溃
    func tast(x int) {
    	defer func() {
    		if err := recover(); err != nil {
    			fmt.Println(err)
    		}
    	}()
    
    	var a [5]int
    	a[x] = 666
    	fmt.Println(a)
    }
    func main() {
    	tast(5)
    	fmt.Println("golang not carsh")
    }
    
    > go run .\main.go
    runtime error: index out of range [5] with length 5
    golang not carsh
    
  • panic自定义错误,终止程序
    	if err != nil{
    		panic(err)
    	}
    
      > go run .\main.go
      panic: errrrrrr
    
3.10 泛型
  • golang在1.18版本提出了对泛型的支持
  • 用法
    func funcName[T int|float64](params ...T)(sum T){
      // 代码内容
      return xxx
    }
    
  • 自定义泛型类型
    type TType interface {
    	~int | ~int32
    }
    
    func funcName[T TType](params ...T)(sum T) {
      // 代码内容
      return xxx
    }
    
  • 案例
    type TType interface {
    	~int | ~int32 | string
    }
    
    func myfunc[T TType, T2 TType](p1t T, p2t T, p1t2 T2, p2t2 T2) (res1 T, res2 T2) {
    	return p1t + p2t, p1t2 + p2t2
    }
    
    func main() {
    	v1, v2 := myfunc(1, 2, "aaaa", "bbbbb")
    	fmt.Println(v1, " ", v2)
    }
    
    • 在函数内运行的T类型,由传入的类型决定,和传入的类型一致
    • 和空接口的区别最大还是对类型的处理
    • 泛型无法和switch一起使用
    • 通常情况使用较少,例如通用工具模块
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值