当前位置:首页 > 话题广场 > 财经专区 > 银行卡

0061开头的银行卡专题之golang 如何学习for语句

关于For语句的问题

在Golang中,循环语句只有for。在代码中编写循环通常需要for(当然也可以使用goto)。golang的for语句很方便,但很多初学者也对for语句有很多疑问。例如:

For语句有几种表达式格式?

For语句的临时变量是什么?(有时候通过赋值后,为什么所有值都等于最后一个元素?)

Range后支持哪些数据类型?

为什么范围字符串类型是rune类型?

遍历Slice时,如果添加或删除数据会发生什么情况?

遍历地图时,如果添加或删除数据会发生什么情况?

事实上,这里的很多疑问可以看到golang编程语言规范,感兴趣的学生可以自己看,也可以根据自己的理解解答这些问题。

for语句的规范

for语句的功能用来指定重复执行的语句块,for语句中的表达式有三种:

官方的规范: ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .

  • Condition = Expression .

  • ForClause = [ InitStmt ] “;” [ Condition ] “;” [ PostStmt ] .

  • RangeClause = [ ExpressionList “=” | IdentifierList “:=” ] “range” Expression .

单个条件判断

形式:

for a < b { f(doThing) } // or 省略表达式,等价于truefor { // for true { f(doThing) }

这种格式,只有单个逻辑表达式, 逻辑表达式的值为true,则继续执行,否则停止循环。

for语句中两个分号

形式:

for i:=0; i < 10; i++ { f(doThing) }// orfor i:=0; i < 10; { i++ f(doThing) }// or var i intfor ; i < 10; { i++ f(doThing) }

这种格式,语气被两个分号分割为3个表达式,第一个表示为初始化(只会在第一次条件表达式之计算一次),第二个表达式为条件判断表达式, 第三个表达式一般为自增或自减,但这个表达式可以任何符合语法的表达式。而且这三个表达式, 只有第二个表达式是必须有的,其他表达式可以为空。

for和range结合的语句

形式:

for k,v := range []int{1,2,3} { f(doThing) }// or for k := range []int{1,2,3} { f(doThing) }// orfor range []int{1,2,3} { f(doThing) }

用range来迭代数据是最常用的一种for语句,range右边的表达式叫范围表达式, 范围表达式可以是数组,数组指针,slice,字符串,map和channel。因为要赋值, 所以左侧的操作数(也就是迭代变量)必须要可寻址的,或者是map下标的表达式。 如果迭代变量是一个channel,那么只允许一个迭代变量,除此之外迭代变量可以有一个或者两个。

范围表达式在开始循环之前只进行一次求值,只有一个例外:如果范围表达式是数组或指向数组的指针, 至多有一个迭代变量存在,只对范围表达式的长度进行求值;如果长度为常数,范围表达式本身将不被求值。

每迭代一次,左边的函数调用求值。对于每个迭代,如果相应的迭代变量存在,则迭代值如下所示生成:

Range expression 1st value 2nd value array or slice a [n]E, *[n]E, or []E index i int a[i] E string s string type index i int see below runemap m map[K]V key k K m[k] V channel c chan E, <-chan E element e E
  1. 对于数组、数组指针或是分片值a来说,下标迭代值升序生成,从0开始。有一种特殊场景,只有一个迭代参数存在的情况下, range循环生成0到len(a)的迭代值,而不是索引到数组或是分片。对于一个nil分片,迭代的数量为0。

  2. 对于字符串类型,range子句迭代字符串中每一个Unicode代码点,从下标0开始。在连续迭代中,下标值会是下一个utf-8代码点的 第一个字节的下标,而第二个值类型是rune,会是对应的代码点。如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD, 也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。

  3. map中的迭代顺序是没有指定的,也不保证两次迭代是一样的。如果map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。 如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过,但是每个元素的迭代值顶多出现一次。如果map是nil,那么迭代次数为0。

  4. 对于管道,迭代值就是下一个send到管道中的值,除非管道被关闭了。如果管道是nil,范围表达式永远阻塞。

迭代值会赋值给相应的迭代变量,就像是赋值语句。

迭代变量可以使用短变量声明(:=)。这种情况,它们的类型设置为相应迭代值的类型,它们的域是到for语句的结尾,它们在每一次迭代中复用。 如果迭代变量是在for语句外声明的,那么执行之后它们的值是最后一次迭代的值。

