Go 零基础教程

Go 类型转换

类型转换允许你把一种数据类型的值当成另一种数据类型来处理。

在 Go 语言中,类型转换是绝对显式 (Explicit) 的。这意味着你必须清清楚楚地告诉编译器:“我要把这个值从 A 类型转换成 B 类型”。这与 JavaScript 或 C 等语言中常见的“隐式类型转换(编译器偷偷帮你转)”形成了鲜明对比。

1. 理解类型转换

在 Go 中,类型转换本质上是基于一个已有的值,去创建一个不同类型的新值。它的通用语法非常简单:

Type(value)

其中,Type 是你想要转换成的目标类型,而 value 是你要转换的那个值或表达式。

注意: Go 并非允许你随意转换任何类型。为了保证数据的完整性并防止无意义的操作,Go 制定了严格的转换规则。

1.1 允许的转换 (Allowed Conversions)

只要两种类型的底层数据结构兼容,且转换意义明确,Go 通常会放行:

  • 数字类型之间: 不同的整数之间(如 intint64)、不同的浮点数之间(如 float32float64),以及整数与浮点数之间的互转,都是被允许的。但也暗藏杀机:把大整数转成小整数会导致数据截断溢出;把浮点数转成整数会直接丢失所有小数部分。
  • 字符串与 Byte/Rune 切片: 你可以极其顺滑地将一个字符串 (string) 转换为一个字节切片 []byte 或一个字符切片 []rune,反之亦然。这在处理底层数据或中文字符时非常有用。
  • 字符串与数字(必须借助 strconv 包):不能Type(value) 的语法直接把 int 转成 string(比如 string(123) 不会得到 "123",而是一个奇怪的字符)。你必须使用 Go 内置的 strconv 包里的函数,比如 strconv.Atoi(字符串转整数)或 strconv.Itoa(整数转字符串)。

1.2 禁止的转换 (Disallowed Conversions)

Go 会严厉禁止那些可能导致行为未定义或数据损坏的转换:

  • 布尔值与数字/字符串: 你绝对不能直接把 true/false 转换成数字 1/0 或字符串 "true"/"false",反过来也不行。
  • 结构体 (Struct) 之间: 即便两个结构体包含一模一样的字段,只要它们的名字(类型)不同,就不能直接互转。
  • 不兼容的切片: 你不能把一个 []int 直接转换为 []string

2. 数字类型之间的转换

处理数字类型的互相转换是家常便饭,让我们仔细看看里面的门道。

2.1 整数转整数

在整数互转时,你最需要关心的是目标类型的容量够不够大

package main

import "fmt"

func main() {
	var i int32 = 1000
	
	var j int64 = int64(i) // int32 转 int64,从小转大,绝对安全
	fmt.Println(j)
	
	var k int8 = int8(i) // int32 转 int8,从大转小,面临数据丢失风险
	fmt.Println(k)       // 输出: -24 (因为 1000 超出了 int8 的最大值 127,发生了内存溢出环绕)
}

2.2 浮点数转整数

把浮点数转换为整数时,Go 采取的最简单粗暴的策略:直接砍掉所有小数部分(向下取整/截断)

package main

import "fmt"

func main() {
	var f float64 = 3.14
	var i int = int(f) // float64 转 int
	
	fmt.Println(i) // 输出: 3 (小数部分被无情抛弃)
}

2.3 整数转浮点数

整数转浮点数通常很安全,但如果整数大得离谱,可能会丢失一定的精确度。

package main

import "fmt"

func main() {
	var i int = 10000000000000001 // 一个极大的整数
	var f float64 = float64(i)    // int 转 float64
	
	fmt.Println(f) // 输出: 1e+16 (由于 float64 的精度限制,最后一位的 '1' 可能丢失)
}

3. 字符串 (String) 相关的转换

处理字符串的转换有点特殊。我们来看看如何进行底层的字节/字符转换,以及如何借助强大的 strconv 包处理数字互转。

3.1 字符串转 Byte/Rune 切片

  • []byte 将字符串拆解为原始的字节序列(常用于网络传输或文件写入)。
  • []rune 将字符串拆解为 Unicode 码点序列(常用于正确处理中文字符)。
