在Go中使用变量
没有变量你能编程吗?也许在一些深奥的语言中可以,但在Go中,你不能。因此,熟悉变量在语言中的作用是很重要的,这样你才能写出有效的程序。而这正是本文的内容。
变量是Go程序的一个重要组成部分。变量是对内存中存储某些数据的位置的一种命名。每个变量都有一个与之相关的数据类型(string,int,bool e.t.c. )。
要写出好的Go程序,你需要学习在语言中使用变量的习惯性方法,所以这就是本教程要讲的内容。您可以在[Go playground] 中运行本页介绍的代码片段,如果您愿意,也可以在本地计算机上运行。
命名规则
在讨论如何在 Go 中创建变量之前,让我们看看你应该注意的命名惯例。Go中的名字以Unicode字母或下划线开始,后面是任意数量的其他字母、数字和下划线。Go中的名字也是区分大小写的,所以fetchNews 和fetchnews 是不同的名字。
Go中的惯例是使用camelCase来命名,Go程序员倾向于使用简短的名字,特别是在变量范围较小的情况下,尽管名字的长度没有限制。Go还有25个关键字,如var 、func ,这些关键字在语言中不能作为标识符使用。
如何在Go中创建变量
下面的片段显示了在Go中创建变量的两种主要方式。
package main
import (
"fmt"
)
func main() {
var hello = "Hello"
world := "world!"
fmt.Println(hello, world) // Hello world!
}
我们有一个使用var 关键字声明的变量hello ,它被分配了字符串 "Hello",还有一个使用短变量声明语法声明的第二个变量world ,它被分配了字符串 "world!"。fmt.Println() 方法随后将文本 "Hello world!"打印到标准输出。
这两种在Go中创建变量的方式有一些不同。我们先来谈谈var 关键字的问题。
变量声明
一个var 声明的一般形式如下所示。
var name [type] = expression
如
var str string = "A String"
类型或表达式都可以省略,但不能同时省略两者。正如你已经看到的,在上一节中声明的hello 变量省略了类型,但包括表达式。在声明中省略了类型的情况下,它将从表达式中推断出来。这被称为类型推断,它使得声明一个变量很容易,而不需要明确地注释其类型。
下面是一些例子。
var a = "Hello"
var b = 23
var c = true
var d = 2.3
fmt.Printf("The type of a, b, c, d is: %T, %T, %T and %T respectively", a, b, c, d)
// Prints: The type of a, b, c, d is: string, int, bool and float64 respectively
也可以用一个var 声明多个变量,如下图所示。
var a, b, c, d = "Hello", 23, true, 2.3
// or
var (
a = "Hello"
b = 23
c = true
d = 2.3
)
零值
Go允许你创建变量而不明确地将其初始化为一个值。在这种情况下,变量的类型必须存在。
var a string
var b int
var c bool
var d float64
// which can also be written as
var (
a string
b int
c bool
d float64
)
如果你要声明的多个变量都是同一类型,而不对它们进行初始化,你可以使用下面的语法。
var a, b, c int // a, b, c are all of type int
当变量声明中的表达式部分被省略时,变量将被赋予该类型的零值,对于数字类型来说是0 ,对于字符串来说是空字符串(""),对于布尔类来说是false ,对于接口和指针来说是nil 。
这里有一个例子,演示了这个概念。
package main
import (
"fmt"
)
func main() {
var (
a string
b int
c bool
d float64
)
fmt.Println(a, b, c, d) // Output: "" 0 false 0
}
这个零值的概念有一个重要的含义,你必须知道:在Go中没有任何东西是未初始化的变量。一个变量总是包含一个值,要么是你分配给它的值,要么是为其类型隐含分配的零值。这种行为使 Go 程序员无需处理其他编程语言中存在的未初始化变量问题。
简短的变量声明
Go中的第二种类型的变量声明被称为短变量声明,使用:= 操作符。由于Go语言的语法简洁,所以大多数变量都是用这种方式来声明的。
str := "A string"
ans := 22 + 20
就像var 声明一样,在一行中可以进行多个短变量声明。
a, b, c := 1, 2, 3
d, e, f := "Hello", true, 5.8
与var 声明不同的是,在使用短变量声明语法时,没有隐式赋值到零值,而且不能注释变量的类型;它总是从右侧的表达式中推断出来。
变量赋值
您可以使用= 操作符对已经声明的变量进行赋值。在Go中,所有的变量都是可变的,但是在声明之后,与一个变量相关的类型不能改变。
var name = "sally"
fmt.Println(name) // sally
name = "polly"
fmt.Println(name) // polly
name = true // cannot use true (type untyped bool) as type string in assignment
您还可以使用另一种形式,即元组赋值,一次赋值给几个变量。右边的表达式在任何左边的变量被更新之前就被评估了。
// declaration
a, b := 1, 2
// tuple assignment
a, b = a + 1, b + 2 // 2, 4
当使用这种元组赋值方式将变量分配给函数的返回值时,变量的数量必须与返回值的数量一致。
变量范围
Go中的变量可以在包或块级别声明。包级变量是指出现在任何函数之外的变量。它可以在整个包内访问,并且由于:= 操作符在函数外不可用,所以只能是var 声明。
另一方面,块级变量是指在一个块内声明的变量,例如在一个函数、for 循环、if 块,甚至是一个独立的块。声明块级变量的惯用方法是使用短变量声明语法。
下面是一个同时表示包级和块级变量声明的例子。
package main
import (
"fmt"
)
// t is a package-level variable that can be accessed
// anywhere in the `main` package. You can say that it is
// global to the package
var t = true
func main() {
// f is a block-level variable accessible from its point of declation to
// to the end of the function
f := false
{
// i is a block-level variable that's only valid from this point
// until the end of the block
i := 20
fmt.Println(i) // 20
} // this block scope is now over so i is no longer valid
fmt.Println(t, f) // true false
}
如果你以前使用过词法范围的编程语言,这种行为对你来说应该是熟悉的。除了用一对大括号明确定义的显式块之外,Go还有一些隐式块,你应该注意到。例如,switch 或select 语句中的每个子句也是一个隐式块,尽管没有大括号。
func main() {
s := "world"
switch s {
case "hello":
// i can be accessed only within this clause
// and not the whole switch block
i := 10
fmt.Println(i)
case "world":
// i is out of scope here so this code will fail to compile
fmt.Println(i)
}
}
另一个例子是在if 语句中。这是你在Go程序中可能更经常遇到的。
package main
import (
"errors"
"fmt"
)
func main() {
// this `err` variable is scoped to the whole
// function
err := errors.New("An error")
// this `err` variable is scoped to only this `if`
// block (and `else` blocks if any). It shadows the
// previous `err` variable
if err := fmt.Errorf("Another error"); err != nil {
fmt.Println(err) // Another error
} // `err` goes out of scope here
fmt.Println(err) // An error
}
顺便提一下,var 声明语法也可以用来声明块级变量,但通常只在你想把一个变量初始化为零值,然后再对其进行赋值时使用。
func main() {
var s string // s is initialised to its zero value which is ""
s = "foo" // assignment occurs later in the function
}
阴影
阴影是Go中的一项功能,它允许你在一个块中声明一个变量,并在一个内部块中声明另一个同名的变量。下面是一个例子。
func main() {
str := "world"
{
str := "hello" // outer str is inaccessible from this point
fmt.Println(str) // hello
}
fmt.Println(str) // world
}
在main函数中声明的第一个str 变量被分配为字符串值 "world"。在一个内部代码块中,声明了第二个str 变量,并分配了字符串值 "hello"。每当你访问内块中的str 变量时,它将总是引用内块的声明。所以我们说,第一个str 变量被第二个变量所影射。
请注意,内部的str 并不以任何方式影响外部的str 。事实上,内部变量不需要和外部变量具有相同的类型,因为它们实际上是完全不同的。它们只是碰巧共享同一个名字。请记住,由于这种行为,你将无法从内部块访问外部str ,除非你改变它的名称。
如果你试图在同一个块内重新声明一个变量,那么变量阴影就不适用。该程序将无法编译。
func main() {
s := "world"
s := "hello" // no new variables on left side of :=
}
然而,下面的程序可以正常编译和运行。
func main() {
var a, b int
c, a, b := 3, 1, 2
fmt.Println(a, b, c) // 1 2 3
}
虽然看起来好像是在a 和b 变量在main 函数的第二行被重新声明,但实际上不是这样的。这里发生的情况是,只有c 变量被声明,而其他两个变量被分配到。这个原因是::= 操作符确实总是声明其左侧的所有变量。
在使用短声明语法时,至少需要有一个新的变量,这样才能发挥作用。如果我们从上面的片段中删除c 变量声明,代码会像以前一样无法编译。
func main() {
var a, b int
a, b := 1, 2 // no new variables on left side of :=
fmt.Println(a, b)
}
结论
在这篇文章中,你学到了如何在Go中创建和分配变量,什么是零值,以及变量范围和阴影如何在语言中工作。现在你已经探索了变量是如何工作的,下一篇文章将探讨变量可以有哪些数据类型。
谢谢你的阅读,并祝你编码愉快