读《Go并发编程实战》第4章 流程控制方式

       说实话,该书前面讲的枯燥冗长,看的有点打瞌睡,而我本身又是有一个有强迫症的人,喜欢一个字一个字地抠,最终结果是一看就困,而后转天再看再困,依次循环......。编程

       这就总会让我本身有点遐想,本身也写一本关于Go的书算了,但由于平时真的太忙了,稍有时间时又贡献给我女儿。我想后面我录一些视频,帮助那些想学习编程的人快速入门,打消非计算机专业人员的入门门槛。编程语言

       好了,废话有点多,仍是耐着性子把做者郝林的这本书看完。ide


       Go语言与其它编程语言相似,也有if语句、switch语句、for循环语句、goto语句,但不一样之处在于Go语言的循环语句只有for,没有平时咱们见的while、do-while;在其它编程语言中咱们常常听到尽可能避免使用goto语句,而读过谢孟军的beego代码的人应该能看到谢大神使用了很多goto语句;此外,Go语言还有defer语句、针对异常处理引入了panic和recover语句等,本章就基本围绕着这些展开。函数


4.1 基本流程控制学习

首先看一个源代码:字体

package main

import (
    "fmt"
)

func main(){
    v := []int{1, 2, 3}
    if v != nil{
         var v int = 123
         fmt.Printf("%v\n", v)
    }
    fmt.Printf("%v\n", v)
}

       第一次看Go代码的人可能会有点不舒服,不过没有关系,看的多了就舒服了,掌握编程语言最大的诀窍就是多写,随便解释一下:动画

  • package和import不用多说,属于工程化思想中的体系部分ui

  • v := []int{1,2,3},上看下看左看右看,也没有看到v的定义呢?其实这里:=就是定义加赋值spa

       好,偏离原文太多了,原文在这里主要说代码块和做用域。视频


4.1.1 代码块和做用域

       所谓代码块就是一个由花括号“{”和“}”括起来的若干表达式和语句的序列。固然,代码块中也能够不包含任何代码 ,即为空代码块。就上面源代码为例,代码块就是main()函数中的内容。

       那么这个程序运行结果是什么呢?结果以下:

       123

       [1  2  3]

       之因此两次打印内容不一样,就是因为做用域的缘由,先采用做者的原话。

       “咱们能够在某个代码块中对一个已经在包含它的外层代码块中声明过的标识符进行重声明。而且,当咱们在内层代码块中使用这个标识符的时候,它表明的老是它在内层代码块中被重声明时与它绑定在一块儿的那个程序实体。也就是说,在这种状况下,在外层代码中声明的那个同名标识符被屏蔽了。

       看懂了吗?是否是有点绕死了?脑子稍短路一下就死机了,用人话来讲是这样的:   

       main()函数是一个大的代码块,它里面又包括了一个小的代码块(对应的if语句),这至关于一间大房子(main)里面有一个小卧室(if{}),大房子客厅里有一个叫郝林的人,小卧室中也有一个叫郝林的人,且房子隔音效果很是好。当你进入小卧室时轻轻地喊一声“郝林”,那么是小卧室的郝林会回应你,由于大房子客厅中的那个郝林听不到;一样地,当你进入大房子客厅时,你轻轻地喊一声“郝林”,那么是大房子客厅的郝林会回应你,由于小房子中的那个郝林听不到。

       若是您学过程序编译原理的话,就很容易明白其中的原理,由于在编译时,这根本就是两个不一样的变量,此处再也不展开,因此有时候想一想视频有市场是应当的,若是是视频两句话就能交待清楚,但文字就得啰嗦不少。


if 100 < number{
     number++
}

这个是一个if语句,固然也符合代码块的定义,由花括号括起来的若干表达式和语句的序列。

{
}

这个也是一个代码块,尽快没有任何内容,由花括号括起来的若干表达式和语句的序列,这里的若干包括0。

for i :=0; i <100; i++ {
    i = i + 4
}

一样,这也是一个代码块。


