Go 变量与数据类型:给数据贴上标签
上一篇我们一起搭建了 Go 的开发环境,写出了第一个 Hello World 程序。今天,我们要聊一个更基础的话题——变量和数据类型。
你可能会问:变量有什么好讲的?不就是存个值嘛。
没错,变量的本质就是存储数据。但在不同的编程语言中,变量的使用方式差别很大。Go 语言在变量和数据类型上有一些独特的设计,理解了这些,你才能写出地道的 Go 代码,而不是写出一堆"看起来像 Java 的 Go 代码"。
让我们开始吧!
什么是变量?
如果你把程序比作一个厨房,那变量就是厨房里的各种容器。
你有一个碗,上面贴着"糖"的标签,里面装的就是糖。你有一个瓶子,上面贴着"酱油",里面装的就是酱油。在程序中,变量就是这些容器,标签就是变量名,里面的东西就是变量的值。
sugar := "白砂糖" // 一个名叫 sugar 的变量,里面存着 "白砂糖"
amount := 500 // 一个名叫 amount 的变量,里面存着数字 500
在 Go 语言中,每个变量都有一个明确的类型(type),就像容器有特定的用途一样——碗装固体,瓶子装液体。类型决定了变量能存什么样的数据,能做什么操作。
Go 的变量声明方式
Go 语言提供了多种声明变量的方式,从"最啰嗦"到"最简洁",任君选择。
方式一:完整的 var 声明
这是最正式的声明方式:
var name string = "张三"
var age int = 25
var height float64 = 175.5
语法是:var 变量名 类型 = 值
你也可以同时声明多个变量:
var (
name string = "张三"
age int = 25
height float64 = 175.5
)
这种用括号把多个变量包在一起的写法,叫做"变量组"。在声明全局变量或者包的级别变量时特别常用。
方式二:省略类型
如果赋值的时候给出了值,Go 编译器会自动推断变量的类型,所以你可以省略类型声明:
var name = "张三" // 编译器推断为 string
var age = 25 // 编译器推断为 int
var height = 175.5 // 编译器推断为 float64
编译器看到 "张三" 就知道它是字符串,看到 25 就知道它是整数,看到 175.5 就知道它是浮点数。
方式三:短变量声明(最常用)
在函数内部,你可以使用 := 操作符来声明变量,这是最简洁也最常用的方式:
name := "张三"
age := 25
height := 175.5
:= 是 Go 语言的一个特色语法,它把声明和赋值合二为一了。编译器会自动推断变量的类型。
⚠️ 注意::= 只能在函数内部使用。在函数外面(包的级别),你必须使用 var 关键字。
// 包级别变量:必须用 var
var globalVar = "我是全局变量"
func main() {
// 函数内部:可以用 :=
localVar := "我是局部变量"
}
方式四:先声明后赋值
有时候你需要先声明变量,后面再给它赋值:
var name string
name = "张三"
var age int
age = 25
这种方式在你还不知道变量具体值的时候很有用,比如你打算后面通过用户输入或者从文件读取来给变量赋值。
Go 的基本数据类型
Go 语言的数据类型可以分为几大类:布尔型、整数型、浮点型、字符串、以及复合类型(数组、切片、map、结构体等)。今天我们主要讲基本类型,复合类型会在后面的文章中详细讨论。
布尔型(bool)
布尔型变量只有两个值:true 和 false。它通常用于条件判断。
var isGoFun bool = true
var isJavaHard bool = false
// 也可以从表达式中得到布尔值
age := 25
isAdult := age >= 18 // true
💡 小贴士:Go 语言的布尔类型不会自动转换成整数。在某些语言(如 C)中,true 等于 1,false 等于 0,但在 Go 中这是不允许的。你不能把 true 当成 1 来用。
整数类型
Go 语言提供了非常丰富的整数类型,按照是否有符号和占用内存大小来区分:
| 类型 | 大小 | 范围 |
|---|---|---|
int8 | 1 字节 | -128 到 127 |
int16 | 2 字节 | -32768 到 32767 |
int32 | 4 字节 | -2147483648 到 2147483647 |
int64 | 8 字节 | -9223372036854775808 到 9223372036854775807 |
uint8 | 1 字节 | 0 到 255 |
uint16 | 2 字节 | 0 到 65535 |
uint32 | 4 字节 | 0 到 4294967295 |
uint64 | 8 字节 | 0 到 18446744073709551615 |
另外还有两个特殊类型:
| 类型 | 说明 |
|---|---|
int | 在 32 位系统上是 int32,在 64 位系统上是 int64 |
uint | 在 32 位系统上是 uint32,在 64 位系统上是 uint64 |
大多数时候,你只需要用 int 就够了。除非你有特殊的需求(比如需要处理超大数字,或者需要确保跨平台一致性),否则不要纠结于用 int32 还是 int64。
var count int = 100
var negative int = -42
// 其他整数类型
var small uint8 = 255 // byte 的别名
var big int64 = 9999999999
fmt.Println(count, negative, small, big)
// 输出:100 -42 255 9999999999
关于 byte 和 rune
Go 语言中有两个特殊的类型别名,你需要了解:
byte:是uint8的别名,通常用来表示字节数据rune:是int32的别名,通常用来表示 Unicode 字符
var b byte = 'A' // 字节,存储 ASCII 码 65
var r rune = '中' // Unicode 字符
fmt.Printf("byte: %c (%d)\n", b, b) // byte: A (65)
fmt.Printf("rune: %c (%d)\n", r, r) // rune: 中 (20013)
为什么要专门搞个别名?这是为了让代码更有可读性。当你看到 byte 的时候,你就知道这里处理的是原始字节数据;当你看到 rune 的时候,你就知道这里处理的是 Unicode 字符。
浮点数类型
Go 语言有两种浮点数类型:
| 类型 | 大小 | 精度 |
|---|---|---|
float32 | 4 字节 | 约 7 位有效数字 |
float64 | 8 字节 | 约 15 位有效数字 |
默认使用 float64,因为它的精度更高,而且 Go 编译器在推断类型时也会优先选择 float64。
pi := 3.14159265358979 // float64
var gravity float32 = 9.8
fmt.Printf("pi = %f\n", pi) // pi = 3.141593
fmt.Printf("gravity = %f\n", gravity) // gravity = 9.800000
⚠️ 踩坑提示:浮点数运算可能会有精度问题,这不只是 Go 的问题,是所有编程语言都有的。如果你在做金融计算等对精度要求极高的场景,建议使用专门的库(比如 github.com/shopspring/decimal),不要用浮点数。
// 浮点数精度问题
a := 0.1
b := 0.2
c := a + b
fmt.Println(c == 0.3) // false!
fmt.Printf("%.20f\n", c) // 0.30000000000000004441
字符串类型(string)
字符串是用来存储文本数据的。在 Go 中,字符串是不可变的(immutable)——一旦创建,就不能修改。
// 双引号:普通的字符串
name := "Hello, Go!"
// 反引号:原始字符串(raw string),支持多行
poem := `
静夜思
床前明月光,
疑是地上霜。
举头望明月,
低头思故乡。
`
fmt.Println(name)
fmt.Println(poem)
字符串的一些常用操作:
s := "Hello, Go!"
// 获取长度
fmt.Println(len(s)) // 10
// 字符串拼接
greeting := "Hello" + ", " + "World!"
fmt.Println(greeting)
// 访问单个字符(返回的是 byte)
fmt.Println(s[0]) // 72('H' 的 ASCII 码)
fmt.Printf("%c\n", s[0]) // H
💡 小贴士:len() 返回的是字节数,不是字符数。对于包含中文的字符串,这两者是不同的:
s := "你好"
fmt.Println(len(s)) // 6(每个中文字符占 3 个字节)
// 如果要获取字符数,使用 utf8.RuneCountInString
import "unicode/utf8"
fmt.Println(utf8.RuneCountInString(s)) // 2
复数类型
Go 语言内置了复数类型,虽然在大多数项目中不太会用到,但作为一门系统编程语言,这个特性还是很酷的。
var c1 complex64 = 3 + 4i
c2 := 5 + 6i
fmt.Printf("c1 = %v\n", c1) // c1 = (3+4i)
fmt.Printf("c2 = %v\n", c2) // c2 = (5+6i)
// 获取实部和虚部
fmt.Println(real(c1)) // 3
fmt.Println(imag(c1)) // 4
类型转换
Go 语言的类型转换是显式的——你必须明确告诉编译器你想做什么转换。Go 不会自动帮你做隐式转换,这一点和 JavaScript、Python 这些语言很不一样。
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(f) // float64 → uint
fmt.Println(i, f, u) // 42 42 42
注意,类型转换可能会导致数据丢失:
var big int64 = 1 << 40 // 一个很大的数
var small int32 = int32(big) // 溢出!
fmt.Println(small) // 结果不是你想的那样
字符串与数字的互转
这是日常开发中非常常见的需求,需要用到 strconv 包:
import "strconv"
// 字符串 → 整数
num, err := strconv.Atoi("42")
if err != nil {
fmt.Println("转换失败:", err)
} else {
fmt.Println(num) // 42
}
// 整数 → 字符串
str := strconv.Itoa(42)
fmt.Println(str) // "42"
// 更通用的转换函数
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Println(f) // 3.14
s := strconv.FormatFloat(3.14, 'f', 2, 64)
fmt.Println(s) // "3.14"
strconv 包的名字是 “string conversion” 的缩写,记住这个包名,你会经常用到它。
常量和 iota
常量(const)
常量就是值不能改变的变量。一旦定义了,就不能再修改。
const Pi = 3.14159265358979
const Language = "Go"
// 常量组
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
fmt.Println(Pi, Language, StatusOK)
// 输出:3.14159265358979 Go 200
常量不能使用 := 声明,只能用 const 关键字。
iota:自增常量生成器
iota 是 Go 语言中一个非常有趣的特性。它是一个常量计数器,从 0 开始,每出现一次就自动加 1。它主要用于定义一组相关的常量。
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
Thursday // 4
Friday // 5
Saturday // 6
)
fmt.Println(Sunday, Monday, Saturday)
// 输出:0 1 6
注意,从第二个常量开始,你不需要重复写 = iota,Go 编译器会自动延续前面的表达式。
iota 还可以用在更复杂的表达式中:
const (
_ = iota // 跳过第一个值
KB = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20 = 1048576
GB // 1 << 30 = 1073741824
TB // 1 << 40 = 1099511627776
)
fmt.Println(KB, MB, GB)
// 输出:1024 1048576 1073741824
这段代码利用位运算定义了存储单位的常量,非常优雅。_ 是 Go 中的空白标识符,用来丢弃不需要的值。
Go 的零值机制
这是 Go 语言一个很重要的特性:当你声明一个变量但不给它赋值时,Go 会自动给它一个默认值,叫做"零值"(zero value)。
不同数据类型的零值是不同的:
| 类型 | 零值 |
|---|---|
int, int8, int16… | 0 |
uint, uint8, uint16… | 0 |
float32, float64 | 0 |
bool | false |
string | ""(空字符串) |
指针, 切片, map, 通道, 函数, 接口 | nil |
var i int
var f float64
var b bool
var s string
fmt.Printf("int: %d\n", i) // int: 0
fmt.Printf("float: %f\n", f) // float: 0.000000
fmt.Printf("bool: %v\n", b) // bool: false
fmt.Printf("string: %q\n", s) // string: ""
零值机制让 Go 程序更加安全。你永远不会遇到"未初始化的变量"这种问题,因为所有变量在声明时就已经有了明确的默认值。
💡 小贴士:注意 %q 这个格式化动词,它会给字符串加上引号,这样你就能看到空字符串 "" 和没输出之间的区别。
变量的作用域
变量的作用域(scope)决定了你在哪里可以访问这个变量。Go 语言的作用域规则很直观:
var globalVar = "我是全局变量" // 整个包都可以访问
func main() {
var outerVar = "我在 main 函数里" // main 函数内可以访问
if true {
var innerVar = "我在 if 块里" // 只在 if 块内可以访问
fmt.Println(innerVar) // ✅ OK
fmt.Println(outerVar) // ✅ OK
fmt.Println(globalVar) // ✅ OK
}
// fmt.Println(innerVar) // ❌ 编译错误!innerVar 在这里不可见
fmt.Println(outerVar) // ✅ OK
}
简单来说:内层可以访问外层的变量,外层不能访问内层的变量。这就像俄罗斯套娃一样,里面的娃娃能看到外面的娃娃,但外面的娃娃看不到里面的。
短变量声明的一个"坑"
使用 := 有一个容易踩的坑:如果同一个作用域内已经存在同名变量,:= 会创建一个新的变量,而不是覆盖原来的值。但在多变量赋值的情况下,只要有一个变量是新的,:= 就是合法的:
func main() {
x := 10
y := 20
// x 已经存在,但 z 是新的,所以可以用 :=
// 这会修改 x 的值,并创建新变量 z
x, z := 30, 40
fmt.Println(x, y, z) // 30 20 40
}
实战小项目:一个简单的温度转换器
让我们把今天学到的知识综合起来,写一个温度转换器程序:
package main
import (
"fmt"
)
func main() {
// 摄氏温度
celsius := 36.6
// 转换为华氏温度:F = C × 9/5 + 32
fahrenheit := celsius*9/5 + 32
// 转换为开尔文温度:K = C + 273.15
kelvin := celsius + 273.15
fmt.Printf("%.1f°C = %.1f°F = %.2fK\n", celsius, fahrenheit, kelvin)
// 输出:36.6°C = 97.9°F = 309.75K
// 常量
const (
AbsoluteZeroCelsius float64 = -273.15
BoilingPointCelsius float64 = 100.0
FreezingPointCelsius float64 = 0.0
)
// 判断温度
if celsius > BoilingPointCelsius {
fmt.Println("水已经沸腾了!")
} else if celsius < FreezingPointCelsius {
fmt.Println("水已经结冰了!")
} else {
fmt.Println("水是液态的")
}
// 输出:水是液态的
}
小结
今天我们学习了 Go 语言中关于变量和数据类型的基础知识。让我们回顾一下关键要点:
- 变量声明的四种方式:完整 var 声明、省略类型、短变量声明
:=、先声明后赋值 - 基本数据类型:bool、整数类型(int 家族)、浮点数(float32/float64)、字符串(string)、复数
- byte 和 rune:分别是 uint8 和 int32 的别名,用于处理字节和 Unicode 字符
- 类型转换:Go 要求显式转换,不会自动做隐式转换
- 常量和 iota:const 定义不可变的值,iota 是自增常量生成器
- 零值机制:所有变量声明后都有默认值,不会存在未初始化的变量
- 作用域:内层可以访问外层变量,反之不行
这些知识看似简单,但它们是 Go 编程的基石。理解了这些,你才能更好地理解后面要学的函数、结构体、接口等更高级的概念。
练习时间
- 声明各种类型的变量:分别用四种方式声明一个
string、int、float64、bool类型的变量 - 类型转换练习:写一个程序,把一个整数转换成浮点数,再把浮点数转换回整数,看看结果有没有变化
- 字符串处理:写一个程序,计算 “Hello, 世界!” 这个字符串的字节数和字符数
- 用 iota 定义常量:定义一组表示 HTTP 状态码的常量(200, 301, 404, 500),使用 iota 和表达式
- BMI 计算器:写一个程序,输入身高(米)和体重(公斤),计算 BMI 值
下一篇预告
在下一篇文章中,我们将学习 Go 语言的控制流——让程序学会做选择和循环。你会学到:
if/else条件判断switch多分支选择for循环(Go 唯一的循环结构!)break、continue、goto的用法range遍历
我们下篇见!👋
参考资料:
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。