Skip to content

1.3 变量声明

若你习惯 Python 这类动态语言,可能对「先声明、带类型」不太熟悉:在 Go 里,变量必须有确定类型,编译器会在编译期检查,因此使用前需要声明(或短声明),并约定该标识符只能承载对应类型的值。

程序里的数据在内存中,代码通过变量名访问数据,而不是直接操作裸地址(可读性更好,也不容易写错)。Go 还要求:先声明再使用同一作用域内不能重复声明同名变量声明出来的变量必须被使用(否则编译报错,避免残留无效代码)。

下面先分清 包级与函数内 能写哪些语句,再归纳 五种常见写法。前两种都是 varconstvar 在「能写在哪」上类似,文末有一节简述。

包级与函数内

包级(函数外)指文件顶层:任意 func花括号外,与 packageimport 同级。

包级

在包级,每条顶层语句必须以关键字开头,例如 packageimportvarconsttypefunc。因此:

  • 声明变量只能用 var(或下面要说的 const 声明常量),不能使用 :=
  • 不能写类似 x = 1 当作「第一次出现变量」——那会被当成赋值,而 x 尚未声明,会报错。
go
package main

import "fmt"

var buildVersion = "1.0.0" // 类型由右侧推断;包级只能用 var,不能 :=

var (
	Debug      bool // 零值 false
	maxRetries = 3
)

func main() {
	fmt.Println(buildVersion, Debug, maxRetries)
}

函数内

func { ... } 里面

  • 仍可使用 var(规则与包级相同)。
  • 还可使用 := 短声明(由编译器推断类型)。
  • 对已声明过的名字改值,只用 =(赋值)。
go
// 在某个 func 体内:
count := 0            // 短声明:仅函数内可用
var done bool         // var 也可以
count, done = 1, true // 赋值:两侧标识符必须已经声明过

对照

位置常用写法备注
包级varvar (...)禁止 :=;顶层须以关键字起句
函数内var:=new(T):= 左侧至少一个新名;避免无意遮蔽外层

遮蔽(shadowing):若包级有 var m = 100,某函数里再写 m := 200,则函数内的 m 是新局部变量,包级 m 在该块内被遮住。为可读性,尽量少内外同名


var、= 与 :=

  • var name type:在作用域里声明变量,可只给类型(零值初始化),也可带初始值。
  • =赋值。左侧必须是已经声明过的变量;不产生新名字。
  • :=短变量声明,只在函数体内可用;编译器根据右侧推断类型。要求:左侧至少有一个名字对当前块而言是新声明的(同一行里可以混用「新声明 + 对已存在变量赋值」)。

典型对比:

go
var a, b int = 1, 2 // 声明并初始化
a, b = b, a         // 仅是赋值:交换 a、b,没有新变量

下面这段则是短声明x 是新变量,b 是已有变量,在同一行合法(右侧的 b 先参与求值,再赋给左侧的 b)。

go
b := 1
x, b := 10, b // x 新声明;b 已存在:本行对 b 是赋值,最终 b 为 1(右侧旧值)

包级不能:=,只能 var / const(见上文「包级与函数内」)。

五种写法速览

方式形式要点常见用途
var name type 或带 = 初值包级变量、需要明确类型、零值开始
var ( ... ) 一组多个相关变量、可读性
name := expr函数内局部变量,类型由右侧推断
a, b := v1, v2函数内一次声明多个变量
new(T)得到 *T,指向 T 的零值,常用于指针或传给 API

var 单行

语法:

text
var <name> <type>

var 是关键字;name 为变量名;type 为类型。只写类型时,Go 会做零值初始化:string"",数值 → 0boolfalse,指针 → nil

若要声明同时初始化:

go
var name string = "Go编程时光"

完整示例:

go
package main

import "fmt"

func main() {
	var name string = "Go编程时光"
	fmt.Println(name)
}

从右侧表达式能明显看出是字符串时,可省略类型,由编译器推断:

go
var name = "Go编程时光"

注意:在 Go 里 " 表示 string' 表示 rune(字符码点),与 Python 里单双引号混用不同,后续在字符串章节会细讲。

若右侧是带小数点的字面量且不写类型,默认推断为 float64。若你希望 float32 等,应显式写出类型,避免隐式变宽类型:

