Go 数据类型
数据类型
数据类型是 Go 的基础。其中,包括基本类型,聚合类型,引用类型和接口类型。
基本类型
基本类型如整型,浮点型等,传的是值。如:
var x int = 10
这里需要注意的是,10 是无类型常量,在编译时将 10 赋给了 x。我们可以改造代码示例如下:
var x int8 = 256
fmt.Println(x)
编译器检查时报错:cannot use 256 (untyped int constant) as int8 value in variable declaration (overflows)compilerNumericOverflow
。
聚合类型
数组和结构体是聚合类型,聚合的是多种基本类型。
结构体中成员如果是可比较的,则该结构体是可比较结构体,可以作为 map 的 key。
可比较结构体:
type Comparable struct {
id string
}
func main() {
hxia := Comparable{id: "62151280"}
mzhi := Comparable{id: "51151280"}
if hxia == mzhi {
fmt.Println("repeat id")
}
}
不可比较结构体:
type UnComparable struct {
IDs []string
}
func main() {
hxia := UnComparable{IDs: []string{"62151280"}}
mzhi := UnComparable{IDs: []string{"51151280"}}
if hxia == mzhi {
fmt.Println("repeat id")
}
}
编译器检查时报错:invalid operation: hxia == mzhi (struct containing []string cannot be compared)compilerUndefinedOp
结构体的长度
结构体的长度是固定的,其在编译期间就确定下来了。结构体的长度由结构体内各个字段的类型和排列顺序决定。
type Comparable struct {
a bool // 1 byte
c float64 // 8 bytes
b int32 // 4 bytes
}
func main() {
var hxia Comparable
fmt.Println(unsafe.Sizeof(hxia))
}
输出:
24
如果我们给结构体字段换个顺序,其输出的长度就不一样了:
type Comparable struct {
a bool // 1 byte
b int32 // 4 bytes
c float64 // 8 bytes
}
func main() {
var hxia Comparable
fmt.Println(unsafe.Sizeof(hxia))
}
输出:
16
不同的字段顺序会影响结构体在内存中的分配,这一部分在汇编一节里会详细阐述,为什么会产生不一样的输出,这里本着知识聚集原则暂时略过。
引用类型
切片,map 和通道是数据类型中的引用类型。引用类型是不可直接比较的。如:
func main() {
x := []int{1, 2, 3}
y := []int{1, 2, 3}
if x == y {}
}
编译器检查时将报错:invalid operation: x == y (slice can only be compared to nil)compilerUndefinedOp
。
为什么不可比较呢?我们看下面的例子:
func demo(x []int) []int {
x[1] = 1
return x
}
func main() {
x := []int{1, 2, 3}
fmt.Printf("%p %v\n", &x, x)
y := demo(x)
fmt.Printf("%p %v\n", &x, x)
fmt.Printf("%p %v\n", &y, y)
}
输出:
0xc0000a4000 [1 2 3]
0xc0000a4000 [1 1 3]
0xc0000a4030 [1 1 3]
切片 x 在经过函数 demo()
之后其值变了,而地址是不变的。因为传递给函数的是引用,实际函数内操作的是同一个内存地址。
这就导致切片在运行时会更改,如果做了判断,其判断结果是不稳定的。
这是切片,或者说引用类型不能做比较的本质原因,传递的是引用而不是值。
切片
切片 append
切片是可以扩容的。扩容规则如下:
# src/builtin/builtin.go
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
//
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
//
// As a special case, it is legal to append a string to a byte slice, like this:
//
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
给出示例代码:
func main() {
x := []int{1, 2, 3}
y := append(x, 4)
fmt.Printf("%p %p %v\n", &x, &x[0], x)
fmt.Printf("%p %p %v\n", &y, &y[0], y)
fmt.Printf("%p %p %v\n", &x, &x[0], x)
m := make([]int, 3, 4)
m[0] = 1
m[1] = 2
m[2] = 3
n := append(m, 4)
fmt.Printf("%p %p %v\n", &m, &m[0], m)
fmt.Printf("%p %p %v\n", &n, &n[0], n)
fmt.Printf("%p %p %v\n", &m, &m[0], m)
}
输出:
0xc000010030 0xc000018108 [1 2 3]
0xc000010048 0xc0000200f0 [1 2 3 4]
0xc000010030 0xc000018108 [1 2 3]
0xc0000100a8 0xc00001c0c0 [1 2 3]
0xc0000100c0 0xc00001c0c0 [1 2 3 4]
0xc0000100a8 0xc00001c0c0 [1 2 3]
可以看到,当容量不够时,扩容将分配新的底层数组。所以我们在赋值时需要 x := append(x, ...)
以防止操作的底层数组不一致。
随之而来的问题是:
这是我们要解决的问题,这里本着知识聚集的原则,暂略过。
切片比较
直接给出示例代码如下:
func compareSlice(x, y []int) bool {
if len(x) != len(y) {
return false
}
for i, xv := range x {
if xv != y[i] {
return false
}
}
return true
}
map
map 的背后实现是散列表。
map 比较
map 比较示例:
func compareMap(m, n map[string]string) bool {
if len(m) != len(n) {
return false
}
if m == nil || n == nil {
if m == nil && n == nil {
return true
} else {
return false
}
}
for k := range m {
if m[k] != n[k] {
return false
}
}
return true
}
func main() {
x := map[string]string{"name": "hxia", "sex": "male"}
y := map[string]string{"name": "hxia"}
if compareMap(x, y) {
fmt.Println("same map")
} else {
fmt.Println("not the same map")
}
var m map[string]string
var n = map[string]string{}
if compareMap(m, n) {
fmt.Println("same map")
} else {
fmt.Println("not the same map")
}
}
这里特别需要注意的 map 的 nil 和空的判断,如:
var m map[string]string // m: nil
var n = map[string]string{} // n: []
类似的,切片的判断也需要考虑 nil 和空切片。
map 的键
map 中的键是唯一且可比较的。如果要将切片作为 map 的键该怎么做呢?
我们可以通过以转换函数将切片转换为唯一可比较的键,如下:
func main() {
s1 := []string{"hxia", "maz"}
s2 := []string{"xfeng", "qyan"}
k1 := fmt.Sprint(s1)
k2 := fmt.Sprint(s2)
x := map[string]string{k1: "cloud", k2: "classic"}
fmt.Println(x)
}
这里的 fmt.Sprint
就是转换函数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY