Go之路 - 3.go的数据类型与转换

一、Go类型系统概述

Go是静态类型 语言,编译时检查类型,同时通过类型推断 简化代码。类型分为基础类型复合类型两大类。

二、基本数据类型

1. 布尔类型 (bool)

go 复制代码
var b1 bool = true
var b2 = false           // 类型推断
b3 := true               // 简短声明

// 零值:false
// 大小:1字节
// 只能为 true/false,不能与数字互转

2. 整数类型

有符号整数(默认int)
go 复制代码
// 明确大小的类型
var i1 int8   = 127     // -128 ~ 127
var i2 int16  = 32767   // -32768 ~ 32767
var i3 int32  = 100000  // -21亿 ~ 21亿,别名rune
var i4 int64  = 1<<63-1 // -9.22e18 ~ 9.22e18

// 平台相关类型(推荐使用)
var i5 int   = 100      // 32位系统占4字节,64位系统占8字节
var i6 = 42             // 自动推断为int

// 字面量类型推断
n1 := 42      // int
n2 := 3.14    // float64
n3 := 1 << 33 // 如果int是32位,这会溢出,最好用int64(1) << 33
无符号整数
go 复制代码
var u1 uint8   = 255    // 0~255,别名byte
var u2 uint16  = 65535
var u3 uint32  = 0xFFFFFFFF
var u4 uint64  = 1<<64-1
var u5 uint    = 100    // 平台相关
var u6 uintptr          // 存储指针,用于底层编程

// byte是uint8的别名
var b byte = 'A'        // 65
var b2 byte = 0xFF      // 255
特殊整数类型
go 复制代码
// rune:int32别名,表示Unicode码点
var r1 rune = 'A'      // 65
var r2 rune = '中'     // 20013
var r3 rune = '\u4e2d' // "中"的Unicode
var r4 rune = 0x4e2d   // 同样表示"中"

// byte与rune转换
b := byte('A')         // 65
r := rune(b)           // 65

3. 浮点数类型

go 复制代码
var f1 float32 = 3.1415926
var f2 float64 = 3.141592653589793

// 默认float64
f3 := 3.14             // float64
f4 := float32(3.14)    // 必须显式转换

// 科学计数法
f5 := 1.23e4           // 12300
f6 := 1.23e-4          // 0.000123

// 特殊值
var inf float64 = 1.0 / 0.0  // +Inf
var negInf = -1.0 / 0.0      // -Inf
var nan = 0.0 / 0.0          // NaN

4. 复数类型

go 复制代码
var c1 complex64 = 3 + 4i      // 实部虚部都是float32
var c2 complex128 = 3 + 4i     // 实部虚部都是float64
c3 := complex(3.0, 4.0)        // 使用complex函数

// 操作
realPart := real(c1)           // 3.0
imagPart := imag(c1)           // 4.0
conjugate := complex(real(c1), -imag(c1))

5. 字符串类型 (string)

go 复制代码
// 基本操作
s1 := "Hello, 世界"
len1 := len(s1)                // 13字节(非字符数)
len2 := len([]rune(s1))        // 8字符

// 遍历(按字节)
for i := 0; i < len(s1); i++ {
    fmt.Printf("%02x ", s1[i])
}

// 遍历(按字符)
for i, ch := range s1 {        // ch是rune类型
    fmt.Printf("%d: %c\n", i, ch)
}

// 原始字符串(反引号)
raw := `第一行
第二行\t不会转义
第三行`

三、复合数据类型

1. 数组 (Array) - 固定长度

go 复制代码
// 声明方式
var arr1 [3]int               // [0 0 0]
var arr2 = [3]int{1, 2, 3}
arr3 := [...]int{1, 2, 3, 4} // 编译器推断长度
arr4 := [5]int{1: 10, 3: 30} // [0 10 0 30 0]

// 多维数组
var matrix [2][3]int = [2][3]int{
    {1, 2, 3},
    {4, 5, 6},
}

// 数组是值类型
a1 := [2]int{1, 2}
a2 := a1      // 复制整个数组
a2[0] = 100   // 不影响a1

2. 切片 (Slice) - 动态数组

