1. 空结构体的内存占用
空结构体struct{}不占用任何内存空间,这在某些场景下非常有用:
package main
import (
"fmt"
"unsafe"
)
func main() {
var s struct{}
fmt.Println(unsafe.Sizeof(s)) // 输出: 0
}
2. 方法值和方法表达式
Go支持方法值和方法表达式,这提供了灵活的函数调用方式:
type MyType int
func (m MyType) Method(x int) int {
return int(m) + x
}
func main() {
var t MyType = 5
// 方法值
f := t.Method
fmt.Println(f(3)) // 输出: 8
// 方法表达式
g := MyType.Method
fmt.Println(g(t, 3)) // 输出: 8
}
3. 通道的方向性
通道可以指定方向,这在接口设计中很有用:
func sendOnly(ch chan<- int) {
ch <- 42
}
func receiveOnly(ch <-chan int) {
fmt.Println(<-ch)
}
4. defer的参数求值时机
defer语句中的参数在defer声明时就被求值,而不是在函数返回时:
func main() {
i := 0
defer fmt.Println(i) // 输出: 0
i++
defer fmt.Println(i) // 输出: 1
}
5. 切片的容量和长度
理解切片的容量和长度对于避免意外行为很重要:
s := make([]int, 3, 5)
fmt.Printf("len: %d, cap: %d\n", len(s), cap(s)) // len: 3, cap: 5
6. 接口的动态类型和值
接口变量包含动态类型和动态值:
var i interface{} = "hello"
fmt.Printf("%T, %v\n", i, i) // string, hello
7. 空接口和类型断言
空接口可以存储任何类型的值,配合类型断言使用:
func process(val interface{}) {
if str, ok := val.(string); ok {
fmt.Println("String:", str)
} else if num, ok := val.(int); ok {
fmt.Println("Number:", num)
}
}
8. select语句的随机性
当多个case都准备好时,select会随机选择一个执行:
ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch1 <- 1 }()
go func() { ch2 <- 2 }()
select {
case <-ch1:
fmt.Println("Received from ch1")
case <-ch2:
fmt.Println("Received from ch2")
}
9. 函数类型的比较
函数类型只有在都是nil时才能比较:
var f1 func() = nil
var f2 func() = nil
fmt.Println(f1 == f2) // true
f1 = func() {}
// fmt.Println(f1 == f2) // 编译错误
10. 数组的值传递
数组是值类型,赋值时会复制整个数组:
arr1 := [3]int{1, 2, 3}
arr2 := arr1
arr2[0] = 10
fmt.Println(arr1) // [1 2 3]
fmt.Println(arr2) // [10 2 3]
11. map的并发不安全性
map不是并发安全的,需要使用sync.Mutex保护:
var mu sync.Mutex
m := make(map[string]int)
go func() {
mu.Lock()
m["key"] = 1
mu.Unlock()
}()
go func() {
mu.Lock()
fmt.Println(m["key"])
mu.Unlock()
}()
12. 字符串的不可变性
字符串在Go中是不可变的:
s := "hello"
// s[0] = 'H' // 编译错误
13. iota的妙用
iota在常量声明中非常有用:
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
14. 函数的命名返回值
命名返回值可以提高代码可读性:
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
15. build tags的使用
build tags允许条件编译:
// +build linux
package main
func init() {
fmt.Println("Linux specific code")
}
结论
这些微妙的特性展示了Go语言的深度和灵活性。理解这些特性不仅能帮助你写出更优雅的代码,还能避免一些常见的陷阱。继续探索Go语言的更多特性,你会发现这门语言还有更多值得学习的地方。