翻译: Effective Go (6)
初始化
尽管从表面上来看,Go的初始化和C或C++差不多,但其实Go的更强大。Go的初始化不仅可以构建复杂的结构,还可以正确处理不同包的初始化顺序问题。
常量
Go中的常量就是普通意义的常量。就算它们被定义为函数的局部变量,也会在编译时创建,并且只能是数字,字符(字节),字符串或布尔类型。正因为编译时的限制,定义常量的表达式必须是常量表达式,能被编译器运算。比如1 << 3是常量表达式,但是math.Sin(math.Pi/4)
不是,因为调用math.Sin是在运行时候调用的。
在Go中,枚举常量使用iota枚举器创建。由于iota可以是表达式的一部分,并且表达式可以被隐式重复,因此可以很容易的创建一些复杂的值集合。
诸如将String附加到用户定义的类型上的能力,使得任意值都可以格式化自己进行打印输出。尽管这常被用在结构(structs),但这技巧对于类似ByteSize的浮点标量也很有用。
表达式YB会打印输出1.00YB,ByteSize(1e13)会输出9.09TB。
上面的代码中,用Sprintf实现ByteSize的String方法是安全的(不会无限递归),安全的原因不是因为类型转换,而是因为%f不是一个字符串格式:Sprintf只会在需要字符串格式时候才会调用String,%f只是需要一个浮点值。
变量
变量可以像常量一样被初始化,不过变量的初始化可以使用更通用的在运行时执行的表达式。
初始化函数
最后,每个源文件可以定义自己的无参init函数去设置所需的状态。(实际上,每个文件可以有多个init函数)最后意味着:在包中的所有变量都初始化过后才调用init,并且只有在其所有导入的包都被初始化之后才执行init。除了不能为声明的初始化外,init函数的常见用法是在实际执行开始前验证或修复程序的某些状态。
方法
指针 VS. 值
正如我们在ByteSize中所见的,可以为任何命名类型(指针或接口除外)定义方法。接收者可以不是结构体。
在上面的切片讨论中,我们写了一个Append函数。其实我们可以把它定义成切片的方法。为此,我们首先声明一个可以绑定该方法的命名类型,任何使该方法的接收者为该类型的值。
这仍然需要方法返回更新过的切片。我们可以通过重新定义该方法,将指向Byteslice的指针作为该方法的接收者来消除这种笨拙,这样方法就能覆盖掉调用者的切片。
实际上,我们可以做的更好。稍微修改方法就能像标准的Write方法一样:
这样的话*ByteSlice就满足标准接口io.Writer,这样很方便,比如,我们可以这么打印输出:
我们传递ByteSlice的地址,因为只有*ByteSlice满足io.Writer。关于指针和值接收器的规则是:值方法可以通过指针和值调用,而指针方法只能通过指针来调用。
这个规则的出现是因为指针方法可以修改接收者;通过值调用时,方法会收到值的拷贝,因此对拷贝的任意修改都不会影响值本身。因此Go不允许出现此类错误。但是有一个例外,当值是可寻址时,Go会自动插入地址运算符来处理在值上调用指针方法的情况。在我们的例子中,变量b是可寻址的,所以我们可以仅使用b.Write调用其Write方法。编译器会帮我们重写成(&b).Write。
顺便说一句,在切片上使用Write的想法对于Bytes.Buffer的实现非常重要。(言外之意,已经被实现了)
版权声明: 本文为 InfoQ 作者【申屠鹏会】的原创文章。
原文链接:【http://xie.infoq.cn/article/3279dacb6fc1441de7dda02f7】。文章转载请联系作者。
评论