Go 数据类型

Go 语言提供了丰富的数据类型,主要分为基本数据类型和复合数据类型两大类。

基本数据类型包括整数、浮点数、字符、字符串、布尔值等

复合数据类型包括数组、切片、映射(Map)等。

理解这些数据类型的特性和用法是学习 Go 语言的基础。


一、基本数据类型

Go 语言提供了多种基本数据类型,主要包括

  • 整型
  • 浮点型
  • 字符型
  • 字符串型
  • 布尔型

1.1 整型

Go 语言提供了多种整数类型,分为有符号和无符号两大类:

  • int8int16int32int64(有符号整数)
  • uint8uint16uint32uint64(无符号整数)
  • intuint 的大小取决于平台(32 位系统通常为 32 位,64 位系统通常为 64 位)
类型 大小(位) 范围(有符号) 范围(无符号)
int8 8 -128 到 127 0 到 255
int16 16 -32768 到 32767 0 到 65535
int32 32 -2147483648 到 2147483647 0 到 4294967295
int64 64 -9223372036854775808 到 9223372036854775807 0 到 18446744073709551615
int 32 或 64 与平台相关,通常为 -2^31 到 2^31-1 或 -2^63 到 2^63-1 与平台相关,通常为 0 到 2^32-1 或 0 到 2^64-1
uint8 8 N/A 0 到 255
uint16 16 N/A 0 到 65535
uint32 32 N/A 0 到 4294967295
uint64 64 N/A 0 到 18446744073709551615
uint 32 或 64 N/A 与平台相关,通常为 0 到 2^32-1 或 0 到 2^64-1
  • intuint 的大小取决于平台(32 位系统通常为 32 位,64 位系统通常为 64 位),因此在需要明确大小的场景下,建议使用 int32int64 等具体类型。

1.2 浮点型

Go 语言提供了两种浮点类型:

  • float32
  • float64(默认浮点类型)
类型 大小(位) 精度(有效数字位数) 范围(近似值)
float32 32 6-9 ±1.18e-38 到 ±3.4e+38
float64 64 15-17 ±2.22e-308 到 ±1.8e+308
  • 最大值和最小值可以通过 math 包中的常量获取,如 math.MaxFloat32math.SmallestNonzeroFloat64 等。

1.3 字符型

Go 语言没有专门的字符类型,但可以使用 rune(int32 的别名)来表示 Unicode 码点,或使用 byte(uint8 的别名)来表示 ASCII 字符。

类型 大小(位) 描述 示例
byte 8 uint8 的别名,表示单字节字符 'a'
rune 32 int32 的别名,表示 Unicode 码点 '中'

示例:

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

import (
"fmt"
)

func main() {
var b byte = 'A'
var r rune = '中'
fmt.Printf("byte: %d, rune: %c\n", b, r) // 输出 byte: 65, rune: 中
}

1.4 字符串型

Go 语言中的字符串是不可变的 UTF-8 编码的字节序列,可以使用双引号 "" 来定义字符串。

1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
str := "Hello,世界"
fmt.Println(str) // 输出 Hello,世界
}

1.4.1 字符串长度

长度

  • len() 返回的是字符串包含的字节数,而不是字符数
    1
    2
    str := "Hello,世界"
    fmt.Println(len(str)) // 输出 12(Hello, 占 6 字节,世界占 6 字节)

字符数

  • utf8.RuneCountInString() 获取字符串中的字符数
    1
    2
    3
    4
    import "unicode/utf8"

    str := "Hello,世界"
    fmt.Println(utf8.RuneCountInString(str)) // 输出 8(Hello, 占 6 字符,世界占 2 字符)
  • len([]rune(str)) 也可以获取字符数,但会先将字符串转换为 []rune,效率较低,若后续本来就要按 rune 切片处理,可以一举两得
    1
    2
    str := "Hello,世界"
    fmt.Println(len([]rune(str))) // 输出 8

