if(runtime.GOOS =="linux")&&(runtime.GOARCH =="amd64")&&(runtime.Compiler !="gccgo"){println("we are using standard go compiler on linux os for amd64")}
Go 语言的 if 语句还有其他多种形式,比如二分支结构和多(N)分支结构。多分支结构引入了 else if。
支持声明 if 语句的自用变量
无论是单分支、二分支还是多分支结构,我们都可以在 if 后的布尔表达式前,进行一些变量的声明,在 if 布尔表达式前声明的变量,称为 if 语句的自用变量。顾名思义,这些变量只可以在 if 语句的代码块范围内使用。
在 if 语句中声明自用变量是 Go 语言的一个惯用法,这种使用方式直观上可以让开发者有一种代码行数减少的感觉,提高可读性。同时,由于这些变量是 if 语句自用变量,它的作用域仅限于 if 语句的各层隐式代码块中,if 语句外部无法访问和更改这些变量,这就让这些变量具有一定隔离性,这样你在阅读和理解 if 语句的代码时也可以更聚焦。
接下来,switch 后面的大括号内是一个个代码执行分支,每个分支以 case 关键字开始,每个 case 后面是一个表达式或是一个逗号分隔的表达式列表。
这里还有一个以 default 关键字开始的特殊分支,被称为默认分支。
最后,我们再来看 switch 语句的执行流程。switch 语句会用 expr 的求值结果与各个 case 中的表达式结果进行比较,如果发现匹配的 case,也就是 case 后面的表达式,或者表达式列表中任意一个表达式的求值结果与 expr 的求值结果相同,那么就会执行该 case 对应的代码分支,分支执行后,switch 语句也就结束了。
如果所有 case 表达式都无法与 expr 匹配,那么程序就会执行 default 默认分支,并且结束 switch 语句。
Go 先对 switch expr 表达式进行求值,然后再按 case 语句的出现顺序,从上到下进行逐一求值。在带有表达式列表的 case 语句中,Go 会从左到右,对列表中的表达式进行求值。
switch 语句的灵活性
C 语言中的 switch 语句对表达式类型有限制,每个 case 语句只可以有一个表达式。而且,除非你显式使用 break 跳出,程序默认总是执行下一个 case 语句。这些特性开发人员带来了使用上的心智负担。
相较于 C 语言中 switch 语句的“死板”,Go 的 switch 语句表现出极大的灵活性,主要表现在如下几方面:
首先,switch 语句各表达式的求值结果可以为各种类型值,只要它的类型支持比较操作就可以了。
C 语言中,switch 语句中使用的所有表达式的求值结果只能是 int 或枚举类型,其他类型都会被 C 编译器拒绝。
Go 语言就宽容得多了,只要类型支持比较操作,都可以作为 switch 语句中的表达式类型。比如整型、布尔类型、字符串类型、复数类型、元素类型都是可比较类型的数组类型,甚至字段类型都是可比较类型的结构体类型,也可以。
type person struct{
name string
age int}funcmain(){
p := person{"tom",13}switch p {case person{"tony",33}:println("match tony")case person{"tom",13}:println("match tom")case person{"lucy",23}:println("match lucy")default:println("no match")}}
第二点:switch 语句支持声明临时变量。
第三点:case 语句支持表达式列表。
第四点:取消了默认执行下一个 case 代码逻辑的语义。
type switch
"type switch”这是一种特殊的 switch 语句用法:
funcmain(){var x interface{}=13switch x.(type){casenil:println("x is nil")caseint:println("the type of x is int")casestring:println("the type of x is string")casebool:println("the type of x is string")default:println("don't support the type")}}
switch 关键字后面跟着的表达式为 x.(type),这种表达式形式是 switch 语句专有的,而且也只能在 switch 语句中使用。这个表达式中的 x 必须是一个接口类型变量,表达式的求值结果是这个接口类型变量对应的动态类型。
接着,case 关键字后面接的就不是普通意义上的表达式了,而是一个个具体的类型。这样,Go 就能使用变量 x 的动态类型与各个 case 中的类型进行匹配,之后的逻辑就都是一样的了。
如果循环体中的代码执行到一半,要中断当前迭代,忽略此迭代循环体中的后续代码,并回到 for 循环条件判断,尝试开启下一次迭代,这个时候我们可以使用 continue 语句来应对。
Go 语言中的 continue 在 C 语言 continue 语义的基础上又增加了对 label 的支持。
label 语句的作用,是标记跳转的目标。
funcmain(){var sum intvar sl =[]int{1,2,3,4,5,6}
loop:for i :=0; i <len(sl); i++{if sl[i]%2==0{// 忽略切片中值为偶数的元素continue loop
}
sum += sl[i]}println(sum)// 9}
在这段代码中,我们定义了一个 label:loop,它标记的跳转目标恰恰就是我们的 for 循环。也就是说,我们在循环体中可以使用 continue+ loop label 的方式来实现循环体中断。
for range 形式的循环语句,使用短变量声明的方式来声明循环变量,循环体将使用这些循环变量实现特定的逻辑,但在使用的时候,可能会发现循环变量的值与“预期”不符。
有时候循环变量在 for range 语句中仅会被声明一次,且在每次迭代中都会被重用。
问题二:参与循环的是 range 表达式的副本
在 for range 语句中,range 后面接受的表达式的类型可以是数组、指向数组的指针、切片、字符串,还有 map 和 channel(需具有读权限)。
funcmain(){var a =[5]int{1,2,3,4,5}var r [5]int
fmt.Println("original a =", a)for i, v :=range a {if i ==0{
a[1]=12
a[2]=13}
r[i]= v
}
fmt.Println("after for range loop, r =", r)
fmt.Println("after for range loop, a =", a)}
每次迭代的都是从数组 a 的值拷贝 a’ 中得到的元素。
a’是 Go 临时分配的连续字节序列,与 a 完全不是一块内存区域。
因此无论 a 被如何修改,它参与循环的副本 a’依旧保持原值,因此 v 从 a’中取出的仍旧是 a 的原值,而不是修改后的值。
那么应该如何解决这个问题,让输出结果符合我们的预期呢?
大多数应用数组的场景我们都可以用切片替代:
funcmain(){var a =[5]int{1,2,3,4,5}var r [5]int
fmt.Println("original a =", a)for i, v :=range a[:]{if i ==0{
a[1]=12
a[2]=13}
r[i]= v
}
fmt.Println("after for range loop, r =", r)
fmt.Println("after for range loop, a =", a)}
在 range 表达式中,我们用了 a[:]替代了原先的 a,也就是将数组 a 转换为一个切片,作为 range 表达式的循环对象。
当进行 range 表达式复制时,我们实际上复制的是一个切片,也就是表示切片的结构体。表示切片副本的结构体中的 array,依旧指向原切片对应的底层数组,所以我们对切片副本的修改也都会反映到底层数组 a 上去。
而 v 再从切片副本结构体中 array 指向的底层数组中,获取数组元素,也就得到了被修改后的元素值。
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…
搞懂大模型的智能基因,RLHF系统设计关键问答 RLHF(Reinforcement Learning with Human Feedback,人类反馈强化学习)虽是热门概念,并非包治百病的万用仙丹。本问答探讨RLHF的适用范围、优缺点和可能遇到的问题ÿ…