教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang之我被for-range循环进去了

Golang之我被for-range循环进去了

发布时间:2022-03-03   编辑:jiaochengji.com
教程集为您提供Golang之我被for-range循环进去了等资源,欢迎您收藏本站,我们将为您提供最新的Golang之我被for-range循环进去了资源
<h2>看个例子</h2>

在我们平时的代码场景中,常常需要改变切片中某个元素的值,先来看一下常见的代码实现方式:

<pre><code class="lang-go hljs">package main import "fmt" func test1() { slice1 := []int{1, 2, 3, 4} for _, val := range slice1 { val } fmt.Println(slice1) } func test2() { slice2 := []int{1, 2, 3, 4} for k, _ := range slice2 { slice2[k] } fmt.Println(slice2) } func test3() { slice3 := []int{1, 2, 3, 4} for i := 0; i < len(slice3); i { slice3[i] } fmt.Println(slice3) } func main() { test1() test2() test3() } </code></code></pre>

非常简单,test1() 中的修改并未对原数据产生影响,而 test2() 和 test3() 中的修改真正改变了原数据。我们看一下打印的结果:

<pre><code class="lang-go hljs">[1 2 3 4] [2 3 4 5] [2 3 4 5] </code></code></pre>

最终输出也是跟我们预想的一致。

主要原因是因为:

<ul><li>val是slice1内元素的副本,对val的改变不会导致slice1内元素的改变</li> <li>而在test2() 和 test3() 中是直接对切片进行索引修改,改变了底层的数组</li> </ul>

为什么会出现这种情况呢?我们去了解一下for - range 原理

<h2>for - range 原理</h2>

for range的实现

<pre><code class="lang-go hljs">// Arrange to do a loop appropriate for the type. We will produce // for INIT ; COND ; POST { // ITER_INIT // INDEX = INDEX_TEMP // VALUE = VALUE_TEMP // If there is a value // original statements // } </code></code></pre>

其中针对 slice 的编译方式如下:

<pre><code class="lang-go hljs">// The loop we generate: // for_temp := range // len_temp := len(for_temp) // for index_temp = 0; index_temp < len_temp; index_temp { // value_temp = for_temp[index_temp] // index = index_temp // value = value_temp // original body // } </code></code></pre>

具体代码细节可查看 https://github.com/golang/gofrontend/blob/e387439bfd24d5e142874b8e68e7039f74c744d7/go/statements.cc#L5384 源代码

从上面的源码我们可以看到,针对slice,for range 做了一下事情:

<ul><li>对要遍历的 Slice 做一个拷贝</li> <li>获取长度大小</li> <li>使用常规for循环进行遍历,返回值的拷贝,并把值存放到全局变量 index 和 value中</li> </ul>

也就是说,对于 for k, val := range(slice) 环过程中,val 在循环内始终都是同一个全局变量

结合上面的结论,我们接下来再看一道题:

<pre><code class="lang-go hljs">package main import "fmt" func test4() { s := []int{0, 1, 2, 3} m := make(map[int]*int) for index, value := range s { m[index] = &value } printMap(m) } func printtMap(m map[int]*int) { for key, value := range m { fmt.Printf("map[%v]=%v\n", key, *value) } } func main() { test4() } </code></code></pre>

打印输出:

<pre><code class="lang-go hljs">map[2]=3 map[3]=3 map[0]=3 map[1]=3 </code></code></pre>

test4() 中直接存的是地址,因为在整个for index, value := range s 循环中,value都是同一个全局变量,地址是一样的,每次遍历该地址上的值都会被新的值覆盖掉,所以在遍历结束后,该地址存的值是切片上的最后一个元素3

如果我把 test4()方法 换成下面这种形式呢

<pre><code class="lang-go hljs"> func test4() { s := []int{0, 1, 2, 3} m := make(map[int]*int) for index, value := range s { valueCopy := value m[index] = &valueCopy } printtMap(m) } </code></code></pre>

上面主要改变是:每次进入循环体,都声明一个新变量valueCopy,并把value赋值给它,最后把新变量valueCopy的地址存到 m 中

打印输出:

