Go 零基础教程

Go 变量声明

在 Go 语言中,变量是用于存储和操作数据的基础构建块。掌握如何正确地声明变量,是写出高效、可维护代码的关键前提。

Go 语言提供了两种主要的变量声明方式:使用传统的 var 关键字,以及使用更现代的短变量声明符 :=。这两种方式各有千秋,也有着不同的适用场景。本章我们将深入探讨它们的细节,帮助你在 Go 程序中游刃有余地管理数据。

1. 使用 var 关键字声明变量

var 关键字是 Go 语言中最正规、最显式的变量声明方式。它提供了一种结构化的语法,允许你清晰地指定变量的名称和具体类型。

1.1 基础语法

使用 var 声明变量的标准语法如下:

var 变量名 变量类型

来看几个具体的例子:

var age int     // 声明一个名为 'age' 的整数 (int) 变量
var name string // 声明一个名为 'name' 的字符串 (string) 变量
var isLoggedIn bool // 声明一个名为 'isLoggedIn' 的布尔 (bool) 变量

极其重要的一点: 在上述声明中,即便我们没有给它们赋值,Go 语言也会自动赋予它们一个“零值” (Zero Value)

  • 对于 int(整数),零值是 0
  • 对于 string(字符串),零值是 ""(空字符串)。
  • 对于 bool(布尔值),零值是 false。 关于零值的更多细节,我们会在后续章节详细讨论。

1.2 声明并初始化

你可以在声明变量的同时,直接赋予它一个初始值:

var age int = 30                 // 声明整数变量 'age' 并赋值为 30
var name string = "张三"         // 声明字符串变量 'name' 并赋值为 "张三"
var isLoggedIn bool = true       // 声明布尔变量 'isLoggedIn' 并赋值为 true

1.3 类型推断 (Type Inference)

Go 语言非常聪明。如果你在声明时提供了初始值,Go 通常能够自动推断出变量的类型。此时,你可以省略类型声明,让代码更简洁:

var age = 30              // Go 自动推断 'age' 为 int 类型
var name = "张三"         // Go 自动推断 'name' 为 string 类型
var isLoggedIn = true     // Go 自动推断 'isLoggedIn' 为 bool 类型

即使省略了类型,Go 依然是一门强类型语言,变量一旦被推断为某种类型,后续就不能再赋给它其他类型的值了。这在保持代码简洁的同时,依然维持了极高的清晰度和安全性。

1.4 批量声明变量

var 关键字还允许你在一条语句中同时声明多个变量。这主要有两种写法:

单行批量声明:

var x, y int = 10, 20      // 声明两个整数变量 x 和 y,并分别赋值为 10 和 20
var a, b = 100, "你好"      // 结合类型推断,同时声明并初始化不同类型的变量

代码块批量声明 (推荐):

var (
    firstName string = "John"
    lastName  string = "Doe"
    age       int    = 30
)

这种“块声明”方式在 Go 语言中非常地道,特别适合将相关的变量放在一起,极大地提升了代码的可读性。

2. 短变量声明符::=

短变量声明符 := 提供了一种极其简短、快捷的方式来声明和初始化变量。它是日常开发中最常用的语法,但它伴随着一些必须遵守的硬性规则。

2.1 基础语法

短变量声明的语法非常直观:

变量名 := 值

例如:

age := 30              // 声明并初始化整数变量 'age'
name := "张三"         // 声明并初始化字符串变量 'name'
isLoggedIn := true     // 声明并初始化布尔变量 'isLoggedIn'

使用 := 时,变量的类型必然是由右侧的值自动推断得出的。

2.2 核心限制规则

虽然 := 很好用,但你不能滥用它。请牢记以下三个限制:

  • 只能在函数内部使用: 短变量声明 := 绝对不能用在包级别(即任何函数之外的全局作用域)。在函数外部,你只能使用 var
package main

// message := "Hello, World!" // 错误!会导致编译失败,包级别不能用 := 

import "fmt"

func main() {
    message := "Hello, World!" // 正确:在函数内部使用
    fmt.Println(message)
}
  • 必须包含声明和初始化: := 运算符代表的是“声明并赋值”。你不能用它来给一个已经存在的变量重新赋值(纯赋值请使用等号 =)。
  • 左侧至少要有一个“新”变量: 当你使用 := 同时给多个变量赋值时,等号左边至少得有一个变量是以前没声明过的新变量。
