变量
变量
变量是用于保存一个值的存储位置,允许其存储的值在运行时动态的变化。每声明一个变量,都会为其分配一块内存以存储对应类型的值,前往参考手册-变量以查看更多细节。
声明
在go中的类型声明是后置的,变量的声明会用到var
关键字,格式为var 变量名 类型名
,变量名的命名规则必须遵守标识符的命名规则。
var intNum int
var str string
var char byte
当要声明多个相同类型的变量时,可以只写一次类型
var numA, numB, numC int
当要声明多个不同类型的变量时,可以使用()
进行包裹,可以存在多个()
。
var (
name string
age int
address string
)
var (
school string
class int
)
一个变量如果只是声明而不赋值,那么变量存储的值就是对应类型的零值。
赋值
赋值会用到运算符=
,例如
var name string
name = "jack"
也可以声明的时候直接赋值
var name string = "jack"
或者这样也可以
var name string
var age int
name, age = "jack", 1
第二种方式每次都要指定类型,可以使用官方提供的语法糖:短变量初始化,可以省略掉var
关键字和后置类型,具体是什么类型交给编译器自行推断。
name := "jack" // 字符串类型的变量。
虽然可以不用指定类型,但是在后续赋值时,类型必须保持一致,下面这种代码无法通过编译。
a := 1
a = "1"
还需要注意的是,短变量初始化不能使用nil
,因为nil
不属于任何类型,编译器无法推断其类型。
name := nil // 无法通过编译
短变量声明可以批量初始化
name, age := "jack", 1
短变量声明方式无法对一个已存在的变量使用,比如
// 错误示例
var a int
a := 1
// 错误示例
a := 1
a := 2
但是有一种情况除外,那就是在赋值旧变量的同时声明一个新的变量,比如
a := 1
a, b := 2, 2
这种代码是可以通过编译的,变量a
被重新赋值,而b
是新声明的。
在go语言中,有一个规则,那就是所有在函数中的变量都必须要被使用,比如下面的代码只是声明了变量,但没有使用它
func main() {
a := 1
}
那么在编译时就会报错,提示你这个变量声明了但没有使用
a declared and not used
这个规则仅适用于函数内的变量,对于函数外的包级变量则没有这个限制,下面这个代码就可以通过编译。
var a = 1
func main() {
}
匿名
用下划线可以表示不需要某一个变量
Open(name string) (*File, error)
比如os.Open
函数有两个返回值,我们只想要第一个,不想要第二个,可以按照下面这样写
file, _ := os.Open("readme.txt")
未使用的变量是无法通过编译的,当你不需要某一个变量时,就可以使用下划线_
代替。
交换
在Go中,如果想要交换两个变量的值,不需要使用指针,可以使用赋值运算符直接进行交换,语法上看起来非常直观,例子如下
num1, num2 := 25, 36
num1, num2 = num2, num1
三个变量也是同样如此
num1, num2, num3 := 25, 36, 49
num1, num2, num3 = num3, num2, num1
思考下面这一段代码,这是计算斐波那契数列的一小段代码,三个变量在计算后的值分别是什么
a, b, c := 0, 1, 1
a, b, c = b, c, a+b
答案是
1 1 1
你可能会疑惑为什么不是
1 1 2
明明a已经被赋予b的值了,为什么a+b的结果还是1?go在进行多个变量赋值运算时,它的顺序是先计算值再赋值,并非从左到右计算。
a, b, c = b, c, a+b
你可能会以为它会被展开成下面这段
a = b
b = c
c = a + b
但实际上它会将a, b, c三个数的值分别计算好再赋给它们,就等同于下面这段代码
a, b, c = 1, 1, 0+1
当涉及到函数调用时,这个效果就更为明显,我们有一个函数sum
可以计算两个数字的返回值
func sum(a, b int) int {
return a + b
}
通过函数来进行两数相加
a, b, c := 0, 1, 1
a, b, c = b, c, sum(a, b)
结果没有变化,在计算sum
函数返回值时,它的入参依旧是0和1
1 1 1
所以代码应该这样分开写。
a, b = b, c
c = a + b
比较
变量之间的比较有一个大前提,那就是它们之间的类型必须相同,go语言中不存在隐式类型转换,像下面这样的代码是无法通过编译的
func main() {
var a uint64
var b int64
fmt.Println(a == b)
}
编译器会告诉你两者之间类型并不相同
invalid operation: a == b (mismatched types uint64 and int64)
所以必须使用强制类型转换
func main() {
var a uint64
var b int64
fmt.Println(int64(a) == b)
}
在没有泛型之前,早期go提供的内置min
,max
函数只支持浮点数,到了1.21版本,go才终于将这两个内置函数用泛型重写,现在可以使用min
函数比较最小值
minVal := min(1, 2, -1, 1.2)
使用max
函数比较最大值
maxVal := max(100, 22, -1, 1.12)
它们的参数支持所有的可比较类型,go中的可比较类型有
- 布尔
- 数字
- 字符串
- 指针
- 通道 (仅支持判断是否相等)
- 元素是可比较类型的数组(切片不可比较)
- 字段类型都是可比较类型的结构体(仅支持判断是否相等)
除此之外,还可以通过导入标准库cmp
来判断,不过仅支持有序类型的参数,在go中内置的有序类型只有数字和字符串。
import "cmp"
func main() {
cmp.Compare(1, 2)
cmp.Less(1, 2)
}
代码块
在函数内部,可以通过花括号建立一个代码块,代码块彼此之间的变量作用域是相互独立的。例如下面的代码
func main() {
a := 1
{
a := 2
fmt.Println(a)
}
{
a := 3
fmt.Println(a)
}
fmt.Println(a)
}
它的输出是
2
3
1
块与块之间的变量相互独立,不受干扰,无法访问,但是会受到父块中的影响。
func main() {
a := 1
{
a := 2
fmt.Println(a)
}
{
fmt.Println(a)
}
fmt.Println(a)
}
它的输出是
2
1
1