04. 控制结构

30 range下元素copy #

range介绍
range 可作用于 string, array, 指针数组, slice, map, 接收chan.

复习下range的写法:

s := []string{"a", "b", "c"}

for i, v := range s {
fmt.Printf("index=%d, value=%s\n", i, v)
}
for _, v := range s {
fmt.Printf("value=%s\n", v)
}
for i := range s {
fmt.Printf("index=%d\n", i)
}

output:

index=0, value=a
index=1, value=b
index=2, value=c
value=a
value=b
value=c
index=0
index=1
index=2
range下值类型
accounts := []account{{balance: 100.}, {balance: 200.}, {balance: 300.}}
for _, a := range accounts {

a.balance += 1000
}
fmt.Println(accounts)

[{100} {200} {300}]

因为我们修改的是拷贝出来的a 原来的slice没有改变到

下面才是能改变的

for i := range accounts {
accounts[i].balance += 1000
}

或者

for i := 0; i < len(accounts); i++ {
accounts[i].balance += 1000
}

那么这种改法呢 accounts := []*account{} 作者不是很赞同,在#91时会提及

31 range下元素计算 #

slice

s在range时被拷贝
func sliceCopy() {
s := []int{0, 1, 2}
for range s {
s = append(s, 10)
}
fmt.Println(s)
}

[0 1 2 10 10 10]

陷入死循环,因为s每次迭代都是重新求值
func sliceNeverStop() {
	s := []int{0, 1, 2}
	for i := 0; i < len(s); i++ {
		s = append(s, 10)
	}
	fmt.Println(s)
}
channel 在range时拷贝值,类似slice
func chanCopy() {
	ch1 := make(chan int, 3)
	ch2 := make(chan int, 3)
	go func() {
		ch1 <- 1
		ch1 <- 11
		ch1 <- 111
		close(ch1)
	}()
	go func() {
		ch2 <- 2
		ch2 <- 22
		ch2 <- 222
		close(ch2)
	}()
	ch := ch1
	for v := range ch {
		fmt.Println(v) // 保持ch1的来源,copy in range first time
		// 此时v的来源是ch1的值
		ch = ch2
	}
    fmt.Println("---")
	for v := range ch { // ch 还是被ch2 赋值了
		fmt.Println(v)
	}

}

output

1
11
111
---
2
22
222
array 在range时发生拷贝
func arrayCopy() {
	arr := [3]int{0, 1, 2}
	for i, v := range arr {
		arr[2] = 10
		if i == 2 {
			fmt.Println(v)
		}
	}
    fmt.Println(arr)
}

output

2
[0 1 10]
array 指针指向源数据
func arrayCopy() {
	arr := [3]int{0, 1, 2}
	for i, v := range &arr { //此时为指针
		arr[2] = 10
		if i == 2 {
			fmt.Println(v)
		}
	}
    fmt.Println(arr)
}

output

10
[0 1 10]

32 range下指针元素 #

range时临时变量指向同个指针


type Customer struct {
	ID      string
	Balance float64
}

type Store struct{ m map[string]*Customer }

func (s *Store) storeCustomers(customers []Customer) {
	for _, customer := range customers {
		// 此时customer 在每次迭代中都是同一指针
		s.m[customer.ID] = &customer
	}

}

func rangeLast() {
	s := Store{
		m: map[string]*Customer{},
	}
	s.storeCustomers([]Customer{{ID: "1", Balance: 10}, {ID: "2", Balance: -10}, {ID: "3", Balance: 0}})
	for _, v := range s.m {
		fmt.Println(v)
	}
}
&{3 0}
&{3 0}
&{3 0}

化解

func (s *Store) storeCustomers(customers []Customer) {
	for _, customer := range customers {
		customer1 := customer
		s.m[customer.ID] = &customer1
	}
}

output

&{1 10}
&{2 -10}
&{3 0}

33 map迭代陷阱 #

map的迭代无序性
在同一迭代中操作元素
func rangeMap() {
	m := map[int]bool{
		0: true, 1: false, 2: true,
	}

	for k, v := range m {
		if v {
			m[10+k] = true 
		}
	}
	fmt.Println(m)
}

无序的体现,在map range中改变map,每次生成都是无序.

建议用新变量m2来存储赋值结果.

34 break工作机制 #

break只终结当前循环
func breakCurr() {
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", i)
		switch i {
		default:
		case 2:
			break
		}
	}
}

output

0 1 2 3 4

化解

func breakCurr() {
loopLabel:
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", i)
		switch i {
		default:
		case 2:
			break loopLabel
		}
	}
}

output

0 1 2

例子2

func breakFor() {
	ch := make(chan int, 3)
	ctx, canF := context.WithCancel(context.Background())
	go func() {
		time.Sleep(1 * time.Second)
		canF()
	}()
	for {
		select {

		case <-ch:
			fmt.Println("from ch")
		case <-ctx.Done():
			// Do something case <-ctx.Done():
			fmt.Println("wanna break for and exit")
			break
		}
	}
}

以上函数无法中终结,只break select

同样修复方式:


func breakFor() {
	ch := make(chan int, 3)
	ctx, canF := context.WithCancel(context.Background())
	go func() {
		time.Sleep(1 * time.Second)
		canF()
	}()
loopLabel:
	for {
		select {

		case <-ch:
			fmt.Println("from ch")
			break
		case <-ctx.Done():
			// Do something case <-ctx.Done():
			fmt.Println("wanna break for and exit")
			break loopLabel
		}
	}
	fmt.Println("im arrive")
}

output

wanna break for and exit
im arrive

35 在for中使用defer #

defer的执行是推入函数的调用栈,在函数return时才会执行.

如果在for循环中打开文件,defer关闭文件句柄的行为存在风险

建议其一,将打开文件,defer关闭文件放在一个函数,在for中调用单独函数,保证defer的执行