go 复制代码
// 创建方式
var s1 []int                    // nil切片,len=0, cap=0
s2 := []int{}                   // 空切片,len=0, cap=0(有底层数组)
s3 := make([]int, 5)           // len=5, cap=5
s4 := make([]int, 3, 10)       // len=3, cap=10
s5 := []int{1, 2, 3}           // 字面量
s6 := arr1[1:3]                // 从数组创建

// 切片操作
slice := []int{1, 2, 3, 4, 5}
sub1 := slice[1:3]             // [2, 3]
sub2 := slice[:3]              // [1, 2, 3]
sub3 := slice[2:]              // [3, 4, 5]

// 追加和扩容
slice = append(slice, 6)       // 容量不足时自动扩容(通常2倍)
slice = append(slice, 7, 8, 9)

// 复制切片
src := []int{1, 2, 3}
dst := make([]int, 2)
n := copy(dst, src)            // n=2,只复制前两个元素

// 删除元素
slice = []int{1, 2, 3, 4, 5}
slice = append(slice[:2], slice[3:]...)  // 删除索引2

3. 映射 (Map) - 键值对

go 复制代码
// 创建方式
var m1 map[string]int          // nil map,不能直接使用
m2 := make(map[string]int)     // 空map
m3 := make(map[string]int, 10) // 指定初始容量
m4 := map[string]int{
    "apple": 5,
    "banana": 2,
}

// 操作
m4["orange"] = 3              // 添加/修改
value, exists := m4["apple"]  // exists=true
delete(m4, "banana")          // 删除

// 遍历(无序)
for key, value := range m4 {
    fmt.Println(key, value)
}

// 注意事项
var nilMap map[string]int
// nilMap["key"] = 1          // panic!

4. 结构体 (Struct)

go 复制代码
// 定义
type Person struct {
    Name    string
    Age     int
    Email   string
    private string  // 小写开头,不可导出
}

// 初始化
p1 := Person{"Alice", 25, "alice@test.com", ""}           // 必须按顺序
p2 := Person{Name: "Bob", Age: 30}                        // 部分字段
p3 := Person{}                                            // 零值
p4 := &Person{Name: "Charlie"}                            // 返回指针

// 匿名字段
type Point struct {
    X, Y int
}

// 嵌入(组合)
type Circle struct {
    Point   // 匿名字段
    Radius  int
}

c := Circle{Point{1, 2}, 3}
fmt.Println(c.X, c.Y)  // 可直接访问

5. 指针 (Pointer)

go 复制代码
var x int = 10
var p *int = &x

// 操作
fmt.Println(*p)  // 10
*p = 20
fmt.Println(x)   // 20

// new函数
ptr := new(int)   // 分配零值,返回指针
*ptr = 100

// 指针的零值
var p2 *int       // nil
// *p2 = 10       // panic!

// 结构体指针(可直接访问字段)
type Vertex struct { X, Y int }
v := &Vertex{1, 2}
v.X = 10          // 等价于 (*v).X

6. 函数类型

go 复制代码
// 函数类型定义
type MathOp func(int, int) int
type Predicate func(string) bool

// 函数变量
var add MathOp = func(a, b int) int {
    return a + b
}

// 高阶函数
func calculate(x, y int, op MathOp) int {
    return op(x, y)
}

// 闭包
func makeMultiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}
double := makeMultiplier(2)
result := double(5)  // 10

7. 通道 (Channel)

go 复制代码
// 创建
ch1 := make(chan int)        // 无缓冲
ch2 := make(chan string, 5)  // 缓冲大小为5

// 单向通道
var sendOnly chan<- int = ch1  // 只能发送
var recvOnly <-chan int = ch1  // 只能接收

// select多路复用
select {
case msg := <-ch1:
    fmt.Println(msg)
case ch2 <- "hello":
    fmt.Println("sent")
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
}

// 关闭和检测
close(ch1)
value, ok := <-ch1  // ok=false表示通道已关闭

8. 接口 (Interface)

go 复制代码
// 定义
type Writer interface {
    Write([]byte) (int, error)
}

// 空接口
var any interface{}
any = 42
any = "hello"
any = []int{1, 2, 3}

