《effective Go》读后记录

Posted 张伯雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《effective Go》读后记录相关的知识,希望对你有一定的参考价值。


格式化
让所有人都遵循一样的编码风格是一种理想,现在Go语言通过gofmt程序,让机器来处理大部分的格式化问题。gofmt程序是go标准库提供的一段程序,可以尝试运行它,它会按照标准风格缩进,对齐,保留注释,它默认使用制表符进行缩进。Go标准库的所有代码都经过gofmt程序格式化的。
注释
Go注释支持C风格的块注释/* */和C++风格的行注释//。块注释主要用作包的注释。Go官方提倡每个包都应包含一段包注释,即放置在包子句前的一个块注释。对于有多个文件的包,包注释只需要出现在其中一个文件即可。
godoc 既是一个程序,又是一个 Web 服务器,它对 Go 的源码进行处理,并提取包中的文档内容。 出现在顶级声明之前,且与该声明之间没有空行的注释,将与该声明一起被提取出来,作为该条目的说明文档。
命名
  • Go语言的命名会影响语义:某个名称在包外是否可见,取决于其首个字符是否为大写字母。
  • 包:应当以小写的单个单词来命名,且不应使用下划线或驼峰记法。
  • 包名:应为其源码目录的基本名称,例如在 src/pkg/encoding/base64 中的包应作为"encoding/base64" 导入,其包名应为 base64
  • 获取器:若有个名为 owner (小写,未导出) 的字段,其获取器应当名为 Owner(大写,可导出) 而非 GetOwner。若要提供设置器方法,可以选择SetOwner。
  • 接口:只包含一个方法的接口应当以该方法的名称加上 - er 后缀来命名
  • 驼峰记法:Go 中约定使用驼峰记法 MixedCaps 或 mixedCaps
分号
  • Go的词法分析器会用简单的规则来自动插入分号
  • 如果在一行中写多个语句,需要用分号隔开
  • 控制结构的左大括号不能放在下一行,因为根据词法分析器的规则,会在大括号前加入一个分号,造成错误
初始化
常量必须在定义的时候就进行初始化。常量只能是数字、字符、字符串、布尔值等基本类型,定义它们的表达式必须是在编译期就可以求值的类型。使用const来定义一个常量:
const LENGTH int = 10 const WIDTH int = 5
在Go中,枚举常量使用iota来创建,iota是一个自增长的值:
type AudioOutput int const ( OutMute AudioOutput = iota // 0 OutMono // 1 OutStereo // 2 _ _ OutSurround // 5 )
iota总是用于increment,但它也可以用于表达式,在《effective Go》展示了一个定义数量级的表示:
type ByteSize float64 const ( _ = iota // 使用_来忽略iota=0 KB ByteSize = 1 << (10 * iota) // 1 << (10*1) MB // 1 << (10*2) GB // 1 << (10*3) TB // 1 << (10*4) PB // 1 << (10*5) EB // 1 << (10*6) ZB // 1 << (10*7) YB // 1 << (10*8) )
源文件可以定义无参数init函数,该函数在真正执行函数逻辑之前被自动调用,下面的程序简单说明这一点:
package main import "fmt" func init() { fmt.Print("执行init函数0\\n") } func init() { fmt.Print("执行init函数1\\n") } func init() { fmt.Print("执行init函数2\\n") } func main() { fmt.Print("执行main函数\\n") } //output : 执行init函数0 执行init函数1 执行init函数2 执行main函数
可以看到,在执行main函数中的逻辑前,init函数会先被调用,而且同一个源文件中可以定义多个init函数。init函数通常被用在程序真正执行之前对变量、程序状态进行校验。它的执行机制是这样的:
  • 该包中所有的变量都被初始化器求值后,init才会被调用
  • 之后在所有已导入的包都被初始化之后,init才会被调用
控制结构
Go使用更加通用的for来代替do与while循环,for的三种形式为:
// Like a C for for init ; condition;post { } //Like a C while for condition{ } //Like a C for(;;) for {}
对于数组、切片、字符串、map,或者从信道读取消息,可以使用range子句
for key ,value := range oldMap { newMap[key] = value }
Go的switch要更加灵活通用,当switch后面没有表达式的时候,它将匹配ture,这也意味着if-else-if-else链可以使用switch来实现:
func unhex(c byte) byte { switch { //switch将匹配true case \'0\' <= c && c <= \'9\': return c - \'0\' case \'a\' <= c && c <= \'f\': return c - \'a\' + 10 case \'A\' <= c && c <= \'F\': return c - \'A\' + 10 } return 0 }
函数
Go的函数可以进行多值返回。在C语言中经常有这种笨拙的用法:函数通过返回值来告知函数的执行情况,例如返回0代表无异常,返回-1表示EOF等,而通过指针实参来传递数据给外部。现在使用Go函数的多值返回可以解决解决这个问题。下面是Go标准库中打开文件的File.Write的签名:
func (file *File) Write(b []byte) (n int, err error)
Write函数返回写入的字节数以及一个错误。如果正确写入了,则errnil,否则,err为一个非nilerror错误值,这在Go中是一种常见的编码风格。
Go函数的返回值可以被命名。Go的返回值在函数体内可以作为常规的变量来使用,称为结果“形参”结果“形参”在函数开始执行时被初始化与其类型相应的零值。如果函数执行了不带参数的return,则把结果形参的当前值返回:
func abs(i int) (result int){ if i < 0{ result = -i //返回值result可以直接当成常规变量使用 } return }
这样做的好处是函数的签名即为文档,返回值的含义也写到了函数签名中,提高了代码的可读性。
Go提供defer语句用于延迟执行函数。defer语句修饰的函数,在外层函数结束之前被调用。可以这样来使用defer语句:
func printStr (a string){ fmt.Print(a); } func main() { defer printStr("one\\n") defer printStr("two\\n") defer printStr("three\\n") fmt.Print("main()\\n") } //output : main() three two one
关于defer语句
  • 适用于关闭打开的文件,避免多个返回路径都需要去关闭文件。
  • 被推迟执行的函数的实参,才推迟执行时就会求值,而不是在调用执行时才求值。
  • 被推迟的函数按照后进先出(LIFO)的顺序执行。
  • defer语句是在函数级别的,即使把它写在大括号(块)中,也只会在调用函数结束时才调用被推迟执行的函数。