作为一门高级语言,Go同样提供了流程控制的支持。在了解了基础结构之后,继续学习Go的流程控制,里面涉及到的基础结构的内容还能对其有更多的了解。

<!--more-->

说流程控制之前先说一下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 := 10switch 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 {//直接判断casecase 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 = 0for i < len(a) {    fmt.Print(a[i],"  ")    i++}//结果: 1  1  1  3  4  4  6  9  29  43  46  55  89  132

死循环写法更简单了,不过需要注意使用break进行跳出,否则电脑就该嗡嗡嗡~响不停了

i = 0for{    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: 1index: 1, value: 1index: 2, value: 1index: 3, value: 3index: 4, value: 4index: 5, value: 4index: 6, value: 6index: 7, value: 9index: 8, value: 29index: 9, value: 43index: 10, value: 46index: 11, value: 55index: 12, value: 89index: 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: worldkey: 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/...