Go 语言变量
基本概念
变量是存储特定类型数据的基本单位,在程序运行时分配内存(初始化),并能被修改。
初始化
初始化主要指为变量分配内存并设置初始值。通过变量初始化可以避免程序出现意外行为,优化内存使用效率。在 Go 语言中,初始化会在编译时或运行时进行:
- 编译时初始化:编译器在编译过程中分析代码,对可在编译时确定值的变量和常量进行初始化。
- 运行时初始化:如果变量依赖运行时信息或是复杂数据结构,如切片、映射和通道等,初始化会在程序运行时进行。
声明类型、函数和方法和声明变量不同,它们会在编译时被转换成机器指令,没有初始化过程。
初始化步骤
变量初始化可分为以下几个环节:
- 声明变量:首先在全局或局部声明变量名称和类型。
- 内存分配:对于需要动态分配的类型如切片、映射等分配内存,一般通过函数
make()
或new()
来完成。 - 赋初值:为变量设置初始值,可以是常量或表达式。如未显式赋值,则赋予类型零值。一些复杂的结构体和对象,可能有专门构造函数来进行初始化操作。
初始化方法
new
函数用于为给定的任何类型分配内存,初始化数据为零值,返回指针:
package main
import "fmt"
func main() {
type Data struct {
id int
valid bool
}
nPtr := new(int) // 分配内存并初始化为 0,类型是 *int
tPtr := new(Data) // 初始化值为元素类型零值 {0 false}
sPtr := new([]int) // 初始化为 nil,类型是 *[]int,代表指向指针的指针,没有意义。
fmt.Println(nPtr, tPtr, sPtr)
}
make
函数用于给内置引用类型(包括切片、映射和通道)初始化,返回一个立即可用的类型实例:
- 初始化切片:可以指定切片长度和容量参数。
- 初始化映射:可以指定映射容量参数。
- 初始化通道:初始化通道唯一的方式,而通道只有初始化后才能使用。
映射和通道在使用前必须初始化,切片没要求,但最好都使用 make
函数来初始化:
package main
import "fmt"
func main() {
// 使用 make 来初始化切片,等价于使用零值字面量初始化切片
s := make([]int, 3)
r := []int{0, 0, 0}
// 初始化映射和通道
m := make(map[string]int)
c := make(chan int, 5)
fmt.Println(s, r, m, c)
}
变量声明
声明变量使用 var
关键字,后跟变量名、变量类型和初始值,值和类型都是可选,但两者不能同时省略:
var VariableName VariableType = Expression
-
VariableName
:变量名称。 -
VariableType
:变量类型,可以是任何有效类型。如果省略,将由编译器自动推断。 -
Expression
:变量值或赋值表达式。如果省略,变量值将初始化为其类型零值。
编译器自动推断出的默认类型有下面几种:
- 整数值:
int
。 - 小数值:
float64
。 - 字符串:
string
。 - 布尔值:
bool
。
变量被声明后会立即分配内存空间,局部变量禁止只声明不使用。此外在同一作用域内,不能重复声明变量:
package main
import "fmt"
func main() {
// 变量声明示范
var a float32 = 1.1 // 完整声明,用于强调类型
var b = 1 // 省略类型,自动推断为 int
var c bool // 省略值,默认零值 flase
var ( // 声明变量组
d = "d"
e []rune
)
// 错误示范
//var f = 3 // 编译错误:定义后没用上
//var a = 22 // 语法错误:重复定义
fmt.Println(a, b, c, d, e)
}
简短声明
在函数内部,可以使用 :=
语法快速声明并初始化局部变量:
VariableName := Expression
-
VariableName
:变量名称。 -
Expression
:初始值或赋值表达式,类型由值自动推断得出。
短变量声明等价于 var VariableName = Expression
,但还能用于流程控制初始化语句中:
package main
import "fmt"
// 语法错误:在函数外使用简短声明
//a := 1
func main() {
// 简短变量声明格式只能用于函数内部
b := 3.0 // 使用字面量赋值
c := float32(b / 0) // 变量表达式赋值
// 用于初始化语句中
for i := 0; i < int(b); i++ {
fmt.Println(i)
}
// 语法错误:无新变量
//b := 2.2
fmt.Println(b, c) // 输出:3 +Inf
fmt.Printf("%T %T", b, c) // 自动推断为:float64 float32
}
短变量声明依赖一个明确的作用域,通常约定在函数内部使用简短声明,在函数外使用 var
声明变量。
初始化零值
所有变量在 Go 语言中都需要初始化,当变量被声明但没显式初始化时,会被自动赋予一个类型默认值,也被称为零值。常见数据类型零值见下表,对于复合类型如数组或结构体,零值是内部所有元素的零值:
类型 | 零值 |
---|---|
布尔型 | false |
整数型 | 0 |
浮点型 | 0 |
复数型 | 0+0i |
字符串 | "" |
函数、接口、通道、映射、切片、指针 | nil |
要检查变量是否被赋值,可以将变量值和零值比较:
package main
import (
"fmt"
)
func main() {
// 函数类型变量
var f func(string) int
fmt.Println(f) // 输出:<nil> 表示不引用任何函数实现
// 接口类型变量
var i interface{ DoSomething() }
fmt.Println(i) // 输出:<nil> 表示没有包含任何值或具体类型
// 通道类型变量
var ch chan int
fmt.Println(ch) // 输出:<nil> 表示未打开,不能用于数据传递
// 映射类型变量
var m map[string]int
fmt.Println(m) // 输出:map[] 表示没有被初始化,且不能直接使用
// 切片类型变量
var s []int
fmt.Println(s) // 输出:[] 表示长度和容量都是 0,且没有底层数组
// 整型指针变量
var p *int
fmt.Println(p) // 输出:<nil> 表示不指向任何内存位置
// 对指针变量赋值
v := 10
p = &v
// 和零值比较看是否赋值
if p == nil {
fmt.Println("p 未被赋值。")
} else {
fmt.Printf("p 被赋值,值为 %d\n", *p)
}
}
零值为 nil
的类型作为函数参数时,可以直接传递 nil
值给函数,减少多余变量定义。同样作为函数返回值时,也能直接返回 nil
代表没有数据:
package main
import "fmt"
func nilCaller(i *int) []byte {
if i == nil {
return nil
}
return nil
}
func main() {
// 直接传入 nil
var f = nilCaller(nil)
println(f) // 输出:[0/0]0x0
if f == nil {
fmt.Println(f) // 输出:[]
}
}
重声明
声明变量后,相同作用域内只能修改变量值,不能重复声明变量。但有一种特例:当使用简短变量声明进行多重赋值时,如果有引入新变量,能同时对相同作用域内已声明变量进行重声明。
重声明其实是退化成赋值操作,并不是真正的变量声明,因此值要匹配变量类型。重声明常用于处理函数返回值,例如重用 err
错误变量,无需为每次函数调用创建临时变量:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
f, err := os.Open("config.ini")
fmt.Println(err)
// 为新变量 r 赋值,重用 err 变量
r, err := http.Get("http://localhost")
fmt.Println(err)
// 由于出现错误,所以都为 nil
fmt.Println(f, r)
}
特别注意重声明条件,只能作用于同个作用域内,外部作用域变量不会参与到重声明:
package main
import "fmt"
var a int
// 在初始化函数中没有触发重声明
func init() {
// 此处 a 是新的局部变量
a, b := 1, 2
fmt.Println("init:", a, b)
}
func main() {
fmt.Println("main:", a) // 输出:0
}
变量赋值
赋值运算符由等号 =
实现,用于将等号右边表达式的值赋予等号左边的变量,也叫赋值语句(Assignment Statement)。赋值方式有三种,包括简单赋值、多重赋值以及复合赋值。
简单赋值
赋值最基本形式是将一个表达式的值赋给一个变量:
VariableName = Expression
VariableName
:变量名称。Expression
:计算得到值,赋给变量。字面量也算表达式,直接代表自己的值。
赋值时,左侧变量类型必须与右侧结果类型相同,或者右侧类型可以被隐式转为左侧类型:
package main
import "fmt"
func main() {
var a, b int
a = 10 // 将 10 赋值给 a
b = a * 2_0. // 右侧是赋值表达式,可以使用任意二元算术运算符
fmt.Println(b)
}
多重赋值
多重赋值也称为「并行赋值」或「元组赋值」,是一种同时对多个变量进行赋值的语法。多重赋值中,右侧所有表达式先计算完成,然后再统一赋值给左侧变量,常用于处理函数返回值:
package main
import "fmt"
// caller 返回两个整数
func caller() (int, int) {
return 7, 13
}
func main() {
var a, b = 5, 10 // 用于初始化变量
a, b = caller() // 用于接受返回值
fmt.Println(a, b)
}
多重赋值还可用于变量值交换,不需要用到中间变量:
package main
import "fmt"
func main() {
// 直接交换变量值
x, y := 5, 10
x, y = y, x
fmt.Println(x, y) // 输出:10 5
// 右侧先计算得到 2,3,1 再赋值
// 完全不同于 a=b;b=c;c=a
a, b, c := 1, 2, 3
a, b, c = b, c, a
fmt.Println(a, b, c) // 输出:2 3 1
}
注意,只有变量类型相同才能进行值交换。
复合赋值
使用复合赋值运算符在赋值同时进行算术或位运算:
package main
import "fmt"
func main() {
a := 10
a *= 2 // 等同于 a = a * 2
fmt.Println(a)
}