5.5 变量的作用域
在 Go 语言中,变量的作用域定义了它们在代码中可以被访问的区域。变量的作用域通常由它们声明的位置决定。Go 语言有以下四个级别的作用域:
- 全局作用域:在函数体外部定义的变量具有全局作用域。它们可以在整个程序中被访问,包括其他源文件。
- 函数级别作用域:在函数体内部声明的变量具有函数级别作用域。它们只能在声明它们的函数中被访问。
- 块级别作用域:在 if、for 或 switch 语句的块中声明的变量具有块级别作用域。它们只能在声明它们的语句块中被访问。
此外,
5.5.1 全局作用域
在 Go 语言中,全局变量是在函数体之外声明的变量。它们具有全局作用域,因此可以在程序中的任何位置访问和使用。可以在任何源文件中定义全局变量,只要在使用它们的源文件中使用 import 导入相应的包即可。
全局变量在程序启动时就已经分配了内存空间,并且在整个程序执行期间都可以使用。需要注意的是,全局变量在多个 Goroutine 之间共享,因此在并发程序中使用时需要特别小心,需要采取必要的同步措施以确保数据的正确性。
下面是一个简单的例子(源码路径:Go-codes\5\quan.go),展示了在全局作用域中声明变量的过程。
package main
import "fmt"
// 定义全局变量
var globalVar int = 10
func main() {
// 访问全局变量
fmt.Println("Global variable:", globalVar)
}
在上述代码中,变量globalVar在函数体之外定义,因此具有全局作用域。在主函数main()中访问了这个全局变量,并打印了它的值。执行后会输出:
Global variable: 10
5.5.2 函数级别作用域
函数级别作用域是指在函数内部声明的变量。这些变量只能在函数内部访问,也就是说,它们具有函数级别作用域。函数级别作用域的变量在函数被调用时创建,函数执行完毕后销毁。这种变量的生命周期与函数的生命周期相同,因此也被称为局部变量。以下是一个简单的例子(源码路径:Go-codes\5\han.go),展示了如何在函数级别作用域中声明变量的用法。
package main
import "fmt"
func main() {
// 声明函数级别作用域变量
var localVariable int = 30
// 访问函数级别作用域变量
fmt.Println("Local variable:", localVariable)
}
在上述代码中,变量localVariable在函数main()内部声明,因此具有函数级别作用域。只有在main内部才能访问这个变量。执行后会输出:
Local variable: 30
需要注意的是,函数级别作用域变量的名称只在函数内部可见,这意味着函数内部的变量名称可以与外部的变量名称相同,而不会导致冲突。这也是为什么在 Go 语言中,函数参数和返回值可以与全局或包级别变量具有相同的名称。
请看下面的演示代码(源码路径:Go-codes\5\jin.go),在函数main()内部定义了一个名为 globalVariable 的变量,与全局变量的名称相同。在函数main()中访问 globalVariable 时,将访问函数级别作用域变量,而不是全局变量。如果需要访问全局变量,可以使用全局变量的名称来访问它。
package main
import "fmt"
var globalVariable int = 40
func main() {
// 定义与全局变量同名的函数级别作用域变量
var globalVariable int = 50
// 访问函数级别作用域变量
fmt.Println("Local variable:", globalVariable)
// 访问全局变量
fmt.Println("Global variable:", globalVariable)
}
执行后会输出:
Local variable: 50
Global variable: 50
5.5.3 块级别作用域
在 Go 语言中,块级别作用域是指在代码块内部声明的变量。这些变量只能在代码块内部访问,也就是说,它们具有块级别作用域。代码块可以是一个函数内部的代码块,也可以是一个 if 语句、for 循环或其他控制结构内部的代码块。在一个代码块内部声明的变量在该代码块执行期间可见,一旦代码块执行完毕,这些变量就会被销毁。
下面是一个简单的例子(源码路径:Go-codes\5\kuai.go),展示了在块级别作用域中声明变量的过程。
package main
import "fmt"
func main() {
// if 语句块级别作用域
if x := 10; x > 5 {
fmt.Println("x is greater than 5")
}
// for 循环块级别作用域
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 大括号内的代码块块级别作用域
{
var y int = 20
fmt.Println("y =", y)
}
}
对上述代码的说明如下:
- 变量x是在 if 语句的代码块内部声明的,因此具有块级别作用域。只有在 if 语句内部才能访问这个变量。
- 变量i是在 for 循环的代码块内部声明的,因此也具有块级别作用域。在 for 循环之外访问变量i将导致编译器错误。
- 变量 y 是在一个代码块中声明的,该代码块由大括号括起来,因此也具有块级别作用域。在该代码块之外访问 y 变量也会导致编译器错误。
执行后会输出:
x is greater than 5
0
1
2
3
4
y = 20
注意:与函数级别作用域变量类似,块级别作用域变量的名称只在代码块内部可见,这意味着代码块内部的变量名称可以与外部的变量名称相同,而不会导致冲突。
下面是一个综合使用全局、包级别、函数级别和块级别作用域的趣味实用示例。
实例5-14:密码生成器(源码路径:Go-codes\5\guss.go)
假设我们正在编写一个猜数字游戏,我们需要一个随机数生成器来生成要猜测的数字,并记录玩家的猜测次数。我们可以使用各种不同级别的作用域来实现这个游戏。实例文件guss.go的具体实现流程如下所示。
(1)在全局作用域下引入包math/rand 和包time,代码如下所示。
package main
import (
"fmt"
"math/rand"
"time"
)
(2)在包级别作用域下定义一个常量 maxGuesses,表示玩家最多可以猜测的次数。我们还需要定义一个变量 guesses,表示玩家已经猜测的次数。代码如下所示。
const maxGuesses = 5
var guesses int
(3)定义函数 generateRandomNumber(),该函数返回一个随机数作为要猜测的数字。这个函数使用函数级别作用域。代码如下所示。
func generateRandomNumber() int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(100)
}
(4)在主函数中使用块级别作用域来记录玩家的每次猜测和结果,并在每次猜测后更新 guesses 变量。代码如下所示。
func main() {
// 生成要猜测的数字
secretNumber := generateRandomNumber()
// 提示玩家进行猜测
fmt.Println("I'm thinking of a number between 0 and 100. Can you guess what it is?")
// 循环猜测直到猜对或超过最大猜测次数
for {
// 如果玩家已经猜了最大次数,游戏结束
if guesses >= maxGuesses {
fmt.Println("Sorry, you ran out of guesses. The number was", secretNumber)
break
}
// 提示玩家猜测数字
fmt.Println("Guess a number:")
// 读取玩家的猜测
var guess int
fmt.Scan(&guess)
// 比较猜测和答案
if guess == secretNumber {
fmt.Println("Congratulations! You guessed the number in", guesses, "guesses.")
break
} else if guess < secretNumber {
fmt.Println("Too low. Try again.")
} else {
fmt.Println("Too high. Try again.")
}
// 更新猜测次数
guesses++
}
}
在本实例中使用了各种不同级别的作用域来实现猜数字游戏。全局作用域用于引入所需的包,包级别作用域用于定义常量和变量,函数级别作用域用于生成随机数,块级别作用域用于记录。因为是随机的,所以每次正确的数不同,例如某次执行后会输出:
I'm thinking of a number between 0 and 100. Can you guess what it is?
Guess a number:
3
Too low. Try again.
Guess a number:
4
Too low. Try again.
Guess a number:
5
Too low. Try again.
Guess a number:
8
Too low. Try again.
Guess a number:
88
Too high. Try again.
Sorry, you ran out of guesses. The number was 78
注意:Go 语言中的变量遵循“就近原则”,即在嵌套作用域中,如果存在同名的变量,那么内层作用域中的变量将覆盖外层作用域中的同名变量。