《Go小技巧&易错点100例》第二十二篇

2024-09-03 23:02:52 浏览数 (2)

有符号类型和无符号类型

在Go语言的基本整数类型中分为有符号和无符号类型。

比如intuint 是两种基本的数据类型,它们之间的主要区别在于它们是否能表示负数以及它们的大小(即它们能表示的数值范围)。

1)int:有符号的整数类型,能表示正数、负数和零。

int 的大小依赖于具体的运行平台(即32位还是64位系统)。在32位系统上,int 通常是32位的,而在64位系统上,int 可能是32位或64位的,但Go语言在64位系统上选择了32位的int作为默认值,以保持与32位系统的兼容性。

int 的大小可以通过unsafe.Sizeof(0)来检查,但请注意,使用unsafe包可能会引入平台依赖性和不可移植性。

2)uint:无符号的整数类型,它只能表示非负整数(即零和正数)。

int类似,uint的大小也依赖于具体的运行平台,但因为它没有符号位,所以在相同的位数下,uint 能表示的正数范围比int大(两倍)。在需要非负整数且数值范围较大的情况下,uint 是个不错的选择。

总的来说,选择int还是uint取决于具体需求

  • 如果你需要表示负数,那么应该使用int
  • 如果你确定数值不会是负数,且希望获得更大的正数范围,那么uint可能更合适。

在设计API和库时,如果不确定用户会如何使用数据(是否会有负数),则默认使用int可能更安全。另外,Go还提供了固定大小的整数类型,如int8int16int32int64以及对应的无符号类型uint8uint16uint32uint64,这些类型提供了更明确的大小保证,使得跨平台编程更加容易和可靠。

在Go语言中,uint 类型(以及所有无符号整数类型,如 uint8 , uint16 , uint32 , uint64 等)都存在溢出问题。这是因为这些类型能表示的最大值是由它们所占用的位数决定的,当它们的值增加到超出这个最大值时,就会发生溢出。

溢出(Overflow)是指当一个数值达到其类型的最大值后,如果继续增加,其值会突然回绕(wrap around)到该类型能表示的最小值,并继续从这个值开始增加。这是因为这些无符号整数类型在底层存储时使用的是二进制补码表示法,并且它们的存储大小是固定的。

例如,uint8 类型的变量能表示的最大值是 255(即二进制 11111111)。如果尝试给 uint8 类型的变量加上 1(在这个例子中,即 255 1),它的值不会变成 256,而是会回绕到 0,因为 256 在 uint8 的表示范围内是不存在的。

解决溢出问题的方法包括

  • 使用更大范围的整数类型:如果可能,使用 uint32uint64 或更大的整数类型来存储变量,以提供更大的表示范围。
  • 检查边界条件:在进行数值计算之前,检查数值是否接近其类型的最大值或最小值,并在必要时采取适当的措施(如抛出错误、使用大数库等)。
  • 使用有符号整数类型:如果应用场景允许负值,并且担心无符号整数溢出,可以考虑使用有符号的整数类型(如 intint32int64),这样至少可以避免因正数溢出而突然变成负数的情况(尽管它仍然可能因负值溢出而变成正数)。
  • 使用大数库:对于需要处理极大数值的应用,可以考虑使用支持大数(任意精度整数)的库,如 Go 语言中的 math/big 包。

Go数组和切片

在Golang中,数组(Array)和切片(Slice)是两种用于存储一系列元素的数据结构,但它们之间存在显著的区别和联系。

1)区别

  • 固定大小 vs 动态大小:数组的大小在声明时确定,之后不能改变。数组的长度是其类型的一部分,因此 [5]int[10]int 是两种不同的类型。切片的大小可以动态改变。切片有一个指向数组的指针、长度(当前元素数量)和容量(底层数组从切片开始到数组末尾的元素数量)。
  • 内存分配:数组在声明时就在栈上分配了内存空间(如果作为函数内的局部变量),或者如果数组是全局的或作为结构体的一部分,则可能在堆上分配。切片本身是在栈上分配的(一个很小的结构体),但它指向的底层数组是在堆上分配的。这意味着切片可以非常灵活地增长和收缩,而不需要移动整个数据结构。
  • 操作:数组的大小在编译时已知,因此可以通过索引直接访问数组中的元素,但不能动态改变数组的大小。切片提供了丰富的内置函数来操作序列,如 append() 用于向切片添加元素,copy() 用于复制切片,len()cap() 分别用于获取切片的长度和容量。
  • 用途:数组通常用于固定大小的集合,例如一周的天数(7个)。切片则更常用于需要动态增长或收缩的序列,如存储来自文件或网络的数据。

2)联系

  • 底层实现:切片是对数组的抽象,切片内部包含了指向一个数组的指针、长度和容量。这意呀着切片可以看作是数组的一个“视图”或“窗口”。
  • 相互转换:可以从数组创建切片(通过切片字面量或直接使用数组的切片表达式),但不能直接将切片转换为数组,因为切片的大小是可变的,而数组的大小是固定的。但是,可以将切片的元素复制到数组中。
  • 共同操作:尽管切片和数组在内存分配和操作上有所不同,但它们都支持索引和范围(range)操作来遍历元素。

总结来说,数组和切片在Golang中都用于存储一系列元素,但切片提供了更高的灵活性和动态性,是Go语言中更常用的数据结构。

Go结构体类型比较

在Go语言中,结构体(Struct)的比较行为取决于结构体中字段的类型。简单来说,如果结构体中的所有字段都是可比较的(即没有不可比较的字段,如切片、映射、函数、通道等),那么这个结构体就是可比较的。

可比较的结构体: 如果一个结构体仅包含可比较的字段(如整型、浮点型、字符串、布尔型、结构体(如果这些结构体也是可比较的)、数组等),那么这个结构体实例之间可以使用 ==!= 操作符进行比较。

代码语言:go复制
type Point struct {
    X, Y int
}

func main() {
    p1 := Point{1, 2}
    p2 := Point{1, 2}
    p3 := Point{2, 2}

    fmt.Println(p1 == p2) // 输出: true
    fmt.Println(p1 == p3) // 输出: false
}

不可比较的结构体: 如果结构体中包含任何不可比较的字段(如切片、映射、函数、通道等),那么这个结构体就是不可比较的。尝试使用 ==!= 操作符比较这样的结构体实例会导致编译错误。

代码语言:go复制
type MyStruct struct {
    Value int
    Slice []int
}

func main() {
    ms1 := MyStruct{Value: 1, Slice: []int{1, 2}}
    ms2 := MyStruct{Value: 1, Slice: []int{1, 2}}

    // 下面的比较会导致编译错误
    // fmt.Println(ms1 == ms2) // 编译错误: invalid operation: ms1 == ms2 (struct containing []int cannot be compared)
}

自定义比较:对于包含不可比较字段的结构体,如果需要比较它们,你通常需要实现自定义的比较逻辑,比如通过遍历所有可比较的字段并逐一比较它们,或者使用反射(reflection)来动态比较结构体中的字段(但请注意,反射可能会影响性能,并且可能会使代码更难理解和维护)。

Go语言中的结构体是否可比较取决于其字段的类型。如果结构体仅包含可比较的字段,则可以使用 ==!= 操作符进行比较。如果结构体包含不可比较的字段,则需要实现自定义的比较逻辑。

0 人点赞