基本数据类型
类型概览
Go 是一门静态类型语言,每个变量在编译时都有明确的类型。Go 的基本数据类型可以分为以下几类:
- 整数类型(
int、int8、int16、int32、int64、uint系列) - 浮点类型(
float32、float64) - 布尔类型(
bool) - 字符串类型(
string)
基本数据类型是编程语言内置的最基础的数据表示形式,是构建复合数据类型的基石。Go 中的基本类型又称为”值类型”,它们的值直接存储在变量中,赋值时会进行值拷贝。
整数类型
Go 提供了丰富的整数类型,分为有符号和无符号两大类:
| 类型 | 大小 | 范围 |
|---|---|---|
int8 | 1 字节(8 位) | -128 ~ 127 |
int16 | 2 字节(16 位) | -32768 ~ 32767 |
int32 | 4 字节(32 位) | -2147483648 ~ 2147483647 |
int64 | 8 字节(64 位) | -2^63 ~ 2^63-1 |
uint8 | 1 字节 | 0 ~ 255 |
uint16 | 2 字节 | 0 ~ 65535 |
uint32 | 4 字节 | 0 ~ 2^32-1 |
uint64 | 8 字节 | 0 ~ 2^64-1 |
int | 平台相关(32 或 64 位) | 32 位或 64 位平台上的范围 |
uint | 平台相关 | 与 int 大小相同 |
uintptr | 足够存放指针 | 平台相关 |
平台相关类型
int 和 uint 的大小取决于编译目标平台的字长。在 64 位系统上,int 等同于 int64;在 32 位系统上,等同于 int32。在大多数现代系统中,使用 int 即可,除非你明确需要特定位数。
var a int = 42
var b int64 = 9223372036854775807
var c uint8 = 255
var d uintptr = 0x7ffe1234abcd
fmt.Printf("a: %T, %d\n", a, a) // a: int, 42
fmt.Printf("b: %T, %d\n", b, b) // b: int64, 9223372036854775807
fmt.Printf("c: %T, %d\n", c, c) // c: uint8, 255
fmt.Printf("d: %T, %x\n", d, d) // d: uintptr, 7ffe1234abcd整数溢出
整数运算时,如果结果超出类型的表示范围,会发生溢出。Go 不会自动检测溢出,需要程序员自行注意:
var maxUint8 uint8 = 255
var result uint8 = maxUint8 + 1 // 溢出!结果为 0
fmt.Println(result) // 0
var minInt8 int8 = -128
var result2 int8 = minInt8 - 1 // 溢出!结果为 127
fmt.Println(result2) // 127注意溢出风险
Go 不会对整数溢出报错。在处理大数运算或跨类型运算时,应使用足够大的类型,或借助 math/big 包处理任意精度整数。
进制表示
Go 支持多种进制的整数字面量:
// 十进制
dec := 42
// 八进制(以 0 开头)
oct := 052 // 等于十进制 42
// 十六进制(以 0x 开头)
hex := 0x2A // 等于十进制 42
// 二进制(以 0b 开头,Go 1.13+)
bin := 0b101010 // 等于十进制 42
fmt.Println(dec, oct, hex, bin) // 42 42 42 42Go 1.13+ 还支持使用 _ 作为数字分隔符,增强大数的可读性:
million := 1_000_000
maxInt64 := 9_223_372_036_854_775_807浮点类型
Go 提供两种浮点类型:
| 类型 | 大小 | 精度 |
|---|---|---|
float32 | 4 字节 | 约 6-7 位有效十进制数字 |
float64 | 8 字节 | 约 15-16 位有效十进制数字 |
推荐使用 float64
Go 的默认浮点类型是 float64。在绝大多数场景下,优先使用 float64,因为它精度更高,且现代 CPU 处理 float64 的性能与 float32 相当。
var f1 float32 = 3.14
var f2 float64 = 3.141592653589793
// math.MaxFloat64 是 float64 能表示的最大值
fmt.Println(math.MaxFloat64) // 1.7976931348623157e+308浮点数精度问题
浮点数采用 IEEE 754 标准存储,某些十进制小数无法精确表示,需要注意精度问题:
a := 0.1 + 0.2
fmt.Println(a) // 0.30000000000000004
fmt.Println(a == 0.3) // false!避免浮点数直接比较
不要使用 == 直接比较浮点数。应该使用一个小的容差值(epsilon)进行比较:
func equal(a, b, epsilon float64) bool {
return math.Abs(a-b) < epsilon
}
fmt.Println(equal(0.1+0.2, 0.3, 1e-9)) // true布尔类型
bool 类型只有两个值:true 和 false,零值为 false。
布尔类型是表示逻辑真假的类型,只能取 true 或 false 两个值,常用于条件判断。
var isActive bool // 零值:false
var isValid := true // 短声明
fmt.Println(isActive) // false
fmt.Println(isValid) // true
// 布尔运算
fmt.Println(true && false) // false
fmt.Println(true || false) // true
fmt.Println(!true) // false布尔类型不可转换为整数
Go 中 bool 类型与整数类型之间不能互相转换。int(b) 或 bool(1) 都会导致编译错误。如果需要转换,必须使用 if 语句。
// 错误
// n := int(true) // 编译错误
// 正确:使用条件表达式
var b bool = true
var n int
if b {
n = 1
} else {
n = 0
}字符串类型
字符串是 Go 中表示文本的基本类型。Go 中的字符串是不可变的字节序列(只读的字节 slice),底层使用 UTF-8 编码。
字符串基础
s1 := "Hello, World!" // 双引号字符串,支持转义字符
s2 := `Hello\nWorld!` // 反引号字符串(原始字符串),不解析转义字符
s3 := "你好" + "世界" // 字符串拼接
s4 := "Hello"
s4 += " World" // 字符串追加
fmt.Println(s1) // Hello, World!
fmt.Println(s2) // Hello\nWorld!(反斜杠和 n 都原样输出)
fmt.Println(s3) // 你好世界
fmt.Println(s4) // Hello World常用字符串操作
s := "Hello, 世界"
// 长度(字节数,不是字符数)
fmt.Println(len(s)) // 13("Hello, "占7字节,"世"和"界"各占3字节)
// 字符串拼接
s1 := "Hello" + " " + "World"
// fmt.Sprintf 格式化
s2 := fmt.Sprintf("name: %s, age: %d", "Alice", 25)
// strings 包
fmt.Println(strings.Contains(s, "Hello")) // true
fmt.Println(strings.HasPrefix(s, "Hello")) // true
fmt.Println(strings.HasSuffix(s, "界")) // true
fmt.Println(strings.Index(s, ",")) // 5
fmt.Println(strings.ToUpper(s)) // HELLO, 世界
fmt.Println(strings.Split("a,b,c", ",")) // [a b c]
fmt.Println(strings.TrimSpace(" hello ")) // hello
// strings.Builder 高效拼接
var builder strings.Builder
for i := 0; i < 1000; i++ {
builder.WriteString("x")
}
result := builder.String()字符串不可变
Go 中的字符串是不可变的。任何看似”修改”字符串的操作,实际上都是创建了新的字符串。频繁的字符串拼接应使用 strings.Builder 以获得更好的性能。
byte 与 rune
byte是uint8的别名,代表一个字节(8 位),用于表示 ASCII 字符。rune是int32的别名,代表一个 Unicode 码点,用于表示 UTF-8 编码中的单个字符。
| 类型 | 别名 | 大小 | 用途 |
|---|---|---|---|
byte | uint8 | 1 字节 | ASCII 字符、原始字节 |
rune | int32 | 4 字节 | Unicode 字符 |
// byte 用于 ASCII 字符
var ch byte = 'A' // 65
fmt.Printf("%c %d\n", ch, ch) // A 65
// rune 用于 Unicode 字符
var r rune = '世' // 19990
fmt.Printf("%c %d\n", r, r) // 世 19990UTF-8 编码
Go 的字符串底层使用 UTF-8 编码。UTF-8 是一种变长编码:
- ASCII 字符(0-127):1 个字节
- 大多数常用字符(包括中文):3 个字节
- 其他字符:2 或 4 个字节
s := "Hello世界"
// len() 返回字节数
fmt.Println(len(s)) // 11(5 + 3 + 3)
// 使用 for range 遍历,按 rune 遍历
for i, r := range s {
fmt.Printf("索引: %d, 字符: %c, rune值: %d\n", i, r, r)
}
// 输出:
// 索引: 0, 字符: H, rune值: 72
// 索引: 1, 字符: e, rune值: 101
// 索引: 2, 字符: l, rune值: 108
// 索引: 3, 字符: l, rune值: 108
// 索引: 4, 字符: o, rune值: 111
// 索引: 5, 字符: 世, rune值: 19990
// 索引: 8, 字符: 界, rune值: 30028注意 len() 与字符数不同
len(s) 返回的是字符串的字节数,不是字符数。要获取字符(rune)数,应使用 utf8.RuneCountInString(s) 或 len([]rune(s))。
s := "Hello世界"
fmt.Println(len(s)) // 11(字节数)
fmt.Println(utf8.RuneCountInString(s)) // 7(字符数)
fmt.Println(len([]rune(s))) // 7(字符数)
// 将字符串转换为 rune 切片
runes := []rune(s)
fmt.Println(runes) // [72 101 108 108 111 19990 30028]
fmt.Println(string(runes)) // Hello世界类型转换
Go 是强类型语言,不支持隐式类型转换。不同类型之间的转换必须使用显式的类型转换表达式 T(v)。
数值类型之间的转换
var i int = 42
var f float64 = float64(i) // int → float64
var u uint = uint(i) // int → uint
var i32 int32 = int32(i) // int → int32
// 浮点数 → 整数(截断小数部分)
var x float64 = 3.99
var y int = int(x) // 3,不是四舍五入!
fmt.Println(y) // 3注意精度损失
类型转换可能导致精度损失或溢出。例如 int64 转 int32 时,如果值超出 int32 范围,高位会被截断。
字符串与数值的转换
import (
"fmt"
"strconv"
)
// 字符串 → 整数
n, err := strconv.Atoi("42") // n=42, err=nil
n2, err := strconv.ParseInt("42", 10, 64) // n2=int64(42)
// 整数 → 字符串
s := strconv.Itoa(42) // "42"
s2 := strconv.FormatInt(42, 10) // "42"
s3 := fmt.Sprintf("%d", 42) // "42"
// 字符串 → 浮点数
f, err := strconv.ParseFloat("3.14", 64) // f=3.14
// 浮点数 → 字符串
s4 := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
// 字符串 → 布尔值
b, err := strconv.ParseBool("true") // b=true常见编译错误
以下写法在 Go 中是不允许的:
var i int = 42
var f float64 = i // 编译错误:cannot use i (type int) as type float64
var s string = 42 // 编译错误:cannot use 42 (type int) as type string必须显式转换:
var f float64 = float64(i) // 正确
var s string = strconv.Itoa(42) // 正确练习题
练习 1:类型转换与溢出
以下代码的输出是什么?请解释原因。
package main
import "fmt"
func main() {
var x int64 = 300
var y int8 = int8(x)
fmt.Println(y)
}解题思路:int64 转 int8 时,如果值超出 int8 的范围(-128 ~ 127),会发生截断。
输出:44
解释:300 的二进制表示为 0000 0001 0010 1100。转换为 int8 时,只取低 8 位 0010 1100,即十进制的 44。这是典型的整数溢出截断行为。
练习 2:字符串与 rune
编写一个函数 reverseString,接收一个包含中文的字符串,返回其反转结果。例如输入 "Hello世界",输出 "界世olleH"。
解题思路:不能直接按字节反转(会导致 UTF-8 字符损坏),需要先将字符串转换为 []rune,再反转 rune 切片。
代码:
package main
import "fmt"
func reverseString(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func main() {
fmt.Println(reverseString("Hello世界")) // 界世olleH
fmt.Println(reverseString("你好世界")) // 界世好你
fmt.Println(reverseString("abc")) // cba
}关键点:将 string 转换为 []rune 可以正确处理 UTF-8 多字节字符,反转后再用 string() 转换回来。
练习 3:浮点数比较
编写一个函数 approximatelyEqual,判断两个 float64 值是否近似相等。要求支持相对误差比较。
解题思路:使用相对误差和绝对误差的组合策略。当数值接近零时使用绝对误差,否则使用相对误差。
代码:
package main
import (
"fmt"
"math"
)
func approximatelyEqual(a, b float64) bool {
epsilon := 1e-9
absA := math.Abs(a)
absB := math.Abs(b)
diff := math.Abs(a - b)
// 当 a 或 b 接近零时,使用绝对误差
if a == b {
return true
}
if a == 0 || b == 0 || diff < math.SmallestNonzeroFloat64 {
return diff < epsilon
}
// 否则使用相对误差
return diff/math.Min(absA+absB, math.MaxFloat64) < epsilon
}
func main() {
fmt.Println(approximatelyEqual(0.1+0.2, 0.3)) // true
fmt.Println(approximatelyEqual(1.0, 1.000000001)) // true
fmt.Println(approximatelyEqual(1.0, 2.0)) // false
}