ebook 学习资料开源项目

我要开发同款
匿名用户2021年11月10日
51阅读
所属分类Google Go、云计算、云原生
授权协议Readme

作品详情

Go编码注意事项

new和make的区别,前者返回的是指针,后者返回引用,且make关键字只能创建channel、slice和map这三个引用类型。

如果User结构想实现Test方法,以下写法:func(this*User)Test(),User的实例和*User都可以调到Test方法,不同的是作为接口时User没有实现Test方法。

interface 作为两个成员实现,一个是类型和一个值,varxinterface{}=(*interface{})(nil) 接口指针x不等于nil。下面一段代码深入展示:

typeUserstruct{IdintNamestringTester}typeTesterinterface{Test()}func(this*User)Test(){fmt.Println(this)}funccreate()Tester{varx*User=nilreturnx}funcTest(t*testing.T){varxTester=create()ifx!=nil{t.Log("notnil")}varu*User=x.(*User)ifu==nil{t.Log("nil")}}

继承通过嵌入实现,也可说go语言没有继承语法。

import关键字前面的"."和"_"的用法,点号表示调用这个包的函数时可以省去包名,下划线表示,纯引入包,因为go语法内没有使用这个包是不能导入的,包引入了,系统会自动调用包的init函数。

selectcase必须是chan的io操作,为了避免饥饿问题,当多个通道都同时监听到数据时,select机制会随机性选择一个通道读取,一个通道被多个select语句监听时,同理。

关闭通道时所有select监听都会收到通道关闭信号,某种意义上关闭通道是广播事件。

goroutine的panic如果没有捕获,整个应用程序会crash,所以安全起见每个复杂的go线都要recover。

在函数退出时,defer的调用顺序是写在后面的先被调用。

init函数在main之前调用,被编译器自动调用,每个包理论上允许有多个init函数,编码上尽量避免同一个包内出现多个init函数。

panic可以中断原有的控制流程,进入一个令人恐慌的流程中,这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组.recover的用法,recover可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在defer函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。

Array和Slice的区别,Array就是一个数据块,值类型而非引用类型,传参时会进行内存拷贝,Slice是个reflect.SliceHeader结构体。Slice由make函数或者Array[:]创建。

闭包要注意循环调用时,upvalue值一不留意可能只是循环退出的值。如下代码:

funcTest(t*testing.T){vardataintfori:=0;i<10;i++{data++gofunc(){listen2(data)}()}<-time.After(time.Second)}funclisten2(dataint){fmt.Print(data)}

输出:26101010101010106,跟你期望的输出可能不一样。

普通类型向接口类型的转换是隐式的,定义该接口变量直接赋值。接口类型向普通类型转换需要类型断言:value,ok:=element.(T)。

Go设计上模糊了堆跟栈的边界,go编译器帮程序员做了对象逃逸分析,优化了内存分配,t:=T{}是可以在函数里返回的,并不是像C语言中在栈里分配内存了。

无论以接口或接口指针传递参数,接口指向的值都会被拷贝传递,引用类型(Map/Chan/Slice)拷贝该引用对象,值类型拷贝整个值(string除外)。

go线程的调用时机是由goruntime决定的。

funcTest(t*testing.T){fori:=0;i<10;i++{golisten2(i)}<-time.After(time.Second)}funclisten2(dataint){fmt.Print(data)}

输出:3456781209

调用log.Fatal系列函数后,会再调用os.Exit(1) 退出程序,FatalisequivalenttoPrint()followedbyacalltoos.Exit(1)。

如果管道关闭则退出for循环,因为管道关闭不会阻塞导致for进入死循环,如下:

//错误的做法funcTest_Select_Chan(t*testing.T){readerChannel:=make(chanint)gofunc(readerChannelchanint){for{select{//判断管道是否关闭case_,ok:=<-readerChannel:if!ok{break}}t.Log("for")}}(readerChannel)close(readerChannel)<-time.After(time.Second*2)}//正确的做法funcTest_Select_Chan1(t*testing.T){readerChannel:=make(chanint)gofunc(readerChannelchanint){for{select{//判断管道是否关闭case_,ok:=<-readerChannel:if!ok{gotoBB//return}}t.Log("for")}BB:}(readerChannel)close(readerChannel)<-time.After(time.Second*2)}

