变量的使用
1 变量的定义
变量定义范式:var 变量名字 类型 = 表达式
,
其中类型和表达式可以省略一个, 省略类型时, 变量的类型由表达式的类型确定, 省略表达式时, 变量的初始值为类型的零值.
var a int = 0 // 完全的定义范式
var b int // 省略初始化,执行默认初始化
var c = 0 // 省略类型,根据初始化值确定类型
d := 0 // 省略 var关键字,使用 := 定义,只能在函数内部使用,这个叫做简短变量声明
2 简短变量声明
简短变量声明的方式可以省略var关键值和显示类型指定,使用 “名字 := 表达式”
形式声明变量,变量的类型根据表达式来自动推导。
i := 100 // an int
其次,可以同时声明一组多个的变量:
i, j := 0, 1
在函数的返回值里面也接受返回一组变量,并使用简单声明接收:
f, err := os.Open(name)
if err != nil {
return err
}
// ...use f...
f.Close()
另外,声明一组简短变量时,允许部分变量是已存在的变量(但不能全部都是已存在的变量,编译器会报错 no new variables on left side of :=
),观看下面例子:
// 示例1,编译ok
in, err := os.Open(infile) // 首次声明,变量被创建
// ...
out, err := os.Create(outfile) // 只有out是首次声明才被新创建,err复用之前的变量
// 示例2,编译ok
var a int = 0
a, b := 0, 0 // a执行赋值,b执行创建
// 示例3,编译failed
var a int = 0
var b int = 0
a, b := 0, 0 // 编译器报错,no new variables on left side of :=
3 指针
go里面继承了c的指针编程风格,允许任何一个变量执行进行取地址操作,并使用一个指针变量指向这个地址。
一个 T
类型的变量的指针类型是 *T
,这和C/C++
语言的风格有所不同,访问,修改指针指向的值和c/c++
相同,使用``* + 指针变量名`即可
func main() {
var a int = 0
var ptr *int = nil
ptr = &a
*ptr = 1
fmt.Println(a, ptr, *ptr) // 程序输出:1 0xc0000100c0 1
}
3.1 new操作
另外,go提供了内建函数(built-in) new
来便捷的创建一个指针变量,使用范式是 new(T)
:
p := new(int) // p, *int 类型, 指向匿名的 int 变量
fmt.Println(*p) // "0"
值得注意的是,go语言中,new是一个函数,不是一个关键字(这意味着你在你的代码里面可以shadow
这个定义),同时new
也不像c/c++
里面是在堆上分配内存, 变量是分配在堆上还是栈上,要视具体情况而定,这是由go的编译器自动推断完成。
4 变量的生命周期
包一级变量(类比于C/C++
中的全局变量)的生命周期是跟随程序的生命周期的,局部变量的生命周期要看它的作用域和引用范围。
与C/C++
语言不同的是,局部变量离开了自己的作用域不一定马上被回收,或者说这块儿内存直接失效,也就是:
局部变量可能在函数返回之后依然存在,一个循环迭代内部的局部变量的生命周期也可能超出其局部作用域。
代码示例如下:
package main
import (
"fmt"
)
var globalVar *int
func f() {
var x int
x = 1
globalVar = &x
}
func g() {
y := new(int)
*y = 1
}
func main() {
f()
*globalVar = 1
fmt.Println(*globalVar) // 程序输出: 1
}
如代码所示,包一级变量引用了函数 f
里面的x
局部变量,f
函数退出后,x
变量所在的内存区域仍然有效。
5 变量的内存分配
不同于C++
语言,go
中的局部变量并不是固定说要分配到栈上的,这要看变量的一个引用情况,如果这个变量离开作用域仍然被外部引用(这种行为,将其称之为逃逸)。针对于go语言中变量的默认分配原则大致如下:
- 全局变量分配在堆上,生命周期贯穿整个程序的运行;
- 只要局部变量逃逸到函数外部,编译器通常会将其分配在堆上, 从而避免该变量因为栈的回收,导致外部引用该变量的地方不能再使用这个变量;
var globalVar *int
func escape1() {
var x int
x = 1
globalVar = &x
}
func escape2() *int {
x := 42 // x 逃逸到堆上,因为它的地址被返回
return &x
}
func noEscape() int {
y := 42 // y 分配在栈上,因为它的生命周期仅限于函数内部
return y
}
总的来说,Go语言中的变量分配位置由编译器根据逃逸分析决定,虽然这无需开发者显示管理,但是了解其中原理,有利于帮助我们优化程序性能。
6 支持的赋值形式
整体形式的形式和C++无异,编程范式为 被赋值的变量或元组 = 需要被取值的变量,常量或其他表达式
x = 1 // 命名变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // 结构体字段赋值
count[x] = count[x] * scale // 数组、slice或map的元素赋值
支持二元操作符的简化:
count[x] *= scale
自增减操作只支持++
和--
操作放右边,且这是一个语句,不是一个表达式。
// 示例1,编译ok
v := 1
v++ // 等价方式 v = v + 1;v 变成 2
v-- // 等价方式 v = v - 1;v 变成 1
// 实例2,编译failed
x = i++ // ++操作是一个独立的语句,=号右边只能是一个表达式
6.1 元组赋值
go允许对多个变量进行赋值操作,赋值之前,赋值语句右边的表达式会先进行求值,然后再统一更新左边对应变量的值。
i, j, k = 2, 3, 5
a[i], a[j] = a[j], a[i]
// 利用元组赋值轻松实现最大公约数算法
func gcd(a int, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}