- 位移右操作数必须是无符号整数,或可以转换的无显式类型常量
func main() {
b := 23
x := 1 << b
}
- 如果是非常量位移表达式,那么会优先将无显式类型的常量左操作数转型
func main() {
a := 1.0 << 3
fmt.Printf("%T,%v\n", a, a)
var s uint = 3
b := 1.0 << s
var c int32 = 1.0 << s
fmt.Printf("%T,%v\n", c, c)
}
- 指针类型支持相等运算符,但不能做加减法运算和类型转换。如果两个指针指向同一地址,或都为nil,那么它们相等。可通过unsafe.Pointer将指针转换为uintptr后进行加减法运算,但可能会造成非法访问。Pointer类似C语言中的void *万能指针,可用来转换指针类型。它能安全持有对象或对象成员,但uintptr不行。后者近视一种特殊整型,并不引用目标对象,无法阻止垃圾回收器回收对象内存
- 零长度对象的地址是否相等和具体的实现版本有关,不过肯定不等于nil。即便长度为0,该对象依然是“合法存在”的,拥有合法内存地址,这与nil语义完全不同。在runtime/malloc.go里有个zerobase全局变量,所有通过mallocgc分配的零长度对象都使用该地址。当然也可能在栈上分配,不调用mallocgc函数
- 对复合类型(数组,切片,字典,结构体)变量初始化时,初始化表达式必须含类型标签;左花括号必须在类型尾部,不能另起一行;多个成员初始值以逗号分隔;允许多行,但每行须以逗号或右花括号结束
type data struct {
x int
s string
}
var a data = data{1, "abc"}
b := data {
1,
"abc",
}
c := []int {
1,
2 }
d := []int {1, 2,
3, 4,
5,
}
- 死代码是指永远不会被执行的代码,可使用专门的工具,或用代码覆盖率测试进行检查。某些比较智能的编译器也可主动清除死代码
- 对于某些过于复杂的组合条件,建议将其重构为函数,函数调用虽然有一些性能损失,可却让主流程变得更加清爽。条件语句独立之后,更易于测试,同样会改善代码可维护性
- switch语句
8.1func main() {
switch x {
case a, b:
println()
}
}
8.2 条件表达式支持非常量值
8.3 switch同样支持初始化语句,按从上到下,从左到右顺序匹配case执行。只有全部匹配失败时,才会执行default块func main() {
switch x := 5; x {
default:
x += 100
println(x)
case 5:
x += 50
println(x)
}
}
8.4 相邻的空case不构成多条件匹配func main() {
switch x {
case a:
case b:
println("b")
}
}
8.5 不能出现重复的case常量值func main() {
switch x := 5; x {
case 5:
println("a")
case 6, 5:
println("b")
}
}
8.6 无须显式执行break语句,case执行完毕后自动中断。如需贯通后续case(源码顺序),须执行fallthrough,但不再匹配后续条件表达式func main() {
switch x := 5; x {
default:
println(x)
case 5:
x += 5
println(x)
fallthrough
case 6:
x += 20
println(x)
fallthrough
}
}
8.7 注意,fallthrough必须放在case块结尾,可使用break语句阻止func main() {
switch x := 5; x {
default:
println(x)
case 5:
x += 5
println(x)
if x >= 15 {
break
}
fallthrough
case 6:
x += 20
println(x)
}
}
8.8 某些时候,switch还被用来替换if语句。被省略的switch条件表达式默认值为true,继而与case比较表达式结果匹配func main() {
switch x := 5; {
case x > 5:
println("a")
case x > 0 && x <= 5:
println("b")
default:
println("z")
}
}
- for 语句
9.1 条件表达式中如有函数调用,须确认是否会重复执行。可能会被编译器优化掉,也可能是动态结果须每次执行确认func count() int {
print("count.")
return 3
}
func main() {
for i, c := 0, count(); i < c; i++ {
println("a", i)
}
c := 0
for c < count() {
println("b", c)
c++
}
}
9.2 for … range 迭代for range data {
}
9.3 range会复制目标数据func main() {
data := [3]int{10, 20, 30}
for i, x := range data {
if i == 0 {
data[0] += 100
data[1] += 200
data[2] += 300
}
fmt.Printf("x:%d, data:%d\n", x, data[i])
}
for i, x := range data[:] {
if i == 0 {
data[0] += 100
data[1] += 200
data[2] += 300
}
fmt.Printf("x:%d, data:%d\n", x, data[i])
}
}
var s = "spf"
fmt.Printf("%T, %v\n", s, s)
fmt.Printf("%T, %v\n", s[:], s[:])
9.4 for range遍历汉字func main() {
s := "abc汉字"
for i := 0; i < len(s); i++ {
fmt.Printf("%c,", s[i])
}
fmt.Println()
for _, r := range s {
fmt.Printf("%c,", r)
}
}
- goto不能跳转到其他函数或内层代码块内
func test() {
test:
println("test")
}
func main() {
for i := 0; i< 3; i++ {
loop:
println(i)
}
goto test
goto loop
}
- break 可用于 for、switch、select,而 continue 仅能用于 for 循环
- 根据调用者不同,方法分为两种表现形式:
instance.method(args…) —> .func(instance, args…)
前者称为 method value,后者 method expression。
两者都可像普通函数那样赋值和传参,区别在于 method value 绑定实例,而 method expression 则须显式传参。type User struct {
id int
name string
}
func (self *User) Test() {
fmt.Printf("%p, %v\n", self, self)
}
func main() {
u := User{1, "Tom"}
u.Test()
mValue := u.Test
mValue()
mExpression := (*User).Test
mExpression(&u)
}
输出:
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
需要注意,method value 会复制 receiver。type User struct {
id int
name string
}
func (self User) Test() {
fmt.Println(self)
}
func main() {
u := User{1, "Tom"}
mValue := u.Test
u.id, u.name = 2, "Jack"
u.Test()
mValue()
}
输出:
{2 Jack}
{1 Tom}