语法糖
语法糖(Syntactic sugar)的概念是由英国计算机科学家彼得·兰丁提出的,用于表示编程语言中的某种类型的语法,这些语法不会影响功能,但使用起来却很方便。 语法糖,也称糖语法,这些语法不仅不会影响功能,编译后的结果跟不使用语法糖也一样。
简短变量声明 :=
- 多变量赋值可能会重新声明(这并没有引入新的变量,只是把变量的值改变了。)
- 作用域问题
可变参数(…parameters)
开发时候遇到如下场景,某个方法原来的入参是切片,并在内部遍历了切片,现在需要添加一个方法,入参是一个元素,处理方式除了遍历完全相同,洁癖犯了之后不想额外再添加一个相似度极高的方法,想到Go的某些内置函数似乎有类似的场景,参数不固定的情况下,例如append,既可以append切片也可以append一个元素,点进去看了下源码
1func append(slice []Type, elems ...Type) []Type
append 就使用了可变参数
1result := []int{1, 3}
2data := []int{5, 7}
3result = append(result, data...) // 和 result = append(result, data[0], data[1]) 等价
4// 最终 result == []int{1, 3, 5, 7}
- 可变参数使用name ...Type的形式声明在函数的参数列表中,而且需要是参数列表的最后一个参数(只能包含一个可变参数);
- 可变参数在函数内部是作为切片来解析的;
- 可变参数可以不填,不填时函数内部当成 nil 切片处理;
- 可变参数可以填入切片;(函数内部使用的切片与传入的切片共享相同的存储空间。说得再直白一点就是,如果函数内部修改了切片,可能会影响外部调用的函数)
- 可变参数必须是相同类型的(如果需要是不同类型的可以定义为 interface{}类型);
func init()
- 每个包可以有多个 - init()函数:- 在一个包中,可以定义多个 init()函数,它们可以分布在不同的文件中。
- 每个 init()函数都会被自动调用,且调用顺序是按照文件名的字母顺序进行的。
 
- 在一个包中,可以定义多个 
- 每个文件可以有多个 - init()函数:- 在一个文件中,可以定义多个 init()函数。
- 这些 init()函数会按照它们在文件中出现的顺序依次执行。
 
- 在一个文件中,可以定义多个 
- init()函数只会执行一次:- 无论包被导入多少次,init()函数只会执行一次。
- 即使包被多个文件导入,init()函数也只会执行一次。
 
- 无论包被导入多少次,
- init()函数的执行时机:- init()函数会在包的变量初始化之后、- main()函数执行之前自动调用。
- 如果一个包被多个包导入,它的 init()函数会在所有导入它的包的init()函数执行之前调用。
 
- 从其他 - Package中读取- init()函数- 当 - main()函数要单独读取- package内的- init()而不读取额外的函数,这时候需要透过**- _**来读取。- 1import ( 2 _ "xxxx" 3)
- 如果不加上,当编译的时候会报错,因为 - main()没有用到任何非- init()之外的东西。
- 如果有多个 - package的- init()需要同时引入 ,这边也是会依照- import的顺序来读取。
 
slices
Delete
在 Go 1.22 及以上版本中,可以使用 slices 包中的 Delete 函数来简化删除切片元素的操作。
1// 删除下标为 i 的元素
2infos = append(infos[:i}, infos[i+1]...) // 传统写法
3infos = slices.Delete(infos, i, i + 1)   // 更推荐的写法
- 函数签名:func Delete[S ~[]E, E any](s S, i, j int) S
- 参数:- s:要操作的切片
- i:删除的起始索引
- j:删除的结束索引(不含)
 
- 返回值:删除元素后的新切片
for range
for loop can be modernized using range over int
for 循环可以通过 Go 1.22 引入的 range over int 特性来现代化或简化。在 Go 1.22 之前,range 只能用于遍历切片、数组、映射等集合类型。如果要遍历一个整数范围(例如从 0 到 n-1),必须使用传统的 for 循环,像这样:
1for i := 0; i < n; i++ {
2    // 循环体
3}
从 Go 1.22 开始,Go 语言支持 range over int,即可以直接用 range 遍历一个整数范围。例如:
1for i := range n {
2    // 循环体
3}
坑
循环变量重用问题(闭包陷阱)
现象:在循环中启动协程或闭包时,所有闭包可能共享同一个循环变量的最终值。
原因:for-range 的循环变量(如 i 和 v)在每次迭代中会被 重用,而非重新声明。
示例:
1values := []int{1, 2, 3}
2for _, v := range values {
3    go func() { fmt.Println(v) }()  // 可能输出 3,3,3
4}
解决:
- 传递参数:将循环变量作为参数传入闭包。
- 局部变量拷贝:在循环内部声明局部变量保存当前值。
1for _, v := range values {
2    v := v  // 创建局部变量
3    go func() { fmt.Println(v) }()
4}
5// 或
6for i := range values {
7    go func(v int) { fmt.Println(v) }(values[i])
8}
底层机制:循环变量的内存地址在迭代中被复用,闭包捕获的是地址而非值 。
Go 1.22 优化了循环变量作用域,每次迭代重新声明变量,减少闭包陷阱。
遍历时修改原数据无效
现象:直接通过 for-range 的 v 修改元素无法影响原数组/切片。
原因:v 是元素的 值拷贝,而非引用。
示例:
1users := []User{{Name: "A"}, {Name: "B"}}
2for _, u := range users {
3    u.Name = "C"  // 修改无效,users 未变
4}
解决:通过 索引 直接修改原数据:
1for i := range users {
2    users[i].Name = "C"  // 正确修改
3}
但是如果 v 也是