_Variables 是一个重要的编程概念,它们是代表您在程序中使用的值的符号。
本教程将涵盖您创建的 Go 程序中使用变量基本知识和最佳实践。
变量理解
从技术上讲,变量是将存储位置分配给与象征名称或标识符相关的值,我们使用变量名称来参考计算机程序中的存储值。
我们可以将变量视为具有名称的标签,你将其绑定到一个值。
假设我们有一个整数,‘1032049348’,我们希望将其存储在变量中,而不是不断地重写长数一遍又一遍。
1i := 1032049348
我们可以把这个变量看成是与价值相关的标签。
标签上写着变量名称i
,并与整数值1032049348
相关。
i := 1032049348
是由几个部分组成的声明和委托声明:
- 变量名称(
i
) - 短变量声明分配(
:=
) - 与变量名称(
1032049348
)相关的值 - Go 推断的数据类型(
int
)
稍后我们将看到如何在下一节明确设置类型。
这些部分共同构成定义变量i
等于整数值1032049348
的陈述。
一旦我们将一个变量设置为等于一个值,我们就会 initialize 或创建该变量. 一旦我们完成,我们就可以使用该变量而不是该值。
一旦我们将i
设置为1032049348
的值,我们可以用i
代替整数,所以让我们打印出来:
1package main
2
3import "fmt"
4
5func main() {
6 i := 1032049348
7 fmt.Println(i)
8}
1[secondary_label Output]
21032049348
我们还可以通过使用变量快速轻松地做数学。使用 i := 1032049348
,我们可以用以下语法抽取整数值 813
:
1fmt.Println(i - 813)
1[secondary_label Output]
21032048535
在本示例中,Go为我们做数学,从变量i
中扣除813来返回1032048535
的总和。
谈到数学,可将变量设置为等于数学方程的结果,您也可以将两个数字加在一起并将总值存储在变量x
中:
1x := 76 + 145
您可能已经注意到这个示例看起来类似于算法. 就像我们使用字母和其他符号来表示公式和方程式中的数字和数量一样,变量是代表数据类型的值的象征名称。
让我们继续前进,打印x
:
1package main
2
3import "fmt"
4
5func main() {
6 x := 76 + 145
7 fmt.Println(x)
8}
1[secondary_label Output]
2221
Go 返回值221
,因为变量x
被设置为76
和145
的总和。
变量可以代表任何数据类型,而不仅仅是整数:
1s := "Hello, World!"
2f := 45.06
3b := 5 > 9 // A Boolean value will return either true or false
4array := [4]string{"item_1", "item_2", "item_3", "item_4"}
5slice := []string{"one", "two", "three"}
6m := map[string]string{"letter": "g", "number": "seven", "symbol": "&"}
如果您打印了这些变量中的任何一个,Go 将返回该变量等同的值。
1package main
2
3import "fmt"
4
5func main() {
6 slice := []string{"one", "two", "three"}
7 fmt.Println(slice)
8}
1[secondary_label Output]
2[one two three]
我们将字符串值 []string{"一", "两个", "三"} 的值分配给变量
slice,然后使用
fmt.Println函数通过调用
slice` 来打印该值。
变量通过在计算机内雕刻出一个小区域的内存来工作,该区域会接受与该空间相关的特定值。
变量声明
在 Go 中,有几种方式来声明变量,在某些情况下,可以声明相同变量和值的多种方式。
我们可以声明一个名为i
的数据类型int
的变量,而无需初始化,这意味着我们将声明一个空间来放置一个值,但不会给它一个初始值:
1var i int
这会创建一个被声明为i
的数据类型int
的变量。
我们可以使用平等(=
)运算符初始化值,如下面的示例:
1var i int = 1
在Go中,这两种形式的声明都称为长变量声明。
我们也可以使用 short variable declaration:
1i := 1
在这种情况下,我们有一个名为i
的变量和一个名为int
的数据类型. 当我们不指定数据类型时,Go 会推断数据类型。
通过宣布变量的三种方式,Go社区采用了以下语法:
- 只使用长形式,
var i int
,当你没有初始化变量时。 - 使用短形式,
i := 1
,在声明和初始化时。 - 如果你不希望去推断你的数据类型,但你仍然想要使用短变量声明,你可以用以下语法将你的值包裹到你想要的类型:
1i := int64(1)
在 Go 中,当我们初始化值时使用长变量声明表格时,不被视为语言:
1var i int = 1
遵循 Go 社区通常如何声明变量是很好的做法,以便其他人可以无缝地阅读您的程序。
零值
所有内置类型都有零值. 任何分配变量都可以使用,即使它从未有值。
1package main
2
3import "fmt"
4
5func main() {
6 var a int
7 var b string
8 var c float64
9 var d bool
10
11 fmt.Printf("var a %T = %+v\n", a, a)
12 fmt.Printf("var b %T = %q\n", b, b)
13 fmt.Printf("var c %T = %+v\n", c, c)
14 fmt.Printf("var d %T = %+v\n\n", d, d)
15}
1[secondary_label Output]
2var a int = 0
3var b string = ""
4var c float64 = 0
5var d bool = false
我们在fmt.Printf
语句中使用了%T
语法,这告诉函数为变量打印数据类型
。
在Go中,由于所有值都具有零
值,所以我们不能像其他一些语言那样拥有无定义
值。例如,在某些语言中,一个(boolean
)(LINK0
)可以是无定义
、true
或false
,这允许变量的三
状态。
变量命名:规则和风格
变量命名是相当灵活的,但有一些规则要记住:
- 变量名只能是单个单词(如无间隙)。
- 变量名只能由字母、数字和分数(
_
)组成。
按照这些规则,让我们看看有效和无效的变量名称:
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
都是完全不同的变量。
虽然变量是案例敏感的,但变量的第一个字母的案例在Go中具有特殊的含义.如果变量以上面的字母开始,那么该变量可以在其被声明的包(或导出
)之外访问。
1var Email string
2var password string
电子邮件
以上面的信件开始,可以通过其他包访问。 密码
以下面的字母开始,只能在它所声明的包内访问。
在Go中使用非常短的(或短的)变量名称是常见的,鉴于在使用用户名
和用户
之间进行选择,选择用户
将是语的。
范围也对变量名称的 terseness起着作用. 规则是,变量存在的范围越小,变量名称就越小:
1names := []string{"Mary", "John", "Bob", "Anna"}
2for i, n := range names {
3 fmt.Printf("index: %d = %q\n", i, n)
4}
我们使用变量名称
在更大的范围内,所以通常会给它一个更有意义的名称,以帮助记住它在程序中意味着什么。
接下来,我们来覆盖一些关于变量风格的注释。风格是使用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
的值:
1package main
2
3import "fmt"
4
5func main() {
6 i := 76
7 fmt.Println(i)
8
9 i = 42
10 fmt.Println(i)
11}
1[secondary_label Output]
276
342
这个例子表明,我们可以先将变量i
与整数的值分配,然后将变量i
重新分配,这一次将其分配为42
的值。
<$>[注]
注: 当您声明和**初始化变量时,您可以使用 :=
,但是,当您想简单地更改已经声明的变量值时,您只需要使用等操作员(=
)。
因为Go是一个输入
语言,所以我们不能将一种类型分配给另一种类型,例如,我们不能将值Sammy
分配给类型int
的变量:
1i := 72
2i = "Sammy"
尝试将不同的类型分配给对方会导致编译时间错误:
1[secondary_label Output]
2cannot use "Sammy" (type string) as type int in assignment
Go 不允许我们使用一个变量名称多次:
1var s string
2var s string
1[secondary_label Output]
2s redeclared in this block
如果我们尝试为相同的变量名称使用短变量声明多次,我们也会收到编译错误。
1i := 5
2i := 10
1[secondary_label Output]
2no new variables on left side of :=
类似于变量声明,考虑你的变量命名将提高你的程序的可读性,你和其他人,当你再次访问它在未来。
多重分配
Go 还允许我们将多个值分配给相同行内的多个变量,这些值中的每个值可能属于不同的数据类型:
1j, k, l := "shark", 2.05, 15
2fmt.Println(j)
3fmt.Println(k)
4fmt.Println(l)
1[secondary_label Output]
2shark
32.05
415
在本示例中,变量j
被分配到字符串鲨鱼
,变量k
被分配到浮动2.05
,变量l
被分配到整数15
。
将多个变量分配到一个行中的多个值的这种方法可以使代码中的行数降低,但是,重要的是不要损害更少的代码行的可读性。
全球和本地变量
当在程序中使用变量时,重要的是要记住变量范围(variable scope)。变量的范围是指它可以从某个程序的代码中访问的特定地点,也就是说,并非所有变量都可以从某个程序的所有部分访问,有些变量将是全球性的,有些则将是本地的。
全球变量存在于函数之外,局部变量存在于函数内。
让我们来看看行动中的全球和本地变量:
1package main
2
3import "fmt"
4
5var g = "global"
6
7func printLocal() {
8 l := "local"
9 fmt.Println(l)
10}
11
12func main() {
13 printLocal()
14 fmt.Println(g)
15}
1[secondary_label Output]
2local
3global
在这里,我们使用「var g = "global"」来创建函数之外的全球变量,然后我们定义了函数「printLocal()」。在函数内部分配了一个名为「l」的本地变量,然后打印出来。
由于g
是一个全球变量,我们可以在printLocal()
中参考它。
1package main
2
3import "fmt"
4
5var g = "global"
6
7func printLocal() {
8 l := "local"
9 fmt.Println(l)
10 fmt.Println(g)
11}
12
13func main() {
14 printLocal()
15 fmt.Println(g)
16}
1[secondary_label Output]
2local
3global
4global
我们首先声明一个全球变量g
,var g
=全球
。在主
函数中,我们称这个函数为printLocal
,该函数声明一个本地变量l
并打印出来,即fmt.Println(l)
。然后,printLocal
打印出全球变量g
,即fmt.Println(g)
。即使g
没有在printLocal
中定义,它仍然可以访问,因为它在全球范围内被宣布。
现在让我们尝试在函数之外调用本地变量:
1package main
2
3import "fmt"
4
5var g = "global"
6
7func printLocal() {
8 l := "local"
9 fmt.Println(l)
10}
11
12func main() {
13 fmt.Println(l)
14}
1[secondary_label Output]
2undefined: l
如果您尝试这样做,您在编译时会收到一个未定义
错误。
让我们看看另一个例子,我们在全球变量和本地变量中使用相同的变量名称:
1package main
2
3import "fmt"
4
5var num1 = 5
6
7func printNumbers() {
8 num1 := 10
9 num2 := 7
10
11 fmt.Println(num1)
12 fmt.Println(num2)
13}
14
15func main() {
16 printNumbers()
17 fmt.Println(num1)
18}
1[secondary_label Output]
210
37
45
在这个程序中,我们声明了num1
变量两次。首先,我们声明了num1
在全球范围内,var num1 = 5
,再一次在printNumbers
函数的本地范围内,num1 : = 10
。当我们从主要
程序中打印num1
时,我们看到5
的值被打印出来。这是因为main
只能看到全球变量声明。然而,当我们从printNumbers
函数中打印num1
时,它会看到本地声明,并且会打印10
的值。即使printNumbers
会创建一个名为num1
的新变量并分配给它一个值为10
,但它不会影响num1
的全球实例具有5
的值。
在使用变量时,您还需要考虑您的程序的哪些部分需要访问每个变量;相应地采用全球或本地变量。
常态
常数类似于变量,但一旦被声明,它们就无法更改,而常数则有助于定义在程序中使用的值,但不应该能够更改。
例如,如果我们想宣布购物车系统的税率,我们可以使用一个常数,然后计算我们的计划的不同领域的税率。在未来某个时候,如果税率发生变化,我们只需要在我们的计划中一个地方改变这个值。
为了声明一个常数,我们可以使用以下语法:
1const shark = "Sammy"
2fmt.Println(shark)
1[secondary_label Output]
2Sammy
如果我们试图在声明后修改一个常数,我们会收到编译时间错误:
1[secondary_label Output]
2cannot assign to shark
常数可以是未输入
的,这在处理整数类型的数据时可能是有用的,如果常数是未输入
的,那么它就会被明确地转换,而未输入
的常数则不是。
1package main
2
3import "fmt"
4
5const (
6 year = 365
7 leapYear = int32(366)
8)
9
10func main() {
11 hours := 24
12 minutes := int32(60)
13 fmt.Println(hours * year)
14 fmt.Println(minutes * year)
15 fmt.Println(minutes * leapYear)
16}
1[secondary_label Output]
28760
321900
421960
如果你用一个类型声明一个常数,它将是那种准确的类型。在这里,当我们声明leapYear
常数时,我们将其定义为数据类型int32
。因此,它是一个打字
常数,这意味着它只能与int32
数据类型操作。
当小时
被定义时,它被定义为int
类型,因为我们没有明确给它一个类型,小时:=24
。
现在让我们来看看每个计算以及它为什么会起作用:
1hours * year
在这种情况下,hours
是int
,years
是untyped
。当程序编译时,它明确地将years
转换为int
,允许倍增操作成功。
1minutes * year
在这种情况下,分钟
是int32
,年
是 untyped. 当程序编译时,它明确地将年
转换为int32
,这允许倍增操作成功。
1minutes * leapYear
在这种情况下,minutes
是int32
,而leapYear
是int32
的 typed 常数。
如果我们试图重复两个类型,这些类型是编写
的,而不是兼容的,程序将不会编译:
1fmt.Println(hours * leapYear)
1[secondary_label Output]
2invalid operation: hours * leapYear (mismatched types int and int32)
在这种情况下,hours
被推断为int
,而leapYear
被明确宣布为int32
。因为Go是一个编写语言,所以int
和int32
不兼容数学操作。
结论
在本教程中,我们审查了 Go 中变量的一些常见用例,变量是编程的一个重要构件,作为象征,代表我们在程序中使用的数据类型的值。