《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
函数返回写入的字节数以及一个错误。如果正确写入了,则err
为nil
,否则,err
为一个非nil
的error
错误值,这在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语句是在函数级别的,即使把它写在大括号(块)中,也只会在调用函数结束时才调用被推迟执行的函数。
使用
《Go in action》读后记录:Go的并发与并行