Skip to content

Go 语言类型转换

类型转换是编程中的重要概念,Go 语言提供了多种类型转换方式。本章将详细介绍显式类型转换、类型断言、字符串转换等内容。

📋 类型转换基础

显式类型转换

Go 语言不支持隐式类型转换,所有类型转换都必须显式进行。

go
package main

import "fmt"

func main() {
    // 数值类型转换
    var i int = 42
    var f float64 = float64(i)  // int 转 float64
    var u uint = uint(i)        // int 转 uint
    
    fmt.Printf("int: %d, float64: %.2f, uint: %d\n", i, f, u)
    
    // 精度可能丢失的转换
    var bigFloat float64 = 123.456
    var smallInt int = int(bigFloat)  // 小数部分被截断
    
    fmt.Printf("float64: %.3f -> int: %d\n", bigFloat, smallInt)
    
    // 不同大小整数之间的转换
    var big int64 = 1000000
    var small int32 = int32(big)
    
    fmt.Printf("int64: %d -> int32: %d\n", big, small)
    
    // 字符和数字的转换
    var char rune = 'A'
    var ascii int = int(char)
    var backToChar rune = rune(ascii)
    
    fmt.Printf("字符: %c, ASCII: %d, 转回字符: %c\n", char, ascii, backToChar)
}

类型转换规则

go
package main

import "fmt"

func main() {
    fmt.Println("=== 数值类型转换规则 ===")
    
    // 整数类型之间的转换
    var a int8 = 127
    var b int16 = int16(a)
    var c int32 = int32(b)
    var d int64 = int64(c)
    
    fmt.Printf("int8(%d) -> int16(%d) -> int32(%d) -> int64(%d)\n", a, b, c, d)
    
    // 浮点数精度转换
    var f64 float64 = 3.141592653589793
    var f32 float32 = float32(f64)  // 精度降低
    var backF64 float64 = float64(f32)
    
    fmt.Printf("原始 float64: %.15f\n", f64)
    fmt.Printf("转为 float32: %.15f\n", f32)
    fmt.Printf("转回 float64: %.15f\n", backF64)
    
    // 有符号和无符号转换
    var signed int = -42
    var unsigned uint = uint(signed)  // 可能产生意外结果
    
    fmt.Printf("有符号: %d -> 无符号: %d\n", signed, unsigned)
    
    // 布尔值转换(需要手动处理)
    var flag bool = true
    var intFlag int
    if flag {
        intFlag = 1
    } else {
        intFlag = 0
    }
    
    fmt.Printf("布尔值: %v -> 整数: %d\n", flag, intFlag)
}

🔤 字符串类型转换

strconv 包的使用

go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    fmt.Println("=== 字符串与数字转换 ===")
    
    // 字符串转整数
    str1 := "123"
    if num1, err := strconv.Atoi(str1); err == nil {
        fmt.Printf("字符串 '%s' -> 整数: %d\n", str1, num1)
    } else {
        fmt.Printf("转换失败: %v\n", err)
    }
    
    // 整数转字符串
    num2 := 456
    str2 := strconv.Itoa(num2)
    fmt.Printf("整数 %d -> 字符串: '%s'\n", num2, str2)
    
    // 字符串转浮点数
    str3 := "3.14159"
    if float1, err := strconv.ParseFloat(str3, 64); err == nil {
        fmt.Printf("字符串 '%s' -> 浮点数: %.5f\n", str3, float1)
    }
    
    // 浮点数转字符串
    float2 := 2.71828
    str4 := strconv.FormatFloat(float2, 'f', 5, 64)
    fmt.Printf("浮点数 %.5f -> 字符串: '%s'\n", float2, str4)
    
    // 字符串转布尔值
    str5 := "true"
    if bool1, err := strconv.ParseBool(str5); err == nil {
        fmt.Printf("字符串 '%s' -> 布尔值: %v\n", str5, bool1)
    }
    
    // 布尔值转字符串
    bool2 := false
    str6 := strconv.FormatBool(bool2)
    fmt.Printf("布尔值 %v -> 字符串: '%s'\n", bool2, str6)
}

进制转换

go
package main

import (
    "fmt"
    "strconv"
)