var testdata *struct { a *[7]int}for i, _ := range { // is never evaluated; len() is constant // i ranges from 0 to 6 f(i) }var a [10]stringfor i, s := range a { // type of i is int // type of s is string // s == a[i] g(i, s) }var key stringvar val interface {} // value type of m is assignable to valm := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}for key, val = range m { h(key, val) }// key == last map key encountered in iteration// val == map[key]var ch chan Work = producer()for w := range ch { doWork(w) }// empty a channelfor range ch {}

for语句的内部实现-array

golang的for语句,对于不同的格式会被编译器编译成不同的形式,如果要弄明白需要看golang的编译器和相关数据结构的源码, 数据结构源码还好,但是编译器是用C++写的,本人C++是个弱鸡,这里只讲array内部实现。

// The loop we generate:// len_temp := len(range)// range_temp := range// for index_temp = 0; index_temp < len_temp; index_temp++ {// value_temp = range_temp[index_temp]// index = index_temp// value = value_temp// original body// }// 例如代码: array := [2]int{1,2}for k,v := range array { f(k,v) }// 会被编译成: len_temp := len(array) range_temp := arrayfor index_temp = 0; index_temp < len_temp; index_temp++ { value_temp = range_temp[index_temp] k = index_temp v = value_temp f(k,v)}

所以像遍历一个数组,最后生成的代码很像C语言中的遍历,而且有两个临时变量index_temp,value_temp, 在整个遍历中一直复用这两个变量。所以会导致开头问题2的问题(详细解答会在后边)。

问题解答

  1. for语句一共有多少种表达式格式?

    这个问题应该很简单了,上面的规范中就有答案了,一共有3种:

    Condition = Expression .ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

    先看这个例子:

    var a = make([]*int, 3)for k, v := range []int{1, 2, 3} { a[k] = &v }for i := range a { (*a[i]) }// result: // 3 // 3 // 3

    由for语句的内部实现-array可以知道,即使是短声明的变量,在for循环中也是复用的,这里的v一直 都是同一个零时变量,所以&v得到的地址一直都是相同的,如果不信,你可以打印该地址,且该地址最后存的变量等于最后一次循环得到的变量, 所以结果都是3。

  3. range后面支持的数据类型有哪些?

    共5个,分别是数组,数组指针,slice,字符串,map和channel

  4. range string类型为何得到的是rune类型?

    这个问题在for规范中也有解答,对于字符串类型,在连续迭代中,下标值会是下一个utf-8代码点的第一个字节的下标,而第二个值类型是rune。 如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。

    其实看完这句话,我没理解,当然这句话告诉我们了遍历string得到的第二个值类型是rune,但是为什么是rune类型,而不是string或者其他类型? 后来在看了Rob Pike写的blogStrings, bytes, runes and characters in Go 才明白点,首先需要知道runeint32的别名,且go语言中的字符串字面量始终保存有效的UTF-8序列。而UTF-8就是用4字节来表示Unicode字符集。 所以go的设计者用rune表示单个字符的编码,则可以完成容纳所表示Unicode字符。举个例子:

    s := `汉语ab`("len of s:", len(s))for index, runeValue := range s { ("%#U starts at byte position %d\n", runeValue, index) } // result // len of s: 8// U+6C49 '汉' starts at byte position 0// U+8BED '语' starts at byte position 3// U+0061 'a' starts at byte position 6// U+0062 'b' starts at byte position 7

    根据结果得知,s的长度是为8字节,一个汉子占用了3个字节,一个英文字母占用一个字节,而程序go程序是怎么知道汉子占3个字节,而 英文字母占用一个字节,就需要知道utf-8代码点的概念,这里就不深究了,知道go是根据utf-8代码点来知道该字符占了多少字节就ok了。

  5. 遍历slice的时候增加或删除数据会怎么样?

    由for语句的内部实现-array可以知道,获取slice的长度只在循环外执行了一次, 该长度决定了遍历的次数,不管在循环里你怎么改。但是对索引求值是在每次的迭代中求值的,如果更改了某个元素且 该元素还未遍历到,那么最终遍历得到的值是更改后的。删除元素也是属于更改元素的一种情况。

    在slice中增加元素,会更改slice含有的元素,但不会更改遍历次数。

    a2 := []int{0, 1, 2, 3, 4}for i, v := range a2 { (i, v) if i == 0 { a2 = append(a2, 6) } }// result// 0 0// 1 1// 2 2// 3 3// 4 4

    在slice中删除元素,能删除该元素,但不会更改遍历次数。

    // 只删除该元素1,不更改slice长度a2 := []int{0, 1, 2, 3, 4}for i, v := range a2 { (i, v) if i == 0 { copy(a2[1:], a2[2:]) } }// result// 0 0// 1 2// 2 3// 3 4// 4 4// 删除该元素1,并更改slice长度a2 := []int{0, 1, 2, 3, 4}for i, v := range a2 { (i, v) if i == 0 { copy(a2[1:], a2[2:]) a2 = a2[:len(a2)-2] //将a2的len设置为3,但并不会影响临时slice-range_temp } }// result// 0 0// 1 2// 2 3// 3 4// 4 4
  6. 遍历map的时候增加或删除数据会怎么样?

    规范中也有答案,map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。 如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过。

    在遍历中删除元素

    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5} del := falsefor k, v := range m { (k, v) if !del { delete(m, 2) del = true } }// result// 4 4// 5 5// 1 1// 3 3

    在遍历中增加元素,多执行几次看结果

    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5} add := falsefor k, v := range m { (k, v) if !add { m[6] = 6 m[7] = 7 add = true } }// result1// 1 1// 2 2// 3 3// 4 4// 5 5// 6 6// result2// 1 1// 2 2// 3 3// 4 4// 5 5// 6 6// 7 7

    在map遍历中删除元素,将会删除该元素,且影响遍历次数,在遍历中增加元素则会有不可控的现象出现,有时能遍历到新增的元素, 有时不能。具体原因下次分析。