1.4.2 字符串遍历

按字节遍历

  • 使用索引或普通 for 循环遍历字符串,获取的是原始字符
  • 中文等多字节字符会被拆分成多个字节,输出可能是乱码
    1
    2
    3
    4
    str := "Hello,世界"
    for i := 0; i < len(str); i++ {
    fmt.Printf("%c ", str[i]) // 输出 h e l l o , ä ¸ ç
    }

按字符遍历

  • 使用 for range 循环遍历字符串,Go 会自动按 UTF-8 字符解码,每次迭代返回的是一个 rune 类型的字符和一个字符起始位置的索引
    1
    2
    3
    4
    5
    6
    7
    8
    str := "Hello,世界"
    for index, char := range str {
    fmt.Printf("index: %d, char: %c\n", index, char)
    }
    // 输出:
    // index: 0, char: H
    // index: 1, char: e
    // ...

1.4.3 字符串的修改

字符串是不可变的,不能直接修改其中的字符,但可以通过转换为 []rune[]byte 来修改后再转换回字符串。

  • []byte 适合修改 ASCII 字符,修改多字节字符可能会导致乱码
  • []rune 适合修改 Unicode 字符,能正确处理多字节字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
str1 := "Hello"
str2 := "Hello,世界"

// 转换为 []byte 修改字节
bytes := []byte(str1)
bytes[0] = 'h' // 修改第一个字节
modifiedStr2 := string(bytes)
fmt.Println(modifiedStr2) // 输出 hello

// 转换为 []rune 修改字符
runes := []rune(str2)
runes[0] = 'h' // 修改第一个字符
modifiedStr := string(runes)
fmt.Println(modifiedStr) // 输出 hello,世界
}

1.4.4 字符串的类型转换

数值转字符串

  • strconv.Itoa(i int) :将整数转换为字符串
  • strconv.FormatInt(i, base) :将整型数转换为指定进制的字符串
  • strconv.FormatFloat(f, fmt, prec, bitSize) :将浮点数转换为字符串
    • fmt 参数指定格式(‘f’、‘e’、‘g’ 等)
    • prec 指定小数点后精度
    • bitSize 指定 float32 还是 float64

字符串转数值

  • strconv.Atoi(s string) :将字符串转换为整数
  • strconv.ParseInt(s, base, bitSize) :将字符串转换为指定进制的整数
  • strconv.ParseFloat(s, bitSize) :将字符串转换为浮点数

1.4.5 字符串常用操作

函数 描述 示例
len(s) 返回字符串的字节长度 len(“go”) -> 2
s1 + s2 拼接字符串 “go” + “lang” -> “golang”
strings.Split(s, sep) 分割字符串,返回切片 strings.Split(“a,b,c”, “,”) -> [“a”, “b”, “c”]
strings.Join(s, sep) 连接切片元素,返回字符串 strings.Join([]string{“a”,“b”}, “-”) -> “a-b”
strings.Contains(s, substr) 判断是否包含子串 strings.Contains(“golang”, “go”) -> true
strings.HasPrefix(s, prefix) 判断是否以前缀开头 strings.HasPrefix(“hello”, “he”) -> true
strings.Index(s, substr) 查找子串第一次出现的位置(字节索引) strings.Index(“golang”, “a”) -> 3
fmt.Sprintf 格式化拼接字符串(常用于复杂拼接) fmt.Sprintf(“%s版本%d”, “Go”, 1) -> “Go版本1”

1.5 布尔型

Go 语言中的布尔类型是 bool,只能取值 truefalse。默认值为 false

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

import "fmt"

func main() {
// 方式一:默认零值初始化 (false)
var isRaining bool
fmt.Println("默认值:", isRaining) // 输出: 默认值: false

// 方式二:声明时赋值
var isSunny bool = true
fmt.Println("赋值:", isSunny) // 输出: 赋值: true

// 方式三:短变量声明(推荐)
isLoggedIn := false
fmt.Println("短变量声明:", isLoggedIn) // 输出: 短变量声明: false
}

