简介
为什么计算机编程语言需要变量?这是一个由来已久的问题。就像我们能记住 LabEx 的 URL 是 labex.io 一样,计算机程序也需要记住一些数据,以便后续使用。
变量的作用是表示一段数据。本节将介绍如何在 Go 语言中使用变量。
知识点:
- 变量声明
- 变量初始化
- 变量使用
- 变量生命周期
- 常量
什么是变量?
在本步骤中,你将了解变量表示什么,以及 Go 对变量名称和类型有哪些限制。
什么是变量?简单来说,变量是用于存储和保存一段可变数据的容器。
在 Go 这类编译型语言中,变量的类型是固定的。
变量类型固定是什么意思?
这表示一个变量只能保存一种类型的数据。换句话说,变量容器中存储的数据类型是固定的。
如果一个变量用于存放水果,那么它就只能存放水果。容器存放过水果后,不能再用来存放饼干。在代码中,这体现为一个变量不能被赋予两种不同类型的数据。
变量的可变性
顾名思义,只要变量值的变化不超出其类型允许的范围,变量的值就可以改变。
Go 语言对变量有以下规定:
- 名称必须由字母、数字和下划线组成。
- 变量标识符不能以数字开头。
- 标识符不能是保留关键字。查看保留关键字
- 变量名区分大小写,但不建议使用不同的大小写来区分两个名称相同的变量。
通用声明方式
在本步骤中,你将学习如何使用 var 关键字声明带类型的变量。
在 Go 语言中,使用通用方式声明变量时,所用的关键字是 var。
声明形式为:var identifier type,表示:
var variableName variableType.
Go 中常见的变量类型有哪些?
| 关键字 | 说明 |
|---|---|
int |
整数。最常见的数据类型,小学数学中就会学习。 |
string |
字符串。由双引号包围的一串字符,例如:"hello,world" |
bool |
布尔值。表示真或假,只有两个可能的值:true 或 false |
由于变量类型不是本节的重点,这里只列出三种最常见的类型。
更多类型将在后续课程中介绍。
如何声明变量?
现在声明一个名为 a 的整数变量。
var a int
如何记住这种写法?你可以在心里默念:
Define a variable named `a`, which is of type int.
与许多传统编程语言不同,Go 将变量类型放在变量名之后。
这种变量声明方式让代码从左到右更容易阅读,也避免了 C 语言中从内向外的螺旋式阅读逻辑。详情请参考官方文档。
应该为变量取什么名称?
好的变量名应该清楚地表示变量的含义。
为变量命名时,要注意名称的表达性,避免使用缩写。
下面简要介绍一种基本的变量命名方式:驼峰命名法。
驼峰命名法使用大小写混合的字母表示变量。第一个单词使用小写字母开头,后续每个单词的首字母大写。
例如:currentDate。第一个单词 current 以小写字母开头,第二个单词 Date 以大写字母开头。
使用这种方式,表示当前日期的变量就很容易理解。
这种命名方式通常用于重要且常用的变量。只要不会造成重复,临时变量可以使用更简短的名称。
批量声明方式
在本步骤中,你将学习如何一次声明多个变量。
现在声明三个变量:
var a int // Declare an integer variable named a
var b int // Declare an integer variable named b
var c int // Declare an integer variable named c
细心的同学可能已经注意到,三个变量 a, b, c 的类型都是 int。
这种情况下,可以使用逗号连接变量名,从而减少代码量。
var a, b, c int // Declare three variables a, b, c as integers
如果三个变量的类型不同,该怎么办?
var a int // Declare an integer variable named a
var b string // Declare a string variable named b
var c bool // Declare a boolean variable named c
在导入包时,我们似乎遇到过类似的情况。多个名称不同的包需要一起导入,因此可以使用类似的写法:
var (
a int
b string
c bool
)
注意,这种类似导入包的声明方式通常用于定义全局变量。
默认初始化
在本步骤中,你将检查 Go 为变量分配的默认值。
在 Go 中,声明变量时,所有变量都会获得初始值。下面来查看变量的初始值是什么。
在目录 ~/project 中创建名为 varExercise.go 的文件。
touch ~/project/varExercise.go
将以下代码写入该文件:
package main
import "fmt"
func main() {
var a int
var b string
var c bool
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
你可以自行运行这段代码,检查输出是否与下表一致。
go run varExercise.go
初始值的类型总结如下:
| 关键字 | 说明 | 初始值 |
|---|---|---|
int |
整数 | 0 |
string |
字符串 | "" |
bool |
布尔值 | false |
标准初始化
在本步骤中,你将显式初始化变量,然后为变量赋予新值。
由于变量的类型可以根据初始值确定,那么我们能否修改默认值,或者修改已经声明的变量呢?
var a int = 1
var b string = "labex"
var c bool = true
a = 233
b = "labex"
c = false
如上所示,只需在声明变量后添加 =,再写入一个与变量类型兼容的初始值即可。如果要修改变量值,只需写出变量名,接着使用 =,然后赋予同类型的另一个值。
修改 varExercise.go 文件:
package main
import "fmt"
func main() {
// Declare and initialize
var a int = 1
var b string = "labex"
var c bool = true
// Print the variables
fmt.Println("Before modification:")
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
// Modify the variables
a = 233
b = "labex"
c = false
// Print the modified variables
fmt.Println("After modification:")
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
运行代码后,输出如下:
$ go run varExercise.go
Before modification:
1
labex
true
After modification:
233
labex
false
你可以自行运行代码,并修改初始值。
前面提到,赋给变量的初始值必须与变量声明的类型相同。如果两者类型不同,会发生什么?
例如,将 "labex" 作为初始值赋给变量 a:
package main
import "fmt"
func main() {
var a int = "labex"
fmt.Println(a)
}
运行代码:
$ go run varExercise.go
## command-line-arguments
./varExercise.go:6:12: cannot use "labex" (type untyped string) as type int in assignment
如上所示,不能将 "labex" 这样的字符串赋给 int 类型的变量。这是因为 Go 是一种强类型编译型语言,代码无法通过编译。
类型推断声明
在本步骤中,你将学习如何使用类型推断和 := 运算符声明局部变量。
既然 Go 可以根据变量的初始值确定变量类型,那么能否省略显式指定类型这一步,从而简化类型声明过程?
package main
import "fmt"
func main() {
// var a int = 1
var a = 1 // Type is inferred
fmt.Println(a)
}
现在,你甚至不需要使用 var 关键字来定义变量。
这种声明并初始化变量的方式也可以与批量声明方式结合使用:
a, b, c := 0, 0, 0
// Declare variables a, b, c as integers and assign 0 to each variable.
也可以为变量初始化不同类型的值:
a, b, c := 0, "", true
// Declare variables a, b, c as integer, string, and boolean, respectively.
:= 左侧的每个变量,都需要在右侧有一个对应的初始值。
短变量声明非常方便,但要注意,:= 不是赋值操作。它是一种变量声明方式,是 Go 特有的语法,用于在函数内部声明并初始化局部变量。变量的类型会根据表达式自动推断。
有时我们会写出以下代码:
func main() {
a := 1
println(a)
a := 2
println(a)
}
编译器会提示代码存在错误,因为变量 a 被重复声明了。但是,如果写成下面这样:
func main() {
a := 1
if true {
a := 2
println(a) // Output: 2
}
println(a) // Output: 1
}
之所以会得到这样的输出,是因为上面的值为 1 的 a 和下面的值为 2 的 a 不在同一个变量作用域中,也就是不在同一对花括号内。因此,编译器会将它们视为两个不同的变量。
编译器不会指出你的写法有误,但程序会产生意料之外的输出。
在 Go 中有如下规定:
函数外部的每条语句都必须以关键字开头(例如 var、func 等)。
因此,短变量声明只能用于声明局部变量,不能用于声明全局变量。
那么,全局变量和局部变量分别是什么?
这涉及变量生命周期的概念,我们将在下一节解释。
变量作用域
在本步骤中,你将比较局部变量、全局变量和形参的作用域。
变量作用域是指变量在程序中生效的范围,也就是变量可以被使用的范围。
你可能已经注意到,如果声明了变量却不使用,代码将无法通过编译。
换句话说,Go 在编译时会检查每个变量是否被使用,也就是检查变量是否在其作用域内被使用。
根据变量的声明位置,可以简单地将变量分为三类:
- 在函数内部定义的变量,称为局部变量
- 在函数外部定义的变量,称为全局变量
- 在函数定义中定义的变量,称为形参
局部变量
本节中定义的大多数变量都是局部变量:
package main
import "fmt"
func main() { // Function body
var a int = 1 // Local variable
fmt.Println(a)
}
局部变量定义在函数体内部,例如在 main 函数中定义的 a。变量 a 的作用域仅限于 main 函数内部。
同时,如果变量没有在 main 函数中使用,编译器就会报错。
全局变量
当然,也可以定义全局变量。
package main
import "fmt"
var a int = 1 // Global variable
func main() { // Function body
fmt.Println(a)
}
全局变量定义在函数体外部,其作用域覆盖整个程序。即使没有在任何函数中调用它,编译器也不会报错。
你可以思考一下,为什么全局变量即使没有被调用也不会产生错误。
答案
这是因为全局变量可能会在另一个包中被调用。
关于形参的详细内容,将在后续与函数相关的课程中介绍。
变量生命周期
在本步骤中,你将了解变量可以保持可访问状态的时间,以及为什么不能在同一作用域中重复声明变量。
飞鸟尽,良弓藏;狡兔死,良犬烹。——《史记》
变量完成其用途后,应当被销毁,以减少内存使用。
- 全局变量:全局变量的生命周期与整个程序的运行时间一致。程序停止运行时,全局变量会从内存中清除。
- 局部变量:当变量无法再被访问时,其占用的内存空间会被回收。
这种设计是 Go 实现高性能和高效利用空间的关键。
在变量的生命周期内,不能重新声明该变量。
你可以在 varExercise.go 中写入以下代码:
package main
import "fmt"
func main() {
var a int = 1 // Local variable, lifetime is limited to the entire main function
var a int = 2 // Redefinition
fmt.Println(a)
}
运行代码:
go run varExercise.go
你将看到以下错误信息:
./varExercise.go:7:9: a redeclared in this block
previous declaration at ./varExercise.go:6:9
编译器提示我们,a 被再次定义了。
常量
在本步骤中,你将声明常量,并判断哪些表达式可以作为有效的常量值。
生活中的许多事物都像常量。我们可以感知它们,却无法改变它们。
如果某个值在整个程序运行期间都不会改变,就应该将其定义为常量。
常量与变量非常相似,你甚至可以将常量理解为值不可变的变量。
声明常量时,只需将 var 关键字替换为 const 关键字。
const Pi = 3.14159 // Using type inference initialization
在目录 ~/project 中创建名为 constExercise.go 的文件。
touch ~/project/constExercise.go
如果尝试修改常量,会发生什么?
package main
import "fmt"
func main() {
const Pi = 3.14159
Pi = 2 // Error: cannot assign to Pi
fmt.Println(Pi)
}
运行代码。
$ go run constExercise.go
## command-line-arguments
./constExercise.go:7:8: cannot assign to Pi
编译器会提示,不能再次为 Pi 赋值。
声明常量时,必须提供初始值。
而且,赋给常量的初始值必须在编译时固定。
在 Go 中,用户自定义函数的返回值不被视为固定值。
var a int = 1
// Value is fixed, declaration is valid.
const Pi = 3.14159
// The calculated value is also fixed, the declaration is valid.
const c = 1 / Pi
// Fixed return value from built-in function is valid.
const le = len("labex")
// The return value of the user-defined function is not fixed, the declaration is invalid.
const le = getLen("labby")
// `a` is a variable value that is not fixed, the declaration is invalid.
const k = a
常量声明是否有效,可以总结为下表:
| 声明类型 | 有效性 |
|---|---|
| 固定值和固定值表达式 | 有效 |
| 非固定值(变量)及其对应表达式 | 无效 |
内置函数(len())接收固定值和固定值表达式 |
有效 |
| 用户自定义函数 | 无效 |
总结
回顾一下本实验中学到的内容:
- 声明变量的方法
- 初始化变量的方法
- 变量生命周期的概念
- 常量的使用方式,以及如何判断常量声明是否有效
在本实验中,我们回顾了 Go 中变量的基本用法,演示了不同情况下声明和使用变量的方法,并介绍了常量。



