深入Go语言 - 2

常量、变量、动态类型和静态类型

目录 [−]

  1. 常量
  2. 变量
  3. 变量声明简化
  4. 静态类型和动态类型

本文介绍Go语言的常量和变量的语言细节。

常量

只有数值类型和字符串类型才可以作为常量。

数值类型包括 布尔类型、rune类型、各种整型、浮点类型、复数。

常量值既可以是数值类型的字面量,也可以是字符串字面量,以及常量的等价形式, 如:

  1. 常量表达式, 如 4 * 5
  2. 转换结果是常量, 如 int(10.0)
  3. 内建函数的返回值, 如unsafe.Sizeofcaplen
  4. 复数、实数和虚数的应用
  5. truefalse赋值给bool类型的常量
  6. 内建的 iota

用变量赋值给常量是不允许的:

1
2
var vs = "hello world"
const s = vs //错误

常量的命名还是遵循前一篇文章的介绍,所有你看到一些"奇怪"的常量名不要觉得奇怪:

1
2
const π = 3.1415926
const Π = 3.1415926

常量可以不声明类型(untyped), 它会根据常量值设置默认的类型,默认类型为:

  • bool
  • rune
  • int
  • float64
  • complex128
  • string

在需要类型的上下文中,常量可以隐式转换成相应的类型:

1
2
3
var v1 int = i
var v2 float32 = i
var v3 complex64 = i

注意不同类型的变量是不能转换的:

1
var v4 float64 = v2

你不能将一个不能隐式转换成常量类型的值赋值给常量,比如下面的例子中2147483648.0不能赋值给int32, 溢出了:

1
const i2 int32 = 2147483648.0

Go对常量的底层实现有限制:

  • Represent integer constants with at least 256 bits.
  • Represent floating-point constants, including the parts of a complex constant, with a mantissa of at least 256 bits and a signed exponent of at least 32 bits.
  • Give an error if unable to represent an integer constant precisely.
  • Give an error if unable to represent a floating-point or complex constant due to overflow.
  • Round to the nearest representable constant if unable to represent a floating-point or complex constant due to limits on precision.

声明多个变量的时候可以将声明放在一起:

1
2
3
4
const (
i = 1
s = "hello word"
)

或者将多个常量的定义放在一行中:

1
const i1, i2, i3 = 0, 1, "hello world"

常量也可以定义在函数中:

1
2
3
func G() {
const t = 100
}

变量

变量代表一个值的存储位置,每个值都有类型。

变量在声明的时候如果同时有赋值操作,那么类型可以省略,因为可以根据值推断出来:

1
2
var i1 int = 100
var i2 = 100

类似于常量定义,你可以同时声明多个变量:

1
2
3
4
var (
i3 = 120
i4 = "hello world"
)

或者一行声明多个变量:

1
2
var i5, i6, i7 = 100, "hello world", true
var i8, i9, i10 int

在上面的例子中i8、i9、i10都是 int类型,所以将类型写在最后面,下面的写法是不允许的:

1
var i11 int, i12 int, i13 int //错误

下面的语句也是非法的,因为声明的变量和赋值列表的数值数量必须一样:

1
2
var i14, i15, i16 = 100, 200 //错误
var i17, i18, i19 int = 100, 200 //错误

变量声明简化

如果变量在声明的时候同时初始化,那么它就可以简化。
比如

1
var i1, i2 = 100, "hello world"

可以简化为

1
i1, i2 := 100, "hello world"

记住,这个简写方法只能用在函数中,函数之外的变量声明不能简写,下面的写法是错误的。

1
2
3
4
5
6
package main
s := "中国"
func main() {
}

不像普通的变量声明,简写方式声明的变量可以"重新声明"已有变量,只要保证有一个新的变量在变量列表中即可,当然“重新声明”的变量和原有变量的类型相同。
看下面的例子就容易理解了:

1
2
3
4
5
6
func main() {
var f *os.File
f, err := os.Open("dive-into-go.pdf")
fmt.Println(f, err)
}

这个例子中f变量重新被声明了,程序正常编译,没有错误,这是因为err是新的变量。
下面这个例子就编译不过,因为第4行没有新的变量定义。

1
2
3
4
5
6
7
func main() {
var f *os.File
var err error
f, err := os.Open("dive-into-go.pdf")
fmt.Println(f, err)
}

不能用简写方法赋值给一个结构体的字段(field):

1
2
3
4
5
6
7
8
9
10
type MyFile struct {
var F *os.File
}
func main() {
var mf MyFile
//var err error
mf.F, err := os.Open("dive-into-go.pdf")
fmt.Println(f, err)
}

注意,简写方法有时候会迷惑你,因为它可能shadow一个变量,而且正常编译通过:

1
2
3
4
5
6
7
x := 100
func() {
x := 200
fmt.Println(x) //200
}()
fmt.Println(x) //100

在上面的例子中,如果本意是通过一个方法修改变量x的值为200的话,最终打印结果可能是100,因为第三行实际是声明了一个新的变量。

你可以通过vet工具检查代码中是否包含shadow的代码:

1
go tool vet -shadow main8.go

输出结果为:

1
main8.go:8: declaration of x shadows declaration at main8.go:6:

变量和常量都可以定义在函数内或者包下,但是如果函数内的变量没有被使用,则会编译出错,这是Go语言有意这样设计的。包下的变量和常量,函数内的常量没有这个限制。

参考
http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#short_vars

静态类型和动态类型

静态类型(static type)是变量声明的时候的声明类型,在变量声明、new方法创建对象时或者结构体(struct)的元素的类型定义,参数类型等。

接口(interface)类型的变量还有一个动态类型,它是运行时赋值给这个变量的具体的值的类型(当然值为nil的时候没有动态类型)。一个变量的动态类型在运行时可能改变,
这主要依赖它的赋值。

1
2
3
4
var x interface{} // x 为零值 nil,静态类型为 interface{}
var v *T // v 为零值 nil, 静态类型为 *T
x = 42 // x 的值为 42,动态类型为int, 静态类型为interface{}
x = v // x 的值为 (*T)(nil), 动态类型为 *T, 静态类型为 *T