func main() {
    fmt.Println("=== 进制转换 ===")
    
    num := 255
    
    // 十进制转其他进制
    binary := strconv.FormatInt(int64(num), 2)   // 二进制
    octal := strconv.FormatInt(int64(num), 8)    // 八进制
    hex := strconv.FormatInt(int64(num), 16)     // 十六进制
    
    fmt.Printf("十进制 %d:\n", num)
    fmt.Printf("  二进制: %s\n", binary)
    fmt.Printf("  八进制: %s\n", octal)
    fmt.Printf("  十六进制: %s\n", hex)
    
    // 其他进制转十进制
    fmt.Println("\n转换回十进制:")
    
    if val1, err := strconv.ParseInt(binary, 2, 64); err == nil {
        fmt.Printf("二进制 '%s' -> 十进制: %d\n", binary, val1)
    }
    
    if val2, err := strconv.ParseInt(octal, 8, 64); err == nil {
        fmt.Printf("八进制 '%s' -> 十进制: %d\n", octal, val2)
    }
    
    if val3, err := strconv.ParseInt(hex, 16, 64); err == nil {
        fmt.Printf("十六进制 '%s' -> 十进制: %d\n", hex, val3)
    }
    
    // 任意进制转换
    fmt.Println("\n任意进制转换:")
    base36 := strconv.FormatInt(int64(num), 36)  // 36进制
    fmt.Printf("十进制 %d -> 36进制: %s\n", num, base36)
    
    if val4, err := strconv.ParseInt(base36, 36, 64); err == nil {
        fmt.Printf("36进制 '%s' -> 十进制: %d\n", base36, val4)
    }
}

🎯 接口类型转换

类型断言

go
package main

import "fmt"

func main() {
    fmt.Println("=== 类型断言 ===")
    
    // 创建接口变量
    var i interface{} = "Hello, World!"
    
    // 基本类型断言
    if str, ok := i.(string); ok {
        fmt.Printf("断言成功: %s (长度: %d)\n", str, len(str))
    } else {
        fmt.Println("断言失败: 不是字符串类型")
    }
    
    // 类型断言失败的情况
    if num, ok := i.(int); ok {
        fmt.Printf("是整数: %d\n", num)
    } else {
        fmt.Println("不是整数类型")
    }
    
    // 不安全的类型断言(可能panic)
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕获panic: %v\n", r)
        }
    }()
    
    // 这会触发panic,因为i不是int类型
    // num := i.(int)  // 取消注释会panic
    
    fmt.Println("=== 多种类型处理 ===")
    
    // 处理多种可能的类型
    values := []interface{}{
        42,
        "Hello",
        3.14,
        true,
        []int{1, 2, 3},
    }
    
    for i, value := range values {
        fmt.Printf("值 %d: ", i+1)
        
        switch v := value.(type) {
        case int:
            fmt.Printf("整数: %d\n", v)
        case string:
            fmt.Printf("字符串: %s\n", v)
        case float64:
            fmt.Printf("浮点数: %.2f\n", v)
        case bool:
            fmt.Printf("布尔值: %v\n", v)
        case []int:
            fmt.Printf("整数切片: %v\n", v)
        default:
            fmt.Printf("未知类型: %T\n", v)
        }
    }
}

接口转换示例

go
package main

import "fmt"

// 定义接口
type Shape interface {
    Area() float64
}

type Drawable interface {
    Draw()
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return 3.14159 * c.Radius * c.Radius
}

func (c Circle) Draw() {
    fmt.Printf("绘制半径为 %.2f 的圆形\n", c.Radius)
}

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Draw() {
    fmt.Printf("绘制 %.2f x %.2f 的矩形\n", r.Width, r.Height)
}

func main() {
    fmt.Println("=== 接口类型转换 ===")
    
    shapes := []Shape{
        Circle{Radius: 5},
        Rectangle{Width: 4, Height: 3},
    }
    
    for i, shape := range shapes {
        fmt.Printf("形状 %d:\n", i+1)
        fmt.Printf("  面积: %.2f\n", shape.Area())
        
        // 类型断言检查是否实现了Drawable接口
        if drawable, ok := shape.(Drawable); ok {
            fmt.Print("  ")
            drawable.Draw()
        } else {
            fmt.Println("  不支持绘制")
        }
        
        // 使用类型断言获取具体类型
        switch s := shape.(type) {
        case Circle:
            fmt.Printf("  圆形半径: %.2f\n", s.Radius)
        case Rectangle:
            fmt.Printf("  矩形尺寸: %.2f x %.2f\n", s.Width, s.Height)
        }
        
        fmt.Println()
    }
}

🔄 自定义类型转换

定义转换方法

go
package main

import (
    "fmt"
    "strconv"
    "strings"
)

// 自定义类型
type Temperature float64
type Distance int
type Person struct {
    Name string
    Age  int
}

// 温度转换方法
func (t Temperature) ToCelsius() Temperature {
    return t
}

func (t Temperature) ToFahrenheit() Temperature {
    return t*9/5 + 32
}

func (t Temperature) ToKelvin() Temperature {
    return t + 273.15
}

func (t Temperature) String() string {
    return fmt.Sprintf("%.2f°C", float64(t))
}

// 距离转换方法
func (d Distance) ToMeters() float64 {
    return float64(d)
}

