引言
为什么在计算机编程语言中需要变量?这是一个古老的问题。就像我们能记住 LabEx 的 URL 是 labex.io
一样,计算机程序也需要记住一些数据以供使用。
变量的目的是表示一段数据。在本节中,我们将探讨如何在 Go 语言中使用变量。
知识点:
- 变量声明
- 变量初始化
- 变量使用
- 变量生命周期
- 常量
为什么在计算机编程语言中需要变量?这是一个古老的问题。就像我们能记住 LabEx 的 URL 是 labex.io
一样,计算机程序也需要记住一些数据以供使用。
变量的目的是表示一段数据。在本节中,我们将探讨如何在 Go 语言中使用变量。
知识点:
什么是变量?简单来说,变量是一个用于存储和保存一段可变数据的容器。
在 Go 这样的编译型语言中,变量的类型是固定的。
这意味着一个变量只能保存一种类型的数据。换句话说,变量容器中存储的内容是固定的。
如果一个变量用于保存水果,那么它应该只保存水果。一旦容器中保存了水果,就不能再用来保存饼干。这在代码中体现为变量不能被赋予两种不同类型的数据。
顾名思义,变量的值是可以改变的,只要这种改变不超过其类型允许的范围。
Go 语言对变量有以下规则:
在 Go 语言中,通用方法中用于声明变量的关键字是 var
。
声明的形式为:var 标识符 类型
,即:
var 变量名 变量类型。
Go 中常见的变量类型有哪些?
关键字 | 解释 |
---|---|
int |
整数。最常见的数据类型,小学数学中教授的内容。 |
string |
字符串。用双引号包裹的一串字符,例如:"hello,world" |
bool |
布尔值。表示真或假,有两个可能的值:true 或 false |
由于变量类型不是本节的重点,因此仅列出最常见的三种类型。
更多类型将在后续课程中讲解。
现在让我们声明一个名为 a
的整数变量。
var a int
如何记住它?你可以在心里默念:
定义一个名为 `a` 的变量,它的类型是 int。
与许多传统编程语言不同,Go 中使用的变量类型放在变量名之后。
这种声明变量的方式使代码从左到右更易于阅读,并避免了 C 语言的螺旋式阅读逻辑。更多细节请参考官方文档。
一个好的变量名应该清楚地表明变量的含义。
在命名变量时,我们需要注意其表达性,避免使用缩写。
这里我们将简要介绍变量命名的基本方法:驼峰命名法(Camel Case Naming Convention)。
驼峰命名法使用混合大小写字母来表示一个变量。第一个单词小写,后续每个单词的首字母大写。
例如:currentDate
。第一个单词 current
小写,第二个单词 Date
首字母大写。
通过这种方式,一个表示当前日期的变量很容易理解。
这种命名约定通常用于重要且常用的变量,而临时变量只要不引起重复,可以简化命名。
现在让我们声明三个变量:
var a int // 声明一个名为 a 的整数变量
var b int // 声明一个名为 b 的整数变量
var c int // 声明一个名为 c 的整数变量
细心的同学可能已经注意到,这三个变量 a, b, c
都是 int
类型。
在这种情况下,我们可以使用逗号连接变量名,从而减少代码量。
var a, b, c int // 声明三个变量 a, b, c 为整数
但如果这三个变量的类型不同呢?
var a int // 声明一个名为 a 的整数变量
var b string // 声明一个名为 b 的字符串变量
var c bool // 声明一个名为 c 的布尔变量
我们似乎在导入包时遇到过类似的情况。许多不同名称的包需要一起导入,因此我们可以使用类似的写法:
var (
a int
b string
c bool
)
注意,这种类似于导入包的声明行为通常用于定义全局变量。
在 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() {
// 声明并初始化
var a int = 1
var b string = "labex"
var c bool = true
// 打印变量
fmt.Println("修改前:")
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
// 修改变量
a = 233
b = "labex"
c = false
// 打印修改后的变量
fmt.Println("修改后:")
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
运行代码后,输出如下:
$ go run varExercise.go
修改前:
1
labex
true
修改后:
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 // 类型被推断
fmt.Println(a)
}
现在你甚至不需要使用 var
关键字来定义变量。
这种声明和初始化变量的方式也可以与批量声明方法结合使用:
a, b, c := 0
// 声明变量 a, b, c 为整数并赋初始值 0
a, b, c := 0, "", true
// 分别声明变量 a, b, c 为整数、字符串和布尔值
短声明非常方便,但要注意 :=
不是赋值操作。它是一种声明变量的方式,是 Go 独有的,用于在函数内部声明和初始化局部变量。变量的类型将根据表达式自动推断。
有时我们会编写以下代码:
func main() {
a := 1
println(a)
a := 2
println(a)
}
编译器会告诉你代码有错误,因为变量 a
已经被重新声明。然而,如果写成以下方式:
func main() {
a := 1
if true {
a := 2
println(a) // 输出: 2
}
println(a) // 输出: 1
}
之所以有这样的输出,是因为值为 1
的 a
和值为 2
的 a
不在同一个变量作用域内(在同一对大括号内),因此编译器将它们视为两个不同的变量。
编译器不会指出你的错误,但会有意外的输出。
在 Go 中规定:
函数外的每条语句都必须以关键字开头(如 var
、func
等)。
因此,短变量声明只能用于声明局部变量,不能用于声明全局变量。
那么什么是全局变量,什么是局部变量?
这涉及变量生命周期的概念,将在下一节中解释。
变量作用域指的是程序中变量有效的范围,即变量可以被使用的范围。
你一定注意到,如果你声明了一个变量但没有使用它,代码将无法编译。
换句话说,当 Go 编译时,它会检查每个变量是否被使用,即是否在其作用域内被使用。
我们可以根据变量的声明位置简单地将变量分为三种类型:
在本节中,我们定义的大多数变量都是局部变量:
package main
import "fmt"
func main() { // 函数体
var a int = 1 // 局部变量
fmt.Println(a)
}
局部变量在函数体内定义,例如在 main
函数内定义的 a
。变量 a
的作用域仅限于 main
函数内部。
同时,如果变量在 main
函数中未被使用,编译器会抛出错误。
然而,也可以定义全局变量。
package main
import "fmt"
var a int = 1 // 全局变量
func main() { // 函数体
fmt.Println(a)
}
全局变量在函数体外定义,其作用域覆盖整个程序。即使它们在任何函数中未被调用,编译器也不会报错。
你可以思考一下为什么全局变量即使未被调用也不会产生错误。
这是因为全局变量可能会在另一个包中被调用。
关于形式参数变量的详细信息将在后续的函数相关课程中讲解。
飞鸟尽,良弓藏;狡兔死,走狗烹。 ——《史记》
当一个变量完成其使命后,应该被销毁以减少内存占用。
这样的设计是 Go 高性能和高效利用空间的关键。
在变量的生命周期内,它不能被重新声明。
你可以在 varExercise.go
中编写以下代码:
package main
import "fmt"
func main() {
var a int = 1 // 局部变量,生命周期仅限于整个 main 函数
var a int = 2 // 重新定义
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 // 使用类型推断初始化
如果我们尝试修改常量会发生什么?
package main
import "fmt"
func main() {
const Pi = 3.14159
Pi = 2 // 错误:无法赋值给 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
// 值是固定的,声明有效。
const Pi = 3.14159
// 计算值也是固定的,声明有效。
const c = 1 / Pi
// 内置函数的固定返回值有效。
const le = len("labex")
// 用户自定义函数的返回值不固定,声明无效。
const le = getLen("labby")
// `a` 是一个不固定的变量值,声明无效。
const k = a
常量声明是否有效可以总结为下表:
声明类型 | 是否有效 |
---|---|
固定值和固定值表达式 | 有效 |
非固定值(变量)及其对应的表达式 | 无效 |
内置函数(如 len() )接收固定值和固定值表达式 |
有效 |
用户自定义函数 | 无效 |
让我们回顾一下在本实验中学到的内容:
在本实验中,我们回顾了 Go 中变量的基本用法,演示了在不同情况下声明和使用变量的方式,并介绍了常量。