目录
- [🟢 基础入门 | Go 入门到精通 2026(四):变量与数据类型](#🟢 基础入门 | Go 入门到精通 2026(四):变量与数据类型)
-
- 开篇
- [1. 变量声明的四种方式](#1. 变量声明的四种方式)
-
- [1.1 var 标准声明](#1.1 var 标准声明)
- [1.2 短变量声明 `:=`](#1.2 短变量声明
:=) - [1.3 批量声明](#1.3 批量声明)
- [1.4 短变量声明的陷阱](#1.4 短变量声明的陷阱)
-
- [陷阱一:`:=` 不是赋值](#陷阱一:
:=不是赋值) - 陷阱二:作用域遮蔽(Shadowing)
- [陷阱一:`:=` 不是赋值](#陷阱一:
- [2. Go 的基本数据类型全景](#2. Go 的基本数据类型全景)
- [3. 零值:Go 的"出厂设置"](#3. 零值:Go 的"出厂设置")
- [4. 类型转换:Go 没有隐式转换](#4. 类型转换:Go 没有隐式转换)
- [5. 类型推断](#5. 类型推断)
- [6. 命名规范](#6. 命名规范)
- [7. fmt.Printf 格式化动词对照表](#7. fmt.Printf 格式化动词对照表)
- [8. 综合实战:一个个人信息卡片程序](#8. 综合实战:一个个人信息卡片程序)
- 小结与互动
- 参考资料
🟢 基础入门 | Go 入门到精通 2026(四):变量与数据类型
📅 更新于 2026年6月 | ✍️ 原创文章,转载请注明出处 | 作者:布朗克168
开篇
变量和数据类型是任何编程语言的基石。Go 的类型系统有几个鲜明特点:
- 🎯 静态类型:编译时就知道每个变量的类型
- 🎯 显式转换:绝不隐式转换类型(避免暗藏的 bug)
- 🎯 零值机制:声明变量不初始化也会有"合理的默认值"
- 🎯 短声明 :
:=让变量声明像脚本语言一样简洁
这篇文章会从变量声明讲到所有基本类型,再到格式化输出,涵盖你日常编码需要的全部基础。准备好,我们开始!
1. 变量声明的四种方式
Go 提供了多种变量声明方式,从啰嗦到简洁都有。
1.1 var 标准声明
最完整的形式:
go
var name string = "布朗克168"
可以拆解为:
var name string = "布朗克168"
↑ ↑ ↑ ↑ ↑
关键字 变量名 类型 赋值符 初始值
如果提供初始值,类型可以省略(类型推断):
go
var name = "布朗克168" // Go 推断为 string
var age = 25 // Go 推断为 int
var pi = 3.14159 // Go 推断为 float64
如果不提供初始值 ,变量会被赋予该类型的零值:
go
var name string // name = ""(空字符串)
var age int // age = 0
var ok bool // ok = false
var ptr *int // ptr = nil
💡 只声明不初始化时,类型不能省略------编译器没法猜你想要的类型。
1.2 短变量声明 :=
这是 Go 中最常用的声明方式,简洁优雅:
go
// 函数内部使用 :=
name := "布朗克168"
age := 25
pi := 3.14159
// 等价于
var name string = "布朗克168"
var age int = 25
var pi float64 = 3.14159
⚠️ 重要限制 :
:=只能在函数内部 使用,包级别(全局作用域)必须用var。
go
package main
var globalVar = "可以用 var" // ✅ 包级别用 var
// globalVar := "不可以用 :=" // ❌ 编译错误!
func main() {
localVar := "可以用 :=" // ✅ 函数内用 :=
}
1.3 批量声明
当需要声明多个变量时,用括号分组更清晰:
go
// 包级别批量声明
var (
appName = "GoBlog"
version = "1.0.0"
maxRetries = 3
debugMode = false
)
// 函数内部也可以
func main() {
var (
width int = 1920
height int = 1080
)
fmt.Println(width, height)
}
1.4 短变量声明的陷阱
陷阱一::= 不是赋值
go
func main() {
var x int // 声明 x
x = 10 // 赋值 x------用 =
// x := 20 // ❌ 错误!x 已经声明过,不能用 :=
// 除非 := 左侧至少有一个新变量
}
:= 的规则是:左侧至少有一个是新变量,其他可以是已存在的变量(此时对已有变量执行赋值):
go
func main() {
x := 10 // 声明 x
x, y := 20, 30 // ✅ y 是新变量,x 被重新赋值
fmt.Println(x, y) // 20 30
x, y := 40, 50 // ❌ 错误!x 和 y 都不是新变量
// (在同一作用域内)
}
陷阱二:作用域遮蔽(Shadowing)
这是 Go 新手最容易踩的坑:
go
func main() {
x := 100 // 外层 x
fmt.Println(x) // 100
if true {
x := 200 // 注意:这里用的是 :=,声明了一个新的局部 x!
fmt.Println(x) // 200(内层 x)
}
fmt.Println(x) // 100(外层 x,完全没有被修改!)
}
🔑 关键 :
:=在if/for/switch等新代码块中会创建同名新变量 ,遮蔽外层的变量。如果你想修改外层变量,应该用=或确保不创建新作用域:
go
func main() {
x := 100
if true {
x = 200 // ✅ 用 = 赋值,修改的是外层 x
}
fmt.Println(x) // 200
}
2. Go 的基本数据类型全景
2.1 整数类型
Go 的整数类型多到让人眼花缭乱,但理解后就很简单:
有符号整数
| 类型 | 位数 | 取值范围 |
|---|---|---|
int8 |
8 | -128 ~ 127 |
int16 |
16 | -32,768 ~ 32,767 |
int32 |
32 | -2,147,483,648 ~ 2,147,483,647 |
int64 |
64 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
int |
平台相关 | 32位系统为 int32,64位系统为 int64 |
无符号整数
| 类型 | 位数 | 取值范围 |
|---|---|---|
uint8 |
8 | 0 ~ 255 |
uint16 |
16 | 0 ~ 65,535 |
uint32 |
32 | 0 ~ 4,294,967,295 |
uint64 |
64 | 0 ~ 18,446,744,073,709,551,615 |
uint |
平台相关 | 32位系统为 uint32,64位系统为 uint64 |
特殊整数类型
| 类型 | 说明 |
|---|---|
byte |
uint8 的别名,用于表示字节 |
rune |
int32 的别名,用于表示 Unicode 码点 |
uintptr |
足够大的无符号整数,用于存储指针地址 |
使用建议
go
// 日常开发中 99% 的场景,直接用 int 就够了
var count int = 100
var index int = 0
// 需要精确控制大小的场景才用具体类型
var fileSize int64 = 1024 * 1024 * 100 // 100MB
var port uint16 = 8080
// 处理字节流用 byte
var header byte = 0xFF
// 处理 Unicode 字符用 rune
var ch rune = '中'
💡
intvsint64:虽然在 64 位系统上int和int64都是 64 位,但 Go 认为它们是不同的类型,不能直接混用(需要显式转换)。
2.2 浮点数与复数
浮点数
| 类型 | 精度 | 说明 |
|---|---|---|
float32 |
约 7 位有效数字 | IEEE-754 32位 |
float64 |
约 15 位有效数字 | IEEE-754 64位(默认) |
go
var pi float64 = 3.141592653589793
var e float32 = 2.71828
// 科学计数法
var avogadro = 6.022e23 // float64
var planck = 6.62607015e-34 // float64
// 推断类型:带小数点的数字默认是 float64
x := 3.14
fmt.Printf("%T\n", x) // float64
⚠️ 浮点数陷阱 :浮点数不能精确表示十进制小数,不要用
==直接比较两个浮点数:
go
a := 0.1
b := 0.2
c := 0.3
fmt.Println(a+b == c) // false!(浮点精度问题)
fmt.Println(a + b) // 0.30000000000000004
// 正确做法:比较差值是否在容差范围内
const epsilon = 1e-9
fmt.Println(math.Abs((a+b)-c) < epsilon) // true
复数
Go 原生支持复数!这在科学计算和信号处理中非常有用。
| 类型 | 说明 |
|---|---|
complex64 |
实部和虚部都是 float32 |
complex128 |
实部和虚部都是 float64(默认) |
go
c1 := complex(3, 4) // 3 + 4i
c2 := 5 + 12i // 字面量写法
fmt.Println(c1 + c2) // (8+16i)
fmt.Println(real(c1)) // 3(实部)
fmt.Println(imag(c1)) // 4(虚部)
fmt.Println(cmplx.Abs(c1)) // 5(模长,3-4-5 勾股数)
2.3 布尔类型
Go 的布尔类型叫 bool,只有两个值:true 和 false。
go
var isActive bool = true
var isClosed bool = false
// 常用在条件判断
if isActive {
fmt.Println("服务运行中")
}
// 逻辑运算
and := true && false // false
or := true || false // true
not := !true // false
⚠️ Go 中
bool和整数完全不兼容:
govar b bool = 1 // ❌ 编译错误!cannot use 1 (type int) as type bool if 1 { } // ❌ 编译错误!non-bool used as if condition如果你想用数值表示布尔,需要手动转换:
b := n != 0
2.4 字符串
Go 的字符串是不可变的 UTF-8 字节序列。
go
// 双引号:可包含转义字符
s1 := "Hello\nWorld" // 有换行
s2 := "He said: \"Hi\"" // 有引号
// 反引号(raw string):原样输出,不处理转义
s3 := `Hello\nWorld` // 字面输出 \n,不换行
s4 := `{
"name": "布朗克168",
"age": 25
}` // 多行字符串,常用于 JSON/模板
字符串操作
go
// 长度
len("Hello") // 5(字节数,不是字符数!)
len("中国") // 6(每个中文字符占 3 字节,UTF-8)
// 拼接
s := "Hello" + " " + "World" // "Hello World"
// 索引(返回 byte,不是 rune)
s := "Hello"
fmt.Println(s[0]) // 72('H' 的 ASCII 码)
fmt.Printf("%c\n", s[0]) // H
// 切片
fmt.Println(s[1:4]) // "ell"
// 遍历字符(正确处理中文)
for i, ch := range "你好Go" {
fmt.Printf("位置 %d: %c\n", i, ch)
}
// 位置 0: 你
// 位置 3: 好 ← 注意!中文字符占 3 字节,索引跳了 3
// 位置 6: G
// 位置 7: o
// 字符数(而非字节数)
import "unicode/utf8"
fmt.Println(utf8.RuneCountInString("你好Go")) // 4
📌 关键认知 :
len()返回字节数,range遍历按字符(rune)遍历。处理中文等多字节字符时务必注意这个区别。
2.5 byte 与 rune
这两个类型经常让新手困惑,但其实很简单:
| 类型 | 本质 | 占用 | 用途 |
|---|---|---|---|
byte |
uint8 别名 |
1 字节 | 表示 ASCII 字符、原始字节 |
rune |
int32 别名 |
4 字节 | 表示 Unicode 码点(字符) |
go
var b byte = 'A' // byte 用单引号,只能表示 ASCII
fmt.Println(b) // 65('A' 的 ASCII 值)
fmt.Printf("%c\n", b) // A
var r rune = '中' // rune 用单引号,可以表示任何 Unicode 字符
fmt.Println(r) // 20013('中' 的 Unicode 码点)
fmt.Printf("%c\n", r) // 中
var r2 rune = '😀' // 甚至 emoji!
fmt.Printf("%c\n", r2) // 😀
// 字符串和 rune 切片的转换
s := "你好Go"
runes := []rune(s)
fmt.Println(len(s)) // 8(字节数)
fmt.Println(len(runes)) // 4(字符数)
fmt.Println(string(runes[0])) // "你"
3. 零值:Go 的"出厂设置"
Go 中声明变量但不初始化,变量会自动获得该类型的"零值"。这是一个非常贴心的设计,避免了 C/C++ 中未初始化变量的未定义行为。
各类型零值速查表
| 类型 | 零值 | 说明 |
|---|---|---|
int, int8, int16, int32, int64 |
0 |
|
uint, uint8, uint16, uint32, uint64 |
0 |
|
float32, float64 |
0.0 |
|
complex64, complex128 |
0+0i |
|
bool |
false |
|
string |
"" |
空字符串,不是 nil |
byte |
0 |
|
rune |
0 |
|
指针 *T |
nil |
|
切片 []T |
nil |
|
map map[K]V |
nil |
|
channel chan T |
nil |
|
函数 func(...)... |
nil |
|
接口 interface{} |
nil |
|
结构体 struct{} |
每个字段各自为零值 |
零值实践
go
func main() {
var (
name string
age int
salary float64
isAdmin bool
data []int
)
fmt.Printf("name: %q\n", name) // name: ""
fmt.Printf("age: %d\n", age) // age: 0
fmt.Printf("salary: %f\n", salary) // salary: 0.000000
fmt.Printf("isAdmin: %t\n", isAdmin) // isAdmin: false
fmt.Printf("data: %v\n", data) // data: <nil>
}
💡 零值让 Go 代码更安全:不需要手动初始化就能避免"脏数据"。比如
var count int从 0 开始累加,完全合理。
4. 类型转换:Go 没有隐式转换
这是 Go 和 Java/C/C++ 的一个重要区别:Go 不会自动进行类型转换,所有转换必须显式进行。
go
var a int = 10
var b int64 = 20
// c := a + b // ❌ 编译错误!invalid operation: a + b (mismatched types int and int64)
c := a + int(b) // ✅ 显式转换为 int
// 或
c := int64(a) + b // ✅ 显式转换为 int64
转换语法
go
// 语法:Type(value)
var f float64 = 3.14
var i int = int(f) // 3(小数部分截断,不是四舍五入)
var score int = 95
var avg float64 = float64(score) // 95.0
// 字符串与数字的转换需要 strconv 包
import "strconv"
// 字符串 → 整数
num, err := strconv.Atoi("42") // num=42, err=nil
num64, _ := strconv.ParseInt("100", 10, 64)
// 整数 → 字符串
s := strconv.Itoa(42) // "42"
s2 := strconv.FormatInt(100, 16) // "64"(16进制)
// 字符串 → 浮点数
f, _ := strconv.ParseFloat("3.14", 64)
类型转换注意事项
go
// 大类型转小类型可能溢出
var big int64 = 300
var small int8 = int8(big) // 300 > 127,溢出!结果是 44(300 % 256 - 128)
fmt.Println(small) // 44------不会报错,但结果不是你想要的
// float 转 int 截断(不四舍五入)
fmt.Println(int(3.14)) // 3
fmt.Println(int(3.99)) // 3(截断,不是 4!)
fmt.Println(int(-3.14)) // -3
// 如果需要四舍五入
import "math"
fmt.Println(int(math.Round(3.99))) // 4 ✅
5. 类型推断
Go 编译器很聪明,可以根据初始值推断类型:
go
var name = "布朗克168" // string
var age = 18 // int
var pi = 3.14159 // float64
var c = 3 + 4i // complex128
name2 := "布朗克168" // string(:= 同样触发类型推断)
推断规则
| 字面量 | 推断为 |
|---|---|
42 |
int |
3.14 |
float64 |
"hello" |
string |
true |
bool |
'A' |
rune(注意:不是 byte!) |
3+4i |
complex128 |
go
// 'A' 被推断为 rune,不是 byte!
ch := 'A'
fmt.Printf("%T\n", ch) // int32(即 rune)
// 如果想要 byte,需要显式声明
var b byte = 'A'
fmt.Printf("%T\n", b) // uint8(即 byte)
6. 命名规范
Go 社区有一套"不成文"的命名规范,遵循它们能让你的代码更地道。
基本规则
| 规则 | 示例 | 说明 |
|---|---|---|
| 驼峰命名 | userName, getUserByID |
不使用下划线分隔 |
| 缩写全大写 | userID, httpURL, HTTPServer |
HTTP/ID/URL/JSON 等缩写全部大写 |
| 包名小写 | package userservice |
全小写,不用下划线 |
| 接口名 -er | Reader, Writer, Closer |
单方法接口以 -er 结尾 |
| 首字母大写 = 公开 | func GetName() |
包外可访问 |
| 首字母小写 = 私有 | func getName() |
仅包内可访问 |
命名示例对比
go
// ✅ 符合 Go 风格的命名
var userID string
var httpClient *http.Client
var jsonData []byte
func getUserByID(id int) {}
type Reader interface { Read(p []byte) (n int, err error) }
// ❌ 不符合 Go 风格的命名
var user_id string // 应使用 userID
var HttpClient *http.Client // 应使用 httpClient(私有变量不应大写开头)
var JsonData []byte // 应使用 jsonData(缩写全大写是 HTTP,不是 Http)
func get_user_by_id() {} // 应使用 getUserByID
🧠 记忆口诀:缩写全大写(URL、ID、HTTP),驼峰不蛇形(不用下划线),大写公开小写私(首字母控制可见性)。
7. fmt.Printf 格式化动词对照表
fmt.Printf 是 Go 中最常用的调试输出函数,掌握格式化动词能让你的调试效率翻倍。
通用动词
| 动词 | 说明 | 示例 |
|---|---|---|
%v |
默认格式(万能) | fmt.Printf("%v", 42) → 42 |
%+v |
结构体带字段名 | fmt.Printf("%+v", user) → {Name:布朗克 Age:18} |
%#v |
Go 语法表示 | fmt.Printf("%#v", "hi") → "hi" |
%T |
类型 | fmt.Printf("%T", 42) → int |
%% |
字面百分号 | fmt.Printf("100%%") → 100% |
整数动词
| 动词 | 说明 | 示例(42) |
|---|---|---|
%d |
十进制 | 42 |
%b |
二进制 | 101010 |
%o |
八进制 | 52 |
%x |
十六进制(小写) | 2a |
%X |
十六进制(大写) | 2A |
%c |
对应 Unicode 字符 | * (42 = '*') |
%q |
带引号的字符 | '*' |
浮点数动词
| 动词 | 说明 | 示例(3.14159) |
|---|---|---|
%f |
十进制小数 | 3.141590 |
%.2f |
保留 2 位小数 | 3.14 |
%e |
科学计数法(小写 e) | 3.141590e+00 |
%E |
科学计数法(大写 E) | 3.141590E+00 |
%g |
自动选择 %e 或 %f | 3.14159 |
字符串与布尔
| 动词 | 说明 | 示例 |
|---|---|---|
%s |
字符串 | "hello" |
%q |
带引号的字符串 | "hello" |
%t |
布尔值 | true |
%p |
指针地址 | 0xc0000140a0 |
宽度与对齐
| 格式 | 说明 | 示例 fmt.Sprintf("|%6d|", 42) |
|--------|----------|-------------------------------|
| %6d | 右对齐,宽度 6 | ␣␣␣␣42 |
| %-6d | 左对齐,宽度 6 | 42␣␣␣␣ |
| %06d | 零填充,宽度 6 | 000042 |
综合演示
go
package main
import "fmt"
func main() {
name := "布朗克168"
age := 25
score := 95.5
isVIP := true
fmt.Printf("姓名: %s\n", name)
fmt.Printf("年龄: %d 岁\n", age)
fmt.Printf("分数: %.1f 分\n", score)
fmt.Printf("VIP: %t\n", isVIP)
fmt.Printf("类型: %T, 值: %v\n", age, age)
// 表格对齐
fmt.Printf("\n%-10s %-6s %-6s\n", "姓名", "年龄", "分数")
fmt.Printf("%-10s %-6d %-6.1f\n", name, age, score)
}
输出:
姓名: 布朗克168
年龄: 25 岁
分数: 95.5 分
VIP: true
类型: int, 值: 25
姓名 年龄 分数
布朗克168 25 95.5
8. 综合实战:一个个人信息卡片程序
来写一个程序,综合运用上面学的所有知识:
go
package main
import (
"fmt"
"strings"
)
func main() {
// ===== 变量声明 =====
var (
name string = "布朗克168"
age int = 25
height float64 = 175.5
weight float64 = 70.0
isAdmin bool = true
city string = "深圳"
)
tags := []string{"Go", "云原生", "写作"} // 短声明
// ===== 类型转换与运算 =====
bmi := weight / ((height / 100) * (height / 100)) // float64 运算
// ===== 格式化输出 =====
fmt.Println(strings.Repeat("=", 40))
fmt.Println("📇 个人信息卡片")
fmt.Println(strings.Repeat("=", 40))
// 使用格式化动词对齐输出
fmt.Printf("%-12s: %s\n", "姓名", name)
fmt.Printf("%-12s: %d 岁\n", "年龄", age)
fmt.Printf("%-12s: %.1f cm\n", "身高", height)
fmt.Printf("%-12s: %.1f kg\n", "体重", weight)
fmt.Printf("%-12s: %.2f\n", "BMI", bmi)
fmt.Printf("%-12s: %t\n", "管理员", isAdmin)
fmt.Printf("%-12s: %s\n", "城市", city)
// 类型信息
fmt.Printf("\n📊 变量类型信息:\n")
fmt.Printf(" name 的类型: %T, 零值: %q\n", name, "")
fmt.Printf(" age 的类型: %T, 零值: %d\n", age, 0)
fmt.Printf(" bmi 的类型: %T, 零值: %f\n", bmi, 0.0)
// 技能标签
fmt.Printf("\n🏷️ 技能标签:\n")
for i, tag := range tags {
fmt.Printf(" %d. %s\n", i+1, tag)
}
// 字符演示
fmt.Printf("\n🔤 字符演示:\n")
s := "Go语言"
fmt.Printf(" \"%s\" 字节数: %d\n", s, len(s))
fmt.Printf(" \"%s\" 字符数: %d\n", s, len([]rune(s)))
fmt.Println(strings.Repeat("=", 40))
}
小结与互动
📝 本文要点回顾:
- 四种变量声明方式:
var(完整)、var(推断)、:=(短声明)、批量声明 :=只能在函数内使用,注意作用域遮蔽问题- 基本类型:int 系列、float 系列、bool、string、byte、rune
- 零值机制让变量有"合理的默认值"
- Go 没有隐式类型转换,所有转换必须显式进行
- 命名规范:驼峰命名、缩写全大写(如 HTTP、ID)
fmt.Printf的格式化动词是日常调试利器
❓ 互动问题:
- 你之前习惯用什么语言?对 Go 的显式类型转换怎么看?
- 你踩过
:=作用域遮蔽的坑吗? - 试试用
fmt.Printf打印一些有趣的格式化输出,欢迎在评论区分享!
参考资料
🚀 下一篇:【Go 入门到精通 2026(五):常量与运算符】,带你玩转 iota 枚举和各种运算符!
本文由 布朗克168 原创发布,如需转载请联系作者并注明出处。