如何利用Go语言创建和分配变量?

238 阅读9分钟

在Go中使用变量

没有变量你能编程吗?也许在一些深奥的语言中可以,但在Go中,你不能。因此,熟悉变量在语言中的作用是很重要的,这样你才能写出有效的程序。而这正是本文的内容。

变量是Go程序的一个重要组成部分。变量是对内存中存储某些数据的位置的一种命名。每个变量都有一个与之相关的数据类型(string,int,bool e.t.c. )。

要写出好的Go程序,你需要学习在语言中使用变量的习惯性方法,所以这就是本教程要讲的内容。您可以在[Go playground] 中运行本页介绍的代码片段,如果您愿意,也可以在本地计算机上运行。

命名规则

在讨论如何在 Go 中创建变量之前,让我们看看你应该注意的命名惯例。Go中的名字以Unicode字母或下划线开始,后面是任意数量的其他字母、数字和下划线。Go中的名字也是区分大小写的,所以fetchNewsfetchnews 是不同的名字。

Go中的惯例是使用camelCase来命名,Go程序员倾向于使用简短的名字,特别是在变量范围较小的情况下,尽管名字的长度没有限制。Go还有25个关键字,如varfunc ,这些关键字在语言中不能作为标识符使用。

如何在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还有一些隐式块,你应该注意到。例如,switchselect 语句中的每个子句也是一个隐式块,尽管没有大括号。

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
}

虽然看起来好像是在ab 变量在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中创建和分配变量,什么是零值,以及变量范围和阴影如何在语言中工作。现在你已经探索了变量是如何工作的,下一篇文章将探讨变量可以有哪些数据类型。

谢谢你的阅读,并祝你编码愉快