forselect组合不带标签的break语法是跳不出循环,如果要跳出循环,要设置goto标签或者直接return返回。

map,slice,array,chan的数据存取值类型数据都是值拷贝赋值,这个跟很多脚本语言不同,一定要注意:varlist[]mydatavarhashmap[string]mydatatypemydatastruct{Aint}funcTest(t*testing.T){list=make([]mydata,1)data:=list[0]data.A=10hash=make(map[string]mydata)hash["test"]=mydata{}data=hash["test"]data.A=10t.Log(list[0].A,hash["test"].A)}

输出:00

外部可见的属性必须是首字母大写,当转换到json数据是跟预期有偏差,必须添加json标签,如下:typeCMDstruct{Cmdstring`json:"cmd"`DataData`json:"data"`UserIdstring`json:"userId"`}

包的循环引用编译错误,解决方法:提取公共部分到独立的包或者定义接口依赖注入。

returnXXX不是一条原子指令:

funcTest(t*testing.T){t.Log(test())t.Log(test1())}functest()(resultint){deferfunc(){result++}()return1}functest1()(resultint){t:=5deferfunc(){t=t+5}()returnt}

输出:25

returnXXX不是一条原子指令,函数返回过程是,先对返回值赋值,再调用defer函数,然后返回调用函数所以test方法的return1可以拆分为:

result=1func()(resultint){result++}()return内置copy方法,拷贝数组时,如果要整数组拷贝,目标数组长度要和源数组长度相同,否则剩下的数据不会被拷贝:list:=[]int{12,1242,35,23,534,23,1}listNew:=make([]int,1,len(list))copy(listNew,list)fori:=0;i<len(listNew);i++{fmt.Println(listNew[i])}

输出:12

分割切片slice时,新切片引用的内存和老切片引用的是同一块内存:list:=[]int{12,1242,35,23,534,23,1}listNew:=list[:3]listNew2:=list[:5]listNew[1]=999fmt.Println(listNew2[1])

输出:999

编译时设置编译参数去掉调试信息,可以让生成体积更小:gobuild-otarget.exe-ldflags"-w-s"source.go

Go语言里,string字符串类是不可变值类型,字符串的"+"连接操作、字符串和字符数组之间的转换string([]byte)都会生成新的内存存放新字符串,当要对字符串频繁操作时做好先转换成字符数组。但是字符串作为参数传参时,此处go编译器作了优化,不会导致内存拷贝,引用的是同一块内存。benchmark如下:

funcBenchmark_aa(b*testing.B){very_long_string:=""fori:=0;i<b.N;i++{very_long_string+="test"+"andtest"}}funcBenchmark_bb(b*testing.B){very_long_string:=[]byte{}fori:=0;i<b.N;i++{very_long_string=append(very_long_string,[]byte("test")...)very_long_string=append(very_long_string,[]byte("andtest")...)}}

输出:200000135817ns/op5000000039.3ns/op

Gobuild/run/test有个参数-race,设置-race运行时会进行数据竞态检测,并把关键代码打印输出,不过数据竞态不能全依赖race检测,不一定能全部检测出来。

如果非必要必要使用反射reflect和unsafe包内的函数,一定要使用时,要用runtime.KeepAlive函数告知SSA编译器在指定的代码段内不要回收该内存块。

不要打印整个map对象或者对象里有嵌套map的对象,打印函数会不加锁遍历map的每个元素,如果此时外部刚好有方法对map进行写操作,map就进入并发读写,runtime会panic。

注意range循环迭代时key的地址,fork,v:=rangelist其中k在迭代时指向同一个地址。

append追加切片用法:

