一、变量“来源”
变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。当我们定义一个变量时,通常都会给它起一个有意义的名字。名字的规范见 命名和声明 。
Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。
二、变量类型
变量的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等(关于各种数据类型的详细介绍详见第三章)。Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。
三、变量声明
Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。 并且Go语言的变量声明后必须使用。变量声明的一般语法如下:
var 变量名字 类型 = 表达式
“类型”或“= 表达式”两个部分可以省略其中的一个:
- “类型”省略:根据初始化表达式来推导变量的类型信息
- “= 表达式”省略:用零值初始化该变量,所以在Go语言中不存在未初始化的变量
- 数值类型变量对应的零值是0
- 布尔类型变量对应的零值是false
- 字符串类型对应的零值是空字符串""
- 接口或引用类型(包括slice、指针、map、chan和函数)变量对应的零值是nil
- 数组或结构体等聚合类型对应的零值是每个元素或字段都是对应该类型的零值
各变量类型的零值:
除了单个声明之外,还可以“批量”一起声明;一组变量也可以通过调用一个函数,由函数返回的多个返回值初始化。
// int, int, int
var i, j, k int
// bool, float64, string
var b, f, s = true, 2.3, "four"
// os.Open returns a file and an error
var f, err = os.Open(name)
短变量声明
以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导。因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。
有一个比较微妙的地方:简短变量声明左边的变量可能并不是全部都是刚刚声明的。如果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了。简短变量声明语句中必须至少要声明一个新的变量。
关于短变量声明更详细的部分,可以参考 命名和声明 小节内容。
四、指针
上面我们讨论的,都是围绕变量的名字来操作的。一个变量对应一个保存了变量对应类型值的内存空间。而现在,我们要讨论“变量”的另一个“身份”——指针!
一个指针的值是另一个变量的地址。即对应变量在内存中的存储位置。并不是每一个值都会有一个内存地址,但是对于每一个变量必然有对应的内存地址。
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
&x表达式是取址操作,得到变量的内存地址,赋值给*int(我们称之为“指向int类型的指针”)变量p;*p表达式对应p指针指向的变量的值。
任何类型的指针的零值都是nil。如果p指向某个有效变量,那么p != nil测试为真。
五、new函数
内建的new函数是另外一个创建变量的方法。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T。new函数类似是一种语法糖。但new函数通常相对使用比较少。
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
六、变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间段。这部分内容详细参考 代码块和作用域 小节的内容。
Go语言的自动垃圾收集器是如何知道一个变量是何时可以被回收的呢?这里我们可以避开完整的技术细节,基本的实现思路是,从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在并不会影响程序后续的计算结果。
编译器会自动选择在栈上还是在堆上分配局部变量的存储空间。一般局部变量(未产生变量逃逸)的情况下,选择在栈上分配存储空间。而全局变量等生命周期更长的变量则会在堆上分配存储空间。
一般的,在函数内部定义了一个局部变量,然后返回这个局部变量的地址(指针)。这些局部变量是在栈上分配的(静态内存分配),一旦函数执行完毕,变量占据的内存会被销毁,任何对这个返回值作的动作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。比如,下面的程序中,x就发生了“变量逃逸”(x局部变量从函数f中逃逸了,在局部函数外面也可以访问到,即可达)。
var global *int
func f() {
var x int
x = 1
global = &x
}
好了,关于变量的相关的知识点就整理到这里,接下来继续学习 常量 吧!