变量与数据类型
想象你在厨房做饭:变量就是贴了标签的储物盒,标签是名字,盒子里装的是数据;数据类型就是盒子的形状——圆盒子装汤、方盒子装菜,不能搞混。Go 语言对"盒子"管理很严格:你必须先声明要什么盒子(类型),装什么内容(赋值),没装东西的盒子也有默认的东西(零值)。
1. 核心概念
1.1 变量声明:var vs :=
Go 提供两种声明变量的方式:
| 方式 | 语法 | 适用场景 | 作用域 |
|---|---|---|---|
var |
var name int |
包级变量、需要显式类型 | 函数内/外均可 |
短变量 := |
name := 10 |
局部变量、类型推断 | 仅函数内 |
关键区别:
var可以不赋值(自动用零值),:=必须赋值。var可以在函数外声明,:=只能在函数内使用。:=是声明+赋值的简写,左侧至少有一个新变量才算声明。
1.2 基本数据类型
| 类别 | 类型 | 说明 | 示例 |
|---|---|---|---|
| 整数 | int, int8, int16, int32, int64 |
有符号整数 | var a int = 42 |
| 无符号整数 | uint, uint8, uint16, uint32, uint64 |
非负整数 | var b uint = 100 |
| 浮点数 | float32, float64 |
小数(默认 float64) |
var c float64 = 3.14 |
| 布尔 | bool |
true / false |
var d bool = true |
| 字符串 | string |
不可变的字节序列 | var e string = "hello" |
| 字节 | byte |
uint8 的别名,表示一个字节 |
var f byte = 'A' |
| Unicode字符 | rune |
int32 的别名,表示一个 Unicode 码点 |
var g rune = '中' |
平台相关类型: int 和 uint 在 32 位系统上是 32 位,在 64 位系统上是 64 位。日常开发中,大多数情况用 int 就够了。
1.3 零值机制
Go 中声明变量但不赋值时,变量会被自动初始化为零值,而不是 null 或未定义:
| 类型 | 零值 |
|---|---|
整数 (int, uint 等) |
0 |
浮点数 (float32, float64) |
0.0 |
布尔 (bool) |
false |
字符串 (string) |
""(空字符串) |
| 指针、切片、map、channel、函数、接口 | nil |
这是 Go 的安全机制——你永远不会拿到一个"没有值"的变量,避免了很多空指针错误。
1.4 常量与 iota
const声明常量,值在编译期确定,之后不可修改。iota是常量生成器,在const块中从 0 开始自增,适合定义枚举。
1.5 类型转换
Go 不支持隐式类型转换,不同类型之间必须显式转换:
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
1.6 fmt 包格式化输出
fmt 是 Go 最常用的格式化包,主要函数:
| 函数 | 作用 | 示例 |
|---|---|---|
fmt.Println |
打印并换行 | fmt.Println("hello") |
fmt.Printf |
格式化打印(不换行) | fmt.Printf("值=%d", 42) |
fmt.Sprintf |
格式化为字符串(不打印) | s := fmt.Sprintf("%d", 42) |
常用格式占位符:%d(整数)、%f(浮点)、%s(字符串)、%t(布尔)、%T(类型)、%v(通用值)、%q(带引号的字符串)。
2. 基本语法/用法
变量声明
// 方式一:var 声明(可在函数内、外)
var name string // 声明,零值 ""
var age int = 25 // 声明并赋值
var score = 98.5 // 类型推断为 float64
// 方式二:短变量 :=(仅函数内)
city := "北京" // 声明并赋值,类型推断为 string
count := 100 // 类型推断为 int
// 批量声明
var (
width int = 10
height int = 20
area float64
)
var 声明了变量但没使用它,Go 编译器会报错 declared and not used。这是 Go 的设计哲学:不允许无用代码存在。短变量 := 同理。
常量声明
// 单个常量
const Pi = 3.14159
const Language string = "Go"
// 批量常量 + iota 枚举
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
iota 只在 const 块内有效,每新增一行自动 +1。如果某行用 _ 跳过,iota 仍然会自增。
类型转换
var i int = 100
var f float64 = float64(i) // 显式转换:int → float64
var s string = string(rune(i)) // int → rune → string(注意:不是把数字变字符串)
// 把数字转成字符串的正确方式:
s2 := fmt.Sprintf("%d", i) // "100"
string(65) 得到的是字符 "A"(ASCII 码),不是 "65"。要把数字变成十进制字符串,请用 fmt.Sprintf 或 strconv.Itoa。
3. 示例代码
示例 1:基础用法(难度⭐)
场景: 声明不同类型的变量,打印它们的值和类型。
package main
import "fmt"
func main() {
// ========== 变量声明 ==========
// 1. var 声明 + 显式类型
var name string = "张三"
var age int = 28
// 2. var 声明 + 类型推断(编译器根据值自动判断类型)
salary := 15000.5 // 推断为 float64
// 3. 短变量声明(最常用的方式)
isEmployed := true
city := "上海"
// 4. 零值演示——声明但不赋值
var defaultInt int // 零值:0
var defaultFloat float64 // 零值:0
var defaultBool bool // 零值:false
var defaultString string // 零值:""
// ========== 打印输出 ==========
fmt.Println("=== 个人信息 ===")
fmt.Printf("姓名: %s (类型: %T)\n", name, name)
fmt.Printf("年龄: %d (类型: %T)\n", age, age)
fmt.Printf("工资: %.1f (类型: %T)\n", salary, salary)
fmt.Printf("在职: %t (类型: %T)\n", isEmployed, isEmployed)
fmt.Printf("城市: %s (类型: %T)\n", city, city)
fmt.Println("\n=== 零值演示 ===")
fmt.Printf("int 零值: %d\n", defaultInt)
fmt.Printf("float64 零值: %v\n", defaultFloat)
fmt.Printf("bool 零值: %t\n", defaultBool)
fmt.Printf("string 零值: %q\n", defaultString) // %q 显示带引号的字符串
}
输出:
=== 个人信息 ===
姓名: 张三 (类型: string)
年龄: 28 (类型: int)
工资: 15000.5 (类型: float64)
在职: true (类型: bool)
城市: 上海 (类型: string)
=== 零值演示 ===
int 零值: 0
float64 零值: 0
bool 零值: false
string 零值: ""
要点:
%T可以打印变量的底层类型。%q对字符串会加上引号,方便调试时区分空字符串和有内容的字符串。- 零值机制让变量永远有确定的值,这是 Go 安全性的重要保障。
示例 2:进阶用法(难度⭐⭐)
场景: 使用常量、iota 定义枚举,以及类型转换的实际应用。
package main
import "fmt"
// ========== iota 枚举:定义角色权限 ==========
type Role int // 自定义类型,底层是 int
const (
Guest Role = iota // 0:访客
Member // 1:会员
Admin // 2:管理员
SuperAdmin // 3:超级管理员
)
// 给 Role 实现 String() 方法,方便打印
func (r Role) String() string {
names := [...]string{"访客", "会员", "管理员", "超级管理员"}
if int(r) < len(names) {
return names[r]
}
return "未知角色"
}
// ========== iota 高级用法:位运算权限标志 ==========
type Permission int
const (
Read Permission = 1 << iota // 1 (001)
Write // 2 (010)
Execute // 4 (100)
)
// ========== 类型转换实战 ==========
func main() {
// --- iota 枚举使用 ---
var userRole Role = Admin
fmt.Printf("用户角色: %s (值: %d)\n", userRole, userRole)
// --- 位运算权限组合 ---
userPerm := Read | Write // 组合权限:读 + 写 = 3 (011)
fmt.Printf("\n用户权限值: %d (二进制: %03b)\n", userPerm, userPerm)
fmt.Printf("有读权限? %t\n", userPerm&Read != 0)
fmt.Printf("有执行权限? %t\n", userPerm&Execute != 0)
// --- 类型转换 ---
var temperature float64 = 36.6
var intTemp int = int(temperature) // float64 → int(截断小数,不是四舍五入)
fmt.Printf("\n体温: %.1f°C → 整数部分: %d\n", temperature, intTemp)
// 数字 → 字符串的两种方式
var code int = 65
char := string(rune(code)) // 方式1:转成对应 Unicode 字符 → "A"
numStr := fmt.Sprintf("%d", code) // 方式2:转成数字字符串 → "65"
fmt.Printf("数字 %d → 字符: %s, 字符串: %q\n", code, char, numStr)
// --- 字符串与字节/字符 ---
s := "Hello,世界"
fmt.Printf("\n字符串 %q 的长度: %d 字节\n", s, len(s))
fmt.Println("逐字节遍历(不推荐处理中文):")
for i := 0; i < len(s); i++ {
fmt.Printf(" [%d] %d %c\n", i, s[i], s[i])
}
fmt.Println("逐 rune 遍历(正确处理中文):")
for i, r := range s {
fmt.Printf(" [%d] %U '%c'\n", i, r, r)
}
}
输出:
用户角色: 管理员 (值: 2)
用户权限值: 3 (二进制: 011)
有读权限? true
有执行权限? false
体温: 36.6°C → 整数部分: 36
数字 65 → 字符: A, 字符串: "65"
字符串 "Hello,世界" 的长度: 12 字节
逐字节遍历(不推荐处理中文):
[0] 72 H
[1] 101 e
[2] 108 l
[3] 108 l
[4] 111 o
[5] 44 ,
[6] 228 ä
[7] 184 ¸
[8] 150 \x96
[9] 228 ä
[10] 150 \x96
[11] 175 ¯
逐 rune 遍历(正确处理中文):
[0] U+0048 'H'
[1] U+0065 'e'
[2] U+006C 'l'
[3] U+006C 'l'
[4] U+006F 'o'
[5] U+002C ','
[6] U+4E16 '世'
[7] U+754C '界'
要点:
iota从 0 开始,每行自动 +1,非常适合定义枚举常量。- 位运算权限是
iota的经典应用:用|组合权限,用&检查权限。 int(float64)会直接截断小数部分,不会四舍五入。len(string)返回的是字节数,不是字符数。中文在 UTF-8 中占 3 个字节。- 遍历字符串用
range才能正确处理多字节字符(rune)。
示例 3:综合应用(难度⭐⭐⭐)
场景: 模拟一个简单的用户注册信息处理系统,综合运用变量、常量、类型转换、格式化输出。
package main
import (
"fmt"
"strings"
)
// ========== 常量定义 ==========
// 性别枚举
type Gender int
const (
Unknown Gender = iota // 0:未知
Male // 1:男
Female // 2:女
)
func (g Gender) String() string {
return [...]string{"未知", "男", "女"}[g]
}
// 会员等级及对应折扣
const (
LevelBronze = 1
LevelSilver = 2
LevelGold = 3
LevelPlatinum = 4
)
// 使用 iota 生成折扣比例(单位:百分比)
const (
_ = iota // 跳过 0
DiscountBronze = 95 // 95折
DiscountSilver = 90 // 90折
DiscountGold = 85 // 85折
DiscountPlatinum = 80 // 80折
)
// ========== 用户结构体(预览,后续课程详解) ==========
type User struct {
Name string
Age int
Gender Gender
Level int
Balance float64
}
// ========== 核心逻辑 ==========
func main() {
// 模拟用户数据
users := []User{
{Name: "李明", Age: 25, Gender: Male, Level: LevelGold, Balance: 1580.50},
{Name: "王芳", Age: 30, Gender: Female, Level: LevelPlatinum, Balance: 3200.00},
{Name: "张伟", Age: 17, Gender: Male, Level: LevelBronze, Balance: 200.00},
{Name: "赵敏", Age: 22, Gender: Female, Level: LevelSilver, Balance: 800.75},
}
fmt.Println("╔══════════════════════════════════════════════════════════╗")
fmt.Println("║ 用户信息注册系统 ║")
fmt.Println("╚══════════════════════════════════════════════════════════╝")
fmt.Println()
// 统计变量
var totalAge int
var totalBalance float64
var maleCount, femaleCount int
var adultCount int
for _, u := range users {
// 获取折扣
discount := getDiscount(u.Level)
discountAmount := u.Balance * float64(100-discount) / 100.0
// 格式化输出用户信息
ageTag := ""
if u.Age >= 18 {
ageTag = "✅ 成年"
adultCount++
} else {
ageTag = "❌ 未成年"
}
fmt.Printf("👤 %-6s | 性别: %-2s | 年龄: %d岁 (%s) | 等级: Lv.%d | 余额: ¥%.2f\n",
u.Name, u.Gender, u.Age, ageTag, u.Level, u.Balance)
fmt.Printf(" 💰 享受 %d 折优惠,可节省 ¥%.2f\n", discount, discountAmount)
// 统计
totalAge += u.Age
totalBalance += u.Balance
switch u.Gender {
case Male:
maleCount++
case Female:
femaleCount++
}
fmt.Println(strings.Repeat("-", 60))
}
// 输出统计信息
avgAge := float64(totalAge) / float64(len(users))
fmt.Println()
fmt.Println("📊 统计信息:")
fmt.Printf(" 用户总数: %d 人\n", len(users))
fmt.Printf(" 男性: %d 人 | 女性: %d 人\n", maleCount, femaleCount)
fmt.Printf(" 成年人: %d 人 | 未成年: %d 人\n", adultCount, len(users)-adultCount)
fmt.Printf(" 平均年龄: %.1f 岁\n", avgAge)
fmt.Printf(" 总余额: ¥%.2f\n", totalBalance)
fmt.Printf(" 平均余额: ¥%.2f\n", totalBalance/float64(len(users)))
// 演示类型转换和格式化
fmt.Println()
fmt.Println("🔧 类型转换演示:")
demoTypeConversion()
}
// getDiscount 根据会员等级返回折扣
func getDiscount(level int) int {
switch level {
case LevelBronze:
return DiscountBronze
case LevelSilver:
return DiscountSilver
case LevelGold:
return DiscountGold
case LevelPlatinum:
return DiscountPlatinum
default:
return 100 // 无折扣
}
}
// demoTypeConversion 演示各种类型转换场景
func demoTypeConversion() {
// 1. 数值类型互转
var pi float64 = 3.14159
var intPi int = int(pi) // 截断,不是四舍五入
fmt.Printf(" float64 → int: %.5f → %d\n", pi, intPi)
// 2. 数字 → 字符串(两种方式对比)
var num int = 2024
way1 := string(rune(num)) // Unicode 字符 → 不是你想要的
way2 := fmt.Sprintf("%d", num) // 十进制字符串 → "2024"
way3 := fmt.Sprintf("%x", num) // 十六进制字符串 → "7e8"
fmt.Printf(" int → string (rune): %q\n", way1)
fmt.Printf(" int → string (%%d): %q\n", way2)
fmt.Printf(" int → string (%%x): %q\n", way3)
// 3. 字符串 → 数值(使用 fmt.Sscanf)
var parsed int
_, err := fmt.Sscanf("12345", "%d", &parsed)
if err == nil {
fmt.Printf(" string → int (Sscanf): %d\n", parsed)
}
// 4. bool → string
b := true
boolStr := fmt.Sprintf("%t", b)
fmt.Printf(" bool → string: %t → %q\n", b, boolStr)
// 5. rune 和 byte 的区别
var r rune = '中'
var b2 byte = 'A'
fmt.Printf(" rune '中': 值=%d, 十六进制=%U, 占%d字节(UTF-8)\n", r, r, len(string(r)))
fmt.Printf(" byte 'A': 值=%d, 十六进制=%U, 占1字节\n", b2, b2)
}
输出:
╔══════════════════════════════════════════════════════════╗
║ 用户信息注册系统 ║
╚══════════════════════════════════════════════════════════╝
👤 李明 | 性别: 男 | 年龄: 25岁 (✅ 成年) | 等级: Lv.3 | 余额: ¥1580.50
💰 享受 85 折优惠,可节省 ¥237.08
------------------------------------------------------------
👤 王芳 | 性别: 女 | 年龄: 30岁 (✅ 成年) | 等级: Lv.4 | 余额: ¥3200.00
💰 享受 80 折优惠,可节省 ¥640.00
------------------------------------------------------------
👤 张伟 | 性别: 男 | 年龄: 17岁 (❌ 未成年) | 等级: Lv.1 | 余额: ¥200.00
💰 享受 95 折优惠,可节省 ¥10.00
------------------------------------------------------------
👤 赵敏 | 性别: 女 | 年龄: 22岁 (✅ 成年) | 等级: Lv.2 | 余额: ¥800.75
💰 享受 90 折优惠,可节省 ¥80.08
------------------------------------------------------------
📊 统计信息:
用户总数: 4 人
男性: 2 人 | 女性: 2 人
成年人: 3 人 | 未成年: 1 人
平均年龄: 23.5 岁
总余额: ¥5781.25
平均余额: ¥1445.31
🔧 类型转换演示:
float64 → int: 3.14159 → 3
int → string (rune): "\u07e8"
int → string (%d): "2024"
int → string (%x): "7e8"
string → int (Sscanf): 12345
bool → string: true → "true"
rune '中': 值=20013, 十六进制=U+4E16, 占3字节(UTF-8)
byte 'A': 值=65, 十六进制=U+0041, 占1字节
要点:
- 常量块配合
iota可以优雅地定义枚举和递增值。 - 类型转换必须显式进行,Go 不会帮你自动转换。
string(int)和fmt.Sprintf("%d", int)效果完全不同,前者是 Unicode 字符,后者是数字字符串。rune用于处理 Unicode 字符,byte用于处理单字节数据。%q格式化字符串时会带引号并转义特殊字符,调试时非常有用。
3. 常见应用场景
场景一:配置信息管理
package main
import "fmt"
// 应用配置常量(编译期确定,不可修改)
const (
AppName = "GoWeb"
Version = "1.0.0"
MaxRetries = 3
DefaultTimeout = 30 // 秒
)
// 环境枚举
type Env int
const (
Dev Env = iota // 开发环境
Staging // 预发布环境
Prod // 生产环境
)
func (e Env) String() string {
return [...]string{"开发", "预发布", "生产"}[e]
}
func main() {
// 运行时变量
currentEnv := Prod
dbHost := "192.168.1.100"
dbPort := 3306
debug := currentEnv == Dev
fmt.Printf("应用: %s v%s\n", AppName, Version)
fmt.Printf("环境: %s\n", currentEnv)
fmt.Printf("数据库: %s:%d\n", dbHost, dbPort)
fmt.Printf("调试模式: %t\n", debug)
fmt.Printf("最大重试: %d 次, 超时: %d 秒\n", MaxRetries, DefaultTimeout)
}
场景二:数据格式化与报表
package main
import "fmt"
func main() {
// 模拟学生成绩数据
type Student struct {
Name string
Score float64
Grade string
}
students := []Student{
{"小明", 92.5, ""},
{"小红", 87.0, ""},
{"小刚", 76.5, ""},
{"小丽", 95.0, ""},
{"小华", 63.0, ""},
}
// 根据分数自动评定等级
for i := range students {
switch {
case students[i].Score >= 90:
students[i].Grade = "A (优秀)"
case students[i].Score >= 80:
students[i].Grade = "B (良好)"
case students[i].Score >= 70:
students[i].Grade = "C (中等)"
case students[i].Score >= 60:
students[i].Grade = "D (及格)"
default:
students[i].Grade = "F (不及格)"
}
}
// 打印成绩报表
fmt.Println("┌──────┬────────┬──────────┐")
fmt.Println("│ 姓名 │ 分数 │ 等级 │")
fmt.Println("├──────┼────────┼──────────┤")
var total float64
for _, s := range students {
fmt.Printf("│ %-4s │ %6.1f │ %-8s │\n", s.Name, s.Score, s.Grade)
total += s.Score
}
fmt.Println("├──────┼────────┼──────────┤")
fmt.Printf("│ 平均 │ %6.1f │ │\n", total/float64(len(students)))
fmt.Println("└──────┴────────┴──────────┘")
}
❓ 常见问题
Q1:var 和 := 到底该用哪个?(概念混淆型)
A: 遵循简单原则:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 函数内声明并赋值 | := |
更简洁,Go 社区惯例 |
| 函数内声明不赋值 | var |
:= 强制要求赋值 |
| 包级变量 | var |
:= 不能在函数外使用 |
| 需要显式指定类型 | var |
避免类型推断不符预期 |
func example() {
// ✅ 推荐:函数内用 :=
name := "Go"
count := 0
// ✅ 必须用 var:声明时不赋值
var result string
if someCondition {
result = "yes"
} else {
result = "no"
}
// ✅ 必须用 var:需要精确类型
var b byte = 255 // 如果用 := 会推断为 int
}
Q2:为什么 string(65) 不是 "65"?(易错陷阱型)
A: string(int) 把整数当作 Unicode 码点,不是数字的文本表示:
// ❌ 错误理解
s1 := string(65) // 期望 "65",实际得到 "A"(Unicode U+0041)
// ✅ 正确做法
s2 := fmt.Sprintf("%d", 65) // "65"
s3 := strconv.Itoa(65) // "65"(需要 import "strconv")
// string(rune) 的实际用途:Unicode 码点 → 字符
ch := string(rune(20013)) // "中"(U+4E16)
Q3:len("Hello,世界") 为什么不是 8?(易错陷阱型)
A: len() 返回的是字节数,不是字符数。Go 字符串默认 UTF-8 编码,中文占 3 个字节:
s := "Hello,世界"
fmt.Println(len(s)) // 12(5 + 1 + 3×2 = 12 字节)
fmt.Println(utf8.RuneCountInString(s)) // 8(5 + 1 + 2 = 8 个字符)
// 正确计算字符数的方式
count := 0
for range s {
count++
}
fmt.Println(count) // 8
Q4:如何快速交换两个变量?(实用技巧型)
A: Go 支持多重赋值,一行代码搞定:
a, b := 10, 20
a, b = b, a // 交换:a=20, b=10
// 也可以用这种方式忽略某个值
_, err := someFunction() // 用 _ 忽略不需要的返回值
📖 小节
- 变量声明有两种方式:
var(通用,可在函数外)和:=(简洁,仅函数内)。 - Go 有丰富的基本类型:
int/uint/float64/bool/string/byte/rune。 - 零值机制保证变量声明后总有确定的值:数字
0,布尔false,字符串"",指针nil。 const声明常量,iota在const块中从 0 自增,适合定义枚举。- Go 不支持隐式类型转换,不同类型必须显式转换,且转换可能丢失精度。
fmt包是格式化输出的利器:Println打印、Printf格式化、Sprintf生成字符串。len(string)返回字节数,遍历字符串用range才能正确处理多字节字符。string(int)把整数当 Unicode 码点,不是数字转文本;数字转文本用fmt.Sprintf。
📝 作业
练习 1(⭐):变量声明与零值
声明以下变量并打印它们的零值:
- 一个
int变量count - 一个
float64变量price - 一个
bool变量active - 一个
string变量message
预期输出:
count: 0
price: 0
active: false
message: ""
练习 2(⭐⭐):iota 枚举与类型转换
定义一个 Color 类型(底层 int),用 iota 定义三种颜色:Red=0, Green=1, Blue=2。给 Color 实现 String() 方法。然后:
- 声明一个
Color变量,赋值为Green,打印它的名字和数值。 - 把
float64类型的3.14159转为int,打印结果(应为3)。 - 把数字
2025转成字符串"2025",打印结果。
练习 3(⭐⭐⭐):温度转换工具
编写一个程序,完成以下功能:
- 定义常量
AbsoluteZeroC = -273.15(绝对零度,摄氏)。 - 声明一组摄氏温度:
-40, 0, 37, 100。 - 遍历温度数组,将每个摄氏温度转换为华氏温度(公式:
F = C × 9/5 + 32)。 - 格式化输出表格,要求:
- 摄氏和华氏温度保留 1 位小数
- 标注该温度是否低于绝对零度(物理上不可能)
- 对齐列宽
预期输出:
┌─────────┬─────────┬────────────┐
│ 摄氏(°C) │ 华氏(°F) │ 状态 │
├─────────┼─────────┼────────────┤
│ -40.0 │ -40.0 │ 有效 │
│ 0.0 │ 32.0 │ 有效 │
│ 37.0 │ 98.6 │ 有效 │
│ 100.0 │ 212.0 │ 有效 │
└─────────┴─────────┴────────────┘
提示: 使用 const、var(数组/切片)、for 循环、fmt.Printf 格式化、类型转换(int ↔ float64)。