如果slice还有剩余的空间,可以添加这些新元素,那么append就将新的元素放在slice后面的空余空间中如果slice的空间不足以放下新增的元素,那么就需要重现创建一个数组;这时可能是alloc、也可能是realloc的方式分配这个新的数组也就是说,这个新的slice可能和之前的slice在同一个起始地址上,也可能不是一个新的地址如果容量不足触发realloc,重新分配一个新的地址分配了新的地址之后,再把原来slice中的元素逐个拷贝到新的slice中并返回触发realloc时,容量小于1024,会扩展到原来的1倍,如果容量小大于1024,会扩展原来的1/4很多打印函数打印结构体时回调用该结构体的String方法,所以String不能再打印本身这个对象。如下:typeSstruct{}func(thisS)String()string{returnfmt.Sprint("Sstruct:",this)}funcTest_print(t*testing.T){varsSt.Log(s.String())}

34.循环语句里正整型迭代值边界问题,迭代值边界递减到负值,下面的代码会进入死循环:

fori:=uint8(10);i>=0;i--{t.Log(i)}

recover只处理本goroutine调用栈,goroutine的panic如果没有捕获,整个应用程序会crash,所以安全起见每个复杂的go线都要recover。

map并发读写的错误无法用panic捕获。

对于小对象,直接将值类型的对象交由map保存,远比用该对象指针高效。这不但减少了堆内存分配,关键还在于垃圾回收器不会扫描非指针类型key/value对象。

Go使用channel实现CSP模型。处理双方仅关注通道和数据本身,无需理会对方身份和数量,以此实现结构性解耦。在各文宣中都有“Don'tcommunicatebysharingmemory,sharememorybycommunicating.”这类说法。但这并非鼓励我们不分场合,教条地使用channel。在我看来,channel多数时候适用于结构层面,而非单个区域的数据处理。原话中“communicate”本就表明一种“message-passing”,而非“lock-free”。所以,它并非用来取代mutex,各自有不同的使用场景。

变量逃逸和函数内联状态分析gobuild-gcflags"-m"-omain.exemain.go。

go汇编指令gobuild-gcflags"-N-l"-omain.exemain.go&&gotoolobjdump-s"main\.main"main.exe关闭内联优化:gobuild-gcflags"-N-l"。

关于defer机制,编译器通过runtime.deferproc“注册”延迟调用,除目标函数地址外,还会复制相关参数(包括receiver)。在函数返回前,执行runtime.deferreturn提取相关信息执行延迟调用。这其中的代价自然不是普通函数调用一条CALL指令所能比拟的,单个函数里过多的defer调用可尝试合并。最起码,在并发竞争激烈时,mutex.Unlock不应该使用defer,而应尽快执行,仅保护最短的代码片段。

对map预设容量,map会按需扩张,但须付出数据拷贝和重新哈希成本。如有可能,应尽可能预设足够容量空间,避免此类行为发生。

go语言调用c语言:

import"C"这句代码必须紧跟伪注释的C语言代码后面不能有换行go语言的CGO会自动链接编译.c文件,但是必须用gobuild编译指令,不能指定main.go文件Go语言命名规范:文件命名,全小写+下划线结构体名字、变量名字采用驼峰命名(根据包外可见性确定首字母是否大写)常量命名,全大写+下划线删除切片的指定下标元素低效的做法forj:=0;j<len(array);j++{ifINDEX==j{array=append(array[:j],array[j+1:]...)break}}高效的做法forj:=INDEX;j<len(array)-1;j++{array[j]=array[j+1]}array=array[:len(array)-1]
声明:本文仅代表作者观点,不代表本站立场。如果侵犯到您的合法权益,请联系我们删除侵权资源!如果遇到资源链接失效,请您通过评论或工单的方式通知管理员。未经允许,不得转载,本站所有资源文章禁止商业使用运营!
下载安装【程序员客栈】APP
实时对接需求、及时收发消息、丰富的开放项目需求、随时随地查看项目状态

评论