<pre><code class="lang-go hljs">map[0]=0 map[1]=1 map[2]=2 map[3]=3 </code></code></pre>

原因是因为每次循环都声明新变量,对应的地址也是不一样的。

我们再看一个闭包,其原理一样

<pre><code class="lang-go hljs">package main import ( "fmt" "time" ) func main() { str := []string{"I","am","Echo 大叔"} for _, v := range str{ // 每个goroutine的v的地址相同,都是为外部v的地址 go func() { // 这里的v是引用外部变量v的地址 fmt.Println(v) }() } time.Sleep(3 * time.Second) } </code></code></pre>

实际上上面的代码会输出:

<pre><code class="lang-go hljs">Echo 大叔 Echo 大叔 Echo 大叔 </code></code></pre>

原因见注释

上面闭包要想实现输出不同的值,可利用函数的值传递性质:

<pre><code class="lang-go hljs">package main import ( "fmt" "time" ) func main() { str := []string{"I","am","Echo 大叔"} for _, v := range str{ // 把外部的v值拷贝给函数内部的v go func(v string) { fmt.Println(v) }(v) } time.Sleep(3 * time.Second) } </code></code></pre>

打印输出(打印顺序不一定一样):

<pre><code class="lang-go hljs">I am Echo 大叔 </code></code></pre> <h2>对于slice</h2>

由 for range 的原理我们可以知道 for i, v := range x,进入循环前会对x的长度进行快照,决定一共循环len(x)那么多次。后面x的变动不会改变循环次数。通过i,以及最新的x,把x[i]赋予给v。

<pre><code class="lang-go hljs">package main import ( "fmt" ) func main() { x := []int{1, 3, 5, 7, 9, 11, 13, 15} fmt.Println("start with ", x) for i, v := range x { fmt.Println("The current value is", v) x = append(x[:i], x[i 1:]...) fmt.Println("And after it is removed, we get", x) } } </code></code></pre>

上面代码,我们在遍历切片的时候,每遍历一次就把该元素从切片中删掉

打印输出:

<pre><code class="lang-go hljs">The current value is 1 And after it is removed, we get [3 5 7 9 11 13 15] The current value is 5 And after it is removed, we get [3 7 9 11 13 15] The current value is 9 And after it is removed, we get [3 7 11 13 15] The current value is 13 And after it is removed, we get [3 7 11 15] The current value is 15 panic: runtime error: slice bounds out of range [5:4] goroutine 1 [running]: main.main() /data1/htdocs/go_project/src/github.com/cnyygj/go_practice/Interview/for_range.go:13 0x398 exit status 2 </code></code></pre>

从输出我们可以看出,for range 的循环次数取决于循环前会对遍历目标的长度进行快照,并不会随着遍历目标长度的修改而改变。所以最终会出现切片溢出的panic

<h2>作业</h2>

最后,留一道题给大家

<pre><code class="lang-go hljs">package main import ( "fmt" ) type Guest struct { id int name string surname string friends []int } func (self Guest) removeFriend(id int) { for i, other := range self.friends { if other == id { self.friends = append(self.friends[:i], self.friends[i 1:]...) break } } } func main() { test := Guest{0, "Echo", "大叔", []int{1,2, 3, 4, 5}} fmt.Println(test) test.removeFriend(4) fmt.Println(test) } </code></code></pre>

最终会打印输出:

<pre><code class="lang-go hljs">{0 Echo 大叔 [1 2 3 4 5]} {0 Echo 大叔 [1 2 3 5 5]} </code></code></pre>

大家知道其中原因吗?欢迎评论交流~

关注公众号 <em>「大叔说码」</em>,获取更多干货,下期见~


到此这篇关于“Golang之我被for-range循环进去了”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
Golang之我被for-range循环进去了
golang for range原理(转载)
Go range实现原理及性能优化剖析
for-range造就循环永动机?快来看看go中for-range的那些事!
go语言中的for循环
通俗易懂的Python循环讲解
golang遍历时修改被遍历对象
通过两个例子介绍一下 Golang For Range 循环原理
Python(for和while)循环嵌套及用法
Python列表推导式(for表达式)及用法

[关闭]
~ ~