Go 入门到精通-04-变量与数据类型

目录

🟢 基础入门 | 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 = '中'

💡 int vs int64:虽然在 64 位系统上 intint64 都是 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,只有两个值:truefalse

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 和整数完全不兼容

go 复制代码
var 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))
}

小结与互动

📝 本文要点回顾

  1. 四种变量声明方式:var(完整)、var(推断)、:=(短声明)、批量声明
  2. := 只能在函数内使用,注意作用域遮蔽问题
  3. 基本类型:int 系列、float 系列、bool、string、byte、rune
  4. 零值机制让变量有"合理的默认值"
  5. Go 没有隐式类型转换,所有转换必须显式进行
  6. 命名规范:驼峰命名、缩写全大写(如 HTTP、ID)
  7. fmt.Printf 的格式化动词是日常调试利器

互动问题

  • 你之前习惯用什么语言?对 Go 的显式类型转换怎么看?
  • 你踩过 := 作用域遮蔽的坑吗?
  • 试试用 fmt.Printf 打印一些有趣的格式化输出,欢迎在评论区分享!

参考资料


🚀 下一篇【Go 入门到精通 2026(五):常量与运算符】,带你玩转 iota 枚举和各种运算符!


本文由 布朗克168 原创发布,如需转载请联系作者并注明出处。