Go 是一门以简洁设计为原则的语言,虽说简洁,但坑也不少。

用字符串索引得到的不是字符类型而是 byte 类型

这个就很莫名,可能 python 代码敲多了想当然认为用字符串索引可以直接拿到字符。

import "fmt"

func main() {
   s := "sdfsfs"
   fmt.Println(s[1]) // 结果是100, 不是字符d
   fmt.Println(s[1:2]) // 切片的形式拿到 "d"
   
   char := string(s[1]) // []byte -> string 转换的形式拿到 "d"
   fmt.Println(char) // "d"
}

如果想要拿到字符,需要用切片的方式或者将 byte 数组转化为字符串, go 中没有 char 类型。

切片和结构体在函数参数中的值传递和引用传递

在 Go 中,函数的参数是通过值传递的。这意味着当你传递一个切片或者结构题到函数时,实际上传递的是一个切片的副本。虽然切片本身是一个指针、长度和容量的结构体,但在函数内部对这个切片变量重新赋值(如 nums1 = result)不会影响函数外部的切片,如果要改变原参数的值,那么:

  • 对结构体而言,使用指针来引用传递
  • 对于切片而言,指针来引用传递或者循环直接修改切片内部的值,因为当将切片值传递到函数时,Go 会复制这个结构体,但底层数组是共享的,所以对切片底层数组的修改(通过 slice[i] = value)会影响外部的切片
package main

import "fmt"

// 尝试通过重新赋值修改切片(不会影响外部变量)
func modifySliceByValue(slice []int) {
    newSlice := []int{10, 20, 30}
    slice = newSlice // 对切片重新赋值,不影响外部变量
    fmt.Println("Inside modifySliceByValue:", slice)
}

// 使用循环直接修改切片内部的值(会影响外部变量)
func modifySliceContents(slice []int) {
    for i := range slice {
        slice[i] = slice[i] * 2 // 修改切片内部的值
    }
}

// 使用指针修改切片(影响外部变量)
func modifySliceWithPointer(slice *[]int) {
    newSlice := []int{100, 200, 300}
    *slice = newSlice // 通过指针重新赋值,影响外部变量
}

func main() {
    nums := []int{1, 2, 3}

    fmt.Println("Before modifySliceByValue:", nums)
    modifySliceByValue(nums)
    fmt.Println("After modifySliceByValue:", nums) // nums 未被修改

    fmt.Println("Before modifySliceContents:", nums)
    modifySliceContents(nums)
    fmt.Println("After modifySliceContents:", nums) // nums 内容被修改

    fmt.Println("Before modifySliceWithPointer:", nums)
    modifySliceWithPointer(&nums)
    fmt.Println("After modifySliceWithPointer:", nums) // nums 被重新赋值
}

使用 make 时没有注意到 length 和 capacity 导致可能的错误

在 Go 中使用 make 函数可以初始化一个切片:

nums := make([]int, length, capacity)

这里需要注意的是就算直接给 capacity 足够的长度,length 没有设置足够长的话,也会发生数组越界错误:

res := make([]int, 0, 10)
res[9] // index of range,因为数组的长度是0

这里就有一个疑问是如果这样那要这个 capacity 有毛线用😅,反正还是根据数组长度来判断越不越界,应该和 go 的切片实现机制有关系,有时间可以研究研究下。

这里容易踩的另一个坑就是当指定了长度来初始化切片后,用 append 方法来向切片中添加元素是会直接从切片末尾来添加,而不是切片最开始的地方。

result := make([]int, m+n)
result = append(result, 1)

这会创建一个长度为 m+n 的切片,同时会将这些位置初始化为默认值(整数默认是 0)。但是随后用 appendresult 中追加元素时,实际上是往 result后面追加,而不是覆盖掉前面的初始值,这导致 result 的长度会超过 m+n

修正 result 的初始化方式,确保它的初始长度为 0(只分配容量),这样 append 操作不会导致超长。

result := make([]int, 0, m+n) // 初始长度为 0,容量为 m+n
result = append(result, 1) // 从索引0开始给切片添加元素

make 和 new 的区别

数据结构确定时用 make,否则如果只想要一个指针则用 new。

  • make 不返回指针,是为需要底层数据结构的类型(slice、map、channel)专门设计的,它会初始化这些类型的底层结构(适用于 slice、map、channel)。
  • new 是一个通用的工具,只做最基本的内存分配,并不关心具体类型的特殊需求,返回指针。
package main

import "fmt"

func main() {
    // 使用 new 分配一个 int
    p := new(int)
    fmt.Println(*p) // 输出 0,零值
    *p = 42         // 修改通过指针指向的值
    fmt.Println(*p) // 输出 42

    // 使用 new 分配一个 map(不可直接使用)
    mp := new(map[string]int)
    // (*mp)["key"] = 42 // 会 panic,因为底层未初始化
    *mp = make(map[string]int) // 需要用 make 初始化底层 map
    (*mp)["key"] = 42 // 现在可以正常使用了
    fmt.Println(mp) // 输出 &map[]
    
    mp2 := make(map[string]int)
    mp2["key"] = 42 // 通过make来创建map可以直接使用
    fmt.Println(mp2) // map[key:42]
}