Go: Go 变量与数据类型
最后更新:2026-08-26
变量是程序的基石——Go 用最少的语法让变量声明既清晰又安全。
Go 的类型系统设计哲学:显式优于隐式。这意味着每个变量都有明确的类型,编译器能在编译期捕获 90% 以上的错误。这节课你将学会 Go 变量声明的全部语法。
1. 你将学到
var和:=两种变量声明方式- Go 的 14 种基本数据类型
- Go 独特的零值(zero value)机制
const常量声明与iotaenum- 显式类型转换(Go 不允许隐式转换)
fmt.Printf/fmt.Sprintf格式化输出- 用户注册表单综合案例
2. 一个全栈工程师的真实故事
(1) 痛点:动态类型带来的深夜调试
Alice 是一个从 Python 转 Go 的工程师,她刚来公司接手了一个用户注册模块:
"PM 说'上线后用户数据全乱了,出生日期有人填 2024 年 1 月 1 日,有人填 'yesterday',还有人填
null—— 数据库里一半数据无法解析'。"
她打开代码一看,发现 Python 的动态类型是元凶:
# Python:变量类型随时可变
birthday = "1990-01-01"
birthday = 1990 # 整数突然合法
birthday = None # None 也合法
birthday = ["today"] # 列表也合法!
同一个变量在代码不同位置可以是字符串、整数、None、列表——没有任何机制阻止这种混乱。
(2) Go 的解法
Alice 用 Go 重写了用户模型:
// user.go
package main
import "fmt"
type User struct {
Name string // 字符串
Age int // 整数
Height float64 // 浮点
IsActive bool // 布尔
Birthday string // ISO 8601 日期字符串
}
func main() {
user := User{
Name: "Alice",
Age: 28,
Height: 165.5,
IsActive: true,
Birthday: "1996-05-12",
}
// 想把 Age 改成字符串?编译期就报错!
// user.Age = "twenty-eight" // ❌ cannot use "twenty-eight" as int
fmt.Printf("User: %s, Age: %d, Height: %.1fcm\n",
user.Name, user.Age, user.Height)
}
输出:
User: Alice, Age: 28, Height: 165.5cm
(3) 收益:编译期捕获 90% 错误
| 维度 | Python(动态类型) | Go(静态强类型) |
|---|---|---|
| 编译期类型检查 | ❌ 无 | ✅ 严格 |
| 运行时类型错误 | 频繁 | 极少 |
| IDE 自动补全 | 弱 | 强 |
| 重构安全性 | 低 | 高 |
| 空值陷阱(None/null) | 常见 | 零值机制防护 |
3. 变量声明:var 与 :=
Go 提供两种变量声明方式:var 关键字(标准)和 := 短声明(简写)。
(1) var 关键字声明(标准方式)
var 可以在函数内或包级别使用,有 3 种写法:
// 写法 1: 声明变量,类型显式
var name string = "Alice"
// 写法 2: 声明变量,省略类型(由值推断)
var name = "Alice"
// 写法 3: 仅声明,初始化为零值
var name string // name = ""
▶ 示例:var 三种写法对比
package main
import "fmt"
func main() {
// 写法 1: 显式类型 + 初始化
var userName string = "Alice"
var userAge int = 28
// 写法 2: 类型推断
var userEmail = "alice@example.com"
// 写法 3: 仅声明,依赖零值
var userBio string // ""
fmt.Println(userName, userAge, userEmail, userBio)
}
输出:
Alice 28 alice@example.com
(2) := 短声明(最常用)
:= 是 Go 的语法糖,只能在函数内使用,自动推断类型:
package main
import "fmt"
func main() {
// 短声明:变量名 := 值
name := "Alice" // 推断为 string
age := 28 // 推断为 int
height := 165.5 // 推断为 float64
isActive := true // 推断为 bool
fmt.Printf("%s is %d years old, height %.1f, active: %v\n",
name, age, height, isActive)
}
输出:
Alice is 28 years old, height 165.5, active: true
(3) var vs := 怎么选?
| 场景 | 推荐写法 | 理由 |
|---|---|---|
| 包级别变量 | var |
:= 不允许在函数外使用 |
| 函数内局部变量 | := |
简洁,少敲 4 个字符 |
| 需要零值 | var x int |
:= 必须同时初始化 |
| 需要显式类型转换 | var x int = int(3.14) |
类型转换清晰可见 |
| 批量声明 | var (...) |
:= 不支持批量 |
4. Go 的基本数据类型
Go 有 14 种基本数据类型,按用途分 4 大类。
(1) 整数类型
graph TB
A[整数类型] --> B[有符号 int]
A --> C[无符号 uint]
A --> D[字节型 byte]
A --> E[特殊 rune]
B --> B1[int<br/>至少 32 位]
B --> B2[int8 / int16 / int32 / int64]
C --> C1[uint<br/>至少 32 位]
C --> C2[uint8 / uint16 / uint32 / uint64]
D --> D1[byte = uint8<br/>强调是字节]
E --> E1[rune = int32<br/>强调是 Unicode 码点]
| 类型 | 大小 | 范围 | 典型用途 |
|---|---|---|---|
int |
32 或 64 位(依赖平台) | -2^31 ~ 2^31-1 或更大 | 默认整数类型 |
int8 |
8 位 | -128 ~ 127 | 小范围整数 |
int32 |
32 位 | -2^31 ~ 2^31-1 | 跨平台固定大小 |
int64 |
64 位 | -2^63 ~ 2^63-1 | 大整数、时间戳 |
uint |
32 或 64 位 | 0 ~ 2^32-1 或更大 | 默认无符号 |
byte |
8 位 | 0 ~ 255 | 字节、ASCII 字符 |
rune |
32 位 | Unicode 码点 | 单个 Unicode 字符 |
▶ 示例:int 和 int64 的区别
package main
import (
"fmt"
"unsafe"
)
func main() {
var a int = 100
var b int64 = 200
fmt.Printf("a (int) size: %d bytes\n", unsafe.Sizeof(a))
fmt.Printf("b (int64) size: %d bytes\n", unsafe.Sizeof(b))
// int 和 int64 不能直接相加!
// sum := a + b // ❌ mismatched types int and int64
// 必须显式转换
sum := a + int(b)
fmt.Println("sum:", sum)
}
输出(在 64 位平台):
a (int) size: 8 bytes
b (int64) size: 8 bytes
sum: 300
(2) 浮点类型
| 类型 | 大小 | 精度 | 典型用途 |
|---|---|---|---|
float32 |
32 位 | ~7 位有效数字 | 科学计算、图形 |
float64 |
64 位 | ~15 位有效数字 | 默认浮点类型 |
(3) 布尔类型
var isActive bool = true
var hasPermission bool // 零值 = false
(4) 字符串类型
var greeting string = "Hello, Go!"
var emptyString string // 零值 = ""(空字符串)
var multiLine string = `第一行
第二行
第三行` // 反引号支持多行
(5) 14 种类型速查表
| 类别 | 类型 | 零值 |
|---|---|---|
| 整数 | int / int8 / int16 / int32 / int64 |
0 |
| 无符号整数 | uint / uint8 / uint16 / uint32 / uint64 |
0 |
| 字节/字符 | byte (=uint8) / rune (=int32) |
0 |
| 浮点 | float32 / float64 |
0.0 |
| 布尔 | bool |
false |
| 字符串 | string |
"" |
5. Go 独特的零值机制
Go 没有"未初始化"的变量——每个变量在声明时都有一个合理的默认值。
▶ 示例:所有类型的零值
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
var p *int // 指针零值是 nil
fmt.Printf("int: %d\n", i)
fmt.Printf("float64: %f\n", f)
fmt.Printf("bool: %v\n", b)
fmt.Printf("string: %q\n", s) // %q 显示带引号的字符串,空串显示 ""
fmt.Printf("pointer: %v\n", p)
}
输出:
int: 0
float64: 0.000000
bool: false
string: ""
pointer: <nil>
(1) 零值 vs 其他语言
| 类型 | Go 零值 | Java | Python | C/C++ |
|---|---|---|---|---|
| 整数 | 0 | 0 | 不存在 | 未定义(垃圾值) |
| 浮点 | 0.0 | 0.0 | 不存在 | 未定义 |
| 布尔 | false | false | False | 未定义 |
| 字符串 | "" | null | "" | 未定义 |
| 指针 | nil | null | None | NULL(但容易野指针) |
6. const 常量与 iota 枚举
(1) const 声明常量
常量在编译期确定,不能修改,只能是基本类型:
const Pi = 3.14159
const AppName = "web-tutorial"
const MaxConnections = 1000
(2) iota 枚举:Go 的优雅枚举
iota 是 Go 的常量计数器,从 0 开始,每行自动 +1:
package main
import "fmt"
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
func main() {
fmt.Printf("Sunday=%d Monday=%d Saturday=%d\n", Sunday, Monday, Saturday)
}
输出:
Sunday=0 Monday=1 Saturday=6
▶ 示例:自定义 iota 步长(HTTP 状态码)
package main
import "fmt"
const (
StatusOK = iota * 100 // 0
StatusRedirect // 100
StatusClientError // 200
StatusServerError // 300
)
func main() {
fmt.Printf("OK=%d Redirect=%d ClientError=%d ServerError=%d\n",
StatusOK, StatusRedirect, StatusClientError, StatusServerError)
}
输出:
OK=0 Redirect=100 ClientError=200 ServerError=300
(3) iota 高级用法:位掩码(文件权限)
package main
import "fmt"
const (
ReadPermission = 1 << iota // 1 << 0 = 1
WritePermission // 1 << 1 = 2
ExecutePermission // 1 << 2 = 4
)
func main() {
rw := ReadPermission | WritePermission
fmt.Printf("Read=%d Write=%d Execute=%d RW=%d\n",
ReadPermission, WritePermission, ExecutePermission, rw)
}
输出:
Read=1 Write=2 Execute=4 RW=3
iota 是 Go 唯一接近"枚举"的语法,但没有 Python/Java 的 enum 类那么严格。生产环境若需更强类型安全,可自定义类型。
7. 显式类型转换
Go 不允许隐式类型转换——所有转换必须显式声明。这是 Go 的"显式优于隐式"原则的体现。
(1) 数值类型转换
package main
import "fmt"
func main() {
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
fmt.Printf("i=%d f=%v u=%d\n", i, f, u)
// 截断示例:float 转 int 会丢失小数
var pi float64 = 3.14159
var truncated = int(pi)
fmt.Printf("pi=%.5f truncated=%d\n", pi, truncated)
}
输出:
i=42 f=42 u=42
pi=3.14159 truncated=3
(2) 字符串转换:strconv 包
package main
import (
"fmt"
"strconv"
)
func main() {
// 字符串 → 数字
n, err := strconv.Atoi("123")
fmt.Printf("n=%d err=%v\n", n, err)
// 数字 → 字符串
s := strconv.Itoa(456)
fmt.Printf("s=%s (type: %T)\n", s, s)
// 字符串 → float
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Printf("f=%f\n", f)
// float → 字符串(指定精度)
fmt.Println(strconv.FormatFloat(3.14159, 'f', 2, 64)) // "3.14"
}
输出:
n=123 err=<nil>
s=456 (type: string)
f=3.140000
3.14
(3) 隐式转换 vs 显式转换
| 场景 | Java/C++ | Go |
|---|---|---|
int + float |
自动转 float | 编译错误,必须显式 |
int32 + int64 |
自动转 int64 | 编译错误 |
int 转 string |
隐式 | 需 strconv.Itoa() |
8. fmt 格式化输出
(1) 常用格式化动词
| 动词 | 含义 | 适用类型 |
|---|---|---|
%d |
十进制整数 | int, int8/16/32/64 |
%f |
浮点数 | float32, float64 |
%s |
字符串 | string |
%v |
任意类型的默认格式 | 任何类型 |
%T |
类型 | 任何类型 |
%t |
布尔 | bool |
%q |
带引号字符串 | string, rune |
%x |
十六进制 | int, []byte |
%p |
指针地址 | pointer |
%% |
字面百分号 | — |
▶ 示例:fmt.Printf 全功能演示
package main
import "fmt"
func main() {
name := "Alice"
age := 28
height := 165.5
isActive := true
hobbies := []string{"coding", "reading", "hiking"}
fmt.Printf("Name: %s\n", name) // %s 字符串
fmt.Printf("Age: %d\n", age) // %d 整数
fmt.Printf("Height: %.1f cm\n", height) // %.1f 浮点保留 1 位
fmt.Printf("Active: %t\n", isActive) // %t 布尔
fmt.Printf("Hobbies: %v\n", hobbies) // %v 默认格式
fmt.Printf("Name type: %T\n", name) // %T 类型
fmt.Printf("100%% complete!\n") // %% 字面 %
// %v 高级用法:%+v 显示字段名(结构体),%#v 显示 Go 语法
fmt.Printf("hobbies: %#v\n", hobbies)
}
输出:
Name: Alice
Age: 28
Height: 165.5 cm
Active: true
Hobbies: [coding reading hiking]
Name type: string
100% complete!
hobbies: []string{"coding", "reading", "hiking"}
(2) Print / Println / Printf 区别
| 函数 | 输出结尾 | 适用场景 |
|---|---|---|
fmt.Print() |
不换行 | 连续输出 |
fmt.Println() |
自动换行 + 空格分隔 | 简单调试 |
fmt.Printf() |
不换行,需 \n |
格式化输出 |
fmt.Sprintf() |
返回字符串,不输出 | 拼接字符串 |
9. 完整示例:用户注册表单
把本课所有知识点串起来,写一个用户注册数据收集器:
// main.go
package main
import (
"fmt"
"strconv"
)
// 用 iota 定义用户角色枚举
const (
RoleGuest = iota // 0 访客
RoleUser // 1 普通用户
RoleAdmin // 2 管理员
RoleSuperAdmin // 3 超级管理员
)
func main() {
// 1. 用 := 短声明用户信息
name := "Alice"
age := 28
height := 165.5
isActive := true
role := RoleUser
// 2. 显式类型转换
ageAsFloat := float64(age)
heightAsString := strconv.FormatFloat(height, 'f', 1, 64)
// 3. const 常量
const MaxLoginAttempts = 5
// 4. fmt 格式化输出
fmt.Println("========================================")
fmt.Println(" User Registration Form")
fmt.Println("========================================")
fmt.Printf("Name: %s\n", name)
fmt.Printf("Age: %d (%.0f years old)\n", age, ageAsFloat)
fmt.Printf("Height: %s cm\n", heightAsString)
fmt.Printf("Active: %t\n", isActive)
fmt.Printf("Role: %d (User)\n", role)
fmt.Printf("Max Attempts: %d\n", MaxLoginAttempts)
fmt.Printf("Type Check: name is %T, age is %T\n", name, age)
fmt.Println("========================================")
// 5. 变量零值演示
var uninitializedAge int
fmt.Printf("\nUninitialized age (zero value): %d\n", uninitializedAge)
}
预期输出:
========================================
User Registration Form
========================================
Name: Alice
Age: 28 (28 years old)
Height: 165.5 cm
Active: true
Role: 1 (User)
Max Attempts: 5
Type Check: name is string, age is int
========================================
Uninitialized age (zero value): 0
strconv.Atoi("abc") 会返回错误,不能忽略。正确写法:n, err := strconv.Atoi("abc"); if err != nil { ... }。第 8 课深入错误处理。
❓ 常见问题
var 和 := 哪个更好?:=,简洁且强制初始化;包级别变量必须用 var。两者生成的字节码完全一样,只是语法糖。int 在 32 位和 64 位平台大小不同,会有什么问题?int32 或 int64,避免跨平台兼容问题。enum 关键字,怎么做枚举?const + iota。虽然不如 Java 的 enum class 严格,但够用。如果需要类型安全,可以用 type Role int 自定义类型 + 常量集合。_ = iota 跳过 0,或者 iota + 1:const (A = iota + 1; B; C) 得到 A=1, B=2, C=3。常用于 HTTP 状态码、月份等需要从 1 开始的场景。[]byte(s) 把字符串转字节切片(会复制数据);string(b) 把字节切片转字符串。常用于网络 I/O 和加密场景。注意:[]byte 适合修改,string 适合只读。&Pi 会编译报错。📖 小节
- Go 用
var和:=两种方式声明变量,函数内推荐:=,包级必须var - 14 种基本类型分 4 类:整数(int/int8-64)、浮点(float32/64)、布尔、字符串
- 零值机制是 Go 的安全设计:每个变量都有合理默认值,消灭野指针
const声明常量,iota实现优雅枚举(HTTP 状态码、位掩码)- Go 禁止隐式类型转换,必须显式(
int(x)/strconv.Itoa()) fmt.Printf用格式化动词(%d/%f/%s/%v/%T/%q/%t)控制输出- Go 静态强类型在编译期捕获 90% 错误,是工程化协作的基石
📝 作业
-
基础题(难度⭐):声明 5 个变量(name/age/height/isStudent/gpa),分别用
var和:=两种方式,打印它们的值和类型。 -
进阶题(难度⭐⭐):用
iota定义 4 个 HTTP 状态码常量(200/404/500/503),写一个handleRequest(code int)函数,根据状态码返回对应文本(如 "OK"/"Not Found"/"Internal Server Error"/"Service Unavailable")。 -
挑战题(难度⭐⭐⭐):写一个单位转换器:输入华氏温度,输出摄氏温度(公式:C = (F - 32) × 5/9)。要求:(1) 用
fmt.Scanf读取输入;(2) 用strconv.ParseFloat做类型转换;(3) 处理非法输入(返回错误)。