package main

import "fmt"

func main() {
	str := "Hello, 世界"
	
	// 字符串转字节切片
	byteSlice := []byte(str)
	fmt.Printf("%v\n", byteSlice) // 输出: [72 101 108 108 111 44 32 228 184 150 231 149 140] (注意中文字符占了多个字节)
	
	// 字符串转字符切片 (Rune)
	runeSlice := []rune(str)
	fmt.Printf("%v\n", runeSlice) // 输出: [72 101 108 108 111 44 32 19990 30028] (中文字符被正确识别为独立码点)
}

3.2 Byte/Rune 切片转回字符串

package main

import "fmt"

func main() {
	byteSlice := []byte{72, 101, 108, 108, 111}
	str1 := string(byteSlice)
	fmt.Println(str1) // 输出: Hello
	
	runeSlice := []rune{72, 101, 108, 108, 111, 32, 19990, 30028}
	str2 := string(runeSlice)
	fmt.Println(str2) // 输出: Hello 世界
}

3.3 字符串转数字 (通过 strconv)

当你要把 "123" 变成真的数字 123 时,必须使用 strconv 包:

  • strconv.Atoi (ASCII to Integer):字符串转 int
  • strconv.ParseFloat:字符串转 float

极度重要: 这类转换极有可能失败(比如你让它把 "abc" 转成数字),所以必须处理函数返回的错误 (err)。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	strInt := "123"
	intVal, err := strconv.Atoi(strInt)
	if err != nil {
		fmt.Println("字符串转整数失败:", err)
		return
	}
	fmt.Println(intVal) // 输出: 123
	
	strFloat := "3.14"
	floatVal, err := strconv.ParseFloat(strFloat, 64) // 64 代表 float64
	if err != nil {
		fmt.Println("字符串转浮点数失败:", err)
		return
	}
	fmt.Println(floatVal) // 输出: 3.14
}

3.4 数字转字符串 (通过 strconv)

  • strconv.Itoa (Integer to ASCII):int 转字符串。
  • strconv.FormatFloat:浮点数转字符串(可高度定制格式)。
package main

import (
	"fmt"
	"strconv"
)

func main() {
	intVal := 42
	strInt := strconv.Itoa(intVal)
	fmt.Println(strInt) // 输出: "42"
	
	floatVal := 2.71828
	// 参数说明:'f' 表示常规小数格式,5 表示保留5位小数,64 表示源数据是 float64
	strFloat := strconv.FormatFloat(floatVal, 'f', 5, 64) 
	fmt.Println(strFloat) // 输出: "2.71828"
}

4. 实战演示

来看看类型转换在真实场景中的应用。

4.1 计算整数分数的平均值

如果你有一堆整数分数,想求出精确的平均分(带小数),你必须在做除法前把它们转换为浮点数。

package main

import "fmt"

func main() {
	scores := []int{85, 92, 78, 95, 88}
	
	sum := 0
	for _, score := range scores {
		sum += score
	}
	
	// 关键:如果不加 float64() 强转,整数除以整数还是整数,小数部分会被直接砍掉
	average := float64(sum) / float64(len(scores))
	
	fmt.Printf("平均分: %.2f\n", average) // 输出: 平均分: 87.60
}

4.2 处理用户终端输入

从命令行读进来的所有东西都是字符串。你想做算术运算?先转换!

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("请输入一个数字: ")
	
	// 读取用户输入,直到回车换行
	input, _ := reader.ReadString('\n')
	input = strings.TrimSpace(input) // 移除前后的空格和换行符
	
	// 字符串转整数
	num, err := strconv.Atoi(input)
	if err != nil {
		fmt.Println("输入无效,这不是一个数字:", err)
		return
	}
	
	fmt.Printf("你输入的数字是: %d\n", num)
}

4.3 将金额格式化输出

处理价格显示时,我们经常需要把计算好的浮点数,格式化为带两位小数的字符串用于界面显示。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	price := 49.99
	formattedPrice := strconv.FormatFloat(price, 'f', 2, 64)
	
	fmt.Printf("商品价格: $%s\n", formattedPrice) // 输出: 商品价格: $49.99
}