11 How to Use Variables and Constants in Go

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

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

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

理解变量 #

用术语来说,变量将存储位置分配给与符号名称或标识符相关的值。我们使用变量名来引用计算机程序中存储的值。

我们可以将变量视为带有名称的标签,您可以将其绑定到值上。

Variables in Go

假设我们有一个整数 1032049348 ,我们希望将它存储在一个变量中,而不是一遍又一遍地不断地重新输入长数字。为了达到这个目的,我们可以使用一个容易记住的名字,比如变量 i。要将值存储在变量中,我们使用以下语法:

i := 1032049348

我们可以把这个变量想象成一个与值绑定的标签。

Go Variable Example

标签上写有变量名 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 // A Boolean value will return either true or 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,然后使用 fmt.Println 函数通过调用 slice 打印出该值。

变量通过在你的计算机中开辟一小块内存区域来工作,该区域接受指定的值,然后与该空间相关联。

声明变量 #

在 Go 中,有多种声明变量的方法,在某些情况下,声明完全相同的变量和值的方法不止一种。

我们可以在不初始化的情况下声明一个数据类型为 int 的名为 i 的变量。这意味着我们将声明一个空间来放置一个值,但不给它一个初始值:

var i int

这将创建一个数据类型为 inti 的变量。

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

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 中,因为所有值都有一个 值,所以我们不能像其他一些语言那样有“未定义”值。例如,某些语言中的 boolean 可能是 undefinedtruefalse,它允许变量有三种状态。在 Go 中,一个布尔值的状态不能超过 两个

命名变量:规则和样式 #

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

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

按照这些规则,让我们看看有效和无效的变量名:

Valid 有效Invalid 无效无效原因
userNameuser-name不允许使用连字符
name44name不能以数字开头
user$user不能使用符号
userNameuser name不能超过一个单词

此外,在命名变量时请记住它们区分大小写。这些名称 userNameUSERNAMEUserNameuSERNAME 都是完全不同的变量。最好避免在程序中使用相似的变量名称,以确保你和你的合作者(当前和未来)都能保持变量的正确性。

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

var Email string
var password string

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

在 Go 中使用非常简洁(或短)的变量名是很常见的。变量 userNameuser 之间的选择,通常会选择 user

作用域在变量名称的简洁性方面也发挥了作用。规则是变量存在的作用域越小,变量名越小:

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

我们在更大的作用域内使用变量 names,因此通常给它一个更有意义的名称以帮助记住它在程序中的含义。但是,我们在下一行代码中立即使用 in 变量,然后不再使用它们……因此,它不会让阅读代码的人混淆变量的使用位置,或者它们代表的意思。

接下来,让我们介绍一些关于变量样式的注意事项。使用 MixedCapsmixedCaps 而不是下划线作为多词名称。

常用风格非常用风格为什么不常用
userNameuser_name下划线不常用
iindex更倾向用 i 而不是 index 因为它更短
serveHTTPserveHttp专业词缩写应该用大写

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

重新分配变量 #

正如 变量 一词所暗示的那样,我们可以轻松地更改 Go 变量。这意味着我们可以通过重新分配将不同的值与先前分配的变量联系起来。能够重新分配变量是很有用的,因为在整个程序过程中,我们可能需要将用户生成的值赋给已经初始化的变量中。我们可能还需要将分配更改为先前定义的内容。

在处理其他人编写的大型程序时,知道变量可以被重新分配对我们是很有帮助的,特别是当我们不清楚哪些变量已经被定义了。

让我们将 76 的值分配给一个名为 i 的类型为 int 的变量,然后为它分配一个新的值 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" 分配给 int 类型的变量:

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

我们首先声明一个全局变量 gvar g = "global"。在 main 函数中,我们调用函数printLocal,它声明了一个局部变量l并打印出来,fmt.Println(l)。然后,printLocal 打印出全局变量gfmt.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

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

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

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 变量。首先,我们在全局作用域内声明了num1var num1 = 5,然后再次在 printNumbers 函数的本地作用域内声明 num1 := 10。当我们从 main 程序中打印 num1 时,我们看到 5 的值被打印出来了。这是因为 main 只看到全局变量声明。但是,当我们从 printNumbers 函数中打印出 num1 时,它会看到本地声明,并会打印出 10 的值。尽管 printNumbers 创建了一个名为 num1 的新变量并为其分配了 10 的值,但它不会影响值为 5num1 的全局实例。

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

常量 #

常量就像变量一样,除了它们一旦被声明就不能被修改。常量对于定义在程序中多次使用但不能更改的值很有用。

例如,如果我们想声明购物车系统的税率,我们可以使用一个常数,然后在程序的不同区域计算税收。在未来的某个时候,如果税率发生变化,我们只需在程序中的一个位置更改该值。如果我们使用了一个变量,我们可能会在程序中不小心更改某个值,从而导致计算不正确。

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

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

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

Output
cannot assign to shark

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

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。因此它是一个 typed 常量,这意味着它只能对 int32 数据类型进行操作。我们声明的 year 常量没有类型,所以它被认为是 untyped。因此,你可以将它与任何整数数据类型一起使用。

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

现在让我们来看看每个计算以及它的工作原理:

hours * year

在这种情况下,hoursint,而 yearsuntyped。当程序编译时,它显式地将 years 转换为 int,这使得乘法运算成功。

minutes * year

在这种情况下,minutesint32,而 yearuntyped。当程序编译时,它显式地将 years 转换为 int32,从而允许乘法运算成功。

minutes * leapYear

在这种情况下,minutesint32,而 leapYearint32typed 常量。这次编译器无需执行任何操作,因为这两个变量已经属于同一类型。

如果我们尝试将两种类型不兼容的类型相乘,程序将无法编译:

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

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

结论 #

在本教程中,我们回顾了 Go 中变量的一些常见用例。变量是编程的重要组成部分,充当我们在程序中使用的数据类型的值的代表。