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 就是转换函数。


posted @   hxia043  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示