Go 变量简介

Go 语言Beginner
立即练习

简介

为什么计算机编程语言需要变量?这是一个由来已久的问题。就像我们能记住 LabEx 的 URL 是 labex.io 一样,计算机程序也需要记住一些数据,以便后续使用。

变量的作用是表示一段数据。本节将介绍如何在 Go 语言中使用变量。

知识点:

  • 变量声明
  • 变量初始化
  • 变量使用
  • 变量生命周期
  • 常量

什么是变量?

在本步骤中,你将了解变量表示什么,以及 Go 对变量名称和类型有哪些限制。

什么是变量?简单来说,变量是用于存储和保存一段可变数据的容器。

在 Go 这类编译型语言中,变量的类型是固定的。

变量类型固定是什么意思?

这表示一个变量只能保存一种类型的数据。换句话说,变量容器中存储的数据类型是固定的。

如果一个变量用于存放水果,那么它就只能存放水果。容器存放过水果后,不能再用来存放饼干。在代码中,这体现为一个变量不能被赋予两种不同类型的数据。

变量的可变性

顾名思义,只要变量值的变化不超出其类型允许的范围,变量的值就可以改变。

Go 语言对变量有以下规定:

  • 名称必须由字母、数字和下划线组成。
  • 变量标识符不能以数字开头。
  • 标识符不能是保留关键字。查看保留关键字
  • 变量名区分大小写,但不建议使用不同的大小写来区分两个名称相同的变量。

通用声明方式

在本步骤中,你将学习如何使用 var 关键字声明带类型的变量。

在 Go 语言中,使用通用方式声明变量时,所用的关键字是 var

声明形式为:var identifier type,表示:

var variableName variableType.

Go 中常见的变量类型有哪些?

关键字 说明
int 整数。最常见的数据类型,小学数学中就会学习。
string 字符串。由双引号包围的一串字符,例如:"hello,world"
bool 布尔值。表示真或假,只有两个可能的值:truefalse

由于变量类型不是本节的重点,这里只列出三种最常见的类型。

更多类型将在后续课程中介绍。

如何声明变量?

现在声明一个名为 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
}

之所以会得到这样的输出,是因为上面的值为 1a 和下面的值为 2a 不在同一个变量作用域中,也就是不在同一对花括号内。因此,编译器会将它们视为两个不同的变量。

编译器不会指出你的写法有误,但程序会产生意料之外的输出。

在 Go 中有如下规定:

函数外部的每条语句都必须以关键字开头(例如 varfunc 等)。

因此,短变量声明只能用于声明局部变量,不能用于声明全局变量。

那么,全局变量和局部变量分别是什么?

这涉及变量生命周期的概念,我们将在下一节解释。

变量作用域

在本步骤中,你将比较局部变量、全局变量和形参的作用域。

变量作用域是指变量在程序中生效的范围,也就是变量可以被使用的范围。

你可能已经注意到,如果声明了变量却不使用,代码将无法通过编译。

换句话说,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 中变量的基本用法,演示了不同情况下声明和使用变量的方法,并介绍了常量。