赞
踩
defer的执行顺序后进先出(LIFO),在函数结束前执行
func DeferFunc1(i int) (t int) { t = i defer func() { t += 3 }() return t } func DeferFunc2(i int) int { t := i defer func() { t += 3 }() return t } func DeferFunc3(i int) (t int) { defer func() { t += i }() return 2 } func main() { println(DeferFunc1(1)) println(DeferFunc2(1)) println(DeferFunc3(1)) } // 输出 4 1 3
DeferFunc1中,有名返回(指定返回值命名func test() (t int)),执行 return 语句时,并不会再创建临时变量保存,defer 语句修改了t,即对返回值产生了影响,所以返回4。
DeferFunc2中,无名返回(返回值没有指定命名),执行Return语句后,Go会创建一个临时变量保存返回值,defer 语句修改的是 t,而不是临时变量,所以返回1。
DeferFunc3中,有名返回,执行 return 语句时,把2赋值给t,defer 语句再执行t+1,所以返回3。
range 循环会复用地址,&stu实际上一致指向同一个指针,最终该指针的值为遍历的最后一个struct的值拷贝。(for循环复用局部变量i)
func main() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } // 错误写法 for _, stu := range stus { fmt.Printf("%p\n", &stu) m[stu.Name] = &stu } for k, v := range m { fmt.Println(k, "=>", v.Name) } // 正确 for i := 0; i < len(stus); i++ { m[stus[i].Name] = &stus[i] } for k, v := range m { fmt.Println(k, "=>", v.Name) } } // 输出 0xc000008078 0xc000008078 0xc000008078 zhou => wang li => wang wang => wang zhou => zhou li => li wang => wang
for 和 for range有什么区别?
使用场景不同
for可以遍历array和slice、遍历key为整型递增的map、遍历string
for range可以完成所有for可以做的事情,却能做到for不能做的,包括遍历key为string类型的map并同时获取key和value、遍历channel。
实现不同
for可以获取到的是被循环对象的元素本身,可以对其进行修改;
for range使用值拷贝的方式代替被遍历的元素本身,是一个值拷贝,而不是元素本身。
func main() { five := []string{"Annie", "Betty", "Charley", "Doug", "Edward"} for _, v := range five { five = five[:2] fmt.Printf("v[%s]\n", v) } fmt.Println(five) } //v[Annie] //v[Betty] //v[Charley] //v[Doug] //v[Edward] //[Annie Betty] //循环内的切片值会缩减为2,但循环将在切片值的自身副本上进行操作。这允许循环使用原始长度进行迭代而没有任何问题,因为后备数组仍然是完整的。
i++不是原子操作,会出现并发问题。Go 提供了一个检测并发访问共享资源是否有问题的工具, race 参数:go run -race main.go
第一个go协程中i是外部for的一个变量,地址不变化,每次指向的都是一样的地址,遍历完成后,最终i=10。
第二个go协程中i是函数参数,会发生值拷贝,地址会变,内部每次指向新的地址。
func main() { var wg sync.WaitGroup wg.Add(10) // 错误 for i := 0; i < 10; i++ { go func() { fmt.Println(i) wg.Done() }() } // 正确 for i := 0; i < 10; i++ { go func(i int) { fmt.Println(i) wg.Done() }(i) } wg.Wait() }
select会随机选择一个可用通道做收发操作。 所以代码是有肯触发异常,也有可能不会。select可以在多个chan间等待执行,有三点原则:
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}
make初始化是有默认值的,默认值为0。append函数执行完后,返回的是一个全新的 slice,并且对传入的 slice 并不影响。
func main() { s := make([]int, 5) s = append(s, 1, 2, 3) fmt.Println(s) } //[0 0 0 0 0 1 2 3] func main() { s := []int{5} s = append(s, 7) s = append(s, 9) x := append(s, 11) y := append(s, 12) fmt.Println(s, x, y) } //[5 7 9] [5 7 9 12] [5 7 9 12]
结构体是可以比较的,但前提是结构体成员字段全部可以比较,并且结构体成员字段类型、个数、顺序也需要相同,当结构体成员全部相等时,两个结构体相等。
特别注意的点,如果结构体成员字段的顺序不相同,那么结构体也是不可以比较的。如果结构体成员字段中有不可以比较的类型,如map、slice、function 等,那么结构体也是不可以比较的。
func main() { sn1 := struct { age int name string }{age: 11, name: "Zhang San"} sn2 := struct { age int name string }{age: 11, name: "Zhang San"} fmt.Println(sn1 == sn2) // 输出 true sn3 := struct { name string age int }{age: 11, name: "Zhang San"} fmt.Println(sn1 == sn3) // 错误提示:Invalid operation: sn1 == sn3 (mismatched types struct {...} and struct {...}) sn4 := struct { name string age int grade map[string]int }{age: 11, name: "Zhang San"} sn5 := struct { name string age int grade map[string]int }{age: 11, name: "Zhang San"} fmt.Println(sn4 == sn5) // 错误提示:Invalid operation: sn4 == sn5 (the operator == is not defined on struct {...}) }
编译失败,因为type只能使用在interface类型。
//错误 func main(key int) { switch key.(type) { case int: println("int") case interface{}: println("interface") default: println("unknown") } } //正确 func main(key interface{}) { switch t := key.(type) { case int: println("int") case float64: println("float64") case string: println("string") default: println("unknown") } return }
iota是go语言的常量计数器,只能在常量的表达式中使用,只能用于int类型的常量。 使用iota能简化定义,在定义枚举时很有用。
const ( m = iota // m = 0 _ _ n // n = 3 ) const ( y = 2 >> iota // y = 2,y右移0位 x // x = 1,x右移1位 ) const ( _ = iota l = 1 << iota // l = 2,l左移1位 h // h = 4,h左移2位 ) const ( _ = iota KB = 1 << (10 * iota) // KB = 1024,表示1向左移动10*1位 MB // MB = 1024*1024,表示向左移动10*2位 ) const ( t, r = iota + 1, iota + 2 // t = 1,r = 2 (iota = 0) v, u // v = 2,u = 3 (iota = 1) p, q // p = 3,q = 4 (iota = 2) ) const ( frist = iota // frist = 0 sencond = 5 thr = iota // thrid = 2 last // last = 3 )
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,无法获取常量的地址。
package main
const cl = 100
var bl = 123
func main() {
println(&bl,bl)
println(&cl,cl)
}
// Cannot take the address of 'cl'
// 编译错误,无法获取常量的地址
如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
data := make(chan int) data := make(chan<- int) // 定义单向信道,定义只写数据的信道,<-指向chan data := make(<-chan int) // 定义单向信道,定义只读数据的信道 只写通道:chan<- T 只读通道:<-chan T func main(){ data := make(chan int) go func(out chan<- int){ time.Sleep(2* time.Second) out <- 1 }(data) <- data fmt.Println("Receive data, first") go func(out <-chan int){ time.Sleep(2 * time.Second) <-out fmt.Println("Receive data, Second") os.Exit(0) }(data) data <- 2 for { time.Sleep(1 * time.Second) } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。