Go 变量入门

GolangGolangBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

引言

为什么在计算机编程语言中需要变量?这是一个古老的问题。就像我们能记住 LabEx 的 URL 是 labex.io 一样,计算机程序也需要记住一些数据以供使用。

变量的目的是表示一段数据。在本节中,我们将探讨如何在 Go 语言中使用变量。

知识点:

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

Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL go(("`Golang`")) -.-> go/BasicsGroup(["`Basics`"]) go(("`Golang`")) -.-> go/DataTypesandStructuresGroup(["`Data Types and Structures`"]) go(("`Golang`")) -.-> go/FunctionsandControlFlowGroup(["`Functions and Control Flow`"]) go/BasicsGroup -.-> go/values("`Values`") go/BasicsGroup -.-> go/constants("`Constants`") go/BasicsGroup -.-> go/variables("`Variables`") go/DataTypesandStructuresGroup -.-> go/strings("`Strings`") go/DataTypesandStructuresGroup -.-> go/structs("`Structs`") go/FunctionsandControlFlowGroup -.-> go/if_else("`If Else`") go/FunctionsandControlFlowGroup -.-> go/functions("`Functions`") subgraph Lab Skills go/values -.-> lab-149065{{"`Go 变量入门`"}} go/constants -.-> lab-149065{{"`Go 变量入门`"}} go/variables -.-> lab-149065{{"`Go 变量入门`"}} go/strings -.-> lab-149065{{"`Go 变量入门`"}} go/structs -.-> lab-149065{{"`Go 变量入门`"}} go/if_else -.-> lab-149065{{"`Go 变量入门`"}} go/functions -.-> lab-149065{{"`Go 变量入门`"}} end

什么是变量?

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

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

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

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

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

变量的可变性

顾名思义,变量的值是可以改变的,只要这种改变不超过其类型允许的范围。

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

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

通用声明方法

在 Go 语言中,通用方法中用于声明变量的关键字是 var

声明的形式为:var 标识符 类型,即:

var 变量名 变量类型。

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

关键字 解释
int 整数。最常见的数据类型,小学数学中教授的内容。
string 字符串。用双引号包裹的一串字符,例如:"hello,world"
bool 布尔值。表示真或假,有两个可能的值:truefalse

由于变量类型不是本节的重点,因此仅列出最常见的三种类型。

更多类型将在后续课程中讲解。

如何声明一个变量?

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

之所以有这样的输出,是因为值为 1a 和值为 2a 不在同一个变量作用域内(在同一对大括号内),因此编译器将它们视为两个不同的变量。

编译器不会指出你的错误,但会有意外的输出。

在 Go 中规定:

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

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

那么什么是全局变量,什么是局部变量?

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

变量作用域

变量作用域指的是程序中变量有效的范围,即变量可以被使用的范围。

你一定注意到,如果你声明了一个变量但没有使用它,代码将无法编译。

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

您可能感兴趣的其他 Golang 教程