Go: Go 变量与数据类型

最后更新:2026-08-26

变量是程序的基石——Go 用最少的语法让变量声明既清晰又安全。

Go 的类型系统设计哲学:显式优于隐式。这意味着每个变量都有明确的类型,编译器能在编译期捕获 90% 以上的错误。这节课你将学会 Go 变量声明的全部语法。

1. 你将学到


2. 一个全栈工程师的真实故事

(1) 痛点:动态类型带来的深夜调试

Alice 是一个从 Python 转 Go 的工程师,她刚来公司接手了一个用户注册模块:

"PM 说'上线后用户数据全乱了,出生日期有人填 2024 年 1 月 1 日,有人填 'yesterday',还有人填 null —— 数据库里一半数据无法解析'。"

她打开代码一看,发现 Python 的动态类型是元凶:

PYTHON
# Python:变量类型随时可变
birthday = "1990-01-01"
birthday = 1990  # 整数突然合法
birthday = None  # None 也合法
birthday = ["today"]  # 列表也合法!

同一个变量在代码不同位置可以是字符串、整数、None、列表——没有任何机制阻止这种混乱。

(2) Go 的解法

Alice 用 Go 重写了用户模型:

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)
}

输出:

TEXT 📖 仅展示
User: Alice, Age: 28, Height: 165.5cm

(3) 收益:编译期捕获 90% 错误

维度 Python(动态类型) Go(静态强类型)
编译期类型检查 ❌ 无 ✅ 严格
运行时类型错误 频繁 极少
IDE 自动补全
重构安全性
空值陷阱(None/null) 常见 零值机制防护
💡 提示: Go 的"零值机制"让每个变量在声明时都有一个合理的默认值,消灭了 80% 的空指针问题。


3. 变量声明:var 与 :=

Go 提供两种变量声明方式:var 关键字(标准)和 := 短声明(简写)。

(1) var 关键字声明(标准方式)

var 可以在函数内或包级别使用,有 3 种写法:

GO
// 写法 1: 声明变量,类型显式
var name string = "Alice"

// 写法 2: 声明变量,省略类型(由值推断)
var name = "Alice"

// 写法 3: 仅声明,初始化为零值
var name string  // name = ""

▶ 示例:var 三种写法对比

GO
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)
}
▶ 试一试

输出:

TEXT 📖 仅展示
Alice 28 alice@example.com

(2) := 短声明(最常用)

:= 是 Go 的语法糖,只能在函数内使用,自动推断类型:

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)
}

输出:

TEXT 📖 仅展示
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) 整数类型

100%
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 的区别

GO
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 位平台):

TEXT 📖 仅展示
a (int) size: 8 bytes
b (int64) size: 8 bytes
sum: 300

(2) 浮点类型

类型 大小 精度 典型用途
float32 32 位 ~7 位有效数字 科学计算、图形
float64 64 位 ~15 位有效数字 默认浮点类型

(3) 布尔类型

GO
var isActive bool = true
var hasPermission bool  // 零值 = false

(4) 字符串类型

GO
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 没有"未初始化"的变量——每个变量在声明时都有一个合理的默认值。

▶ 示例:所有类型的零值

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)
}
▶ 试一试

输出:

TEXT 📖 仅展示
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(但容易野指针)
🔥 易错: C/C++ 变量未初始化时是内存里的随机值——这是 90% 缓冲区溢出 bug 的根源。Go 用零值机制从语言层面消灭了这个问题。


6. const 常量与 iota 枚举

(1) const 声明常量

常量在编译期确定,不能修改只能是基本类型

GO
const Pi = 3.14159
const AppName = "web-tutorial"
const MaxConnections = 1000

(2) iota 枚举:Go 的优雅枚举

iota 是 Go 的常量计数器,从 0 开始,每行自动 +1

GO
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)
}

输出:

TEXT 📖 仅展示
Sunday=0 Monday=1 Saturday=6

▶ 示例:自定义 iota 步长(HTTP 状态码)

GO
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)
}
▶ 试一试

输出:

TEXT 📖 仅展示
OK=0 Redirect=100 ClientError=200 ServerError=300

(3) iota 高级用法:位掩码(文件权限)