func (d Distance) ToKilometers() float64 {
    return float64(d) / 1000
}

func (d Distance) ToMiles() float64 {
    return float64(d) / 1609.34
}

func (d Distance) String() string {
    return fmt.Sprintf("%d米", int(d))
}

// Person 转换方法
func (p Person) ToString() string {
    return fmt.Sprintf("%s (%d岁)", p.Name, p.Age)
}

func (p Person) ToJSON() string {
    return fmt.Sprintf(`{"name":"%s","age":%d}`, p.Name, p.Age)
}

func PersonFromString(s string) (Person, error) {
    parts := strings.Split(s, ",")
    if len(parts) != 2 {
        return Person{}, fmt.Errorf("格式错误,应为: 姓名,年龄")
    }
    
    name := strings.TrimSpace(parts[0])
    ageStr := strings.TrimSpace(parts[1])
    
    age, err := strconv.Atoi(ageStr)
    if err != nil {
        return Person{}, fmt.Errorf("年龄格式错误: %v", err)
    }
    
    return Person{Name: name, Age: age}, nil
}

func main() {
    fmt.Println("=== 自定义类型转换 ===")
    
    // 温度转换
    temp := Temperature(25)
    fmt.Printf("温度转换:\n")
    fmt.Printf("  摄氏度: %s\n", temp.String())
    fmt.Printf("  华氏度: %.2f°F\n", temp.ToFahrenheit())
    fmt.Printf("  开尔文: %.2f K\n", temp.ToKelvin())
    
    // 距离转换
    distance := Distance(5000)
    fmt.Printf("\n距离转换:\n")
    fmt.Printf("  米: %s\n", distance.String())
    fmt.Printf("  千米: %.2f公里\n", distance.ToKilometers())
    fmt.Printf("  英里: %.2f英里\n", distance.ToMiles())
    
    // Person转换
    person := Person{Name: "张三", Age: 25}
    fmt.Printf("\nPerson转换:\n")
    fmt.Printf("  字符串: %s\n", person.ToString())
    fmt.Printf("  JSON: %s\n", person.ToJSON())
    
    // 从字符串创建Person
    personStr := "李四, 30"
    if p, err := PersonFromString(personStr); err == nil {
        fmt.Printf("  从字符串创建: %s\n", p.ToString())
    } else {
        fmt.Printf("  创建失败: %v\n", err)
    }
}

⚠️ 类型转换的陷阱

精度损失和溢出

go
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("=== 类型转换陷阱 ===")
    
    // 1. 浮点数精度损失
    fmt.Println("1. 浮点数精度损失:")
    var precise float64 = 1.23456789012345
    var imprecise float32 = float32(precise)
    var backToPrecise float64 = float64(imprecise)
    
    fmt.Printf("原始 float64: %.15f\n", precise)
    fmt.Printf("转为 float32: %.15f\n", imprecise)
    fmt.Printf("转回 float64: %.15f\n", backToPrecise)
    fmt.Printf("精度损失: %.15f\n", precise-backToPrecise)
    
    // 2. 整数溢出
    fmt.Println("\n2. 整数溢出:")
    var big int64 = math.MaxInt32 + 1
    var small int32 = int32(big)  // 溢出
    
    fmt.Printf("原始 int64: %d\n", big)
    fmt.Printf("转为 int32: %d\n", small)
    fmt.Printf("最大 int32: %d\n", math.MaxInt32)
    
    // 3. 有符号无符号转换陷阱
    fmt.Println("\n3. 有符号无符号转换:")
    var negative int = -100
    var positive uint = uint(negative)  // 危险!
    
    fmt.Printf("有符号 int: %d\n", negative)
    fmt.Printf("转为 uint: %d\n", positive)
    
    // 4. 字符串转换失败
    fmt.Println("\n4. 字符串转换错误处理:")
    invalidStrings := []string{"abc", "12.34.56", "", "999999999999999999999"}
    
    for _, s := range invalidStrings {
        if num, err := strconv.Atoi(s); err != nil {
            fmt.Printf("字符串 '%s' 转换失败: %v\n", s, err)
        } else {
            fmt.Printf("字符串 '%s' 转换成功: %d\n", s, num)
        }
    }
    
    // 5. 类型断言失败
    fmt.Println("\n5. 类型断言安全检查:")
    var unknown interface{} = 3.14
    
    // 安全的类型断言
    if str, ok := unknown.(string); ok {
        fmt.Printf("是字符串: %s\n", str)
    } else {
        fmt.Println("不是字符串类型")
    }
    
    // 获取实际类型
    fmt.Printf("实际类型: %T, 值: %v\n", unknown, unknown)
}

🛠️ 实用转换工具

通用转换工具函数

go
package main

