Go 从 0 到精通 · 第 02 课:变量、常量与基础数据类型

学习定位:这是整套 Go 教程的第 2 课。
前置要求:已经完成第 1 课,理解了 package mainmain()go rungo buildgo mod init
本课目标:掌握变量与常量的声明方式,理解 Go 的零值、类型推断机制,并学会选择常见基础数据类型。


1. 本课你要解决的核心问题

这一课的重点不是“把语法背下来”,而是把下面几个关键问题真正搞明白:

  • 变量到底是什么
  • Go 为什么既有 var,又有 :=
  • 零值是什么意思,为什么重要
  • 常量和变量本质上有什么区别
  • intfloat64boolstring 应该怎么选
  • 什么情况下会遇到类型问题

如果这一课学扎实,后面的条件、循环、函数、切片、结构体都会顺很多。


2. 什么是变量

先不要急着记语法,先从概念理解。

2.1 变量的本质

变量可以理解为:

一块有名字的内存空间,用来保存某种类型的数据。

例如:

  • 你的名字可以保存在变量里
  • 年龄可以保存在变量里
  • 是否登录成功可以保存在变量里
  • 商品价格也可以保存在变量里

在程序运行过程中,变量里的值通常是可以变化的。

比如:

  • 一开始年龄是 18
  • 后面可能变成 19

这就是“变量”的含义。


3. Go 中变量的几种声明方式

Go 的变量声明并不复杂,但它有几种常见写法。你不能只会一种,要知道每一种适合什么场景。


3.1 最完整写法:var 变量名 类型

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
var name string
var age int

fmt.Println(name)
fmt.Println(age)
}

这段代码里:

  • name 的类型是 string
  • age 的类型是 int

即使你没有手动给它们赋值,它们也不会是“随机值”,而会有默认值。这个默认值在 Go 里叫:零值

上面程序输出通常是:

1
2

0

第一行是空字符串,所以你看到的是空白。第二行是 0

这种写法的特点是:

  • 类型写得很清楚
  • 适合强调变量类型
  • 适合你暂时还不赋值的场景

3.2 声明时顺便赋值:var 变量名 类型 = 值

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
var name string = "Tom"
var age int = 18

fmt.Println(name)
fmt.Println(age)
}

这种写法很好理解:

  • 指定变量名
  • 指定类型
  • 指定初始值

它的优点是可读性很强,因为信息非常完整。


3.3 让编译器帮你推断类型:var 变量名 = 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
var name = "Tom"
var age = 18
var price = 99.8
var isActive = true

fmt.Println(name)
fmt.Println(age)
fmt.Println(price)
fmt.Println(isActive)
}

这里你没有手动写类型,但 Go 编译器会根据右边的值自动推断:

  • namestring
  • ageint
  • pricefloat64
  • isActivebool

这就叫 类型推断

你可以把它理解成:

类型依然存在,只是你不用每次都亲手写出来。

这一点非常重要。Go 是强类型语言,不是没有类型,只是有时候由编译器替你判断。


3.4 最常用写法:短变量声明 :=

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
name := "Tom"
age := 18
price := 99.8
isActive := true

fmt.Println(name)
fmt.Println(age)
fmt.Println(price)
fmt.Println(isActive)
}

这是一种非常常见的 Go 写法。

:= 的作用本质上是:

  • 声明变量
  • 同时完成赋值
  • 让编译器推断类型

它几乎等价于:

1
var name = "Tom"

只是写起来更简洁。


4. 为什么 Go 既有 var,又有 :=

这往往是初学者第一个真正会困惑的地方。

你可能会想:既然 := 很方便,那为什么还需要 var

这个问题问得非常好。

4.1 := 适合什么场景

:= 适合:

  • 在函数内部声明局部变量
  • 声明时就有初始值
  • 类型很容易从右侧看出来

例如:

1
2
3
name := "Tom"
count := 10
ok := false

这种写法非常自然、简洁。


4.2 var 适合什么场景

var 适合:

  • 你想明确写出变量类型
  • 你暂时没有初始值
  • 你在包级作用域声明变量
  • 你想用更清晰、更正式的声明方式

例如:

1
2
var total int
var username string

又比如,包级变量不能用 :=

1
2
3
4
5
6
package main

var appName = "go-demo"

func main() {
}

这个场景下只能用 var,不能用 :=


4.3 一个非常重要的规则

:= 只能在函数内部使用

这句话你一定要记住。

也就是说,下面这样是错误的:

