语法糖
语法糖(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 也是