2.2 Go语言从入门到精通:Go语言变量

712 阅读10分钟

变量来源于数学,是计算机语言中能储存计算结果或能表示值的抽象概念。

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

变量可以通过变量名访问。

Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。

1、变量的声明

声明变量的一般形式是使用 var 关键字:

var name type

其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。

还可以一次声明多个类型相同的变量:

var name1,name2,name3 type

需要注意的是,Go语言和许多编程语言不同,它在声明变量时将变量的类型放在变量的名称之后。这样做的好处就是可以避免像C语言中那样含糊不清的声明形式,例如:int* a, b; 。其中只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写。而在 Go 中,则可以和轻松地将它们都声明为指针类型:

var a, b *int

当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等。所有的内存在 Go 中都是经过初始化的。

示例如下:

package main

import "fmt"

func main() {
	var a,b int
	fmt.Println(a,b)
}

输出结果如下:

0 0

变量的声明有多种形式,整理归纳如下。

1.1 标准声明格式

Go语言的变量声明的标准格式如下:

var 变量名 变量类型

var 变量名1,变量名2 变量类型

变量声明以关键字 var 开头,后置变量类型,行尾无须分号。

如果没有初始化,则变量默认为零值。(零值就是变量没有做初始化时系统默认设置的值。)

var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。

示例如下:

package main

import "fmt"

func main() {
	// 声明一个没有初始化的int类型变量,默认为零值0
	var a int
	fmt.Println(a)

	// 声明一个没有初始化的bool类型变量,默认为零值false
	var b bool
	fmt.Println(b)
}

输出结果如下:

0
false

1.2 根据值自行判定变量类型

var 变量名 = 变量值

示例如下:

package main

import "fmt"

func main() {
	var c = true
	fmt.Println(c)
}

输出结果如下:

true

1.3 短变量声明

在函数中,一种称为短变量声明的可选形式可以用来声明和初始化局部变量。格式如下:

变量名 := 变量值

这里省略了关键字var,变量名的类型由变量值的类型决定,变量值可以是任何函数或表达式计算的结果值。

因其短小、灵活,故而在局部变量的声明和初始化中主要使用。

短变量声明有以下注意点:

  • 定义变量,同时显式初始化。

  • 不能直接提供数据类型。

  • 只能用在函数内部。

  • :=表示声明,而=表示赋值。

  • :=左侧如果是已声明的变量,就会产生编译错误。如:

    var intVal int
    intVal := 1		// 会产生编译错误
    

示例如下:

package main

import "fmt"

func main() {
	// 短变量声明
	d := "xcbeyond"
	fmt.Println(d)
}

输出结果如下:

xcbeyond

1.4 批量声明

觉得每行都用 var 声明变量比较烦琐?没关系,还有一种为懒人提供的定义变量的方法:

var (
	a int
    b string
    c float64
)

使用关键字 var 和括号,可以将一组变量定义放在一起。

示例如下:

package main

import "fmt"

func main() {
    // 批量声明
	var (
		e int
		f string
		g float64
	)
	fmt.Println(e,f,g)
}

输出结果如下:

0  0

2、变量的初始化

变量初始化标准格式如下:

var 变量名 类型 = 变量值

如:

var i int = 100

i为变量名,类型为inti的初始值为100。

上面代码中,100 和 int 同为 int 类型,int 可以认为是冗余信息,因此可以进一步简化初始化的写法。

在标准格式的基础上,将 类型int省略后,编译器会尝试根据等号右边的变量值推导出变量的类型。如:

var i = 100

短变量声明并初始化,如:

i := 100

这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型。

3、变量的多重赋值

赋值是用来更新变量所指的值,它最简单的形式由赋值符=,以及符号左边的变量和右边的表达式组成。

x = 1
person.name = "xcbeyond"
count[x] = count[x] * 5

另一种形式的赋值是多重赋值,它允许几个变量一次性被赋值。

在Go语言语法中,变量初始化和变量赋值是两个不同的概念,Go语言的变量赋值与其他语言一样,但是Go语言提供了其他程序员期待已久的多重赋值功能,可以实现变量交换。多重赋值让Go语言比其他语言减少了代码量。

以简单的算法交换变量为例,传统写法如下所示:

var a int = 10
var b int = 20

var tmp int
tmp = a
a = b
b = t

fmt.Println(a, b)

新定义的变量tmp是需要内存的,于是有人设计了新的算法来取代中间变量,其中一种写法如下所示:

var a int = 10
var b int = 20

a = a ^ b
b = b ^ a
a = a ^ b

fmt.Println(a, b)

Go语言有了多重赋值功能,则简单写法如下所示:

var a int = 10
var b int = 20

b, a = a, b

fmt.Println(a, b)

从以上例子来看,Go语言的写法明显简洁了许多,需要注意的是,多重赋值时,左值和右值按照从左到右的顺序赋值。这种方法在错误处理和函数当中会大量使用。

4、匿名变量

在编码过程中,可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。

匿名变量的特点是一个下画线__本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。

例如,我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。

假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName,若只想获得nickName,则可以用如下方式编写:

package main

import "fmt"

func main() {
	// 匿名变量
	firstName, _, _ := getName()
	_, lastName, _ := getName()
	_, _, nickName := getName()
	fmt.Println(firstName, lastName, nickName)
}

func getName() (firstName, lastName, nickName string) { 
    return "X", "C", "xcbeyond" 
} 

输出结果如下:

X C xcbeyond

这种用法可以让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅降低沟通的复杂度和代码维护的难度。

匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。

5、变量的作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。

了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误。如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。

根据变量定义位置的不同,可以分为以下三个类型:

  • 局部变量:函数内定义的变量。
  • 全局变量:函数外定义的变量。
  • 形式参数:函数定义中的变量。

下面就来分别介绍一下。

5.1 局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。

局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。

main()函数中使用到了局部变量a、b、c,示例如下:

package main

import "fmt"

func main() {
    // 声明局部变量a和b并赋值
    var a int = 3
    var b int = 4
    
    // 声明局部变量c.并计算a和b的和
    c := a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

输出结果如下:

a = 3, b = 4, c = 7

5.2 全局变量

在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。

全局变量声明必须以var关键字定义,如果想要在外部包中使用全局变量的首字母必须大写。

定义全局变量c,示例如下:

package main

import "fmt"

// 声明全局变量
var c int

func main() {
    //声明局部变量
    var a, b int
    
    //初始化参数
    a = 3
    b = 4
    c = a + b
    
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

输出结果如下:

a = 3, b = 4, c = 7

Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。

5.3 形式参数

在定义函数时,函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。

形式参数会作为函数的局部变量来使用。

函数sum(a, b int)定义了形式参数ab,示例如下:

package main

import "fmt"

func main() {
	sum := sum(1,3)
	fmt.Println(sum)
}

// 两数求和
func sum(a, b int) int{
	num := a + b
	return num
}

输出结果如下:

4