做者又说,在3.3.3节讲过,基本数据类型的值都没法与空值nil进行判等,而上面源代码if v != nil { }就没有编译错误,是由于这里的v表明的是一个切片类型而不是一个string类型值。这里稍有点要讲解的内容,通常语言是没有切片的,这是一个特别的类型,回头我作成一个演示动画一看就很清楚明白。


4.1.2 if语句

      “Go语言的if语句老是以关键字if开始,在这以后,能够跟一条简单语句(这里能够的意思是说,也能够没有),而后是一个做为条件判断的布尔类型的表达式以及一个用花括号“{”和“}”括起来的代码块”。

       上面这句话比较常,简单地理解,就是if语句必须是这种形式“if +条件判断 + {}”,举两个粟子:

var i int = 0

if i < 10 {
     i++
}

这个if语句就是典型的if+条件判断+{ },其中条件判断为i<10。固然这个if语句还能够这样写:

if i:=0; i <10 {
     i++
}

这个if语句就是上面蓝色字体中说的,在if以后能够跟一条简单语句i:=0,即变量i的定义和赋值语句。固然这个if语句还能够改写为:

if i:=0; i <10; i++{

}

这里把i++也放到了if和“{”之间。引用一下原书内容:

经常使用的简单语句包括短变量声明、赋值语句和表达式语句。除了特殊的内建函数和代码包unsafe中的函数,针对其余函数和方法的调用表达式和针对通道类型值的接收表达式均可以出如今语句上下文中。换名话说,它们均可以称为表达式语句。在必要时,咱们还可使用圆括号“(”和“)”将它们括起来。其余的简单语句还包括发送语句、自增/自减语句和空语句”。

看懂没有?若没有看懂就算了,本书做者定义严谨,形成没有接触过的人很难理解 :)


       下面引用做者的一个例子:

if 100 < number{
    number++
} else {
    number--
}

       可能没有接触过的编程语言的小伙伴会问,怎么还有else呀?这还算不算if语句,哥告诉你,这才是正宗的,别无二家,回到原书内容。“可能读者已经注意到了,其中的条件表达式100<number并无被圆括号括起来。实际上,这也是Go语言的流程控制语句的特色之一。另外,跟在条件表达式和else关键以后的两个代码块必须由“{”和“}”括起来。这一点是强制的,不论代码包含几条语句以及是否包含语句都是如此”。

       这些东西没有必要特别记忆,多写几个例子,天然而然您就知道什么是正确的什么是错误的。

       “上面示例中,有两个特殊符号:++和--。它们分别表明了自增语句和自减语句。注意它们并非操做符。++的做用是把它左边的标识符表明的值与无类型常量1相加并将结果再赋给左边的标识符,而--的做用则是把它左边的标识符表明的值与无类型常量1相减并将结果再赋给左边的标识符。也就是说,自增语句number++与赋值语句number=number+1具备相同的语义,而自减语句number--则与赋值语句number=number-1具备相同的语义。另外,在++和--左边的并不只限于标识符,还能够是任何可被赋值的表达式,好比应用在切片类型值或字典类型值之上的索引表达式"。


【更多惯用法】:

这有点相似英语经常使用对话300句。

func Open(name string) (file *File, err error)

       这个就是常见的函数惯用用法,该函数来自标准库中os包。

       因为在Go语言中一个函数能够返回多个结果,所以咱们经常会把函数执行的错误也做为返回结果之一,该函数表达意思是说,您指定一个文件路径让Go语言帮您把文件内容读出来,为了读出文件内容,该函数返回您文件的句柄(即第一个参数),同时也返回一个错误(即第二个参数),用以表达在打开文件时是否发生了错误。具体怎么用呢?

f, err := os.Open(name)

if err != nil {
     return err
}

绕了半天后,仍是回到if语句上。“总之,if语句常被用来常规错误”。

       “在一般状况下,咱们应该先云检查变量err的值是否为nil,若是变量err的值不为nil,那么就说明os.Open函数在被执行过程当中发生了错误。这时的f变量的值确定是不可用的。这已是一个约定俗成的规则了”。

        “另外,if语句常被做为卫述语句。卫述语句是指被用来检查关键的先决条件的合法性并在检查未经过的状况下当即终止当前代码块的执行的语句。其实,在上一个示例中的if语句就是卫述语句中的一种。它在有错误发生的时候当即终止了当前代码块的执行并将错误返回给外层代码块”。

       经过理解一下这段蓝色的文字,一般咱们写程序是这样的:

func update(id int, deptment string) bool {
     if id <=0 {
          return false
     }
     // 省略若干行
     return true
}

这个没毛病,update函数开始处的那个if语句就是卫述语句。该函数能够稍改造一下:

func update(id int, deptment string) error {
     if id <=0 {
         return errors.New("The id is INVALID!")
     }
     // 省略若干行
     return nil
}

下面这个update返回结果再也不是bool值,而是error值,它能够表示在函数执行期间是否发生了错误,并且还能够体现出错误的具体描述。


4.1.3 switch语句

       switch语句与if语句相似,都是一种多分支执行语句,刚接触编程的人可能有疑惑,为何要提供两种呢?我是否只用一个就能够了?

       固然,您只用其中之一就足够了,为何要提供两种呢?简单理解仍是惯用习惯吧。

§1.  组成和编写方法

    “switch可使用表达式或者类型说明符做为case断定方法。所以switch语句也就能够分为两类:表达式switch语句和类型switch语句。在表达式switch语句中,每个case携带的表达式都会与switch语句要断定的那个表达式(也称为switch表达式)相比较。而在类型switch语句中,每一个case所携带的不是表达式而是类型字面量,而且switch语句要断定的目标也变成了一个特殊的表达式。这个特殊表达式的结果是一个类型而不是一个类型值。

       在女儿的哭声中我读这句话真的好吃力,静下心来也不难理解,看例子就好:


§2.  表达式switch语句

switch 2*3+5{
    default:
         fmt.Println("运算错误!")
    case 5 + 5:
         fmt.Println("结果为10.")
    case 5 + 6:
         fmt.Println("结果为11.")
}

       快看,快看,switch后面的2*3+5是一个表达式,第一个case后面的5+5也是一个表达式,第二个case后面的5+6也是一个表达式,因此这是一个典型的表达式switch语句。

       在表达式switch语句中,switch表达式和case携带的表达式都会被求值。

       程序运行时,先找计算第一个case后面的表达式获得10,而后与switch的表达式值11进行比较,发现10≠11,接着计算第二个case后面的表达式获得11,而后与switch的表达式值11进行比较,发现二者相同,打印出“结果为11.”后就退出该代码块。

switch content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fmt.Println("An Interpreted Language.")
    case "Go":
        fmt.Println("A Compiled Language.")
}

       这也是一个表达式switch语句,您可能会想这都没有计算,怎么是一个表达式呢?

       姐,表达式不只仅是数学运算,字符串也是哟,若是您实在感受不顺眼,改造一下:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fmt.Println("An Interpreted Language.")
    case "Go":
        fmt.Println("A Compiled Language.")
}

      在这个示例中,switch语句先调用getConent()函数,而且把它的结果赋给了新声明的变量content,后面紧接着的就是对content的值进行断定。看着像是表达式switch吗?


     “如今来看case语句。一条case语句由一个case表达式和一个语句列表组成,而且这二者之间须要用冒号“:”分隔,在上例的switch语句中,一共有3个case语句,注意default case是一种特殊的case语句。

     “一个case表达式由一个case关键字和一个表达式列表组成。注意,这里说的是一个表达式列表,而不是一个表达式。这意味着,一个case表达式中能够包含多个表达式。如今,咱们利用这一特性来改造一下上面的switch语句:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python", "Ruby":
        fmt.Println("An Interpreted Language.")
    case "Go", "Java", "C":
        fmt.Println("A Compiled Language.")
}

其中"Python"和"Ruby"造成一个表达式列表放到了case后面,同理"Go"、"Java"和"C"也造成一个表达式列表放到了另外一个case后面。


因为Go语言有一个fallthrough关键字,因此上面示例可改造以下:

switch content := getContent(); content {
    default:
        fmt.Println("Unknown Language.")
    case "Python":
        fallthrough
    case "Ruby":
        fmt.Println("An Interpreted Language.")
    case "Go", "Java", "C":
        fmt.Println("A Compiled Language.")
}

