跟着我们学Golang流程控制

11次阅读

共计 6044 个字符,预计需要花费 16 分钟才能阅读完成。

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

正文完
 0