1.6 复数

Go 语言内置了复数类型,分别是 complex64complex128,分别对应于使用 float32float64 作为实部和虚部的复数。

1
2
3
4
5
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"

二、复合数据类型

Go 语言提供了多种复合数据类型,主要包括:

  • 数组(Array)
  • 切片(Slice)
  • 映射(Map)

2.1 数组

数组是长度固定的同类型元素的集合,声明时必须指定长度。

1
2
3
var arr [3]int = [3]int{1, 2, 3} // 声明一个长度为3的整数数组

arr2 := [...]int{4, 5, 6} // 编译器推导长度

2.1.1 初始化

Go 语言支持多种方式初始化数组,其中一种特殊的方式是按索引号初始化,可以指定某些元素的值,而其他元素会使用默认值(零值)进行填充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
months := [...]string{1: "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}

// 索引 0:
// 索引 1: January
// 索引 2: February
// 索引 3: March
// 索引 4: April
// 索引 5: May
// 索引 6: June
// 索引 7: July
// 索引 8: August
// 索引 9: September
// 索引 10: October
// 索引 11: November
// 索引 12: December

2.1.2 数组的遍历

传统 for 循环遍历(基于索引)

1
2
3
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}

for range 循环遍历(基于元素)

1
2
3
for index, value := range arr {
fmt.Printf("索引 %d: 值 %d\n", index, value)
}

2.2 切片

切片是动态数组,长度可变,更常用。切片是对数组的一个视图,底层依赖于数组。

2.2.1 切片的创建

通过数组进行切片

  • array[low:high]:左闭右开 [low, high]
    1
    2
    arr := [5]int{1, 2, 3, 4, 5}
    s := arr[1:4] // 包含索引 1、2、3 的元素,即 [2, 3, 4]

使用 make() 构造

  • 格式:make([]T, len, cap)
    • T 是切片元素的类型
    • len 是切片的长度
    • cap 是切片的容量(底层数组的长度)
    1
    2
    s := make([]int, 5, 10) // 创建一个长度为5的切片,底层数组容量也为5
    // s: [0 0 0 0 0] len=5 cap=10

直接声明和初始化

  • 直接使用字面量创建切片
    1
    2
    s := []int{1, 2, 3} 
    var s2 []int // nil 切片,长度和容量都为 0

2.2.2 切片的遍历

按下标循环

  • 使用 len(s) 控制上界:
    1
    2
    3
    4
    s := []int{1, 2, 3}
    for i := 0; i < len(s); i++ {
    fmt.Println(s[i])
    } // 输出 1 2 3

for range 循环

  • 每次迭代返回元素的索引和对应的值:
    1
    2
    3
    4
    5
    6
    7
    8
    s := []string{"a", "b", "c"}
    for i := range s {
    fmt.Printf("索引 %d\n", i)
    } // 输出 索引 0 索引 1 索引 2

    for _, value := range s {
    fmt.Printf("值 %s\n", value)
    } // 输出 值 a 值 b 值 c

2.2.3 切片常用操作

追加元素

  • 使用 append() 函数向切片追加元素,返回新的切片
    1
    2
    3
    s := []int{1, 2, 3}
    s = append(s, 4) // s: [1 2 3 4]
    s = append(s, 5, 6) // s: [1 2 3 4 5 6]

插入元素

  • 通过切片分割后再拼接实现插入
    1
    2
    3
    s := []int{1, 2, 4, 5}
    s = append(s[:2], append([]int{3}, s[2:]...)...)
    // s: [1 2 3 4 5]

删除元素

  • 通过切片分割后再拼接实现删除
    1
    2
    3
    s := []int{1, 2, 3, 4, 5}
    s = append(s[:2], s[3:]...) // 删除索引为2的元素,即值3
    // s: [1 2 4 5]

