Appearance
The Go Programming Language - 变量、常量、作用域
1. 变量
- 所有的内存在 Go 中都是经过初始化的。也就是当一个变量被声明之后,系统会自动赋予它该类型的零值:
int系列为 0、float系列为 0.0、bool为false、string为空字符串、指针为nil等; - 简短定义
name := expression,不能定义全局变量,且必须至少有一个是新定义的; - 匿名变量
_不占用内存空间,不会分配内存; - 可以直接交换两个变量的内容而不需要引入中间变量
i, j = j, i;
Go
// 1、标准格式
var name string
name = "hello world"
var num1, num2 int
num1, num2 = 1, 2
var len float32 = 123.45
var x, y int64 = 100, 200
// 2、省略类型
var name = "hello world"
var num1, num2 = 1, 2
var len = float32(123.45)
var x, y = int64(100), int64(200)
// 3、省略 var 和 类型(简短定义)
name := "hello world"
num1, num2 := 1, 2
len := float32(123.45)
x, y := int64(100), int64(200)
// 4、批量声明
var (
num int
len float32 = 123.45
intArr = [3]int{1, 2, 3}
act func(val string)
data struct {
Id int
Name string
}
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Note
使用简短定义时,在同一个作用域中,已存在同名的变量,则之后的声明初始化,则退化为赋值操作,在新的作用域中,已存在同名的变量,则为新的变量:
Gopackage main import "fmt" func main() { x := 123 fmt.Println(&x, x) x, y := 456, "abc" fmt.Println(&x, x, &y, y) for i := 0; i < 1; i++ { x, y, z := 789, "ABC", 0.9 fmt.Println(&x, x, &y, y, &z, z) } fmt.Println(&x, x, &y, y) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18输出:
Text0xc0000a6058 123 0xc0000a6058 456 0xc000088220 abc 0xc0000a6098 789 0xc000088240 ABC 0xc0000a60a0 0.9 0xc0000a6058 456 0xc000088220 abc1
2
3
4
2. 常量
2.1. 常量的定义
常量定义的格式:const name [type] = expression。 在常量组中如不指定类型和初始化值,则与上一行非空常量右值相同。
Go
// 显示类型定义
const LOW_PREC_PI float32 = 3.14
// 隐式类型定义:
const LOW_PREC_PI = float32(3.14)
// 右值可以一个在编译器就可以其确定值的表达式
const ONE_THIRDS = 1 / 3.0
// 批量声明
const ONE, TWO, THREE = 1, 2, 3
const (
ZERO = 0
HALF = 0.5
ZERO_POINT_FIVE // 没有赋初始值,值默认和上一行一致 0.5
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.2. 字面常量
如果定义常量时没有指定类型,那么它与字面常量一样,是无类型常量。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
例如 0、0.0、0i 和 \u0000 虽然有着相同的常量值,但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样,true 和 false 也是无类型的布尔类型,字符串面值常量是无类型的字符串类型。
在其他语言中,常量通常有特定的类型,比如 -12 在 C 语言中会认为是一个 int 类型的常量。如果要指定一个值为 -12 的 long 类型常量,需要写成 -12l,这有点违反人们的直观感觉。Go 语言的字面常量更接近我们自然语言中的常量概念,它是无类型的。只要这个常量在相应类型的值域范围内,就可以作为该类型的常量,比如上面的常量 -12,它可以赋值给 int、uint、int32、int64、float32、float64、complex64、complex128 等类型的变量。
2.3. iota
iota 常量生成器。在同一个 const 声明语句中,在第一个声明的常量所在行,iota 将会被置为 0,然后在每一个有常量声明的行加 1。
Go
const (
NUM_TWO = 2
NUM_ONE = iota // 1
NUM_ZERO = 0
NUM_THREE = iota // 3
NUM_FOUR // 4
STR_HELLO = "hello"
STR_MYSTERY // "hello"
)
const (
ANOTHER_ZERO = iota // 0
)1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
以下这个例子中,随着 iota 的递增,每个常量对应表达式 1 << iota,是连续的 2 的幂,分别对应一个 bit 位置:
Go
type Flags uint
const (
FlagUp Flags = 1 << iota // is up
FlagBroadcast // supports broadcast access capability
FlagLoopback // is a loopback interface
FlagPointToPoint // belongs to a point-to-point link
FlagMulticast // supports multicast access capability
)1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
下面是一个更复杂的例子,每个常量都是 1024 的幂:
Go
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
3. 作用域
不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
示例一
在函数中词法域可以深度嵌套,因此内部的一个声明可能屏蔽外部的声明。下面的代码有三个不同的变量
x,因为它们是定义在不同的词法域(这个例子只是为了演示作用域规则,但不是好的编程风格):Gofunc main() { x := "hello!" for i := 0; i < len(x); i++ { x := x[i] if x != '!' { x := x + 'A' - 'a' fmt.Printf("%c", x) // "HELLO" (one letter per iteration) } } }1
2
3
4
5
6
7
8
9
10在
x[i]和x + 'A' - 'a'声明语句的初始化的表达式中都引用了外部作用域声明的x变量,稍后我们会解释这个。示例二
下面的
if-else测试链演示了x和y的有效作用域范围:Goif x := f(); x == 0 { fmt.Println(x) } else if y := g(x); x == y { fmt.Println(x, y) } else { fmt.Println(x, y) } fmt.Println(x, y) // compile error: x and y are not visible here1
2
3
4
5
6
7
8第二个
if语句嵌套在第一个内部,因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则:条件部分为一个隐式词法域,然后每个是每个分支的词法域。示例三
Govar cwd string func init() { cwd, err := os.Getwd() // compile error: unused: cwd if err != nil { log.Fatalf("os.Getwd failed: %v", err) } }1
2
3
4
5
6
7
8虽然
cwd在外部已经声明过,但是:=语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的cwd变量。