// 类型断言
if str, ok := any.(string); ok {
    fmt.Println(str)
}

// 类型开关
switch v := any.(type) {
case int:
    fmt.Printf("int: %d\n", v)
case string:
    fmt.Printf("string: %s\n", v)
default:
    fmt.Printf("unknown: %T\n", v)
}

四、类型转换详解

1. 数值类型转换

go 复制代码
// 必须显式转换
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// 注意精度损失和溢出
var f2 float64 = 3.99
var i2 int = int(f2)          // 3,截断小数
var big int64 = 1 << 40
var small int32 = int32(big)  // 溢出,结果为0

// 常量转换(编译时)
const Pi = 3.14159
const IntPi = int(Pi)         // 3

2. 字符串转换

go 复制代码
import "strconv"

// 字符串 ↔ 数值
s1 := "123"
num, err := strconv.Atoi(s1)           // 字符串转int
s2 := strconv.Itoa(456)                // int转字符串

// Parse系列
b, _ := strconv.ParseBool("true")
f, _ := strconv.ParseFloat("3.14", 64) // bitSize: 32或64
i64, _ := strconv.ParseInt("100", 10, 64) // base, bitSize
ui64, _ := strconv.ParseUint("100", 10, 64)

// Format系列
s3 := strconv.FormatBool(true)
s4 := strconv.FormatInt(-42, 16)       // "-2a",16进制
s5 := strconv.FormatUint(42, 2)        // "101010",二进制
s6 := strconv.FormatFloat(3.1415, 'f', 2, 64) // 'f'格式,2位小数

3. 字符串 ↔ 字节/符文切片

go 复制代码
// string ↔ []byte(有拷贝)
str := "Hello, 世界"
bytes := []byte(str)          // 转换为字节切片
str2 := string(bytes)         // 转换回来

// string ↔ []rune(有拷贝)
runes := []rune(str)          // [72 101 108 108 111 44 32 19990 30028]
str3 := string(runes)

// 危险:数字直接转字符串
var n int = 65
s := string(n)                // "A" 而不是 "65"!
// 正确做法:
s = strconv.Itoa(n)           // "65"

// []byte ↔ []rune(通过string中转)
bytes = []byte{228, 184, 173}  // "中"的UTF-8
runes = []rune(string(bytes))  // [20013]

4. unsafe转换(高风险)

go 复制代码
import "unsafe"

// 1) 字符串 ↔ 字节切片(零拷贝,但危险)
func StringToBytes(s string) []byte {
    return *(*[]byte)(unsafe.Pointer(&struct {
        string
        int
    }{s, len(s)}))
}