GO
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)
}

输出:

TEXT 📖 仅展示
Read=1 Write=2 Execute=4 RW=3
💡 提示: iota 是 Go 唯一接近"枚举"的语法,但没有 Python/Java 的 enum 类那么严格。生产环境若需更强类型安全,可自定义类型。


7. 显式类型转换

Go 不允许隐式类型转换——所有转换必须显式声明。这是 Go 的"显式优于隐式"原则的体现。

(1) 数值类型转换

GO
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)
}

输出:

TEXT 📖 仅展示
i=42 f=42 u=42
pi=3.14159 truncated=3

(2) 字符串转换:strconv 包

GO
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"
}

输出:

TEXT 📖 仅展示
n=123 err=<nil>
s=456 (type: string)
f=3.140000
3.14

(3) 隐式转换 vs 显式转换

场景 Java/C++ Go
int + float 自动转 float 编译错误,必须显式
int32 + int64 自动转 int64 编译错误
intstring 隐式 strconv.Itoa()
⚠️ 注意: Go 的严格类型转换看似啰嗦,实则避免了 Java/C++ 中"四舍五入失精""整数溢出"等经典 bug。


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 全功能演示

GO
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)
}
▶ 试一试

输出:

TEXT 📖 仅展示
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. 完整示例:用户注册表单

把本课所有知识点串起来,写一个用户注册数据收集器

GO
// 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)
}

预期输出:

TEXT 📖 仅展示
========================================
      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 课深入错误处理。


❓ 常见问题

Q var:= 哪个更好?
A 函数内推荐 :=,简洁且强制初始化;包级别变量必须用 var。两者生成的字节码完全一样,只是语法糖。
Q Go 的 int 在 32 位和 64 位平台大小不同,会有什么问题?
A 在 64 位平台是 8 字节,32 位平台是 4 字节。如果你的数据要序列化(JSON/数据库),建议显式指定 int32int64,避免跨平台兼容问题。
Q Go 没有 enum 关键字,怎么做枚举?
Aconst + iota。虽然不如 Java 的 enum class 严格,但够用。如果需要类型安全,可以用 type Role int 自定义类型 + 常量集合。
Q iota 的值从 0 开始,能从 1 开始吗?
A 可以。用 _ = iota 跳过 0,或者 iota + 1const (A = iota + 1; B; C) 得到 A=1, B=2, C=3。常用于 HTTP 状态码、月份等需要从 1 开始的场景。
Q Go 为什么不支持隐式类型转换?
A Go 团队认为"显式优于隐式"。隐式转换是 Java/C++ 中很多 bug 的根源(如 int 和 long 混合运算的精度丢失)。Go 的严格类型转换让代码可读性更高、bug 更少。
Q string 和 []byte 怎么转换?
A []byte(s) 把字符串转字节切片(会复制数据);string(b) 把字节切片转字符串。常用于网络 I/O 和加密场景。注意:[]byte 适合修改,string 适合只读。
Q 变量名可以用中文吗?
A 技术上可以(Go 支持 Unicode 标识符),但强烈不推荐。中文变量名会让代码无法在国际团队协作,且部分工具链(lint、IDE)可能识别异常。坚持用英文命名(驼峰或下划线)。
Q 常量可以取地址吗?
A 不能。常量在编译期确定,没有运行时内存地址。&Pi 会编译报错。

📖 小节


📝 作业

  1. 基础题(难度⭐):声明 5 个变量(name/age/height/isStudent/gpa),分别用 var:= 两种方式,打印它们的值和类型。

  2. 进阶题(难度⭐⭐):用 iota 定义 4 个 HTTP 状态码常量(200/404/500/503),写一个 handleRequest(code int) 函数,根据状态码返回对应文本(如 "OK"/"Not Found"/"Internal Server Error"/"Service Unavailable")。

  3. 挑战题(难度⭐⭐⭐):写一个单位转换器:输入华氏温度,输出摄氏温度(公式:C = (F - 32) × 5/9)。要求:(1) 用 fmt.Scanf 读取输入;(2) 用 strconv.ParseFloat 做类型转换;(3) 处理非法输入(返回错误)。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