当content内容为"Python"时,尽管会匹配"Python"对应的case语句,但因为fallthrough关键字的存在,它让程序穿越它而向下执行,因此会打印“An Interpreted Language.”,但必定要记住的是fallthrough只能穿越一次。


§3.  类型switch语句

先看个示例吧:

switch v.(type){
    case string:
        fmt.Printf("The string is '%s'.\n", v.(string))
    case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
        fmt.Printf("The integer is %d.\n", v)
    default:
        fmt.Printf("Unsupported value. (type=%T)\n", v)
}

仔细看case后面的表达式,都是string, int, int8, int16等Go语言的类型,因此所谓类型switch语句就是对类型进行断定,而不是对值进行断定,其余方面与表达式switch通常无二。


把这个代码跑通,须要补充点关于v的内容:

var v interface{} = "aaabbb"

注意这里是把v定义为接口,而非string,即var v string = "aaabbb",若是真的这样定义了v,那么Go的编译器就会抛个异常给你看,并说:“我靠,你都知道是什么类型了,还让switch判断,这是耍我玩呀!”。


如今咱们来具体分析这段示例代码,这个switch语句共包含了3条case语句。

> 第一条case语句的表达式包含了类型string字面量,这意味着若是v的类型是string类型,那么该分支就会被执行。在这个分支中,咱们使用类型断言表达式v.(string)把v的值转换成了string类型的值,并以特定格式打印出来;

> 第二条case语句中的类型字面量有多个,包括了全部的整数类型,这就意味着只要v的类型属于整数类型,该分支就会被执行。在这个分支中,咱们并无使用类型断言表达式把v的值转换成任何一个整数类型的值,而是利用fmt.Printf函数直接打印出了v所表示的整数值;

> 若是v的类型既不是string类型也不是整数类型,那么default case的分支将会被执行,并使用标准输出打印v的动态类型(%T)。


须要特别注意的是:fallthrough语句不容许出如今类型switch语句中的任何case语句的语句列表中


最后,值得特别提出的是,类型switch语句的switch表达式还有一种变形写法。

var v interface{} = "aaabbb"
switch i := v.(type) {
    case string:
        fmt.Printf("The string is '%s'.\n", i)
    case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
        fmt.Printf("The integer is %d.\n", i)
    default:
        fmt.Printf("Unsupport value. (type=%T)\n", i)
}

请注意switch表达式位置上的i := v.(type),这其实是一个短变量声明,当存在这这种形式的switch表达式的时候,就至关于这个变量i被声明在了每一个case语名的语句列表的开始处。在每一个case语句中,变量i的类型都是不一样的,它的类型会和与它处于同一个case语句的case表达式包含的类型字面量所表明的那个类型相等。例如,上面的示例中第一个case语句至关于:

case string:

        i := v.(string)

        fmt.Printf("The string is '%s'.\n", i)


是否是再次被做者的富有九曲十折的表达方式折服?其实做者想表达的意思,简单来讲是这样的:

switch v.(type){

     case string:

            fmt.Printf("The string is '%s'.\n", v.(string))

}

若是switch表达式只是取变量v的类型,那么在case语句中必须把变量v进行强制类型转换;


switch i := v.(type){

     case string:

            fmt.Printf("The string is '%s'.\n", i)

}

若是switch表达式中有对变量v的类型赋值给i,那么当进入某个case语句中时,至关于变量i在每一个case语句中都有一次具体的类型转换,switch那么能够把它理解为模板。


好吧,若是越说越胡涂,请您暂时记住这两种用法就好,随着代码写的愈来愈多,就会逐步明白的。


§4. 更多惯用法

好了,又到了经常使用英语300句了 :)

在很多状况下switch表达式是缺省掉的,即:

switch{
     case number < 100:
          number++
     case number < 200:
          number--
     default:
          number -= 2
}

看这里的switch表达式消失了,此种状况下该switch语句的断定目标被视为布尔值true,也就是说,全部case表达式的结果值都应该是布尔类型,因此才有switch代码块中每一个case语句都是number在与数值进行比较,以得到布尔值。

相关文章
相关标签/搜索