1.《0061开头的银行卡专题之golang 如何学习for语句》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。

2.《0061开头的银行卡专题之golang 如何学习for语句》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。

3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/caijing/2030339.html

上一篇

105开头是哪家银行卡,干货看这篇!警方打掉贩卖“两卡”跨省犯罪团伙,50余张银行卡流水高达2000余万元

下一篇

关于0061开头的银行卡我想说字符串与正则的扩展

【0061开头的银行卡】这一次,终于把XSS理解透彻

  • 【0061开头的银行卡】这一次,终于把XSS理解透彻
  • 【0061开头的银行卡】这一次,终于把XSS理解透彻
  • 【0061开头的银行卡】这一次,终于把XSS理解透彻
0061开头的银行卡专题之中国第一大油气田日产原油突破7万吨

0061开头的银行卡专题之中国第一大油气田日产原油突破7万吨

0061开头的银行卡相关介绍,据5月25日中国石油长庆油田数字生产指挥中心屏幕显示,该公司原油日产量突破7万吨,达到7.0061万吨,今年以来原油日产创新高,也为中国石油第一大油气田长庆油田公司实现年原油产量2545万吨奠定了坚实...

0061开头的银行卡,干货看这篇!安全测试之XSS

0061开头的银行卡,干货看这篇!安全测试之XSS

0061开头的银行卡相关介绍,用于安全测试的站点间脚本攻击(XSS) 前言 随着互联网的不断发展,web应用程序的交互也越来越强。 但正如一个硬币会有两面一样,在用户体验提升的同时安全风险也会跟着有所增加。今天,我们就来讲一讲we...

0061开头的银行卡看这里!安全测试之XSS

0061开头的银行卡看这里!安全测试之XSS

0061开头的银行卡相关介绍,用于安全测试的站点间脚本攻击(XSS) 前言 随着互联网的不断发展,web应用程序的交互也越来越强。 但正如一个硬币会有两面一样,在用户体验提升的同时安全风险也会跟着有所增加。今天,我们就来讲一讲we...

0061开头的银行卡专题之安全测试之XSS

0061开头的银行卡专题之安全测试之XSS

0061开头的银行卡相关介绍,用于安全测试的站点间脚本攻击(XSS) 前言 随着互联网的不断发展,web应用程序的交互也越来越强。 但正如一个硬币会有两面一样,在用户体验提升的同时安全风险也会跟着有所增加。今天,我们就来讲一讲we...