Go语言拾遗
Contents
go语言中有一些被人误用或忽视的细节。
这里只列出了一些一两句话能表达清楚的问题。对于需要对问题展开才能理解清楚的会在后续文章中给出。
本文内容不包含reflect及unsafe内容。
- 几种创建变量别名的情形:
- 对一个变量取地址操作
- 当copy一个引用类型(如slice,map,channel,function,pointer)
- 当copy一个包含引用类型的array,struct,或interface时
go语言的垃圾回收器需要找到每一个变量的访问者,也就是必须知道一个变量的所有别名。
-
在考虑性能优化的时,需要记住逃逸的变量需要在堆上额外分配内存(以及分配时间更长)
-
在赋值之前,赋值语句右边的所有表达式将会先进行求值。
-
某些类型转换会改变值的数据表示:
- 某些数字类型之间互相转换
- string类型转换成[]byte会allocate一个string的copy
-
每个文件都可以包含任意数量的init function,并按照声明的顺序初始化。
-
if, for, switch 控制流语句,含有两个词法域:一个是由显式的花括号组成,更外一层是隐式的。包含循环的初始化,条件测试,循环后的迭代。
-
switch或select的分支也有独立的语法块
-
rune类型和int32类型等价,通常表示一个unicode码点
-
byte类型和uint8类型等价,通常表示数值的原始字节表示,而很少用于表示一个整数。
-
为什么内置len函数返回一个有符号int?
通常我们可能会认为uint无符号类型表示len更合理。事实上用有符号的int,我们可以用来处理逆循环。
|
|
如果len函数返回是一个无符号类型,那么迭代三次之后i==0时, i—语句将不会产生-1,而是变成一个超大的正整数,ints[i]发生panic
-
无符号数往往只有在位运算或其它特殊的运算场景才会使用,例如 bit集合、 分析二进制文件格式、或者哈希和加密操作。并不是单纯的表达非负的场合。
-
通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大。
|
|
-
浮点数的比较测试需要小心精度问题。
-
字符串的不可变性体现在:
- 当用字符串变量赋值或者使用+进行追加时,会重新分配新的字符串。
- 只能访问,不能用索引对其内部修改。
- copy字符串是低廉的,因为会共享底层数据。同理字符串的切片操作也会共享底层数据。
-
字符串的索引操作s[i]返回的是字节类型值
-
字符串的切片操作s[i:j]返回的是字符串类型值。
-
range循环在处理字符串的时候,会自动隐式解码UTF8字符串成rune。需要注意的是对于非ASCII,索引更新的步长将超过1个字节。
-
常量是一种表达式,它的求值发生在编译期,而不是在运行期。对常量间进行的运算的结果也是常量。
-
编译器为无类型常量提供更高精度的算术运算;你可以认为至少有256bit的运算精度。
-
%取模运算符的符号和被取模数的符号总是一致的,因此 -5%3 和 -5%-3 结果都是-2。
-
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是不同数组类型。
-
struct声明时field顺序对类型标识符也有重要影响。顺序不同对应了不同的struct类型。
-
类型S的struct的filed不可以是S类型。但可以是*S指针类型来创建递归的数据结构。
-
struct{} 和[0]int类型的size为0。
-
“.“操作符应用于struct类型变量或者struct值(例如函数返回值)访问field时,如果”.“前面的是变量,那么”.“表示变量,如果前面的是值,.操作符表示一个值。
-
nil slice 没有底层数组,length和capacity都是0。
-
slice切片操作s[i:j],0 ≤ i≤ j≤ cap(s),j可以超过len(s)。
-
nil值的slice和空slice行为一致。把nil slice 当成空slice是安全的行为。go函数应当以相同的方式对待nil slice 和长度为0的slice。
-
大部分map操作对于nil map和空map行为一致:如查找,删除,len,range。但当往nil map 中分配一个元素将会导致panic。
-
map的元素不是一个变量,不能进行取地址操作。原因是:map增长可能会rehashing,把存在的元素分配到一个新的空间。可能会导致悬挂指针。
-
map是间接引用,所以元素value值可以是其自身
-
大部分函数使用固定大小的的函数调用栈,而go语言使用可变栈,栈的大小按需增加。
-
函数的零值是nil,调用你一个nil 函数会引发panic
-
当循环中有闭包环境时候(如匿名函数,go 语句),要注意捕获迭代量可能产生的问题。
-
在一个函数中执行多条defer语句时,他们的执行顺序和声明顺序相反。
-
defer 函数在return更新返回值后立即执行。之后函数再返回给调用者。如果外层函数返回值有名字,defer匿名函数可以捕获函数的返回值,并对返回值进行修改。
-
如果defer函数是nil时,在defer语句执行时不会panic而是在函数真正执行时panic。
-
循环体中的defer可能会有耗尽资源的风险。
-
go的panic机制使得defer函数调用在释放栈之前。注意函数栈的顺序,如果panic发生在defer之前,defer不会生效。
-
当main function返回时,所有的goroutine会被直接终止,然后程序退出。
-
copy 一个channel时或用于参数传递时,只是copy了一个reference。他们指向同一个数据结构。
-
对一个channel进行close操作,并不是进行垃圾回收。当一个channel 不会再访问时,会进行垃圾回收。无论它是否已经关闭。
-
发送方close一个channel是单方面的标记不会再发送任何值。通常只有发送方才会close,如果对对一个receive-only channel关闭会发生panic。发送方也无需关闭每一个channel。只有需要广播接收方时才需要。对一个已经close的channel,再次发送或者再次close会引发panic。
-
当一个channel close之后,接收方不会阻塞,(如果是带buffer的channel,接收完channel最后一个元素之后),并且会收到一个零值。接收方也可以通过显式的接受bool值,或者用range loop 来判断channel的发送是否结束。
-
从具体类型(包括指针)值赋到接口类型值产生隐式类型转换。并且接口值的动态值部分获得了一份具体类型值的拷贝。如果是*T则获得一份指针的拷贝(接口的细节需要在后续文章中展开讲)。
Author againest1
LastMod 2020-03-06