前言大家好,我是asong。每种语言都有自己的语法糖,像java语法糖有方法变长参数、拆箱装箱、枚举、for-each等,Go语言也不例外,它也有自己的语法糖,掌握这些语法糖可以帮助我们提高开发效率,所以本文介绍一些Go语言的语法糖,总结的可能不完整,欢迎评论区补充。变长参数Go语言允许一个函数可以取任意数量的值作为参数。Go语言有一个内置的...运算符。...运算符只能用在函数的最后一个形参中。使用时必须注意:变长参数必须在函数列表的末尾;将变长参数解析为切片,如果变长参数没有值,变长参数的类型必须相同,因为我们的函数可以接收变长参数,那么我们也可以传递切片并使用...传递参数时将它们解包并转换为参数列表。append方法就是最好的例子:varsl[]intsl=append(sl,1)sl=append(sl,sl...)append方法定义如下://slice=append(slice,elem1,elem2)//slice=append(slice,anotherSlice...)funcappend(slice[]Type,elems...Type)[]Type声明了一个长度不确定的数组。该数组具有固定长度。我们在声明数组的时候一定要声明长度,因为数组的长度必须在编译时确定,但是有时候对于想偷懒的我来说,就是不想写数组的长度。有办法让他想通吗?当然,使用...运算符声明数组时,只需填入元素值,剩下的交给编译器;a:=[...]int{1,3,5}//数组长度为3,相当于a:=[3]{1,3,5}有时候我们想声明一个大数组,但是如果某些索引想设置一个特殊的值,我们也可以使用...运算符:a:=[...]int{1:20,999:10}//数组的长度为100,下标为1的元素的值为20,下标为999的元素的值为10,其他元素的值为0。init函数Golanguage提供了一个init函数,在main之前执行功能。每个包初始化完成后,会自动执行init函数。每个包中可以有多个init函数,每个包的源文件中也可以有多个init函数init函数,加载顺序如下:从当前包开始,如果当前包包含多个依赖包,首先初始化依赖包,逐层递归初始化每个包。在每个包中,按照源文件的字典顺序从前到后执行,在每个源文件中,先初始化常量和变量,最后初始化init函数。当有多个init函数时,从前到后依次执行。每个包加载完成后递归返回,最后初始化当前Bag!init函数实现了sync.Once,没有不管包被导入多少次,init函数只会执行一次,所以使用init可以应用于服务注册、中间件初始化、单例模式等,比如我们经常用到的pprof工具,他就用到了init函数,init函数中的路由注册://go/1.15.7/libexec/src/cmd/trace/pprof.gofuncinit(){http.HandleFunc("/io",serveSVGProfile(pprofByGoroutine(computePprofIO)))http.HandleFunc(“/块”,serveSVGProfile(pprofByGoroutine(computePprofBlock)))))http.HandleFunc("/sched",serveSVGProfile(pprofByGoroutine(computePprof))))http.HandleFunc("/regionio",serveSVGProfile(pprofByRegion(computePprofIO)))http.HandleFunc("/regionblock",serveSVGProfile(pprofByRegion(computePprofBlock)))http.HandleFunc("/regionsyscall",serveSVGProfile(pprofByRegion(computePprofSyscall)))http.HandleFunc("/regionsched",serveSVGProfile(pprofByRegion(computePprofSched)))}忽略包导入Go语言设计者代码整洁,设计时尽量避免代码滥用,所以Go语言的包指南必须使用。如果导入了包却没有使用,会出现编译错误。但是在某些场景下,我们会遇到只想导入包却不想使用的情况。比如上面提到的init函数,我们只想初始化包中的init函数,而不会使用包中的任何方法,那么可以使用_操作符号重命名导入一个不用的包:import_"github.com/asong"忽略字段在我们日常的开发中,我们通常会把屎堆起来,遇到可用的方法就直接复用。但是我们不一定要用到这个方法的所有返回值,还得绞尽脑汁给它想个名字。有没有办法不处理不必要的返回值?当然还是_操作符,给空标识符赋不必要的值:_,ok:=test(a,bint)json序列化忽略一个字段在大多数业务场景下,我们都会序列化struct操作,但是有时候我们希望json中的某些字段不参与序列化,-operator可以帮我们处理,Go语言的structure提供了label的功能,在structurelabel中使用-operator来剔除不必要的序列化字段,特殊处理如下:typePersonstruct{namestring`json:"-"`agestring`json:"age"`}json序列化忽略空值字段我们使用json.Marshal进行序列化struct中的空值不会被忽略,默认输出字段类型零值(字符串类型零值是"",对象类型零值是nil...),如果我们想在序列化时忽略这些字段没有值的时候,可以在结构体中加入omitemptytagtag:typeUserstruct{名称字符串`json:"name"`Emailstring`json:"email,omitempty"`Ageint`json:"age"`}functest(){u1:=User{Name:"asong",}b,err:=json.Marshal(u1)如果err!=nil{fmt.Printf("json.Marshalfailed,err:%v\n",err)return}fmt.Printf("str:%s\n",b)}运行结果:str:{"name":"asong","Age":0}我们没有添加的age字段omitempty标签在json中序列化为空值,email字段被忽略;短变量声明必须在每次使用变量时首先声明函数。像我这种懒人,真的不想写了,因为习惯写python那么,Go语言中是否可以不声明变量直接使用呢?我们可以使用name:=表达式的语法形式来声明和初始化局部变量,相比使用var声明可以减少声明步骤:varaint=10等对于a:=10使用short时有两个步骤变量声明注意:短变量声明只能在函数内使用,不能用于初始化全局变量。短变量声明代表引入了一个新的变量,不能在同一个作用域内重复声明变量。如果多变量声明中的其中一个变量是新变量,那么可以使用短变量声明,否则不能重复声明变量;我们通常使用接口进行类型断言,一种是带有方法的接口,另一种是空接口。在Go1.18之前,没有泛型类型,所以我们可以使用空接口{}作为伪泛型。当我们使用空接口{}作为输入或返回值时,我们将使用类型断言来获取我们需要的类型。Go语言中类型断言的语法格式如下:value,ok:=x.(T)orvalue:=x.(T)xisaninterfacetype,Tisaspecifictype,method1isasafeassertion,方法2断言失败,会触发panic;这里,类型断言需要区分x的类型,如果x是空接口类型:空接口类型断言的本质是将eface中的_type与要匹配的类型进行比较,匹配为成功在内存中组装返回值,匹配失败直接清除寄存器,x为非空接口类型则返回默认值:非空接口类型断言的本质是iface中*itab的比较。*itab匹配成功会在内存中组装返回值。如果匹配失败,则直接清除寄存器,返回默认值。切片循环切片/数组是我们经常用到的操作。Go语言提供了forrange语法来快速迭代对象。数组、切片、字符串、映射、通道等都可以遍历。总结起来就是三种方式//方法一:只遍历不关心数据,适用于切片,数组,字符串,maps,channelsforrangeT{}//方法二:遍历获取索引或者数组,切片,arrays,strings是索引,maps是key,channel是key的数据:=rangeT{}//方法三:遍历获取索引和数据,适用于切片,数组,字符串,第一个参数是index,第二个参数是对应的元素值,map的第一个参数是key,第二个参数是对应的value;forkey,value:=rangeT{}判断map的key是否存在。Go语言提供了语法value,ok:=m[key]来判断map中的key是否存在,存在则返回key对应的value,不存在则返回空值:import"fmt"funcmain(){dict:=map[string]int{"asong":1}如果值,ok:=dict["asong"];ok{fmt.Printf(value)}else{fmt.Println("key:asongdoesnotexist")}}select控制结构Go语言提供了select关键字,select配合channel可以让Goroutine等待多个channels同时读取或写入,select会阻塞当前线程或Goroutine,直到channel状态不发生变化。我们先看一个例子:funcfibonacci(chchanint,donechanstruct{}){x,y:=0,1for{select{casech<-x:x,y=y,x+ycase<-done:fmt.Println("over")返回}}}funcmain(){ch:=make(chanint)done:=make(chanstruct{})gofunc(){fori:=0;我<10;i++{fmt.Println(<-ch)}done<-struct{}{}}()fibonacci(ch,done)}select和switch有类似的控制结构,而switch不同的是select的表达式在这种情况下,必须是通道的发送和接收操作。当select中的两个case同时触发时,会随机执行其中一个。为什么会随机执行?引入随机性是为了避免饥饿问题的发生。如果我们每次都按顺序执行,如果这两种case一直满足条件,那么后面的case永远不会执行。在上面的例子中使用select是一个阻塞的发送和接收操作,直到一个通道改变它的状态。我们也可以在select中使用default语句,那么select语句在执行的时候会遇到这两种情况:当有可以发送和接收的通道时,直接处理该通道对应的case;当没有可以发送和接收Channel的通道时,执行default中的语句;注意:在nilchannel上的操作会一直阻塞,如果没有defaultcase,只有nilchannel的select会一直阻塞。总结本文介绍了Go语言的一些开发技巧,也就是Go语言的语法糖。掌握这些可以提高我们的开发效率。你都学会了吗?
