成都网站建设与开发,WordPress上放广告,北京丰台区网站建设,建筑工程类人才招聘和Python、Ruby等动态脚本语言不同#xff0c;Go语言沿袭了静态编译型语言的传统#xff1a;使用变量之前需要先进行变量的声明。 变量声明形式使用决策流程图 这里大致列一下Go语言常见的变量声明形式#xff1a;
var a int32
var s string hello
var i 13
… 和Python、Ruby等动态脚本语言不同Go语言沿袭了静态编译型语言的传统使用变量之前需要先进行变量的声明。 变量声明形式使用决策流程图 这里大致列一下Go语言常见的变量声明形式
var a int32
var s string hello
var i 13
n : 17
var (
crlf []byte(\r\n)
colonSpace []byte(: )
)如果让Go语言的设计者重新设计一次变量声明语法相信他们很大可能不会再给予Gopher这么大的变量声明灵活性但目前这一切都无法改变。对于以面向工程著称且以解决规模化问题为目标的Go语言Gopher在变量声明形式的选择上应尽量保持项目范围内一致。
Go语言有两类变量。
● 包级变量package variable在package级别可见的变量。如果是导出变量则该包级变量也可以被视为全局变量。
● 局部变量local variable函数或方法体内声明的变量仅在函数或方法体内可见。
下面来分别说明实现这两类变量在声明形式选择上保持一致性的一些最佳实践。
包级变量的声明形式
包级变量只能使用带有var关键字的变量声明形式但在形式细节上仍有一定的灵活度。我们从声明变量时是否延迟初始化这个角度对包级变量进行一次分类。
1. 声明并同时显式初始化
下面是摘自Go标准库中的代码Go 1.12
// $GOROOT/src/io/pipe.go
var ErrClosedPipe errors.New(io: read/write on closed pipe)// $GOROOT/src/io/io.go
var EOF errors.New(EOF)
var ErrShortWrite errors.New(short write)我们看到对于在声明变量的同时进行显式初始化的这类包级变量实践中多使用下面的格式
var variableName InitExpressionGo编译器会自动根据等号右侧的InitExpression表达式求值的类型确定左侧所声明变量的类型。
如果InitExpression采用的是不带有类型信息的常量表达式比如下面的语句
var a 17
var f 3.14则包级变量会被设置为常量表达式的默认类型以整型值初始化的变量aGo编译器会将之设置为默认类型int而以浮点值初始化的变量fGo编译器会将之设置为默认类型float64。
如果不接受默认类型而是要显式为包级变量a和f指定类型那么有以下两种声明方式
// 第一种
var a int32 17
var f float32 3.14// 第二种
var a int32(17)
var f float32(3.14)从声明一致性的角度出发Go语言官方更推荐后者这样就统一了接受默认类型和显式指定类型两种声明形式。尤其是在将这些变量放在一个var块中声明时我们更青睐这样的形式
var (
a 17
f float32(3.14)
)而不是下面这种看起来不一致的声明形式
var (
a 17
f float32 3.14
)2. 声明但延迟初始化
对于声明时并不显式初始化的包级变量我们使用最基本的声明形式
var a int32
var f float64虽然没有显式初始化但Go语言会让这些变量拥有初始的“零值”。如果是自定义的类型保证其零值可用是非常必要的这一点将在后文中详细说明。
3. 声明聚类与就近原则
Go语言提供var块用于将多个变量声明语句放在一起并且在语法上不会限制放置在var块中的声明类型。
但是我们一般将同一类的变量声明放在一个var块中将不同类的声 明放在不同的var块中或者将延迟初始化的变量声明放在一个var块而将声明并显式初始化的变量放在另一个var块中称之为“声明聚类”。
比如下面Go标准库中的代码
// $GOROOT/src/net/http/server.go
var (
bufioReaderPool sync.Pool
bufioWriter2kPool sync.Pool
bufioWriter4kPool sync.Pool
)
var copyBufPool sync.Pool {
New: func() interface{} {
b : make([]byte, 32*1024)
return b
},
}
...
// $GOROOT/src/net/net.go
var (
aLongTimeAgo time.Unix(1, 0)
noDeadline time.Time{}
noCancel (chan struct{})(nil)
)
var threadLimit chan struct{}
...我们看到在server.go中copyBufPool变量没有被放入var块中因为它的声明带有显式初始化而var块中的变量声明都是延迟初始化的
net.go中的threadLimit被单独放在var块外面一方面是考虑它是延迟初始化的变量声明另一方面是考虑threadLimit在含义上与var块中标识时间限制的变量有所不同。
局部变量的声明形式
1. 对于延迟初始化的局部变量声明采用带有var关键字的声明形式
比如标准库strings包中byteReplacer的方法Replace中的变量buf
// $GOROOT/src/strings/replace.go
func (r *byteReplacer) Replace(s string) string {
var buf []byte // 延迟分配
for i : 0; i len(s); i {
b : s[i]
if r[b] ! b {
if buf nil {
buf []byte(s)
}
buf[i] r[b]
}
}
if buf nil {
return s
}
return string(buf)
}另一种常见的采用带var关键字声明形式的变量是error类型的变量err将error类型变量实例命名为err也是Go的一个惯用法尤其是当defer后接的闭包函数需要使用err判断函数/方法退出状态时。示例代码如下
func Foo() {
var err error
defer func() {
if err ! nil {
...
}
}()
err Bar()
...
}2. 对于声明且显式初始化的局部变量建议使用短变量声明形式
短变量声明形式是局部变量最常用的声明形式它遍布Go标准库代码。对于接受默认类型的变量可以使用下面的形式
a : 17
f : 3.14
s : hello, gopher!3. 尽量在分支控制时应用短变量声明形式
这应该是Go中短变量声明形式应用最广泛的场景了。在编写Go代码时我们很少单独声明在分支控制语句中使用的变量而是通过短变量声明形式将其与if、for等融合在一起就像下面这样
// $GOROOT/src/net/net.go
func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
// 笔者注在if循环控制语句中使用短变量声明形式
if wv, ok : w.(buffersWriter); ok {
return wv.writeBuffers(v)
}
// 笔者注在for条件控制语句中使用短变量声明形式
for _, b : range *v {
nb, err : w.Write(b)
n int64(nb)
if err ! nil {
v.consume(n)
return n, err
}
}
v.consume(n)
return n, nil
}这样的应用方式体现出“就近原则”让变量的作用域最小化了。 想做好代码中变量声明的一致性需要明确要声明的变量是包 级变量还是局部变量、是否要延迟初始化、是否接受默认类型、是否为分支控制变量并结合聚类和就近原则。