哦,原来是这么回事:Golang 中的一些常识

前言回想起来使用Go已三年有余 , 有很多踩过的坑 。 Go是门活力四射的语言 , 语法简单但表述能力强大且足够高效 , 但是也有很多细微的点 , 这些点就是一些基本细节实现 , 如果能注意这些细节 , 我相信我们能够对Go的理解能更深一些 , 写的bug会少一些 。
作为一名初学者我们时常写对一些固定的写法 , 不知道为什么要这么写;我们时常写了一些bug , 不知道为什么bug;我们时常知道可以这么写 , 但是不知道那样写是否可以;有时候我们很懒 , 懒得去测试是否可以 , 有时候我们很勤快 , 测试了并且知道答案 , 但是不求甚解;
再深一点?很多时候我们不求甚解 , 这天杀的产品经理又在催好像是个不错的借口 。 慢慢地又觉得自己理解不够深刻 , 所以总是闲暇的时候思考这些问题 。 我相信在二进制的世界里 , nothing is magic, 一定是有Why的 , 因为这是我们所创造的世界 ( AI算法除外 )。
希望这篇文章能够帮到你 , 哪怕只是一点点 。
Common Sense in Go1. interface{} 后面是有{}的现象go中其他的类型都是没有{}的 ,只有interface{}有 。
理解go中其他的类型都是没有{} 的 比如 map[int]int, 但是interface{}都是带{}的 , 据说是为了让你瞅瞅里边什么也没有 。
2. 函数参数是值传递的(Passed by value)现象函数的参数是值传递 , 且在调用的时立即执行值拷贝的 。
理解首先 , 函数调用是值传递的 。
所以无论传递什么参数都会被copy到函数的参数变量的内存地址中 , 堆或者栈上 , 具体是堆还是栈上涉及到逃逸问题 , 这里不做过多分析 。 但是毫无疑问的是 , 在调用时立即对变量进行了Copy , 以下例子中通过打印变量地址佐证 。
func main() {var i intfmt.Printf("main: %p\n",--tt-darkmode-color: #EF7060;">return nil, err , 代码精简更加优雅

  • nil pointer panic 应该通过error handling来解决 , 不然即使没有发生panic , 也会执行错误的逻辑 , 引入更多的问题 。
  • 但是指针传递的同时也带来变量逃逸 , 和GC压力 , 也是一把双刃剑 , 好在大部分情况下不需要特别的对GC进行调优 。 所以 , 在make it simple的理念下 , 在需要时再针对性调优是个不错的选择 。
    所以什么时候我们应该传递值 , 什么时候应该传递指针 , 这主要取决于copy开销和是否需要在函数内部对变量值进行更改 。 我们可以用一个简单的例子测试下两者的性能差距:
    func passedByValue(foo Value) {foo.C = "1"}func passedByPointer(bar *Value) {bar.C = "1"}// 值传递func Benchmark_PassedByValue(b *testing.B) {var val Valuestr := bytes.Buffer{}// 这里为了构建一个大值进行传递 , 小值因为copy代价太小性能差距不明显 。for i:=0; i < 10000000; i ++ {str.Write([]byte("====="))}val.C = str.String()for i := 0; i < b.N; i++ {passedByValue(val)}}// 指针传递func Benchmark_PassedByPointer(b *testing.B) {var val = new(Value)str := bytes.Buffer{}for i:=0; i < 10000000; i ++ {str.Write([]byte("====="))}val.C = str.String()for i := 0; i < b.N; i++ {passedByPointer(val)}}// Benchmark结果差距也很明显 , 但是一般值的copy代价都比较小 , 差距不明显 。 goos: darwingoarch: amd64pkg: demo/goBenchmark_PassedByValue-410000000000.676 ns/opBenchmark_PassedByPointer-410000000000.383 ns/opPASS一般来说 , 基本类型我们都应该传值 , 自定义类型中一般内容不可控 , 所以养成良好的习惯很关键 。 特别注意的是slice、map、ctx是引用值类型 , 所以copy时并没有copy其中数据 , 所以一般也进行值传递 , 除非你要对其中更改其中的元素 。 但如果你需要更改其中的内容 , 还是建议更改完尽量返回回来一个新的 , 像内置的append函数一样 , 通过返回新的地址来实现 。 这样会更加清晰一些 , 写代码时自己尽量不要和自己过不去 。