1
2
3
4
5
6
package main

name := "Tom"

func main() {
}

因为函数外部不能使用短变量声明。


5. 一次声明多个变量

在 Go 中,你可以一次声明多个变量。

5.1 同一行声明多个变量

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
var name, city string = "Tom", "Shanghai"
var age, score int = 18, 95

fmt.Println(name, city)
fmt.Println(age, score)
}

这种写法适合多个变量类型相同的情况。


5.2 使用分组方式声明

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
var (
name string = "Tom"
age int = 18
city string = "Beijing"
)

fmt.Println(name, age, city)
}

这种写法在变量较多时更整齐。


5.3 使用 := 同时声明多个变量

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
name, age := "Tom", 18
fmt.Println(name, age)
}

这种写法也很常见。


6. 变量重新赋值与声明,不要混淆

这是初学者特别容易混的地方。

6.1 重新赋值用 =

1
2
3
4
5
6
7
8
9
10
package main

import "fmt"

func main() {
age := 18
age = 19

fmt.Println(age)
}

这里:

  • 第一次用 :=,是声明变量并赋值
  • 第二次用 =,是对已有变量重新赋值

6.2 不要对已经存在的同名变量反复用 :=

例如:

1
2
3
4
5
6
package main

func main() {
age := 18
age := 19
}

这通常会报错,因为同一作用域下你不能把同一个变量再次当成“新变量”声明。

你应该写成:

1
2
3
4
5
6
package main

func main() {
age := 18
age = 19
}

这个区别非常关键:

  • := 是“声明并赋值”
  • = 是“给已存在变量赋值”

7. 零值:Go 非常重要的基础概念

这一节很重要,很多后续知识都和它有关。

7.1 什么是零值

在 Go 中,如果一个变量声明了但没有手动赋值,它会自动得到该类型的默认值,这个默认值叫 零值

不同类型的零值不同。

例如:

  • int 的零值是 0
  • float64 的零值是 0
  • bool 的零值是 false
  • string 的零值是空字符串 ""

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
var age int
var price float64
var ok bool
var name string

fmt.Println(age)
fmt.Println(price)
fmt.Println(ok)
fmt.Println(name)
}

7.2 为什么零值设计很重要

很多语言里,未初始化变量可能是危险的;但 Go 的思路是:

让变量即使没有显式赋值,也处于一个可预期状态。

这样做有几个好处:

  • 降低未初始化风险
  • 让代码行为更稳定
  • 让很多结构体字段天然具备默认基础状态

你后面学结构体、切片、map、指针时,会越来越感受到零值的意义。


8. 常量:不会变化的值

变量是可以改变的,而常量通常是定义后不再变化的值。

在 Go 中,常量使用 const 声明。


8.1 基本写法

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
const pi = 3.14
const siteName string = "Go 学习站"

fmt.Println(pi)
fmt.Println(siteName)
}

这里:

  • pi 是常量
  • siteName 也是常量

它们和变量最大的区别是:不能被重新赋值

例如下面这样会报错:

1
2
const pi = 3.14
pi = 3.14159

8.2 为什么要用常量

常量适合表示那些程序运行期间不会变化的值,例如:

  • 圆周率近似值
  • 固定状态码
  • 固定配置标识
  • 业务中的常量文本

使用常量的好处是:

  • 语义更明确
  • 避免误修改
  • 代码更容易维护

8.3 常量也可以分组声明

1
2
3
4
5
const (
StatusPending = "pending"
StatusRunning = "running"
StatusDone = "done"
)

这种写法在定义一组固定值时很常见。


8.4 常量和变量的本质区别

你可以这样理解:

  • 变量:运行过程中可能变化
  • 常量:一旦定义,不允许变化

再从工程角度理解:

  • 会变的数据,用变量
  • 不该变的规则值,用常量

这是一种很重要的编码习惯。


9. 基础数据类型概览

Go 的基础数据类型并不算特别多,但每个都非常重要。

这一课先建立一个清晰地图。


9.1 布尔类型:bool

布尔值只有两个:

  • true
  • false

示例:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
isAdmin := true
isLogin := false

fmt.Println(isAdmin)
fmt.Println(isLogin)
}

bool 常用于:

  • 条件判断
  • 状态标识
  • 开关控制

零值是:

1
false

9.2 整数类型

Go 提供了多种整数类型,例如:

  • int
  • int8
  • int16
  • int32
  • int64
  • uint
  • uint8
  • uint16
  • uint32
  • uint64

