Go 变量与数据类型:给数据贴上标签

深入理解 Go 语言的变量声明、基本数据类型、类型转换和常量,打好编程基本功

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)

布尔型变量只有两个值:truefalse。它通常用于条件判断。

var isGoFun bool = true
var isJavaHard bool = false

// 也可以从表达式中得到布尔值
age := 25
isAdult := age >= 18  // true

💡 小贴士:Go 语言的布尔类型不会自动转换成整数。在某些语言(如 C)中,true 等于 1false 等于 0,但在 Go 中这是不允许的。你不能把 true 当成 1 来用。

整数类型

Go 语言提供了非常丰富的整数类型,按照是否有符号和占用内存大小来区分:

类型大小范围
int81 字节-128 到 127
int162 字节-32768 到 32767
int324 字节-2147483648 到 2147483647
int648 字节-9223372036854775808 到 9223372036854775807
uint81 字节0 到 255
uint162 字节0 到 65535
uint324 字节0 到 4294967295
uint648 字节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 语言有两种浮点数类型:

类型大小精度
float324 字节约 7 位有效数字
float648 字节约 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, int160
uint, uint8, uint160
float32, float640
boolfalse
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 语言中关于变量和数据类型的基础知识。让我们回顾一下关键要点:

  1. 变量声明的四种方式:完整 var 声明、省略类型、短变量声明 :=、先声明后赋值
  2. 基本数据类型:bool、整数类型(int 家族)、浮点数(float32/float64)、字符串(string)、复数
  3. byte 和 rune:分别是 uint8 和 int32 的别名,用于处理字节和 Unicode 字符
  4. 类型转换:Go 要求显式转换,不会自动做隐式转换
  5. 常量和 iota:const 定义不可变的值,iota 是自增常量生成器
  6. 零值机制:所有变量声明后都有默认值,不会存在未初始化的变量
  7. 作用域:内层可以访问外层变量,反之不行

这些知识看似简单,但它们是 Go 编程的基石。理解了这些,你才能更好地理解后面要学的函数、结构体、接口等更高级的概念。

练习时间

  1. 声明各种类型的变量:分别用四种方式声明一个 stringintfloat64bool 类型的变量
  2. 类型转换练习:写一个程序,把一个整数转换成浮点数,再把浮点数转换回整数,看看结果有没有变化
  3. 字符串处理:写一个程序,计算 “Hello, 世界!” 这个字符串的字节数和字符数
  4. 用 iota 定义常量:定义一组表示 HTTP 状态码的常量(200, 301, 404, 500),使用 iota 和表达式
  5. BMI 计算器:写一个程序,输入身高(米)和体重(公斤),计算 BMI 值

下一篇预告

在下一篇文章中,我们将学习 Go 语言的控制流——让程序学会做选择和循环。你会学到:

  • if/else 条件判断
  • switch 多分支选择
  • for 循环(Go 唯一的循环结构!)
  • breakcontinuegoto 的用法
  • range 遍历

我们下篇见!👋


参考资料:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页