Golang——变量
变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
一、变量声明
var 变量名字 类型 = 表达式
其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用零值初始化该变量。
零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
一组变量也可以通过调用一个函数,由函数返回的多个返回值初始化
二、简短变量声明
在函数内部,有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。它以“名字 := 表达式”形式声明变量,变量的类型根据表达式来自动推导
简短变量声明语句也可以用函数的返回值来声明和初始化变量
i, j := 0, 1
f, err := os.Open(name)
简短变量声明语句中必须至少要声明一个新的变量
简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量
var形式的声明语句往往是用于需要显式指定变量类型的地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方
注意:
- 简短变量声明只可用于声明和初始化局部变量。而全局变量应当以
var a = 1这样的形式才可赋值。比如你想声明并初始化一个全局的map,应该类似这么写:var m = make(map[int]int)
三、指针
一个指针的值是另一个变量的地址。通过指针,我们可以直接读或更新对应变量的值,而不需要知道该变量的名字
&x表达式(取x变量的内存地址)
*p表达式对应p指针指向的变量的值
任何类型的指针的零值都是nil。指针之间也是可以进行相等测试的,只有当它们指向同一个变量或全部是nil时才相等。
在Go语言中,返回函数中局部变量的地址也是安全的
//调用f函数时创建局部变量v,在局部变量地址被返回之后依然有效,因为指针p依然引用这个变量
var p = f()
func f() *int {
v := 1
return &v
}
因为指针包含了一个变量的地址,因此如果将指针作为参数调用函数,那将可以在函数中通过该指针来更新变量的值。
每次我们对一个变量取地址,或者复制指针,我们都是为原变量创建了新的别名
四、new函数
另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为*T
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(T)
每次调用new函数都是返回一个新的变量的地址
new函数使用通常相对比较少
五、变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间段。
对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。
局部变量的生命周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。
函数的右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样:
for t := 0.0; t < cycles*2*math.Pi; t += res {
x := math.Sin(t)
y := math.Sin(t*freq + phase)
img.SetColorIndex(
size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个特性
) // 小括弧另起一行缩进,和大括弧的风格保存一致
}
因为一个变量的有效周期只取决于是否可达,因此一个循环迭代内部的局部变量的生命周期可能超出其局部作用域。同时,局部变量可能在函数返回之后依然存在。
var global *int
func f() {
var x int
x = 1
global = &x
}
func g() {
y := new(int)
*y = 1
}
f函数里的x变量必须在堆上分配,因为它在函数退出后依然可以通过包一级的global变量找到,虽然它是在函数内部定义的
相反,当g函数返回时,变量*y将是不可达的,也就是说可以马上被回收的。编译器可以选择在栈上分配*y的存储空间,也可以选择在堆上分配,然后由Go语言的GC回收这个变量的内存空间。
逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响
如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。