前言大家好,我是asong。每种语言都有自己的语法糖,比如Java的语法糖就有变长参数、拆箱装箱、枚举、for-each等方法,Go语言也不例外,它也有自己的语法糖。掌握这些语法糖可以帮助我们提高开发效率,所以本文介绍了Go语言的一些语法糖。总结可能不完整,欢迎补充。变长参数Go语言允许一个函数可以取任意数量的值作为参数。Go语言有一个内置的...运算符。...运算符只能用在函数的最后一个形参中。使用时必须注意以下事项:变长参数必须在函数列表的末尾;将变长参数解析为一个slice,如果变长参数没有值,则变长参数的类型必须相同既然我们的函数可以接收变长参数,那么我们也可以传递slice并使用...在传递参数时将它们解包并转换为参数列表。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函数提供Go语言在main函数之前自动执行的init函数在每个包初始化后执行。每个包中可以有多个init函数,每个包的源文件中也可以有多个init函数。加载顺序如下:从当前包开始,如果当前包包含多个依赖包,则先初始化依赖包,逐层递归初始化每个包,在每个包中,按照字典顺序从前到后执行源文件,每个源文件中,常量和变量先初始化,init函数最后初始化。当有多个init函数时,从前到后依次执行。每个包加载完成后递归返回,最后初始化当前包!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("/block",serveSVGProfile(pprofByGoroutine(computePprofBlock)))http.HandleFunc("/syscall",serveSVGProfile(pprofByGoroutine(computePprofSyscall)))http.HandleFunc("/sched",serveSVGProfile(pprofByGoroutine(computePprofSched)))"http.HandleioFunc("/reg,serveSVGProfile(pprofByRegion(computePprofIO)))http.HandleFunc("/regionblock",serveSVGProfile(pprofByRegion(computePprofBlock)))http.HandleFunc("/regionsyscall",serveSVGProfile(pprofByRegion(computePprofSyscall)))http.HandleFunc("/region",serveSVGProfile(pprofByRegion(computePprofSched)))}忽略导入包Golanguag设计者有代码洁癖,在设计中尽量避免代码滥用,所以必须使用Go语言的引导包,如果不使用引导包会出现编译错误,但是在某些场景下我们会遇到只想要的情况引入包但不使用它,比如上面提到的init函数,我们只想初始化包中的init函数,但是我们不会使用包中的init函数的任何方法,这时可以使用_运算符重命名不使用的导入package:import_"github.com/asong"忽略该字段。在我们的日常开发中,我们通常会一事无成。当我们遇到可以使用的方法时,我们直接复用它,但不返回这个方法的值。不一定全都用上,但是你得绞尽脑汁给他起个名字。有没有办法不处理不必要的返回值?当然还是_操作符,给空的标识符赋不必要的值:_,ok:=test(a,bint)json序列化忽略一个字段在大多数业务场景下,我们都会序列化结构体,但是有时候我们希望json中的某些字段不参与序列化,-运算符可以帮助我们处理,Go语言的结构体提供了标签功能,在结构体标签中使用-运算符可以让不需要序列化的字段被特殊处理为如下:typePersonstruct{namestring`json:"-"`agestring`json:"age"`}json序列化忽略空值字段我们不会使用json.Marshal进行序列化忽略struct中的空值,默认输出字段类型零值(字符串类型零值是“”,对象类型零值是nil...),如果我们想在序列化时忽略这些没有值的字段,可以在结构体标签中添加omitemptytag:type用户结构{名称字符串`json:"name"`电子邮件字符串`json:"email,omitempty"`Ageint`json:"age"`}functest(){u1:=User{Name:"asong",}b,err:=json.Marshal(u1)iferr!=nil{fmt.Printf("json.Marshalfailed,err:%v\n",err)return}fmt.Printf("str:%s\n",b)}运行结果:str:{"name":"asong","Age":0}age字段我们没有在json序列化结果中加上omitemptytag,如果值为空,则忽略email字段;短变量声明必须在每次使用变量时首先声明函数。像我这样的懒人,真的不想写了,因为写惯了python,那么在Go语言中不声明变量也可以直接使用吗?用它?我们可以使用name:=表达式的语法形式来声明和初始化局部变量,相比使用var声明可以减少声明步骤:varaint=10等对于a:=10使用short时有两个步骤变量声明注意:短变量声明只能在函数内使用,不能用于初始化全局变量。短变量声明代表引入了一个新的变量,不能在同一个作用域内重复声明变量。如果多变量声明中的其中一个变量是新变量,那么可以使用短变量声明,否则不能重复声明变量;我们通常使用接口进行类型断言,一种是有方法的接口,一种是空接口。在Go1.18之前,没有泛型类型,所以我们可以使用emptyinterface{}作为伪泛型。当我们使用空接口{}作为输入或返回值时,我们将使用类型断言来获取我们需要的类型。Go语言中类型断言的语法格式如下:value,ok:=x.(T)orvalue:=x.(T)x是接口类型,T是具体类型,第一个方法是a安全断言,如果断言失败,第二种方法将触发恐慌;这里,类型断言需要区分x的类型。如果x是空接口类型:空接口类型断言的本质是比较eface中的_type和要匹配的类型。如果匹配成功,则将返回值组装到内存中。如果匹配失败,则直接清除寄存器,如果x为非空接口类型则返回默认值:非空接口类型断言的本质是iface中*itab的比较。*itab会在匹配成功后将返回值组装到内存中。如果匹配失败,则直接清除寄存器,返回默认值。切片循环切片/数组是我们经常用到的操作。Go语言提供了forrange语法来快速迭代对象。数组、切片、字符串、映射、通道等都可以遍历。总结起来就是三种方式://方式一:只遍历,不关心数据,适用于切片,数组,字符串,maps,channelsforrangeT{}//方式二:遍历获取索引或者arrays,slice,arrays,strings是索引,maps是keys,channel是key的data:=rangeT{}//方法三:遍历获取索引和数据,适用于slice,arrays,strings,第一个参数是index,第二个参数是对应的元素值,map第一个参数是key,第二个参数是对应的value;forkey,value:=rangeT{}判断map的key是否存在。Go语言提供了语法value,ok:=m[key]来判断map中的keykey是否存在,存在则返回key对应的value,不存在则返回a空值:import"fmt"funcmain(){dict:=map[string]int{"asong":1}ifvalue,ok:=dict["asong"];ok{fmt.Printf(value)}else{fmt.Println("key:asongdoesnotexist")}}select控制结构Go语言提供了select关键字,select可以配合channel让Goroutine等待多个channel读写同时。Select会阻塞当前线程或者Goroutine,直到通道状态没有改变。我们先看一个例子: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有类似的控制结构,不同的是select中的case表达式必须是通道的发送和接收操作。当select中的两个case同时触发时,会随机执行其中一个。为什么会随机执行?随机引入是为了避免饥饿问题的发生。如果我们每次都按顺序执行,如果这两个case总是满足条件,那么后面的case永远不会执行。在上面的例子中使用select是一个阻塞的发送和接收操作,直到一个通道改变状态。我们也可以在select中使用default语句,那么select语句在执行过程中会遇到这两种情况:当有Channel可以发送和接收时,直接处理Channel对应的case;当没有可以收发的Channel时,默认执行该语句;注意:对nil通道的操作会一直阻塞,如果没有defaultcase,只会一直阻塞nil通道的select。总结本文介绍了Go语言的一些开发技巧,也就是Go语言的语法糖。掌握这些可以提高我们的开发效率。你都学会了吗?
