go语言中经常犯的错误

0.1、索引https://waterflow.link/articles/1664080524986
1、未知的枚举值我们现在定义一个类型是unit32的Status,他可以作为枚举类型,我们定义了3种状态
type Status uint32const ( StatusOpen Status = iota StatusClosed StatusUnknown)其中我们使用了iota,相关的用法自行google 。最终对应的状态就是:
0-开启状态,1-关闭状态,2-未知状态
现在我们假设有一个请求参数过来,数据结构如下:
{"Id": 1234,"Timestamp": 1563362390,"Status": 1}可以看到是一个json类型的字符串,其中就包含了Status状态,我们的请求是希望把状态修改为关闭状态 。
然后我们在服务端创建一个结构体,方便把这些字段解析出来:
type Request struct { IDint`json:"Id"` Timestamp int`json:"Timestamp"` StatusStatus `json:"Status"`}好了,我们在main中执行下代码,看下解析是否正确:
package mainimport ( "encoding/json" "fmt")type Status uint32const ( StatusOpen Status = iota StatusClosed StatusUnknown)type Request struct { IDint`json:"Id"` Timestamp int`json:"Timestamp"` StatusStatus `json:"Status"`}func main() { js := `{"Id": 1234,"Timestamp": 1563362390,"Status": 1}` request := &Request{} err := json.Unmarshal([]byte(js), request) if err != nil {fmt.Println(err)return }}执行后的结果如下:
go run main.go&{1234 1563362390 1}可以看到解析是没问题的 。
然而,让我们再提出一个未设置状态值的请求(无论出于何种原因):
{"Id": 1234,"Timestamp": 1563362390}在这种情况下,请求结构的状态字段将被初始化为其零值(对于 uint32 类型:0) 。因此,StatusOpen 而不是 StatusUnknown 。
最佳实践是将枚举的未知值设置为 0:
type Status uint32const ( StatusUnknown Status = iota StatusOpen StatusClosed)在这里,如果状态不是 JSON 请求的一部分,它将被初始化为 StatusUnknown,正如我们所期望的那样 。
2、指针无处不在?按值传递变量将创建此变量的副本 。而通过指针传递它只会复制内存地址 。
因此,传递指针总是会更快,对么?
如果你相信这一点,请看看这个例子 。这是一个 0.3 KB 数据结构的基准测试,我们通过指针和值传递和接收 。0.3 KB 并不大,但这与我们每天看到的数据结构类型(对于我们大多数人来说)应该相差不远 。
当我在本地环境中执行这些基准测试时,按值传递比按指针传递快 4 倍以上 。这可能有点违反直觉,对吧?
这其实与 Go 中如何管理内存有关 。我们都知道变量可以分配在堆上或栈上,也知道:

  • 栈包含给定 goroutine 的正在进行的变量 。一旦函数返回,变量就会从堆栈中弹出 。
  • 堆包含共享变量(全局变量等) 。
让我们看下下面这个简单的例子:
type foo struct{}func getFooValue() foo { var result foo // Do something return result}这里,一个结果变量由当前的 goroutine 创建 。这个变量被压入当前堆栈 。一旦函数返回,客户端将收到此变量的副本 。变量本身从堆栈中弹出 。它仍然存在于内存中,直到它被另一个变量擦除,但它不能再被访问 。
我们现在修改下上面的例子,使用指针:
type foo struct{}func getFooPointer() *foo { var result foo // Do something return &result}结果变量仍然由当前的 goroutine 创建,但客户端将收到一个指针(变量地址的副本) 。如果结果变量从堆栈中弹出,则此函数的客户端无法再访问它 。
在这种情况下,Go 编译器会将结果变量转移到可以共享变量的地方:堆 。
但是,传递指针是另一种情况 。例如:
type foo struct{}func main(){ p := &foo{} f(p)}因为我们在同一个 goroutine 中调用 f,所以 p 变量不需要被转移 。它只是被压入堆栈,子函数可以访问它 。
比如在 io.Reader 的 Read 方法中接收切片而不是返回切片的直接结果,也不会转移到堆上 。
但是返回一个切片(它是一个指针)会将其转移到堆中 。
为什么堆栈那么快?主要原因有两个: