文章

Day02(数据类型、变量与常量)

Go语言数据类型详解、变量声明与初始化、常量与iota枚举

Day02(数据类型、变量与常量)

概述

Go 是一门静态类型语言,每个变量都有明确的类型。本文详细介绍 Go 的数据类型系统、变量声明方式和常量定义。

目录

  1. 基本数据类型
  2. 复合数据类型
  3. 变量声明与初始化
  4. 常量与 iota
  5. 类型转换
  6. 最佳实践

    基本数据类型

1. 整型(Integer Types)

整型分为有符号无符号两大类,区别在于如何解释二进制数据。

有符号整型 vs 无符号整型

特性 有符号整型 (Signed) 无符号整型 (Unsigned)
表示范围 负数、零、正数 零、正数
最高位用途 符号位(0=正,1=负) 数值位
相同位数范围 较小(需表示负数) 较大(全用于正数)
应用场景 通用计算、温度、坐标 计数、索引、位运算

整型类型表

类型 位数 最小值 最大值 说明
int8 8 -128 127 小范围整数
int16 16 -32,768 32,767 中等范围
int32 / rune 32 -2,147,483,648 2,147,483,647 Unicode 字符
int64 64 -9.2×10¹⁸ 9.2×10¹⁸ 大范围整数
uint8 / byte 8 0 255 字节数据
uint16 16 0 65,535 无符号中等范围
uint32 32 0 4,294,967,295 无符号大范围
uint64 64 0 1.8×10¹⁹ 无符号超大范围
int 平台相关 依赖架构 依赖架构 32位或64位
uint 平台相关 0 依赖架构 32位或64位
uintptr 平台相关 - - 指针整型

注意: intuint 的大小取决于平台:

  • 32位系统:4字节(32位)
  • 64位系统:8字节(64位)

实际示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
    // 声明不同整型
    var age int8 = 25
    var population int64 = 7800000000
    var byteData byte = 255
    var runeChar rune = '中'
    
    fmt.Printf("age: %d (type: %T)\n", age, age)
    fmt.Printf("population: %d (type: %T)\n", population, population)
    fmt.Printf("byteData: %d (type: %T)\n", byteData, byteData)
    fmt.Printf("runeChar: %c (type: %T, code: %d)\n", runeChar, runeChar, runeChar)
}

输出:

1
2
3
4
age: 25 (type: int8)
population: 7800000000 (type: int64)
byteData: 255 (type: uint8)
runeChar: 中 (type: int32, code: 20013)

2. 浮点型(Floating-Point Types)

Go 支持 IEEE 754 标准的浮点数,分为 float32float64

浮点型对比

类型 位数 精度 最小值 最大值 适用场景
float32 32 ~6-7位有效数字 ±1.4×10⁻⁴⁵ ±3.4×10³⁸ 图形处理、科学计算(节省内存)
float64 64 ~15-16位有效数字 ±4.9×10⁻³²⁴ ±1.8×10³⁰⁸ 推荐默认使用,金融计算

浮点数注意事项

⚠️ 重要警告: 浮点数存在精度问题,不适合精确计算!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
    // 浮点数精度问题
    a := 0.1
    b := 0.2
    c := a + b
    
    fmt.Printf("0.1 + 0.2 = %.20f\n", c)
    fmt.Printf("c == 0.3: %v\n", c == 0.3)  // false!
    
    // 正确做法:使用误差范围比较
    epsilon := 1e-9
    isEqual := (c - 0.3) < epsilon && (c - 0.3) > -epsilon
    fmt.Printf("Approximately equal: %v\n", isEqual)  // true
}

输出:

1
2
3
0.1 + 0.2 = 0.30000000000000004441
c == 0.3: false
Approximately equal: true

建议:

  • 一般场景使用 float64
  • 金融计算使用 decimal 库(如 github.com/shopspring/decimal
  • 避免直接比较浮点数相等

3. 布尔型(Boolean Type)

布尔型只有两个值:truefalse,默认值为 false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
    var isActive bool       // 默认 false
    var isCompleted bool = true
    
    // 布尔运算
    fmt.Println(isActive && isCompleted)   // AND: false
    fmt.Println(isActive || isCompleted)   // OR:  true
    fmt.Println(!isActive)                 // NOT: true
    
    // 常见用法
    age := 20
    isAdult := age >= 18
    fmt.Printf("Is adult: %v\n", isAdult)  // true
}

4. 字符串(String Type)

字符串是不可变的 UTF-8 编码字节序列。

字符串特性

  • 不可变性: 创建后不能修改
  • UTF-8 编码: 原生支持 Unicode
  • 双引号: "Hello" - 支持转义字符
  • 反引号: `Hello` - 原始字符串,不转义

字符串操作示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
    "fmt"
    "strings"
)

func main() {
    // 字符串声明
    s1 := "Hello, World!"
    s2 := `Raw string
with newline
and \t tab`
    
    fmt.Println(s1)
    fmt.Println(s2)
    
    // 常用操作
    fmt.Println(len(s1))                    // 长度: 13
    fmt.Println(s1[0])                      // 字节: 72 ('H')
    fmt.Println(s1[7:])                     // 切片: "World!"
    fmt.Println(strings.ToUpper(s1))        // 大写: "HELLO, WORLD!"
    fmt.Println(strings.Contains(s1, "World")) // true
    
    // 字符串拼接
    s3 := s1 + " Welcome"
    fmt.Println(s3)
    
    // 遍历字符串(按 rune)
    for i, char := range "你好世界" {
        fmt.Printf("Index: %d, Char: %c, Bytes: %d\n", i, char, len(string(char)))
    }
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Hello, World!
Raw string
with newline
and \t tab
13
72
World!
HELLO, WORLD!
true
Hello, World! Welcome
Index: 0, Char: 你, Bytes: 3
Index: 3, Char: 好, Bytes: 3
Index: 6, Char: 世, Bytes: 3
Index: 9, Char: 界, Bytes: 3

byte vs rune

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

func main() {
    s := "Hello 世界"
    
    // 按 byte 遍历
    fmt.Println("Bytes:")
    for i := 0; i < len(s); i++ {
        fmt.Printf("%d ", s[i])
    }
    fmt.Println()
    
    // 按 rune 遍历
    fmt.Println("\nRunes:")
    for _, r := range s {
        fmt.Printf("%c ", r)
    }
    fmt.Println()
}

关键点:

  • byte = uint8: 处理 ASCII 或二进制数据
  • rune = int32: 处理 Unicode 字符(中文、emoji 等)

复合数据类型

5. 其他基本类型

复数类型(Complex Types)

Go 原生支持复数运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
    var c1 complex64 = 3 + 4i   // float32 实部和虚部
    var c2 complex128 = 5 + 6i  // float64 实部和虚部
    
    fmt.Printf("c1: %v, real: %f, imag: %f\n", c1, real(c1), imag(c1))
    fmt.Printf("c2: %v, real: %f, imag: %f\n", c2, real(c2), imag(c2))
    
    // 复数运算
    sum := c1 + c2
    fmt.Printf("Sum: %v\n", sum)
}

6. 复合类型概览

类型 说明 示例
数组 (Array) 固定长度序列 [5]int{1,2,3,4,5}
切片 (Slice) 动态数组 []int{1,2,3}
映射 (Map) 键值对集合 map[string]int{"a": 1}
通道 (Channel) 协程通信 make(chan int)
结构体 (Struct) 自定义类型 type User struct {...}
接口 (Interface) 方法集合 type Reader interface {...}
指针 (Pointer) 内存地址 *int
函数 (Function) 一等公民 func(int) int

这些类型将在后续章节详细讲解

变量声明与初始化

1. 变量声明方式

Go 提供了多种变量声明方式,各有适用场景。

方式一:var 声明(标准方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
    // 单个变量声明
    var name string
    var age int
    var isActive bool
    
    fmt.Printf("name: '%s', age: %d, isActive: %v\n", name, age, isActive)
    // 输出: name: '', age: 0, isActive: false (零值)
    
    // 批量声明
    var (
        host = "localhost"
        port = 8080
        path = "/api"
    )
    
    fmt.Printf("%s:%d%s\n", host, port, path)
}

方式二:短变量声明 := (推荐在函数内使用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
    // 自动推断类型
    name := "Alice"
    age := 25
    isActive := true
    
    fmt.Printf("%T: %v\n", name, name)   // string: Alice
    fmt.Printf("%T: %v\n", age, age)     // int: 25
    fmt.Printf("%T: %v\n", isActive, isActive)  // bool: true
    
    // 多变量声明
    x, y, z := 1, 2.5, "hello"
    fmt.Println(x, y, z)
}

注意: := 只能在函数内部使用,且至少有一个新变量

方式三:带初始化的 var 声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
    // 声明并初始化
    var name string = "Bob"
    var age int = 30
    
    // 类型推断
    var city = "Beijing"  // 自动推断为 string
    
    fmt.Println(name, age, city)
}

2. 零值(Zero Values)

未显式初始化的变量会被赋予类型的零值:

类型 零值
整型 (int, int8, etc.) 0
浮点型 (float32, float64) 0.0
布尔型 (bool) false
字符串 (string) "" (空字符串)
指针、切片、Map、Channel、函数、接口 nil
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
    var i int
    var f float64
    var b bool
    var s string
    var ptr *int
    
    fmt.Printf("int: %d\n", i)           // 0
    fmt.Printf("float64: %f\n", f)       // 0.000000
    fmt.Printf("bool: %v\n", b)          // false
    fmt.Printf("string: '%s'\n", s)      // ''
    fmt.Printf("pointer: %v\n", ptr)     // <nil>
}

iota

iota是一个预定义的标识符,用于创建自增的无类型整数常量。它通常用于枚举类型,为每个枚举值生成一个唯一的常量。iota的行为类似于一个计数器,每次在常量声明中出现时都会自增一次。

基本用法

iota在每个const关键字出现时被重置为 0,并在每个const块中为每个新声明的常量自增。如果在同一行声明了多个常量,则它们将共享相同的iota值,除非显式地给其中一个常量赋值。

使用示例

以下是一个使用iota来定义星期几的枚举类型的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "fmt"

func main() {
    const (
        Sunday = iota // iota 初始化为 0
        Monday        // iota 自增为 1
        Tuesday       // iota 自增为 2
        Wednesday     // iota 自增为 3
        Thursday      // iota 自增为 4
        Friday        // iota 自增为 5
        Saturday      // iota 自增为 6
    )

    fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday) // 输出: 0 1 2 3 4 5 6
}

在这个例子中,iota从 0 开始,并在每个新的常量声明中自动递增。因此,Sunday的值为 0,Monday的值为 1,依此类推。

递增规则

  • iota从 0 开始递增。
  • 每个const块中的iota值都会自动递增,不需要显式操作。
  • 在同一个const块中,每次遇到iota都会自增。
  • 在不同的const块中,iota会重新从 0 开始递增。

通过使用iota,可以简洁地定义一系列有序的常量,提高代码的可读性和可维护性。

特殊例子

展示iota的特殊情况,包括在同一行声明多个常量时如何共享相同的iota值,以及如何显式地给某个常量赋值以改变iota的自动递增。

特殊情况示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

func main() {
    const (
        // iota 在这里初始化为 0
        Sunday = iota // iota 值为 0
        Monday        // iota 值为 1
        Tuesday       // iota 值为 2

        // 显式赋值会改变 iota 的值
        WeekdayStart = Monday // WeekdayStart 值为 1,但 iota 仍然为 2

        // 同一行声明的常量共享相同的 iota 值
        Wednesday, Thursday = iota + 2, iota + 3 // Wednesday 值为 4,Thursday 值为 5

        // iota 继续自增
        Friday        // iota 值为 6
        Saturday      // iota 值为 7

        // 可以在新的 const 块中重置 iota
        const (
            firstQuarter = iota // iota 被重置为 0
            secondQuarter       // iota 值为 1
        )
    )

    fmt.Println(Sunday, Monday, Tuesday, WeekdayStart) // 输出: 0 1 2 1
    fmt.Println(Wednesday, Thursday, Friday, Saturday) // 输出: 4 5 6 7
    fmt.Println(firstQuarter, secondQuarter)          // 输出: 0 1
}

在这个例子中,首先定义了星期几的常量,并使用iota进行自动递增。然后,我们显式地将Monday的值赋给了WeekdayStart,但请注意,这并不会改变iota的自动递增行为,iota的值在赋值之后继续递增。

接下来,在同一行声明了WednesdayThursday,并为它们分别赋了iota + 2iota + 3的值。由于它们在同一行声明,所以它们都使用了同一个iota值(此时为 2),但加上了不同的偏移量。

最后,在一个新的const块中再次使用了iota,它会被重置为 0,并在该块中继续递增。

通过这个示例,可以看到iota的灵活性和在定义枚举或常量序列时的强大功能。

常量

在 Go 语言中,常量(Constants)是固定不变的值,它们在程序编译时被确定,并且在整个程序的执行过程中都不能被修改。常量可以是布尔值、数字(整数或浮点数)、字符串或枚举值。

常量的声明和初始化

在 Go 语言中,常量可以使用const关键字进行声明。你可以在同一行声明多个常量,如果它们具有相同的类型。如果常量的类型没有明确指定,那么 Go 编译器会根据常量的值来推断其类型。

以下是一些常量声明的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

func main() {
    // 声明并初始化单个常量
    const Pi = 3.14159

    // 在同一行声明多个相同类型的常量
    const (
        StatusOK    = 200
        StatusFound = 302
        StatusBad   = 400
    )

    // 使用 iota 进行递增的常量声明
    const (
        x = iota // x == 0
        y        // y == 1
        z        // z == 2
        wp       // 如果在这一行前没有显式赋值,则 wp 也会自增,wp == 3
    )

    // 显式赋值会改变 iota 的自增行为
    const (
        v = iota     // v == 0
        u = 100      // u == 100, iota 的自增被这个显式赋值打断了
        vv = iota    // vv == 1, iota 继续从上一个显式值开始自增
        ww = "hello" // ww 是字符串类型,iota 的自增被打断
        xx           // xx 在这里会是一个新的 iota 值,但它不会被使用(因为没有赋值给它)
    )

    fmt.Println(Pi)
    fmt.Println(StatusOK, StatusFound, StatusBad)
    fmt.Println(x, y, z, wp)
    // 注意:vv 和 ww 不会被打印,因为它们没有在这段代码中被使用
}

常量的类型

  • 数值常量:可以是整数或浮点数。
  • 布尔常量:可以是truefalse
  • 字符串常量:用双引号(")或反引号(```)括起来的字符序列。反引号中的字符串会原样输出,不进行转义。

常量的特点

  • 常量在声明时必须赋值。
  • 常量一旦被声明,其值就不能再改变。
  • 常量在编译时就已经确定,因此它们的值在运行时不会改变。
  • 常量可以是任何基本数据类型,也可以是数组、结构体和切片的组合类型,但不能是函数、映射或通道类型。

iota 枚举

iota是 Go 语言中的一个特殊常量,用于创建枚举值。在const声明中,iota会被自动赋值为 0,然后在每次新的const声明中自增 1。iota通常用于生成一组递增的整数常量。如上面示例中所示,你可以在同一行使用iota为多个常量赋值,或者在不同的行中使用iota来生成连续的整数常量。

本文由作者按照 CC BY 4.0 进行授权