切片复制

  • 使用 copy(dst, src) 函数将 src 的内容复制到 dst 中
    • dst 需已分配长度,copy 不会自动扩容
    1
    2
    3
    src := []int{1, 2, 3}
    dst := make([]int, len(src))
    copy(dst, src) // dst: [1 2 3]

2.2.4 切片与数组总结

特性 数组 (Array) 切片 (Slice)
长度 固定不变。长度是类型的一部分 动态可变,长度不是类型的一部分 ([]int)
类型 值类型 (Value Type) 引用类型 (Reference Type)
赋值/传参 复制整个数组内容(全量复制) 复制切片头(指针、长度、容量),底层数组共享
操作 长度固定,不支持 append 等动态操作 支持 append、copy 等操作,可动态增长
零值 元素的零值 nil
使用场景 长度已知且固定,性能要求高的底层结构 所有需要序列容器的场景,更灵活、常用

2.3 映射(Map)

Go 语言中的映射(Map)是一种无序的键值对集合,提供了快速的键值查找功能。Map 的键必须是可比较的类型(如字符串、整数等),值可以是任意类型。

2.3.1 Map 的定义

Map 的定义语法:map[KeyType]ValueType

1
var m map[string]int // 声明一个键为 string,值为 int 的 Map

2.3.2 Map 的创建

使用 make() 创建 Map

  • make(map[KeyType]ValueType, [cap]) 创建一个容量为 cap 的 Map
    1
    2
    emptyM := make(map[string]int) // 创建一个空 Map
    m := make(map[string]int, 10) // 创建一个容量为 10 的 Map

使用字面量创建 Map

  • 直接使用字面量初始化 Map
    1
    2
    3
    4
    5
    6
    emptyM := map[int]string{} // 创建一个空 Map
    m := map[string]int{
    "a": 1,
    "b": 2,
    "c": 3,
    }

2.3.3 Map 的遍历

使用 for range 循环遍历 Map

  • 每次迭代返回一个键值对,顺序不固定
    1
    2
    3
    4
    5
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    for key, value := range m {
    fmt.Printf("键: %s, 值: %d\n", key, value)
    }
    // 输出的键值对顺序可能不同

按照指定顺序遍历 Map

  • 先将 Map 的键提取到切片中,对切片排序后再遍历
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    m := map[string]int{"b": 2, "a": 1, "c": 3}

    // 提取键到切片
    keys := make([]string, 0, len(m))
    for key := range m {
    keys = append(keys, key)
    }

    // 对键进行排序
    sort.Strings(keys)

    // 按照排序后的键遍历 Map
    for _, key := range keys {
    fmt.Printf("键: %s, 值: %d\n", key, m[key])
    }
    // 输出:
    // 键: a, 值: 1
    // 键: b, 值: 2
    // 键: c, 值: 3

2.3.4 Map 的基本操作

存取键值对

  • 通过 m[key] 获取值,若键不存在返回值类型的零值
  • 通过 m[key] = value 设置键值对
    1
    2
    3
    m := map[string]int{"a": 1, "b": 2}
    fmt.Println(m["a"]) // 输出 1
    m["c"] = 3 // 添加新键值对

判断键是否存在

  • 使用 value, ok := m[key] 判断键是否存在
    • value 是对应键的值,如果键不存在则为值类型的零值
    • oktrue 表示键存在,false 表示键不存在
    1
    2
    3
    4
    5
    6
    7
    8
    m := map[string]int{"a": 1, "b": 2}
    value, ok := m["c"]
    if ok {
    fmt.Println("键存在,值为:", value)
    } else {
    fmt.Println("键不存在")
    }
    // 输出 键不存在

删除键值对

  • 使用 delete(m, key) 删除指定键的键值对
    1
    2
    3
    m := map[string]int{"a": 1, "b": 2}
    delete(m, "a") // 删除键 "a"
    fmt.Println(m) // 输出 map[b:2]