其中最常用的是:

  • int
  • int64

新手阶段怎么选

一个非常实用的建议是:

没有特殊需求时,优先用 int

因为:

  • 它最自然
  • 最常用
  • 和很多标准库配合方便

如果你明确知道需要更大范围、或者需要和数据库字段、协议字段严格对齐,再考虑 int64 等更具体类型。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
age := 18
var count int = 100
var bigNumber int64 = 9000000000

fmt.Println(age)
fmt.Println(count)
fmt.Println(bigNumber)
}

关于无符号整数 uint

uint 表示无符号整数,也就是不能表示负数。

虽然它看起来适合表示“不会小于 0 的值”,但在实际业务开发中,很多时候并不建议新手随便使用 uint,因为:

  • 它和 int 混用容易带来类型问题
  • 某些场景转换不够自然

实战中的经验是:

如果没有非常明确的理由,先优先用 int


9.3 字节类型:byte

byte 本质上是 uint8 的别名。

它常用于表示:

  • 一个字节的数据
  • 二进制内容
  • ASCII 字符

例如:

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
var b byte = 'A'
fmt.Println(b)
}

这里你会看到输出的是对应的数值编码。

后面讲字符串、字节切片、文件和网络读写时,byte 会非常重要。


9.4 字符类型:rune

rune 本质上是 int32 的别名,常用于表示一个 Unicode 码点。

先不用急着深挖,只需要先建立一个印象:

  • byte 更偏字节
  • rune 更偏字符语义

例如中文字符、emoji 等场景,后面讲字符串时会详细展开。


9.5 浮点类型:float32float64

Go 中最常见的浮点数类型是:

  • float32
  • float64

通常更常用的是:

float64

原因是精度更高,也更常见。

示例:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
price := 19.99
var rate float64 = 3.14159

fmt.Println(price)
fmt.Println(rate)
}

这里如果你直接写 19.99,编译器通常会推断成 float64

一个实战提醒

浮点数适合表示“近似小数”,但不适合直接处理需要绝对精确的金额计算。

例如:

  • 科学计算、比例、平均值,用浮点数通常没问题
  • 金额计算,需要更谨慎,后面工程实践里我会专门讲

你现在先建立这个意识即可。


9.6 字符串类型:string

字符串用来表示文本。

示例:

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
name := "Tom"
city := "北京"

fmt.Println(name)
fmt.Println(city)
}

字符串的零值是:

1
""

也就是空字符串。

字符串的一个重要特征

你现在先记住一句话:

Go 中的字符串是只读的文本值。

后面我们会专门用一整课深入讲:

  • 字符串长度
  • 中文字符问题
  • byterune 的区别
  • 字符串拼接
  • 常用字符串处理函数

这一课先不展开太深。


10. 类型推断到底怎么理解

很多有其他语言经验的人,会把类型推断误解成“Go 类型不严格”。这是错误的。

10.1 Go 依然是强类型语言

例如:

1
2
3
4
5
6
7
8
9
package main

func main() {
age := 18
name := "Tom"

_ = age
_ = name
}

这里:

  • age 不是“随便什么都能放”,它已经被确定为某种整数类型
  • name 也不是“任何类型都能塞进去”,它已经是 string

所以类型推断只是:

编译器替你决定类型,而不是取消类型。


10.2 为什么 Go 要支持类型推断

它解决的是一个很现实的问题:

1
var name string = "Tom"

这里右边已经明显是字符串,再重复写一遍 string 有时会显得冗余。

所以 Go 提供:

1
name := "Tom"

这样更简洁。

这也是 Go 的典型风格:

  • 保持强类型
  • 避免无意义啰嗦

11. 类型不一致时,Go 不会“替你乱猜”

Go 在类型上比较严格,这其实是优点。

例如某些语言允许你随意混合不同数值类型,但 Go 通常要求你明确处理。

比如:

1
2
3
4
5
6
7
8
9
package main

func main() {
var a int = 10
var b int64 = 20

_ = a
_ = b
}

虽然这段代码本身没问题,但如果你想直接把 ab 相加,往往就会遇到类型不一致问题。

这体现了 Go 的原则:

类型要明确,转换要显式。

这种设计会让代码更安全,也更容易排查问题。

详细的类型转换,我们放到下一课重点展开。


12. 常见坑总结

这一节非常重要,我把新手在变量、常量和基础类型阶段最容易踩的坑给你提前列出来。

12.1 把 :== 搞混