func BytesToString(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

// 2) 数字类型转换
var floatBits float64 = 3.14
var intBits uint64 = *(*uint64)(unsafe.Pointer(&floatBits))

// 3) 结构体转换(相同内存布局)
type A struct { X, Y int }
type B struct { X, Y int }

var a A = A{1, 2}
var b *B = (*B)(unsafe.Pointer(&a))

5. 自定义类型转换

go 复制代码
// 方法实现转换
type Celsius float64
type Fahrenheit float64

func CToF(c Celsius) Fahrenheit {
    return Fahrenheit(c*9/5 + 32)
}

func FToC(f Fahrenheit) Celsius {
    return Celsius((f - 32) * 5 / 9)
}

// 实现Stringer接口
func (c Celsius) String() string {
    return fmt.Sprintf("%.1f°C", c)
}

五、类型断言与类型开关

1. 类型断言

go 复制代码
var i interface{} = "hello"

// 安全断言
s, ok := i.(string)    // s="hello", ok=true
f, ok := i.(float64)   // f=0.0, ok=false

// 直接断言(可能panic)
s2 := i.(string)       // 成功
// s3 := i.(int)       // panic!

// 多返回值函数中的使用
if s, ok := getValue().(string); ok {
    // 安全使用s
}

2. 类型开关

go 复制代码
func describe(i interface{}) {
    switch v := i.(type) {
    case nil:
        fmt.Println("nil")
    case int:
        fmt.Printf("int: %d\n", v)
    case string:
        fmt.Printf("string: %s\n", v)
    case bool:
        fmt.Printf("bool: %v\n", v)
    case []interface{}:
        fmt.Printf("slice of length %d\n", len(v))
    case map[string]interface{}:
        fmt.Printf("map with %d keys\n", len(v))
    case func(int) int:
        fmt.Printf("function, call result: %d\n", v(10))
    default:
        fmt.Printf("unknown type: %T\n", v)
    }
}

六、零值与初始化

1. 所有类型的零值

go 复制代码
bool          // false
int           // 0
float64       // 0.0
string        // ""
pointer       // nil
slice         // nil
map           // nil
channel       // nil
interface{}   // nil
func          // nil
array         // 所有元素为零值
struct        // 所有字段为零值

2. 初始化方式

go 复制代码
// 零值初始化
var a int          // 0
var b string       // ""

// 显式初始化
var c int = 10
var d = 20         // 类型推断

// 简短声明(函数内)
e := 30
f := "hello"

// 多变量初始化
var x, y = 1, "a"
i, j := 10, 20

// 交换值
i, j = j, i        // 不需要临时变量

七、类型比较与相等性

1. 可比较类型

go 复制代码
// 可比较:== 和 !=
bool
int, float, string
pointer
channel
interface{}
array(当元素可比较时)
struct(当所有字段可比较时)

// 不可比较
slice(除了与nil比较)
map(除了与nil比较)
func

2. 比较规则

go 复制代码
// 基本类型
fmt.Println(1 == 1)               // true
fmt.Println(3.14 == 3.14)         // true
fmt.Println("hello" == "hello")   // true

// 接口类型
var i1, i2 interface{} = 10, 10
fmt.Println(i1 == i2)             // true

// 切片只能与nil比较
var s []int = nil
fmt.Println(s == nil)             // true
// fmt.Println(s1 == s2)          // 编译错误!

// map也只能与nil比较
var m map[string]int
fmt.Println(m == nil)             // true

八、类型别名与定义

1. 类型别名 (Type Alias)

go 复制代码
// 完全相同的类型
type ByteAlias = byte
type StringAlias = string

var b byte = 'A'
var ba ByteAlias = b  // 可直接赋值

// 别名可用于解耦
type ID = int64
type Timestamp = int64

func Process(id ID, ts Timestamp) {
    // 实际上是两个int64,但语义不同
}

2. 类型定义 (Type Definition)

go 复制代码
// 新类型
type Celsius float64
type Fahrenheit float64

var c Celsius = 20
var f Fahrenheit = 68
// c = f  // 编译错误!

// 但可显式转换
c = Celsius(f)

总结

Go的类型系统设计哲学:

  1. 显式优于隐式 - 类型转换必须明确
  2. 简单一致 - 零值机制统一
  3. 类型安全 - 编译时检查
  4. 实用主义 - 接口和类型断言提供灵活性

掌握Go的类型系统需要理解:

  • 值类型 vs 引用类型
  • 零值机制
  • 类型转换的显式性
  • 接口的动态特性
  • 类型安全和运行时安全的平衡

通过合理使用类型定义、接口和类型转换,可以编写出既安全又灵活的Go代码。

相关推荐
龙门吹雪2 小时前
Go 语言包初始化顺序详解
golang·init·初始化顺序·依赖包·导入包
Li_7695322 小时前
Spring Cloud — SkyWalking(六)
java·后端·spring·spring cloud·skywalking
2201_757830872 小时前
SpringBoot
java·spring boot·后端
程序员Sunday2 小时前
为什么 AI 明明写后端更爽,但却都网传 AI 取代前端,而不是 AI 取代后端?就离谱...
前端·后端
程序员西西2 小时前
深入剖析 Java 中的 ZGC 机制:原理、优势与实践
java·后端·算法
海上彼尚2 小时前
Go之路 - 4.go的集合[完整版]
开发语言·后端·golang
卡尔AI工坊2 小时前
万众瞩目的 GPT 5.2,连个火柴人游戏都做不明白?
后端·算法
coding随想3 小时前
JavaScript Notifications API:告别alert弹窗,开启沉浸式用户体验革命!
开发语言·javascript·ux
阿海5743 小时前
卸载php的shell脚本
开发语言·php