学习Golang范围内的速记变量声明(附代码示例)

104 阅读4分钟

学习Golang范围内的速记变量声明

变量作用域

编程中一个常见的错误来源是在程序的许多不同部分使用同一个变量。如果想深入了解这种做法可能导致的问题,可以看看《全局变量是坏的》。

大多数编程语言采用的一个常见的解决方案是,可以声明只能由程序的一个子集使用的变量。例如,由单个模块、类、函数或函数内部的语句使用。这个可以使用某个标识符的程序区域的概念被称为 "范围"。范围越小,可以处理该变量的逻辑量就越小,细微的错误潜入的机会也就越小。

在语句的范围内声明一个变量

Go有一个速记符号,用于声明一个仅 "可见 "的变量,也就是说,只能在属于一个语句的代码中使用。

这可以通过在语句关键字后立即为变量赋值,并在分号 (;) 字符后继续声明来实现。请看下面的例子。

如果

可以在提供if 语句的条件之前声明(和初始化)一个变量,像这样:

1value := 1
2limit := 0
3if variable := value; variable > limit {
4  fmt.Println(variable)
5}

标识符(在此情况下,variable )只存在于if 语句的范围内。

看看如何在程序中使用这个方法:

package main

import (
  "bufio"
  "fmt"
  "os"
  "strconv"
  "strings"
)

func main() {
  maxPurchase := 1000.0
  quantity := readNumber("Quantity")
  unitPrice := readNumber("Unit price")
  taxes := readNumber("Taxes")
  if total := quantity * unitPrice * (1 + taxes); total > maxPurchase {
    fmt.Printf("Purchase denied: $%.2f is above $%.2f.\n", total, maxPurchase)
  } else {
    fmt.Printf("Purchase approved: $%.2f\n", total)
  }
}

func readNumber(label string) float64 {
  fmt.Print(label + "? ")
  reader := bufio.NewReader(os.Stdin)
  rawInput, _ := reader.ReadString('\n')
  input := strings.TrimSuffix(rawInput, "\n")
  if result, err := strconv.ParseFloat(input, 64); err == nil {
    return result
  }
  panic("Invalid input.")
}

当执行时,会产生像这样的输出:

1Quantity? 2
2Unit price? 12.99
3Taxes? 0.05
4Purchase approved: $27.28

或像这样:

1Quantity? 100
2Unit price? 19.99
3Taxes? 0.12
4Purchase denied: $2238.88 is above $1000.00.

注意这个程序在两个地方声明了if 语句范围内的变量:在main 函数中,以及在readNumber 函数中。

还注意到该变量可以在if 语句的任何地方使用:在条件中,在 "then "块中,以及在else 块中。但是该变量不能在if 语句之外(之前或之后)定义。试图在这个范围之外使用它将导致undefined 编译器错误。

最后,为了完整起见,请注意其他变量,如quantityreader ,是在函数的范围内定义的。这意味着这些变量可以在该函数中使用,但它们是不 "可见 "的,也就是说,它们不能从该函数之外被引用。

转换

switch 语句也允许在其范围内声明一个变量,像这样:

switch variable := value; variable {
case expression:
  doSomething()
}

比如说:

package main

import (
  "bufio"
  "fmt"
  "os"
  "strings"
)

func main() {
  fmt.Print("Which direction? ")
  switch direction := readString(); direction {
  case "north":
    fmt.Printf("Far up %v you see a mountain range.\n", direction)
  case "south":
    fmt.Printf("Looking %v you see sand all the way to the horizon.\n", direction)
  case "east":
    fmt.Printf("A short distance %v you see a house by a tree.\n", direction)
  case "west":
    fmt.Printf("Looking %v you see the shore.\n", direction)
  default:
    fmt.Printf("%v is not a valid direction.\n", direction)
  }
}

func readString() string {
  reader := bufio.NewReader(os.Stdin)
  rawInput, _ := reader.ReadString('\n')
  input := strings.TrimSuffix(rawInput, "\n")
  return input
}

输出示例:

Which direction? north
Far up north you see a mountain range.

注意direction 变量是如何在switch 语句的范围内声明的,以及它是如何在该语句内部的不同条件中使用的。

对于

可以在for循环的 "init "语句中声明一个或多个变量,像这样:

for variable := value; condition; post-statement {
  doSomething()
}

比如说

package main

import (
  "bufio"
  "fmt"
  "os"
  "strconv"
  "strings"
)

func main() {
  iterations := readNumber("Number of iterations")
  sum := 0
  for i := 1; i <= iterations; i++ {
    sum += i
  }
  fmt.Println(sum)
}

func readNumber(label string) int {
  fmt.Print(label + "? ")
  reader := bufio.NewReader(os.Stdin)
  rawInput, _ := reader.ReadString('\n')
  input := strings.TrimSuffix(rawInput, "\n")
  if result, err := strconv.Atoi(input); err == nil {
    return result
  }
  panic("Invalid input.")
}

这个程序产生的输出是这样的:

Number of iterations? 4
10

也可以在for循环中声明一个以上的变量:

package main

import (
  "fmt"
)

func main() {
  fruits := map[string]string{"a": "apple", "b": "banana"}
  for key, value := range fruits {
    fmt.Printf("%s is for %s.\n", key, value)
  }
}

输出:

a is for apple.
b is for banana.

注意到变量keyvalue 是如何在for 循环的范围内声明的,并在其主体中使用。

经验之谈

  • 在尽可能小的范围内声明数值可以使程序更容易理解,并减少出错的机会。
  • 变量可以在if,switchfor 等语句中通过速记符号来声明。
  • 变量只能在它们被声明的范围内使用。它们的标识符不会 "泄露 "出这个范围。