package main
import "fmt"

func main() {
    age := 30
    name := "张三"

    age, newName := 40, "李四" // 合法:因为 newName 是一个全新的变量
    fmt.Println(age, newName)

    // age, name := 50, "王五" // 错误!编译失败,因为 age 和 name 都已经声明过了,这里没有新变量
}

3. 重新声明与变量遮蔽 (Redeclaration and Shadowing)

理解作用域 (Scope) 对于避免 Bug 至关重要。

3.1 同作用域下的重新声明

正如上面的规则 3 所述,在同一个作用域内,如果 := 左侧至少包含一个新变量,Go 允许对已存在的变量进行“重新声明”。很多人会把这和简单的赋值混淆。

package main
import "fmt"

func main() {
    age := 30
    fmt.Println("年龄:", age) // 输出: 年龄: 30
    
    age, name := 40, "张三"   // 'age' 被重新声明,'name' 则是全新声明的变量
    fmt.Println("年龄:", age, "名字:", name) // 输出: 年龄: 40 名字: 张三
    
    age = 50                 // 这里仅仅是普通的赋值操作 (注意用的是 =)
    fmt.Println("年龄:", age, "名字:", name) // 输出: 年龄: 50 名字: 张三
}

3.2 变量遮蔽 (Shadowing)

当你在一个内层代码块(例如 if 语句里面)声明了一个和外层代码块同名的变量时,就会发生变量遮蔽。这非常容易让人感到困惑,导致不可预期的行为。

package main
import "fmt"

func main() {
    age := 30 // 外层作用域的变量
    
    if true {
        age := 40 // 内层作用域的变量 (它“遮蔽”了外层的 'age')
        fmt.Println("内部年龄:", age) // 输出: 内部年龄: 40
    }
    
    fmt.Println("外部年龄:", age) // 输出: 外部年龄: 30 (外层的 age 并没有被修改!)
}

在这个例子中,if 代码块内部用 := 声明的 age 是一个全新的变量。对内部 age 的任何修改,都完全不会影响到外部原本那个等于 30 的 age

4. var:= 该选哪个?

在实际开发中,你应该根据具体的上下文和意图来选择。

优先使用 var 的场景:

  • 你需要声明一个变量,但暂时不想给它初始值(依赖 Go 的零值机制)。
  • 你需要明确指定变量的具体类型(例如,你想声明一个 float64,但初始值写的是 0)。
  • 你需要在包级别(函数外部)声明全局变量。

优先使用 := 的场景:

  • 在函数内部,你需要一种极其紧凑的方式来同时声明并初始化局部变量。
  • 你希望依靠 Go 的类型推断,减少多余的键盘敲击。

对比总结表:

特性var 关键字:= 短声明符
核心功能声明变量(可选是否初始化)强制同时声明并初始化变量
初始化可选(未初始化则为零值)必须提供初始值
类型推断支持(如果在声明时赋了值)总是通过初始值推断类型
作用域既可用于包级别,也可用于函数级别只能用于函数级别内部
重新声明不允许同名重新声明(只能用 = 赋值)允许(前提是同一行至少带上一个新变量)

5. 综合代码示例与练习题

通过以下实际例子,巩固你的理解。

5.1 示例 1:使用 var 声明变量

package main
import "fmt"

func main() {
    var studentName string  // 声明用于存储学生姓名的 string 变量
    var studentAge int      // 声明用于存储学生年龄的 int 变量
    var studentGPA float64  // 声明用于存储学生绩点的 float64 变量
    var isEnrolled bool     // 声明用于记录入学状态的 bool 变量
    
    // 后续进行赋值
    studentName = "Alice Smith"
    studentAge = 20
    studentGPA = 3.85
    isEnrolled = true
    
    fmt.Println("学生姓名:", studentName)
    fmt.Println("学生年龄:", studentAge)
    fmt.Println("学生绩点:", studentGPA)
    fmt.Println("是否入学:", isEnrolled)
}

5.2 示例 2:使用 := 声明变量

package main
import "fmt"

func main() {
    courseName := "Go 语言基础"   // 声明并初始化 string 变量
    courseDuration := 4          // 声明并初始化 int 变量 (周数)
    isCompleted := false         // 声明并初始化 bool 变量
    
    fmt.Println("课程名称:", courseName)
    fmt.Println("课程时长:", courseDuration, "周")
    fmt.Println("是否结课:", isCompleted)
}