目 录CONTENT

文章目录
go

【Go】基本数据类型

hxuanyu
2025-12-19 / 0 评论 / 2 点赞 / 110 阅读 / 0 字

布尔类型

用于存储逻辑值,只有 true​ 和 false 两个取值。布尔值通常作为布尔表达式的求值结果,可与其他布尔表达式进行组合与运算:

  • &&:逻辑与(AND),两个操作数都为真时结果为真
  • ||:逻辑或(OR),至少有一个操作数为真时结果为真
  • !:逻辑非(NOT),对布尔值取反

数值类型

Go 的基本数值类型中,最常用的是整型与浮点型。

整型

Go 的整型分为平台无关整型平台相关整型两类,主要区别在于其位宽是否随 CPU 架构变化。

平台无关整型在任何 CPU 架构或操作系统中的长度都固定,Go 提供如下类型:

类别 类型 长度 取值范围
有符号整型 int8 1 字节 [-128, 127]
int16 2 字节 [-32768, 32767]
int32 4 字节 [-2147483648, 2147483647]
int64 8 字节 [-9223372036854775808, 9223372036854775807]
无符号整型 uint8 1 字节 [0, 255]
uint16 2 字节 [0, 65535]
uint32 4 字节 [0, 4294967295]
uint64 8 字节 [0, 18446744073709551615]

有符号与无符号的本质差别在于二进制最高位是否被解释为符号位,从而导致取值范围不同。

无符号整型与有符号整型

平台相关整型的位宽会根据运行平台变化,Go 原生提供 3 种平台相关整型:

类型 32 位平台 64 位平台
int 32 位(4 字节) 64 位(8 字节)
uint 32 位(4 字节) 64 位(8 字节)
uintptr 足以存储任意指针值的无符号整数 足以存储任意指针值的无符号整数

需要特别注意:由于这些类型的长度与平台相关,在编写有可移植性要求的代码时,不应假设其固定长度。如果需要确认目标平台上的大小,可以使用 unsafe.Sizeof

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var a, b = int(5), uint(6)
	var p uintptr = 0x12345678

	fmt.Println("有符号整型a的长度为:", unsafe.Sizeof(a))
	fmt.Println("无符号整型b的长度为:", unsafe.Sizeof(b))
	fmt.Println("指针类型p的长度为:", unsafe.Sizeof(p))
}

整型溢出问题

当运算结果超出该类型的取值范围时,会发生​整型溢出。对无符号整型与有符号整型而言,溢出通常体现为按模回绕(wrap around),结果仍落在取值范围内,但可能与预期不符:

var s int8 = 127
s += 1 // 预期128,实际结果变为-128

var u uint8 = 1
u -= 2 // 预期-1,实际结果变为255

字面值与格式化输出

Go 的整数(整型)字面值支持多种进制形式。早期 Go 支持:

a := 53        // 十进制
b := 0700      // 八进制(旧写法),以“0”为前缀
c1 := 0xaabbcc // 十六进制,以“0x”为前缀
c2 := 0Xddeeff // 十六进制,以“0X”为前缀

自 Go 1.13 起,支持二进制字面值与更清晰的八进制前缀 0o

d1 := 0b10000001 // 二进制,以“0b”为前缀
d2 := 0B10000001 // 二进制,以“0B”为前缀
e1 := 0o700      // 八进制,以“0o”为前缀
e2 := 0O700      // 八进制,以“0O”为前缀

此外,还支持数字分隔符 _,可用于分组或分隔前缀与数字主体:

a := 5_3_7           // 十进制:537
b := 0b_1000_0111    // 二进制:0b10000111
c1 := 0_700          // 八进制(旧写法):0700
c2 := 0o_700         // 八进制:0o700
d1 := 0x_5c_6d       // 十六进制:0x5c6d

可以用标准库 fmt 对整型进行不同进制的格式化输出:

var a int8 = 59
fmt.Printf("%b\n", a) // 二进制:111011
fmt.Printf("%d\n", a) // 十进制:59
fmt.Printf("%o\n", a) // 八进制:73
fmt.Printf("%O\n", a) // 八进制(带0o前缀):0o73
fmt.Printf("%x\n", a) // 十六进制(小写):3b
fmt.Printf("%X\n", a) // 十六进制(大写):3B

浮点类型

浮点类型的使用场景更集中,常见于科学计算、图形图像处理与仿真、多媒体与游戏、人工智能等领域。

二进制表示

Go 中浮点类型的二进制表示遵循 IEEE 754 标准。