记忆口诀:

  • := 用于声明并赋值
  • = 用于给已有变量赋值

12.2 在函数外使用 :=

这是错误的。
函数外只能用 varconst


12.3 定义了变量但没有使用

Go 编译器会报错。

例如:

1
2
3
4
5
package main

func main() {
name := "Tom"
}

如果 name 没被使用,编译时通常就会出错。

这不是针对你,而是 Go 的统一规则:

  • 保持代码干净
  • 避免无意义变量残留

12.4 以为 var 没赋值就会是随机值

不会。Go 有零值机制。
这是它和某些语言不同的重要特征。


12.5 随便使用 uint

新手不要因为“某个值不可能为负”就本能地改用 uint
工程里优先考虑一致性和可维护性,通常先用 int 更稳妥。


12.6 把字符串、数字、布尔当成可以随意混用

Go 是强类型语言,不会帮你做很多模糊处理。
后面涉及输入输出和类型转换时,你会更明显感受到这一点。


13. 一段综合示例

下面用一段代码,把本课核心知识串起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

const siteName = "Go 学习营"

func main() {
var username string
var age int

city := "上海"
price := 99.9
isVip := true

username = "小明"
age = 20

fmt.Println(siteName)
fmt.Println(username)
fmt.Println(age)
fmt.Println(city)
fmt.Println(price)
fmt.Println(isVip)
}

你要能自己解释清楚:

  • 哪些是变量
  • 哪些是常量
  • 哪些使用了零值机制
  • 哪些使用了类型推断
  • 哪些地方用了重新赋值

如果你能把这段代码完整拆解明白,这节课就掌握得很不错了。


14. 本课练习

一定要亲手写,不要只看。

练习 1:声明并输出你的个人信息

要求:

  • 声明姓名
  • 声明年龄
  • 声明所在城市
  • 声明是否已经开始学习 Go
  • 把它们打印出来

建议同时使用:

  • var
  • :=

目的是感受两种写法的差别。


练习 2:体验零值

要求:

  • var 声明一个 string
  • var 声明一个 int
  • var 声明一个 bool
  • 不赋值,直接打印

观察输出结果,并尝试自己解释为什么会这样。


练习 3:体验重新赋值

要求:

  • 先声明一个年龄变量,值设为 18
  • 然后把它改成 19
  • 打印修改后的值

重点体会:

  • 第一次为什么用 :=var
  • 第二次为什么只能用 =

练习 4:定义常量

要求:

  • 定义一个常量保存网站名称
  • 定义一个常量保存班级名称
  • 打印它们

然后你可以故意尝试修改常量,观察编译器报错。


练习 5:混合声明

自己写一个程序,里面同时包含:

  • const
  • var
  • :=
  • int
  • float64
  • bool
  • string

把它当成本课的小总结作业。


15. 自测题

你可以不看文档,试着回答下面的问题。

15.1 概念题

  1. :== 的区别是什么?
  2. 为什么 Go 既需要 var,又需要 :=
  3. 什么是零值?
  4. string 的零值是什么?
  5. bool 的零值是什么?
  6. 为什么说 Go 是强类型语言?
  7. 包级变量为什么不能用 :=
  8. 没有特殊需求时,为什么建议优先使用 int

如果你能流畅回答这些问题,就说明你已经不只是“看懂”,而是开始形成自己的理解了。


16. 本课总结

这一课你学到的,不只是几个声明语法,而是 Go 变量系统背后的设计思路。

你现在应该已经理解:

  • 变量是保存数据的命名空间
  • Go 提供 var:= 两种常见声明方式
  • := 更简洁,但只能在函数内部使用
  • = 是重新赋值,不是声明
  • 零值让未显式赋值的变量也有可预期状态
  • 常量用 const 声明,适合不会变化的值
  • 基础类型里最常用的是 boolintfloat64string
  • Go 的类型推断并不等于弱类型,它本质上依然是强类型语言

如果把这一课打牢,你写 Go 时就不会总在“声明变量”这一步反复卡住。


17. 下一课预告

下一课我们进入:输入输出与类型转换

会重点讲:

  • fmt.Printfmt.Printlnfmt.Printf 的区别
  • 如何格式化输出变量
  • 如何从控制台读取输入
  • 为什么类型转换在 Go 里必须显式进行
  • intfloat64string 之间如何转换
  • 新手最容易遇到的格式化输出错误

这一课会非常实用,因为从下一课开始,你写的程序会更像“真正能交互的小程序”。