go
var rate float32 = 0.89

与常量:定义常量时把 var 换成 const,同样适用「带类型 / 推断」的写法,例如 const s = "hello"


var 成组

除了多行 var,可以用括号成组声明,适合一批相关变量,结构更清晰:

go
var (
	name   string
	age    int
	gender string
)

同样可以混用初始化:

go
var (
	name   = "Go"
	age    = 1
	gender string // 仍为零值 ""
)

包级变量用 var (...) 很常见;常量则对应 const (...)


短声明 :=

:= 表示「声明 + 赋值」,类型由右侧表达式推断,等价于带推断的 var,但只能写在函数里

go
name := "Go编程时光"

等价于:

go
var name string = "Go编程时光"
// 或
var name = "Go编程时光"

函数外若要「声明包级变量」,必须用 var,不能 :=(再次强调::= 仅出现在函数体内)。


多变量 :=

go
name, age := "wangbm", 28

左侧每个标识符在该块中若是首次出现,则作为新变量;若已存在,则本行对该标识符是赋值。因此 := 常用于接收多返回值:

go
v, ok := m[key]

不要与「多重赋值」混淆:下面交换两个已声明的变量,用的是 =,不是 :=,没有新变量产生。

go
var a int = 100
var b int = 200
a, b = b, a

若写成 a, b := b, a,在 ab 已在本块声明过的情况下,:= 要求至少一个新名字,通常会编译错误;若故意引入新变量(如 a, b := b, a 在嵌套块里产生新的 a),会涉及变量遮蔽(shadowing),新手建议先避免,保持可读性。


new 与指针

变量可分为值变量指针变量:前者存数据本身,后者存指向数据的地址。取地址用 &

go
package main

import "fmt"

func main() {
	var age int = 28
	var ptr = &age // &变量名:取得 age 的地址
	fmt.Println("age: ", age)
	fmt.Println("ptr: ", ptr)
}

输出中 ptr 形如 0xc000010098(具体数值每次运行可能不同),表示 age 的地址。

new(T) 是内建函数:分配一块可存 T 的内存,初始化为 T 的零值,返回 *T,无需先起临时变量名:

go
package main

import "fmt"

func main() {
	ptr := new(int)
	fmt.Println("ptr address: ", ptr)
	fmt.Println("ptr value: ", *ptr) // *指针:解引用取值
}

此时 *ptr0int 的零值)。

与「先声明再取址」对比,下面两种思路类似(都返回 *int):

go
func withNew() *int {
	return new(int)
}

func withVar() *int {
	var dummy int
	return &dummy
}

在表达式里需要「某类型的指针且先零值」时,new(T) 很顺手;结构体还常用 复合字面量取址&T{...}(后续类型章节会见到)。new 不是另一种类型系统,只是分配 + 零值 + 返回指针的语法糖。


选用建议

  • 包级变量:只用 var(或 const),不要 :=
  • 函数内局部变量,且类型明显:优先 :=,代码更短。
  • 需要特定类型(如 float32int64)而字面量会推断成别的:用 var x T = ... 显式写类型。
  • 一组相关零值或初始化:用 var (...)
  • 需要 *T 且先零值:考虑 new(T)var x T; return &x,按可读性选。

以上任意方式,同一作用域内同一名字只能声明一次;重复声明会编译报错。


常量 const

常量在「能写在哪」上与 var 一致:包级、函数内都可 const / const (...)(值须编译期可确定)。不能改值var 可变。

go
package main

import "fmt"

const defaultTimeout = 30 // 包级常量

func main() {
	const localOnly = 1 // 函数内常量,仅在本函数有意义
	fmt.Println(defaultTimeout, localOnly)
}

iota、枚举式常量等在后面章节讲;常量也常用 const (...) 成组书写。


匿名变量 _

_空白标识符,用于必须接收但不需要使用的值(例如多返回值里丢弃某一侧):

  • 不占命名、不引入可引用的变量;
  • 可以多次使用 _,不会像普通变量那样「重复声明」报错。
go
func GetData() (int, int) {
	return 100, 200
}

func main() {
	a, _ := GetData()
	_, b := GetData()
	fmt.Println(a, b)
}

它解决「必须接收但不用」的场景,与上面五种声明方式不是同一类话题。