IEEE 754 定义了多种浮点格式:单精度(32 位)、双精度(64 位)以及若干扩展格式。Go 提供 float32​ 与 float64,分别对应 IEEE 754 的单精度与双精度格式。

双精度在指数与尾数上使用更多比特位,精度更高,日常开发中更常用;同时,未显式指定类型的浮点常量默认是 float64

字面值格式与输出

浮点字面值主要包括十进制小数形式与科学计数法形式。

十进制表示:

3.1415
.15   // 整数部分为0可省略
81.80
82.   // 小数部分为0可省略

科学计数法(十进制):

6674.28e-2 // 6674.28 * 10^(-2) = 66.7428
.12345E+5  // 0.12345 * 10^5 = 12345

科学计数法(十六进制):

0x2.p10  // 2.0 * 2^10 = 2048
0x1.Fp+0 // 1.9375 * 2^0 = 1.9375

十六进制浮点字面值中,小数部分使用十六进制表示,但指数部分是十进制,且底数为 2(使用 p​/P 表示)。

使用 fmt 可对浮点数进行格式化输出:

var f float64 = 123.45678
fmt.Printf("%f\n", f) // 123.456780
fmt.Printf("%e\n", f) // 1.234568e+02
fmt.Printf("%x\n", f) // 0x1.edd3be22e5de1p+06

其中 %e​ 输出十进制科学计数法,%x 输出十六进制科学计数法(浮点)。

复数类型

在数学中,形如 z = a + bi(a、b 均为实数,a 为实部,b 为虚部)的数称为复数。

Go 提供两种复数类型:complex64​ 与 complex128​。complex64​ 的实部与虚部均为 float32​,complex128​ 的实部与虚部均为 float64​。若未显式指定类型,复数常量默认类型为 complex128

复数的初始化方式包括:

使用复数字面值:

var c = 5 + 6i
var d = 0o123 + .12345E+5i // 83+12345i

使用内建函数 complex 创建复数:

var c = complex(5, 6)              // 5+6i
var d = complex(0o123, .12345E+5)  // 83+12345i

使用内建函数 real​ 和 imag 获取实部与虚部:

var c = complex(5, 6)
r := real(c) // 5
i := imag(c) // 6

字符串类型

