读者A:不会吧,阿Sir,这周这么高产~~~面试
asong:固然啦,为了大家,一切都值得~~~设计模式
读者B:净放臭屁屁,就你戏多~~~架构
asong:你凶人家,坏坏~~~app
哈哈哈,戏太足了奥。自导自演可还行。今日分享以前,先放松放松嘛,毕竟接下来的知识,仍是须要咱们思考的。今天给你们分享的是go中的range,这个咱们在实际开发中,是常用,可是他有一个坑,使用很差,是要被开除的。可是,今天你刚好看了我这一篇文章,就避免了这个坑,开心嘛~~~。直接笑,别克制,我知道你嘴角已经上扬了。框架
废话结束,咱们直接开始。微服务
range到底有什么坑呢,咱们先来运行一个例子吧。ui
package main import ( "fmt" ) type user struct { name string age uint64 } func main() { u := []user{ {"asong",23}, {"song",19}, {"asong2020",18}, } n := make([]*user,0,len(u)) for _,v := range u{ n = append(n, &v) } fmt.Println(n) for _,v := range n{ fmt.Println(v) } }
这个例子的目的是,经过u
这个slice构形成新的slice。咱们预期应该是显示u
slice的内容,可是运行结果以下:spa
[0xc0000a6040 0xc0000a6040 0xc0000a6040] &{asong2020 18} &{asong2020 18} &{asong2020 18}
这里咱们看到n
这个slice打印出来的三个一样的数据,而且他们的内存地址相同。这是什么缘由呢?先别着急,再来看这一段代码,我给他改正确他,对比以后咱们再来分析,大家才会恍然大悟。架构设计
package main import ( "fmt" ) type user struct { name string age uint64 } func main() { u := []user{ {"asong",23}, {"song",19}, {"asong2020",18}, } n := make([]*user,0,len(u)) for _,v := range u{ o := v n = append(n, &o) } fmt.Println(n) for _,v := range n{ fmt.Println(v) } }
细心的大家看到,我改动了哪一部分代码了嘛?对,没错,我就加了一句话,他就成功了,我在for range
里面引入了一个中间变量,每次迭代都从新声明一个变量o
,赋值后再将v
的地址添加n
切片中,这样成功解决了刚才的问题。翻译
如今来解释一下缘由:在for range
中,变量v
是用来保存迭代切片所得的值,由于v
只被声明了一次,每次迭代的值都是赋值给v
,该变量的内存地址始终未变,这样讲他的地址追加到新的切片中,该切片保存的都是同一个地址,这确定没法达到预期效果的。这里还须要注意一点,变量v
的地址也并非指向原来切片u[2]
的,因我在使用range
迭代的时候,变量v
的数据是切片的拷贝数据,因此直接copy
告终构体数据。
上面的问题还有一种解决方法,直接引用数据的内存,这个方法比较好,不须要开辟新的内存空间,看代码:
......略 for k,_ := range u{ n = append(n, &u[k]) } ......略
仍是刚才的例子,咱们作一点改动,如今咱们要对切片中保存的每一个用户的年龄进行修改,由于咱们都是永远18岁,嘎嘎嘎~~~。
package main import ( "fmt" ) type user struct { name string age uint64 } func main() { u := []user{ {"asong",23}, {"song",19}, {"asong2020",18}, } for _,v := range u{ if v.age != 18{ v.age = 20 } } fmt.Println(u) }
来看一下运行结果:
[{asong 23} {song 19} {asong2020 18}]
哎呀,怎么回事。怎么没有更改呢。其实道理都是同样,还记得,我在上文说的一个知识点嘛。对,就是这个,想起来了吧。v
变量是拷贝切片中的数据,修改拷贝数据怎么会对原切片有影响呢,仍是这个问题,copy
这个知识点很重要,一不注意,就会出现问题。知道问题了,咱们如今来把这个问题解决吧。
package main import ( "fmt" ) type user struct { name string age uint64 } func main() { u := []user{ {"asong",23}, {"song",19}, {"asong2020",18}, } for k,v := range u{ if v.age != 18{ u[k].age = 18 } } fmt.Println(u) }
能够看到,咱们直接对切片的值进行修改,这样就修改为功了。因此这里仍是要注意一下的,防止之后出现bug
。
来看一段代码:
func main() { v := []int{1, 2, 3} for i := range v { v = append(v, i) } }
这一段代码会形成死循环吗?答案:固然不会,前面都说了range
会对切片作拷贝,新增的数据并不在拷贝内容中,并不会发生死循环。这种题通常会在面试中问,能够留意下的。
range
用法delete
没看错,删除,在range
迭代时,能够删除map
中的数据,第一次见到这么使用的,我刚听到确实不太相信,因此我就去查了一下官方文档,确实有这个写法:
for key := range m { if key.expired() { delete(m, key) } }
看看官方的解释:
The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next. If map entries that have not yet been reached are removed during iteration, the corresponding iteration values will not be produced. If map entries are created during iteration, that entry may be produced during the iteration or may be skipped. The choice may vary for each entry created and from one iteration to the next. If the map is nil, the number of iterations is 0. 翻译: 未指定`map`的迭代顺序,而且不能保证每次迭代之间都相同。 若是在迭代过程当中删除了还没有到达的映射条目,则不会生成相应的迭代值。 若是映射条目是在迭代过程当中建立的,则该条目可能在迭代过程当中产生或能够被跳过。 对于建立的每一个条目以及从一个迭代到下一个迭代,选择可能有所不一样。 若是映射为nil,则迭代次数为0。
看这个代码:
func main() { d := map[string]string{ "asong": "帅", "song": "太帅了", } for k := range d{ if k == "asong"{ delete(d,k) } } fmt.Println(d) } # 运行结果 map[song:太帅了]
从运行结果咱们能够看出,key为asong
的这位帅哥被从帅哥map
中删掉了,哇哦,可气呀。这个方法,相信不少小伙伴都不知道,今天教给大家了,之后能够用起来了。
上面是删除,那确定会有新增呀,直接看代码吧。
func main() { d := map[string]string{ "asong": "帅", "song": "太帅了", } for k,v := range d{ d[v] = k fmt.Println(d) } }
这里我把打印放到了range
里,大家思考一下,新增的元素,在遍历时可以遍历到呢。咱们来验证一下。
func main() { var addTomap = func() { var t = map[string]string{ "asong": "太帅", "song": "好帅", "asong1": "很是帅", } for k := range t { t["song2020"] = "真帅" fmt.Printf("%s%s ", k, t[k]) } } for i := 0; i < 10; i++ { addTomap() fmt.Println() } }
运行结果:
asong太帅 song好帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong1很是帅 asong太帅 song好帅 asong1很是帅 song2020真帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong太帅 song好帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong1很是帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong太帅 song好帅 asong1很是帅 song2020真帅 asong太帅 song好帅 asong1很是帅 song2020真帅
从运行结果,咱们能够看出来,每一次的结果并非肯定的。这是为何呢?这就来揭秘,map内部实现是一个链式hash表,为了保证无顺序,初始化时会随机一个遍历开始的位置,因此新增的元素被遍历到就变的不肯定了,一样删除也是一个道理,可是删除元素后边就不会出现,因此必定不会被遍历到。
怎么样,伙伴们,收获不小吧。一个小小的range
就会引起这么多的问题,因此说写代码必定要实践,光靠想是没有用的,有些问题只有在实践中才会有所提升。但愿今天的分享对大家有用,好啦,这一期就结束啦。咱们下期见。打个预告:下期将介绍go-elastic的使用,有须要的小伙伴留意一下。
结尾给你们发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,本身也收集了一本PDF,有须要的小伙能够到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],便可获取。
我翻译了一份GIN中文文档,会按期进行维护,有须要的小伙伴后台回复[gin]便可下载。
我是asong,一名普普统统的程序猿,让我一块儿慢慢变强吧。欢迎各位的关注,咱们下期见~~~
推荐往期文章: