7

作为一门高级语言,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/...


架构师专栏
6.2k 声望7k 粉丝