TL;DR:本文介绍了在Golang中类型转换
Go 有一条规则你必须得记:不同类型之间不能直接运算或赋值,必须显式转换。 没有隐式转换,没有自动提升,一切都要明确写出来
为什么没有隐式转换
很多语言会自动转换类型,比如 C 里 int 和 float 可以直接运算,语言帮你悄悄处理。Go 不打算这样干
原因很简单:隐式转换会掩盖 bug。你以为两个数字在相加,实际上发生了精度丢失或者溢出,但没有任何提示。Go 让一切都显式发生,出了问题一眼看出来
var a int = 10var b float64 = 3.14fmt.Println(a + b) // 报错:mismatched types int and float64必须手动转换:
fmt.Println(float64(a) + b) // 10 + 3.14 = 13.14数字类型之间的转换
语法是 目标类型 ( 值 ):
var a int = 42var b float64 = float64(a) // int → float64var c int = int(b) // float64 → int简单吧,但有几个细节要注意
浮点转整数:直接截断,不四舍五入
var f float64 = 3.99var n int = int(f)fmt.Println(n) // 3,不是 4小数部分直接丢掉,不管是 3.1 还是 3.9,结果都是 3。需要四舍五入要用 math.Round():
import "math"
f := 3.99n := int(math.Round(f))fmt.Println(n) // 4大类型转小类型可能会溢出
var a int32 = 1000var b int8 = int8(a)fmt.Println(b) // -24,溢出了int8 最大 127,1000 放不下,发生溢出。Go 不报错,静默地给你错误的结果,这是最危险的情况,你要自己保证转换前值在目标类型的范围内
有符号和无符号互转同样可能出问题
var a int = -1var b uint = uint(a)fmt.Println(b) // 18446744073709551615,-1 变成了极大的正数负数转无符号整数,结果完全不符合预期
整数和浮点互转的精度损失
var a int64 = 9999999999999999var b float64 = float64(a)var c int64 = int64(b)fmt.Println(a == c) // false,精度丢失了float64 的有效精度约 15-16 位,超大整数转浮点再转回来,可能和原始值不一样
字符串和 []byte、[]rune 互转
上一篇讲过,这里整理一下:
s := "Hello, 世界"
// string → []bytebs := []byte(s)
// []byte → strings2 := string(bs)
// string → []runers := []rune(s)
// []rune → strings3 := string(rs)单个数字转字符串,不要用 string():
n := 65fmt.Println(string(n)) // "A",不是 "65"string(65) 把 65 当作 Unicode 码点,返回对应的字符 "A",不是数字 65 的字符串形式。要得到 "65" 用 strconv.Itoa()
strconv 包:字符串和数字互转
字符串和数字之间不能用类型转换语法,要用 strconv 包
import "strconv"整数和字符串
整数转字符串,Itoa:
n := 42s := strconv.Itoa(n)fmt.Println(s) // "42"fmt.Printf("%T\n", s) // string字符串转整数,Atoi:
s := "42"n, err := strconv.Atoi(s)if err != nil { fmt.Println("转换失败:", err)} else { fmt.Println(n) // 42}Atoi 返回两个值:结果和错误。字符串不一定是合法的整数,所以必须处理错误:
s := "abc"n, err := strconv.Atoi(s)if err != nil { fmt.Println(err) // strconv.Atoi: parsing "abc": invalid syntax}fmt.Println(n) // 0,转换失败时结果是零值浮点数和字符串
浮点数转字符串,FormatFloat:
f := 3.14159
// 'f' 格式(普通小数),2 位小数,float64s := strconv.FormatFloat(f, 'f', 2, 64)fmt.Println(s) // "3.14"
// 'e' 格式(科学计数法)s2 := strconv.FormatFloat(f, 'e', 4, 64)fmt.Println(s2) // "3.1416e+00"
// 'g' 格式(自动选择最短表示)s3 := strconv.FormatFloat(f, 'g', -1, 64)fmt.Println(s3) // "3.14159"参数说明:第一个是值,第二个是格式(f/e/g),第三个是精度(-1 表示最短表示),第四个是位数(32 或 64)
字符串转浮点数,ParseFloat:
s := "3.14"f, err := strconv.ParseFloat(s, 64) // 64 表示 float64if err != nil { fmt.Println("转换失败:", err)} else { fmt.Println(f) // 3.14}布尔值和字符串
布尔转字符串:
b := trues := strconv.FormatBool(b)fmt.Println(s) // "true"字符串转布尔:
s := "true"b, err := strconv.ParseBool(s)if err != nil { fmt.Println("转换失败:", err)} else { fmt.Println(b) // true}ParseBool 能识别 "1"、"t"、"true"、"TRUE" 为 true,"0"、"f"、"false"、"FALSE" 为 false,其他值报错
ParseInt 和 ParseUint 的通杀解析
Atoi 只能解析十进制整数,ParseInt 更灵活:
// 解析十六进制n, err := strconv.ParseInt("ff", 16, 64) // 基数 16,位数 64fmt.Println(n) // 255
// 解析二进制n2, err := strconv.ParseInt("1010", 2, 64)fmt.Println(n2) // 10
// 基数 0:根据前缀自动判断(0x 十六进制,0b 二进制,0o 八进制)n3, err := strconv.ParseInt("0xff", 0, 64)fmt.Println(n3) // 255整数转指定进制的字符串:
n := 255fmt.Println(strconv.FormatInt(int64(n), 2)) // "11111111",二进制fmt.Println(strconv.FormatInt(int64(n), 16)) // "ff",十六进制fmt.Println(strconv.FormatInt(int64(n), 10)) // "255",十进制fmt.Sprintf 的格式化转换
除了 strconv,还可以用 fmt.Sprintf 把任意值转成字符串:
n := 42f := 3.14b := true
s1 := fmt.Sprintf("%d", n) // "42"s2 := fmt.Sprintf("%.1f", f) // "3.1"s3 := fmt.Sprintf("%v", b) // "true"s4 := fmt.Sprintf("%v", n) // "42",%v 是通用格式%v 是万能格式,任何类型都能用,但不如专用格式精确。Sprintf 比 strconv 灵活,但速度稍慢,需要高性能场合用 strconv
接口类型的转换
接口类型是 Go 里一个特殊的存在,它可以存任意类型的值。从接口类型取回具体类型,需要用类型断言
现在不需要完全理解接口,只需要知道类型断言的语法,后面接口那篇会深入讲
var i interface{} = "hello"
// 类型断言s, ok := i.(string)if ok { fmt.Println(s) // "hello"} else { fmt.Println("不是 string")}i.(string) 断言 i 里存的是 string,返回两个值:实际值和是否成功
不带 ok 的写法,断言失败会直接 panic:
s := i.(string) // 如果 i 不是 string,程序崩溃带 ok 的写法更安全,断言失败 ok 是 false,s 是零值,程序不崩溃
自定义类型之间的转换
上一篇提到可以用 type 定义新类型:
type Celsius float64 // 摄氏度type Fahrenheit float64 // 华氏度虽然两者底层都是 float64,但它们定义上是不同的类型,不能直接运算:
var c Celsius = 100var f Fahrenheit = Fahrenheit(c) // 必须显式转换底层类型相同的自定义类型之间可以互转:
type Celsius float64type MyFloat float64
var c Celsius = 100var m MyFloat = MyFloat(c) // 底层都是 float64,可以转但自定义类型和内置类型之间也需要显式转换:
type MyInt int
var a MyInt = 10var b int = int(a) // MyInt → intvar c MyInt = MyInt(b) // int → MyInt转换性能考量
大多数类型转换在编译阶段处理,运行时没有额外开销。但有几种转换会分配内存,需要注意:
string 和 []byte 互转会复制数据:
s := "hello"bs := []byte(s) // 复制了一份数据频繁转换在高性能场合是个负担。Go 1.20 之后可以用 unsafe 包做零拷贝转换,但那是进阶话题,现在不用管
fmt.Sprintf 比 strconv 慢:
做大量数字转字符串,用 strconv.Itoa,不要用 fmt.Sprintf("%d", n)。差距在基准测试里明显,日常少量转换无所谓
常见坑
string( 整数 ) 不是数字转字符串
fmt.Println(string(65)) // "A",不是 "65"fmt.Println(strconv.Itoa(65)) // "65",这才对嘛浮点转整数不四舍五入
int(3.9) // 3,不是 4需要四舍五入用 math.Round()
大转小溢出不报错
int8(1000) // -24,静默溢出转换前自己确认值在目标范围内
负数转无符号整数
uint(-1) // 18446744073709551615有符号负数转无符号,结果完全不符合预期
Atoi 错误没处理
n, _ := strconv.Atoi("abc")fmt.Println(n) // 0,这里他悄悄失败了转换失败时结果是零值,如果直接用这个零值继续运算,bug 很难追踪。你要养成错误必须要处理的习惯
下一篇讲运算符,算术、比较、逻辑、位运算,以及 Go 里一些和其他语言不同的地方
Auth_Verified: 2026.05.03