Go 原生支持 string,主要带来以下特性:

  1. string 不可变,提升并发场景下的安全性与可预测性。
  2. 字符串不以 '\0'​ 结尾,长度是显式记录的,len(s) 为常数时间复杂度 O(1)。
  3. 支持原始字符串字面值(raw string literal),可用反引号 ` 包裹,多行内容与反斜杠等均不进行转义;唯一不能直接包含的字符是反引号本身。
  4. 源码对非 ASCII 字符提供原生支持(源文件通常以 UTF-8 编码存储),降低跨环境乱码风险。

Go 字符串的组成

  • 字节视角​:字符串是一个(可为空的)字节序列,len 返回字节数。单个字节只是数据,并不一定对应一个“字符”。

    var s = "中国人"
    fmt.Printf("the length of s = %d\n", len(s)) // 9
    for i := 0; i < len(s); i++ {
        fmt.Printf("0x%x ", s[i]) // 0xe4 0xb8 0xad 0xe5 0x9b 0xbd 0xe4 0xba 0xba
    }
    fmt.Printf("\n")
    
  • 字符(rune)视角:字符串可视为 Unicode 码点序列(按 UTF-8 解码得到的 rune 序列)。

    var s = "中国人"
    fmt.Println("the character count in s is", utf8.RuneCountInString(s)) // 3
    for _, c := range s {
        fmt.Printf("0x%x ", c) // 0x4e2d 0x56fd 0x4eba
    }
    fmt.Printf("\n")
    

    Go 使用 Unicode 码点(code point)来表示字符;上例输出的是每个字符对应的码点值。

rune 类型与字符字面值

Go 使用 rune​ 表示一个 Unicode 码点。rune​ 是 int32​ 的别名,与 int32 等价。

字符字面量通常使用单引号括起:

'a'   // ASCII字符
'中'  // Unicode字符
'\n'  // 换行
'\''  // 单引号字符

也可使用 Unicode 转义序列表示字符:

'\u4e2d'     // 中
'\U00004e2d' // 中
'\u0027'     // 单引号

其中 \u​ 后跟 4 个十六进制数,\U 后跟 8 个十六进制数。此外,还可用十六进制或八进制转义表示单字节值(常用于表示 ASCII):

'\x27' // 十六进制的单引号
'\047' // 八进制的单引号

字符串字面值

解释型字符串字面值使用双引号:

"abc\n"                 // 包含换行
"中国人"                 // 直接包含中文
"\u4e2d\u56fd\u4eba"    // Unicode转义表示“中国人”
"\U00004e2d\U000056fd\U00004eba"
"中\u56fd\u4eba"        // 混合写法
"\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba" // UTF-8字节序列

十六进制形式的 \x..​ 表示的是字节序列,并不直接等同于 Unicode 码点;上例字节序列恰好是字符串“中国人”的 UTF-8 编码。

详细介绍 UTF-8 编码

Go 字符串类型的内部表示

通过查看 Go 源码可以看到字符串的运行时表示(不同包中的结构体名称不同,但语义一致):

// $GOROOT/src/reflect/value.go
// StringHeader代表字符串的运行时表示形式
type StringHeader struct {
    Data uintptr
    Len  int
}

// $GOROOT/src/runtime/string.go
type stringStruct struct {
    str unsafe.Pointer
    len int
}

字符串值本质上是一个“描述符”:包含指向底层字节序列的指针与长度字段;字符串数据存放在指针所指向的底层内存中。因此将 string 作为函数参数传递通常开销很小(仅复制指针与长度)。

字符串的内部表示

字符串类型的常见操作

下标操作

字符串支持下标访问,但得到的是某个位置上的字节而非字符:

var s = "中国人"
fmt.Printf("0x%x\n", s[0]) // 0xe4,为“中”的UTF-8编码第一个字节

字符迭代

Go 常见的两种迭代方式:

  • 使用常规 for,按字节遍历:

    var s = "中国人"
    for i := 0; i < len(s); i++ {
        fmt.Printf("index: %d, value: 0x%x\n", i, s[i])
    }
    

    输出:

    index: 0, value: 0xe4
    index: 1, value: 0xb8
    index: 2, value: 0xad
    index: 3, value: 0xe5
    index: 4, value: 0x9b
    index: 5, value: 0xbd
    index: 6, value: 0xe4
    index: 7, value: 0xba
    index: 8, value: 0xba
    
  • 使用 for range​,按 UTF-8 解码后的 rune(码点)遍历,i 是字节偏移:

    var s = "中国人"
    for i, v := range s {
        fmt.Printf("index: %d, value: 0x%x\n", i, v)
    }
    

    输出:

    index: 0, value: 0x4e2d
    index: 3, value: 0x56fd
    index: 6, value: 0x4eba
    

如需统计字符数量,推荐使用 utf8.RuneCountInString​(注意函数名为 RuneCountInString)。

字符串连接

虽然字符串不可变,但可以基于旧字符串生成新字符串。Go 支持用 +​ 与 += 进行连接:

s := "Rob Pike, "
s = s + "Robert Griesemer, "
s += "Ken Thompson"
fmt.Println(s) // Rob Pike, Robert Griesemer, Ken Thompson

大量拼接时,+​ 可能带来额外分配与拷贝开销,常见替代方案包括 strings.Builder​、strings.Join​、fmt.Sprintf 等。

字符串比较

Go 字符串支持 ==​、!=​、>=​、<=​、>​、< 等比较运算符。比较采用字典序(按字节序列逐字节比较;对 UTF-8 字符串通常表现为按编码序比较)。

func main() {
	// ==
	s1 := "世界和平"
	s2 := "世界" + "和平"
	fmt.Println(s1 == s2) // true

	// !=
	s1 = "Go"
	s2 = "C"
	fmt.Println(s1 != s2) // true

	// < and <=
	s1 = "12345"
	s2 = "23456"
	fmt.Println(s1 < s2)  // true
	fmt.Println(s1 <= s2) // true

	// > and >=
	s1 = "12345"
	s2 = "123"
	fmt.Println(s1 > s2)  // true
	fmt.Println(s1 >= s2) // true
}

字符串相等性判断会先比较长度;长度相同再比较内容(实现层面可能会进行指针与长度等快速路径优化,但语义上等价于内容比较)。

字符串转换

Go 支持 string​ 与 []byte​、[]rune 的双向转换,通过显式类型转换即可完成:

var s string = "中国人"

// string -> []rune
rs := []rune(s)
fmt.Printf("%x\n", rs) // [4e2d 56fd 4eba]

// string -> []byte
bs := []byte(s)
fmt.Printf("%x\n", bs) // e4b8ade59bbde4baba

// []rune -> string
s1 := string(rs)
fmt.Println(s1) // 中国人

// []byte -> string
s2 := string(bs)
fmt.Println(s2) // 中国人
2
博主关闭了所有页面的评论