import (
    "fmt"
    "reflect"
    "strconv"
)

// 通用转换工具
type Converter struct{}

// 尝试将任意值转换为字符串
func (c Converter) ToString(value interface{}) string {
    switch v := value.(type) {
    case string:
        return v
    case int, int8, int16, int32, int64:
        return fmt.Sprintf("%d", v)
    case uint, uint8, uint16, uint32, uint64:
        return fmt.Sprintf("%d", v)
    case float32, float64:
        return fmt.Sprintf("%g", v)
    case bool:
        return strconv.FormatBool(v)
    case nil:
        return ""
    default:
        return fmt.Sprintf("%v", v)
    }
}

// 尝试将字符串转换为指定类型
func (c Converter) FromString(s string, targetType reflect.Type) (interface{}, error) {
    switch targetType.Kind() {
    case reflect.String:
        return s, nil
    case reflect.Int:
        return strconv.Atoi(s)
    case reflect.Int64:
        return strconv.ParseInt(s, 10, 64)
    case reflect.Float64:
        return strconv.ParseFloat(s, 64)
    case reflect.Bool:
        return strconv.ParseBool(s)
    default:
        return nil, fmt.Errorf("不支持的类型: %v", targetType)
    }
}

// 检查两个值是否可以进行类型转换
func (c Converter) CanConvert(from, to reflect.Type) bool {
    return from.ConvertibleTo(to)
}

// 安全的类型转换
func (c Converter) SafeConvert(value interface{}, targetType reflect.Type) (interface{}, error) {
    sourceType := reflect.TypeOf(value)
    
    if sourceType == targetType {
        return value, nil
    }
    
    if !sourceType.ConvertibleTo(targetType) {
        return nil, fmt.Errorf("无法从 %v 转换到 %v", sourceType, targetType)
    }
    
    sourceValue := reflect.ValueOf(value)
    convertedValue := sourceValue.Convert(targetType)
    
    return convertedValue.Interface(), nil
}

func main() {
    converter := Converter{}
    
    fmt.Println("=== 通用转换工具测试 ===")
    
    // 测试转换为字符串
    values := []interface{}{
        123,
        3.14159,
        true,
        "hello",
        nil,
        []int{1, 2, 3},
    }
    
    fmt.Println("转换为字符串:")
    for _, value := range values {
        str := converter.ToString(value)
        fmt.Printf("  %T(%v) -> string(%s)\n", value, value, str)
    }
    
    // 测试从字符串转换
    fmt.Println("\n从字符串转换:")
    stringValues := map[string]reflect.Type{
        "123":   reflect.TypeOf(int(0)),
        "3.14":  reflect.TypeOf(float64(0)),
        "true":  reflect.TypeOf(bool(false)),
        "hello": reflect.TypeOf(string("")),
    }
    
    for str, targetType := range stringValues {
        if result, err := converter.FromString(str, targetType); err == nil {
            fmt.Printf("  string(%s) -> %v(%v)\n", str, targetType, result)
        } else {
            fmt.Printf("  string(%s) -> %v 转换失败: %v\n", str, targetType, err)
        }
    }
    
    // 测试安全转换
    fmt.Println("\n安全类型转换:")
    testCases := []struct {
        value  interface{}
        target reflect.Type
    }{
        {int(42), reflect.TypeOf(float64(0))},
        {float64(3.14), reflect.TypeOf(int(0))},
        {true, reflect.TypeOf(int(0))},
        {"hello", reflect.TypeOf(int(0))},
    }
    
    for _, tc := range testCases {
        if result, err := converter.SafeConvert(tc.value, tc.target); err == nil {
            fmt.Printf("  %T(%v) -> %v(%v)\n", tc.value, tc.value, tc.target, result)
        } else {
            fmt.Printf("  %T(%v) -> %v 转换失败: %v\n", tc.value, tc.value, tc.target, err)
        }
    }
}

🎓 小结

本章我们全面学习了 Go 语言的类型转换:

  • 显式转换:基本数据类型之间的转换
  • 字符串转换:strconv 包的使用和进制转换
  • 接口转换:类型断言和接口类型判断
  • 自定义转换:为自定义类型添加转换方法
  • 转换陷阱:精度损失、溢出等常见问题
  • 实用工具:通用转换工具函数的实现

理解和掌握类型转换对于编写健壮的 Go 代码非常重要。


接下来,我们将学习 Go 语言接口,这是 Go 语言面向对象编程的核心特性。

类型转换建议

  • 总是使用显式类型转换,Go 不支持隐式转换
  • 注意数值转换可能的精度损失和溢出问题
  • 使用类型断言时要检查返回的 ok 值
  • 合理处理字符串转换可能出现的错误

本站内容仅供学习和研究使用。