如何在Go中使用变量和常量

变量是掌握编程的一个重要概念。它们是代表程序中使用的值的符号。

本教程将介绍一些变量基础知识和在您创建的Go程序中使用它们的最佳实践。

理解变量

从技术角度讲,变量是将存储位置分配给与符号名称或标识符相关联的值。我们在计算机程序中使用变量名来引用该存储值。

我们可以将变量想象为一个贴有名称的标签,您将其系在值上。

假设我们有一个整数,1032049348,我们希望将其存储在一个变量中,而不是反复重写这个长数字。为此,我们可以使用一个易于记忆的名称,比如变量i。要将值存储在变量中,我们使用以下语法:

i := 1032049348

我们可以将这个变量想象为一个系在值上的标签。

标签上写着变量名i,并系在整数值1032049348上。

i := 1032049348这一句是声明和赋值语句,包含几个部分:

  • 变量名(i
  • 短变量声明赋值(:=
  • 绑定到变量名 (1032049348)
  • Go 推断的数据类型 (int)

我们将在下一节中看到如何显式设置类型。

这些部分共同构成了将变量 i 设置为整数 1032049348 值的语句。

一旦我们将变量设置为某个值,我们就 初始化 或创建该变量。一旦完成,我们就可以准备使用变量而不是值。

一旦我们将 i 设置为 1032049348 的值,我们就可以使用 i 来代替该整数,所以让我们打印出来:

package main

import "fmt"

func main() {
	i := 1032049348
	fmt.Println(i)
}
Output
1032049348

我们还可以通过使用变量快速轻松地进行数学运算。使用 i := 1032049348,我们可以用以下语法减去整数值 813

fmt.Println(i - 813)
Output
1032048535

在这个例子中,Go 为我们进行了计算,从变量 i 中减去 813,返回和 1032048535

说到数学,变量可以被设置为数学方程的结果。你也可以将两个数相加并将和的值存储到变量 x 中:

x := 76 + 145

你可能注意到这个例子看起来像代数。就像我们在公式和方程中使用字母和其他符号来表示数字和数量一样,变量是表示数据类型值的符号名称。为了正确的 Go 语法,你需要确保变量在任何方程的左侧。

让我们继续打印 x

package main

import "fmt"

func main() {
	x := 76 + 145
	fmt.Println(x)
}
Output
221

Go 返回值 221 是因为变量 x 被设定为 76145 的和。

变量可以代表任何数据类型,而不仅仅是整数:

s := "Hello, World!"
f := 45.06
b := 5 > 9 // 布尔值将返回 true 或 false
array := [4]string{"item_1", "item_2", "item_3", "item_4"}
slice := []string{"one", "two", "three"}
m := map[string]string{"letter": "g", "number": "seven", "symbol": "&"}

如果你打印这些变量中的任何一个,Go 将返回该变量所等价的内容。让我们处理字符串 slice 数据类型的赋值语句:

package main

import "fmt"

func main() {
	slice := []string{"one", "two", "three"}
	fmt.Println(slice)
}
Output
[one two three]

我们将 []string{"one", "two", "three"} 的 slice 值赋给了变量 slice,然后使用 fmt.Println 函数通过调用 slice 打印出该值。

变量的工作原理是在你的计算机内开辟一小块内存区域,接受指定的值并与之关联。

声明变量

在 Go 中,声明变量的方式有几种,某些情况下,可以有不止一种方式来声明同一个变量及其值。

我们可以声明一个名为 i 的数据类型为 int 的变量,而不进行初始化。这意味着我们将声明一个存放值的空间,但不赋予它初始值:

var i int

这创建了一个数据类型为 int 的变量 i

我们可以使用等号(=)运算符来初始化值,如下例所示:

var i int = 1

在Go语言中,这两种声明方式被称为长变量声明

我们还可以使用短变量声明

i := 1

在这种情况下,我们有一个名为i的变量,其数据类型为int。当我们不指定数据类型时,Go会推断数据类型。

通过这三种声明变量的方式,Go社区采纳了以下惯例:

  • 仅在未初始化变量时使用长形式,var i int

  • 在声明并初始化时使用短形式,i := 1

  • 如果你不希望Go推断你的数据类型,但仍想使用短变量声明,你可以用以下语法将你的值包装在你期望的类型中:

i := int64(1)

在Go中,当我们初始化值时不建议使用长变量声明形式:

var i int = 1

遵循Go社区通常的变量声明方式是一个好习惯,这样其他人可以无缝阅读你的程序。

零值

所有内置类型都有一个零值。任何已分配的变量即使从未被赋值,也是可以使用的。我们可以看到以下类型的零值:

package main

import "fmt"

func main() {
	var a int
	var b string
	var c float64
	var d bool

	fmt.Printf("var a %T = %+v\n", a, a)
	fmt.Printf("var b %T = %q\n", b, b)
	fmt.Printf("var c %T = %+v\n", c, c)
	fmt.Printf("var d %T = %+v\n\n", d, d)
}
Output
var a int = 0 var b string = "" var c float64 = 0 var d bool = false

我们使用了 fmt.Printf 语句中的 %T 动词。这告诉函数打印变量的 数据类型

在 Go 中,因为所有值都有一个 值,我们不会有像其他语言中那样的 未定义 值。例如,在某些语言中,一个 布尔 类型可以是 未定义,这允许变量有 三种 状态。在 Go 中,布尔值不能有超过 两种 状态。

变量命名:规则与风格

变量的命名相当灵活,但有一些规则需要牢记:

  • 变量名必须是一个单词(即不能有空格)。
  • 变量名只能由字母、数字和下划线(_)组成。
  • 变量名不能以数字开头。

遵循这些规则,让我们来看一些有效和无效的变量名:

Valid Invalid Why Invalid
userName user-name Hyphens are not permitted
name4 4name Cannot begin with a number
user $user Cannot use symbols
userName user name Cannot be more than one word

此外,在命名变量时请记住它们是区分大小写的。这些名称 `userName`、`USERNAME`、`UserName` 和 `uSERnAME` 都是完全不同的变量。最佳实践是避免在程序中使用相似的变量名,以确保你和你的合作者——无论是现在的还是将来的——都能清楚地分辨你的变量。

虽然变量区分大小写,但变量首字母的大小写在Go中具有特殊含义。如果一个变量以大写字母开头,那么该变量可以在声明它的包之外访问(即`exported`)。如果一个变量以小写字母开头,那么它仅在声明它的包内可用。

var Email string
var password string

`Email` 以大写字母开头,可以被其他包访问。`password` 以小写字母开头,仅在其声明的包内可访问。

在Go中,使用非常简洁(或短)的变量名是很常见的。在选择变量名时,如果可以选择 `userName` 或 `user`,那么选择 `user` 是符合Go语言习惯的。

作用域也在变量名的简洁性中起作用。规则是,变量存在的作用域越小,变量名就应该越短:

names := []string{"Mary", "John", "Bob", "Anna"}
for i, n := range names {
	fmt.Printf("index: %d = %q\n", i, n)
}

我们在更大的范围内使用了变量 `names`,因此给它一个更有意义的名称有助于记住它在程序中的含义。然而,我们立即在下一行代码中使用了变量 `i` 和 `n`,之后就不再使用它们了……正因为如此,阅读代码的人不会对这些变量的使用位置或含义感到困惑。

接下来,让我们讨论一些关于变量风格的注意事项。风格是使用 `MixedCaps` 或 `mixedCaps` 而不是下划线来命名多词名称。

Conventional Style Unconventional Style Why Unconventional
userName user_name Underscores are not conventional
i index prefer i over index as it is shorter
serveHTTP serveHttp acronyms should be capitalized

关于风格最重要的一点是保持一致性,并且你所在的团队同意这种风格。

变量重新赋值

正如“变量”这个词所暗示的,我们可以随时改变 Go 变量的值。这意味着我们可以通过重新赋值将不同的值与先前赋值的变量关联起来。能够重新赋值很有用,因为在程序运行过程中,我们可能需要将用户生成的值放入已经初始化的变量中。我们也可能需要将赋值更改为先前定义的内容。

知道我们可以随时重新赋值变量在处理别人编写的大型程序时很有用,因为不清楚哪些变量已经定义。

让我们将值 `76` 赋给一个名为 `i` 的整型变量,然后将其重新赋值为 `42`:

package main

import "fmt"

func main() {
	i := 76
	fmt.Println(i)

	i = 42
	fmt.Println(i)
}
Output
76 42

这个示例展示了我们可以首先将变量 `i` 赋值为一个整数,然后再次将变量 `i` 赋值为 `42`。

注意:当你声明并初始化一个变量时,可以使用 `:=`,然而,当你仅想改变一个已声明变量的值时,只需使用等号运算符(`=`)。

因为 Go 是一种 `类型化` 语言,我们不能将一种类型赋值给另一种类型。例如,我们不能将值 `"Sammy"` 赋给一个整型变量:

i := 72
i = "Sammy"

试图相互赋值不同类型会导致编译时错误:

Output
cannot use "Sammy" (type string) as type int in assignment

Go 不允许我们多次使用同一个变量名:

var s string
var s string
Output
s redeclared in this block

如果我们尝试对同一个变量名多次使用短变量声明,也会收到编译错误。这可能因疏忽而发生,因此理解错误信息的含义是有帮助的:

i := 5
i := 10
Output
no new variables on left side of :=

与变量声明类似,仔细考虑变量命名将提高你(及他人)未来回顾程序时的可读性。

多重赋值

Go 还允许我们在同一行内为多个变量赋值,每个值可以是不同的数据类型:

j, k, l := "shark", 2.05, 15
fmt.Println(j)
fmt.Println(k)
fmt.Println(l)
Output
shark 2.05 15

在这个例子中,变量 j 被赋值为字符串 "shark",变量 k 被赋值为浮点数 2.05,而变量 l 被赋值为整数 15

这种在一行内为多个变量赋多个值的方法可以减少代码的行数。然而,重要的是不要为了减少代码行数而牺牲可读性。

全局变量与局部变量

在程序中使用变量时,重要的是要考虑变量作用域。变量作用域指的是在给定程序的代码中可以访问该变量的特定位置。也就是说,并非所有变量在程序的任何部分都能访问——有些变量是全局的,有些则是局部的。

全局变量存在于函数之外。局部变量存在于函数内部。

让我们看看全局变量和局部变量是如何实际应用的:

package main

import "fmt"


var g = "global"

func printLocal() {
	l := "local"
	fmt.Println(l)
}

func main() {
	printLocal()
	fmt.Println(g)
}
Output
local global

在这里,我们使用 `var g = "global"` 在函数外部创建一个全局变量。然后定义函数 `printLocal()`。在函数内部,分配了一个名为 `l` 的局部变量并将其打印出来。程序通过调用 `printLocal()` 并随后打印全局变量 `g` 结束。

因为 `g` 是一个全局变量,我们可以在 `printLocal()` 中引用它。让我们修改之前的程序来实现这一点:

package main

import "fmt"


var g = "global"

func printLocal() {
	l := "local"
	fmt.Println(l)
	fmt.Println(g)
}

func main() {
	printLocal()
	fmt.Println(g)
}
Output
local global global

我们首先声明一个全局变量 `g`,即 `var g = "global"`。在 `main` 函数中,我们调用函数 `printLocal`,该函数声明了一个局部变量 `l` 并打印出来,即 `fmt.Println(l)`。然后,`printLocal` 打印出全局变量 `g`,即 `fmt.Println(g)`。尽管 `g` 没有在 `printLocal` 内部定义,但它仍然可以被访问,因为它是在全局作用域中声明的。最后,`main` 函数也打印出 `g`。

现在让我们尝试在函数外部调用局部变量:

package main

import "fmt"

var g = "global"

func printLocal() {
	l := "local"
	fmt.Println(l)
}

func main() {
	fmt.Println(l)
}

Output
undefined: l

我们不能在分配它的函数外部使用局部变量。如果你尝试这样做,在编译时会收到一个 `undefined` 错误。

让我们看另一个例子,我们在全局变量和局部变量中使用相同的变量名:

package main

import "fmt"

var num1 = 5

func printNumbers() {
	num1 := 10
	num2 := 7  

	fmt.Println(num1)
	fmt.Println(num2)
}

func main() {
	printNumbers()
	fmt.Println(num1)
}
Output
10 7 5

在这个程序中,我们两次声明了变量 `num1`。首先,我们在全局作用域中声明了 `num1`,即 `var num1 = 5`,然后在 `printNumbers` 函数的局部作用域中再次声明了 `num1 := 10`。当我们从 `main` 程序中打印 `num1` 时,我们看到打印出的值是 `5`。这是因为 `main` 只能看到全局变量声明。然而,当我们从 `printNumbers` 函数中打印 `num1` 时,它看到的是局部声明,因此会打印出值 `10`。尽管 `printNumbers` 创建了一个名为 `num1` 的新变量并赋值为 `10`,但这并不影响全局作用域中值为 `5` 的 `num1`。

在使用变量时,还需要考虑程序的哪些部分需要访问每个变量;相应地采用全局或局部变量。在 Go 程序中,通常会发现局部变量更为常见。

常量

常量类似于变量,只不过一旦声明后就不能被修改。常量对于定义在程序中多次使用但不应改变的值非常有用。

例如,如果我们想要声明一个购物车系统的税率,我们可以使用一个常量,然后在程序的不同部分计算税额。在未来某个时刻,如果税率发生变化,我们只需在程序的一个地方更改该值。如果我们使用变量,可能会不小心在程序的某个地方改变该值,这将导致不正确的计算。

要声明一个常量,我们可以使用以下语法:

const shark = "Sammy"
fmt.Println(shark)
Output
Sammy

如果我们试图在声明后修改一个常量,将会得到一个编译时错误:

Output
cannot assign to shark

常量可以是无类型的。这在处理整数类型数据时非常有用。如果常量是无类型的,它会显式转换,而有类型的常量则不会。让我们看看如何使用常量:

package main

import "fmt"

const (
	year     = 365
	leapYear = int32(366)
)

func main() {
	hours := 24
	minutes := int32(60)
	fmt.Println(hours * year)    
	fmt.Println(minutes * year)   
	fmt.Println(minutes * leapYear)
}
Output
8760 21900 21960

如果你声明一个带有类型的常量,它将是那个确切的类型。这里当我们声明常量leapYear时,我们将其定义为数据类型int32。因此它是一个有类型的常量,这意味着它只能与int32数据类型一起操作。我们声明的year常量没有类型,所以它被认为是无类型的。正因为如此,你可以将其用于任何整数数据类型。

当定义hours时,它被推断int类型,因为我们没有明确给它类型,即hours := 24。当我们声明minutes时,我们明确声明它为int32类型,即minutes := int32(60)

现在让我们逐一解释每个计算及其工作原理:

hours * year

在这种情况下,hours 是一个 int 类型,而 years无类型。当程序编译时,它会显式地将 years 转换为 int 类型,从而使得乘法操作能够成功。

minutes * year

在这种情况下,minutes 是一个 int32 类型,而 year无类型。当程序编译时,它会显式地将 years 转换为 int32 类型,从而使得乘法操作能够成功。

minutes * leapYear

在这种情况下,minutes 是一个 int32 类型,而 leapYear 是一个 有类型 的常量,类型为 int32。这次编译器无需做任何操作,因为两个变量已经是相同类型。

如果我们尝试乘以两个类型为 typed 且不兼容的类型,程序将无法编译:

fmt.Println(hours * leapYear)
Output
invalid operation: hours * leapYear (mismatched types int and int32)

在这种情况下,hours 被推断为 int 类型,而 leapYear 被显式声明为 int32 类型。因为 Go 是一种有类型语言,intint32 类型在数学运算中不兼容。要乘以它们,你需要将其中一个转换为 int32int

结论

在本教程中,我们回顾了Go语言中变量的一些常见应用场景。变量作为编程的重要基石,扮演着代表程序中数据类型值的符号角色。

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-variables-and-constants-in-go