








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