作为一门高级语言,Go同样提供了流程控制的支持。在了解了基础结构之后,继续学习Go的流程控制,里面涉及到的基础结构的内容还能对其有更多的了解。
说流程控制之前先说一下interface,因为后续在流程控制中会穿插着对interface的使用。
interface
interface是一切类型的基类型,类似于Java中的基类Obejct
,所有的结构都是interface的实现,因为interface基类型没有定义任何的函数,所以其他任何结构都认为是interface的实现。当然,也可以自己定义interface自己去实现相应的函数,这个下期面向对象的时候会详细解释。这里先简单说明interface作为基类型时的使用。
在Java中,所有的类型都是Object的子类,所以声明对象时可以将对象的类型声明为Object,在赋值时给一个子类型,在Go中同样可以,但仅限于针对interface声明的使用(还是会牵涉到面向对象的东西),也就是说,声明时可以将变量声明为interface类型,赋值时给一个其他基础类型的值,这是最简单的interface作为基类型的使用。
var hello interface{} = "hello world"
fmt.Println(hello)
例子中声明hello时,声明的类型是interface{}类型,并不是string类型,但是赋值时给的是string类型,说明hello实际类型还是string类型。具体的类型转换下面会详细说明。
if-else
Go中的if-else结构的用户与Java中的特别的类似,仅仅区别在两者的语法上面,Go的语法为:
if 条件1 {
...
} else if 条件2 && 条件3 {
...
} else {
...
}
Go对语法的要求没有Java那么严格,对于括号可以带,也可以不带。同样的,Go也支持&&
、||
、!
这样的运算符进行多个条件的关联判断
func max(a, b int) (max int) {
if a > b {
max = a
} else if a == b {
max = a
} else {
max = b
}
return
}
断言
断言在Go中是一种类型转换的语法,能否方便的进行类型的转换。Go语言中简单的断言语法为 value := element.(type)
//value := element.(type) //type为要转换的类型
var hello interface{} = "helloworld"
fmt.Println(hello.(string))
fmt.Println(hello.(int))//该行会报错,因为hello实际类型是string类型
稍微不注意,直接转换的话就会出现异常,所以一般不推荐使用简单的语法,而是用高级语法 value, ok := element.(type)
,这也是在if-else结构中讲解的原因。
// value, ok := element.(type) //type为要转换的类型,ok为是否成功转换,类型为bool,value为实际转换的值
var hello interface{} = "helloworld"
helloS, ok := hello.(string)
if ok {
fmt.Println("hello tranfer successfully : ", helloS)
} else {
fmt.Println("hello transfer failed")
}
使用高级语法能保证在运行的时候不会出现错误,保证程序的持续执行,这是比较推荐的做法。
map断言是map的一种高级用法。
//map的断言
// value, ok := m[key] //这里的OK不再是简单的成功或者失败,理解成是否存在更合适
var m = make(map[string]interface{})//创建map的方式,具体make的用法后续会讲解
m["key1"] = "value1"
value1, ok := m["key1"]
if ok {
fmt.Println("map m contain 'key1' ", value1)
} else {
fmt.Println("map m contain 'key1'")
}
map在断言的使用上好像是天生支持似的,不需要进行Contains函数的校验等,直接使用,平时在代码中使用的也是非常多。简直不要太好用。
switch
switch感觉像是if-else的高级版,同样是进行条件判断的结构,不同的条件执行不同的语句。语法类似Java,Java中只能使用byte、int、short、char和string,在Go中可没有这些限制。
从上至下的判断,直到找到匹配的case或者执行default语句,case结尾也不需要break进行跳出流程操作,执行完自动跳出。相反,如果想执行下一个case的话,需要使用fallthrough
关键字进行下沉操作,
这时候下一条case的条件将被忽略。
switch value1 { //大括号必须与switch保持一行
case value1:
...
case value2, value3://多个条件使用逗号隔开
...
default://没有符合的条件执行默认
...
}
语法规定 switch后跟的value1可以是任意类型(甚至是不写),但是case后的条件必须和switch后的value保持相同类型
grade := 10
switch grade {
//case code < 60://code为int类型,不能使用code < 60作为case条件
case 10:
fmt.Println("不及格")
case 70:
fmt.Println("及格")
default:
fmt.Println("无效的分数")
}
//用于类型断言
switch hello.(type) {
case string:
fmt.Println("hello is string")
case int:
fmt.Println("hello is int")
default:
fmt.Println("hello is unknown type")
}
switch {//直接判断case
case a < b:
fmt.Println("a less than b")
fallthrough //紧接着执行下一个case,不需要进行判断
case a > b:
fmt.Println("a bigger than b")
}
for
说到循环、重复执行等首先想到的就是for,Go同样提供了支持,相对于Java,Go中for的使用更灵活。
同样的,想跳出for循环时使用break
关键字。
//语法一
for init;条件;赋值{//左侧大括号必须与for同行
...
}
//语法二
for 条件 {//左侧大括号必须与for同行
...
}
//语法三
//这是个死循环
for {//左侧大括号必须与for同行
...
}
//语法四
for index, value := range slice/array/map {//range是关键字
...
}
上手就是一个排序来介绍最基本的for结构
a := []int{1, 3, 9, 4, 1, 4, 6, 132, 1, 29, 43, 55, 89, 46}
for i := 0; i < len(a); i++ {//len为Go内置函数
for j := i + 1; j < len(a); j++ {
if a[i] > a[j] {
a[i], a[j] = a[j], a[i]
}
}
}
fmt.Println(a)//结果:[1 1 1 3 4 4 6 9 29 43 46 55 89 132]
只写条件的for循环,类似Java中的while
var i = 0
for i < len(a) {
fmt.Print(a[i]," ")
i++
}//结果: 1 1 1 3 4 4 6 9 29 43 46 55 89 132
死循环写法更简单了,不过需要注意使用break进行跳出,否则电脑就该嗡嗡嗡~响不停了
i = 0
for{
if i < len(a) {
fmt.Print(a[i], " ")
i++
} else {
break
}
}//结果: 1 1 1 3 4 4 6 9 29 43 46 55 89 132
最牛的语法四就是为slice和array使用的,能遍历所有的集合。当遍历slice和array时,index指的是其中的索引位置;遍历map时指的就是key了。请看下面的例子
for index, value := range a {
fmt.Printf("index: %d, value: %d \n", index, value)
}
/*
结果:
index: 0, value: 1
index: 1, value: 1
index: 2, value: 1
index: 3, value: 3
index: 4, value: 4
index: 5, value: 4
index: 6, value: 6
index: 7, value: 9
index: 8, value: 29
index: 9, value: 43
index: 10, value: 46
index: 11, value: 55
index: 12, value: 89
index: 13, value: 132
*/
m := map[string]string{}
m["hello"] = "world"
m["hey"] = "bye"
for key, value := range m {
fmt.Printf("key: %s, value: %s \n", key, value)
}
/*
结果:
key: hello, value: world
key: hey, value: bye
*/
select
select 第一眼看到可能会想到SQL中的选择,但是它也是Go中的一个流程控制关键字。
select的使用主要是结合channel来使用,所以这里要是讲解channels会设计到很多东西,我们后期会做详细的讲解,这里先做select的介绍。
select的语法跟switch类似,用于选择合适的条件进行执行相应的逻辑,但牵涉到channel,所以select中的case都是对channel的操作,只能是往channel中读或者写。
select {
case channel读操作:
...
case channel写操作:
...
default:
...
}
注意点:
channel包含读和写两种操作,case中必须包含一种操作
case的执行是无序的、随机的,select会执行任意一个可执行的case
没有可执行的case时会执行default,没有default的话就会阻塞,等待可执行的channel
下面是一个简单的例子实现,先不要深究内容含义,了解select语法即可
c := make(chan int, 1)
select {
case c <- 1:
fmt.Println("push into channel")
case <-c:
fmt.Println("get from channel")
default:
fmt.Println("default")
}
//结果:push into channel
...
不要怀疑标题,标题就是三个英文点,这里要说一下这三个点的问题,以此来解释一下为什么在使用fmt.Println()和fmt.Printf()函数时使用逗号将参数隔开的问题。
我们先看一下fmt.Println()和fmt.Printf()的源码
// Println formats using the default formats for its operands and writes to standard output.
// Spaces are always added between operands and a newline is appended.
// It returns the number of bytes written and any write error encountered.
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...)
}
这里看到Println()和Printf()这两个函数其实就一个入参,为什么我能用逗号分隔从而给多个参数呢?
原因是这样的,a ...interface{}
这个其实是slice的一个特殊用法,说明这定义的是一个可变参数,可以接收不定数量的统一类型的参数,定义为...interfaec{}就可以接收不定数量的任意基础类型。定义可变参数时的语法就是在类型前面加上这三个点,这里使用interface就说明可以接收任何类型
想使用这可变参数的语法也很简单,可以将其作为slice使用,也可以继续将其作为可变参数使用。使用可变参数的语法就是在定义的后面加上这三个点。下面看例子
func main(){
definedThreeDot("jack", "rose", "tom", "jerry")//定义多个参数来使用可变参数
}
func definedThreeDot(source ...string) {//定义可变参数,定义时在类型前面加上三个点
useThreeDot(source...)//将可变参数作为可变参数使用,使用时在定义后面加上三个点
useThreeDotAsSlice(source)//将可变参数作为slice使用
}
func useThreeDotAsSlice(ss []string) {//定义slice来接收可变参数
fmt.Println(ss)//直接打印slice
}
func useThreeDot(ss ...string) {//定义可变参数,定义时在类型前面加上三个点
for index, s := range ss {//作为slice来遍历可变参数
fmt.Printf("index : %d, value : %s \n", index, s)//index和s都作为可变参数来使用
}
}
/*
结果:
index : 0, value : jack
index : 1, value : rose
index : 2, value : tom
index : 3, value : jerry
[jack rose tom jerry]
*
/
总结
Go 中的流程控制大致上就这么多,平时项目中使用的也是非常多的,特别是对便利集合时,非常的方便。相信你亲自体验后也会赞不绝口的。
同时也顺带解释了一下可变参数,结合着slice和流程控制也能对这个可变参数有一个更深的了解。
源码可以通过'github.com/souyunkutech/gosample'获取。
关注我们的「微信公众号」
首发微信公众号:Go技术栈,ID:GoStack
版权归作者所有,任何形式转载请联系作者。
作者:搜云库技术团队
出处:https://gostack.souyunku.com/2019/04/29/if-for-switch-select
有疑问加站长微信联系(非本文作者)