go教程2

Go 语言运算符

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

l  算术运算符

l  关系运算符

l  逻辑运算符

l  位运算符

l  赋值运算符

l  其他运算符

算术运算符

下表列出了所有Go语言的算术运算符。假定 A 值为 10,B 值为 20。

运算符

描述

实例

+

相加

A + B 输出结果 30

-

相减

A - B 输出结果 -10

*

相乘

A * B 输出结果 200

/

相除

B / A 输出结果 2

%

求余

B % A 输出结果 0

++

自增

A++ 输出结果 11

--

自减

A-- 输出结果 9

关系运算符

下表列出了所有Go语言的关系运算符。假定 A 值为 10,B 值为 20。

运算符

描述

实例

==

检查两个值是否相等,如果相等返回 True 否则返回 False。

(A == B) 为 False

!=

检查两个值是否不相等,如果不相等返回 True 否则返回 False。

(A != B) 为 True

检查左边值是否大于右边值,如果是返回 True 否则返回 False。

(A > B) 为 False

检查左边值是否小于右边值,如果是返回 True 否则返回 False。

(A < B) 为 True

>=

检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。

(A >= B) 为 False

<=

检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

(A <= B) 为 True

 

逻辑运算符

下表列出了所有Go语言的逻辑运算符。假定 A 值为 True,B 值为 False。

运算符

描述

实例

&&

逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。

(A && B) 为 False

||

逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。

(A || B) 为 True

!

逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。

!(A && B) 为 True

 

位运算符

Go 语言支持的位运算符如下表所示。假定 A 为60,B 为13:

运算符

描述

实例

&

按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。

(A & B) 结果为 12, 二进制为 0000 1100

|

按位或运算符"|"是双目运算符。 其功能是参与运算的两数各对应的二进位相或

(A | B) 结果为 61, 二进制为 0011 1101

^

按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。

(A ^ B) 结果为 49, 二进制为 0011 0001

<< 

左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。

A << 2 结果为 240 ,二进制为 1111 0000

>> 

右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。

A >> 2 结果为 15 ,二进制为 0000 1111

 

赋值运算符

下表列出了所有Go语言的赋值运算符。

运算符

描述

实例

=

简单的赋值运算符,将一个表达式的值赋给一个左值

C = A + B 将 A + B 表达式结果赋值给 C

+=

相加后再赋值

C += A 等于 C = C + A

-=

相减后再赋值

C -= A 等于 C = C - A

*=

相乘后再赋值

C *= A 等于 C = C * A

/=

相除后再赋值

C /= A 等于 C = C / A

%=

求余后再赋值

C %= A 等于 C = C % A

<<=

左移后赋值

C <<= 2 等于 C = C << 2

>>=

右移后赋值

C >>= 2 等于 C = C >> 2

&=

按位与后赋值

C &= 2 等于 C = C & 2

^=

按位异或后赋值

C ^= 2 等于 C = C ^ 2

|=

按位或后赋值

C |= 2 等于 C = C | 2

 

其他运算符

下表列出了Go语言的其他运算符。

运算符

描述

实例

&

返回变量存储地址

&a; 将给出变量的实际地址。

*

指针变量。

*a; 是一个指针变量

 

运算符优先级

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

优先级

运算符

5

* / % << >> & &^

4

+ - | ^

3

== != < <= > >=

2

&&

1

||

 

 

 

1.     函数

这是我们 Golang 系列教程第 6 章,学习 Golang 函数的相关知识。

函数是什么?

函数是一块执行特定任务的代码。一个函数是在输入源基础上,通过执行一系列的算法,生成预期的输出。

函数的声明

在 Go 语言中,函数声明通用语法如下:

func functionname(parametername type) returntype {  
    // 函数体(具体实现的功能)
}

函数的声明以关键词 func 开始,后面紧跟自定义的函数名 functionname (函数名)。函数的参数列表定义在 ( 和 ) 之间,返回值的类型则定义在之后的 returntype (返回值类型)处。声明一个参数的语法采用 参数名 参数类型 的方式,任意多个参数采用类似 (parameter1 type, parameter2 type) (参数1 参数1的类型,参数2 参数2的类型)的形式指定。之后包含在 { 和 } 之间的代码,就是函数体。

函数中的参数列表和返回值并非是必须的,所以下面这个函数的声明也是有效的

func functionname() {  
    // 译注: 表示这个函数不需要输入参数,且没有返回值
}

示例函数

我们以写一个计算商品价格的函数为例,输入参数是单件商品的价格和商品的个数,两者的乘积为商品总价,作为函数的输出值。

func calculateBill(price int, no int) int {  
    var totalPrice = price * no // 商品总价 = 商品单价 * 数量
    return totalPrice // 返回总价
}

上述函数有两个整型的输入 price 和 no,返回值 totalPrice 为 price 和 no 的乘积,也是整数类型。

如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。 例如,price int, no int 可以简写为 price, no int,所以示例函数也可写成

func calculateBill(price, no int) int {  
    var totalPrice = price * no
    return totalPrice
}

现在我们已经定义了一个函数,我们要在代码中尝试着调用它。调用函数的语法为 functionname(parameters)。调用示例函数的方法如下:

calculateBill(10, 5)

完成了示例函数声明和调用后,我们就能写出一个完整的程序,并把商品总价打印在控制台上:

package main
 
import (  
    "fmt"
)
 
func calculateBill(price, no int) int {  
    var totalPrice = price * no
    return totalPrice
}
func main() {  
    price, no := 90, 6 // 定义 price 和 no,默认类型为 int
    totalPrice := calculateBill(price, no)
    fmt.Println("Total price is", totalPrice) // 打印到控制台上
}

运行这个程序

该程序在控制台上打印的结果为

Total price is 540

多返回值

Go 语言支持一个函数可以有多个返回值。我们来写个以矩形的长和宽为输入参数,计算并返回矩形面积和周长的函数 rectProps。矩形的面积是长度和宽度的乘积, 周长是长度和宽度之和的两倍。即:

  • 面积 = *
  • 周长 = 2 * ( + )
package main
 
import (  
    "fmt"
)
 
func rectProps(length, width float64)(float64, float64) {  
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}
 
func main() {  
    area, perimeter := rectProps(10.8, 5.6)
    fmt.Printf("Area %f Perimeter %f", area, perimeter) 
}

运行这个程序

如果一个函数有多个返回值,那么这些返回值必须用 ( 和 ) 括起来。func rectProps(length, width float64)(float64, float64)示例函数有两个 float64 类型的输入参数 length 和 width,并返回两个 float64 类型的值。该程序在控制台上打印结果为

Area 60.480000 Perimeter 32.800000

命名返回值

从函数中可以返回一个命名值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。

上面的 rectProps 函数也可用这个方式写成:

func rectProps(length, width float64)(area, perimeter float64) {  
    area = length * width
    perimeter = (length + width) * 2
    return // 不需要明确指定返回值,默认返回 area, perimeter 的值
}

请注意, 函数中的 return 语句没有显式返回任何值。由于 area 和 perimeter 在函数声明中指定为返回值, 因此当遇到 return 语句时, 它们将自动从函数返回。

空白符

_ 在 Go 中被用作空白符,可以用作表示任何类型的任何值。

我们继续以 rectProps 函数为例,该函数计算的是面积和周长。假使我们只需要计算面积,而并不关心周长的计算结果,该怎么调用这个函数呢?这时,空白符 _ 就上场了。

下面的程序我们只用到了函数 rectProps 的一个返回值 area

package main
 
import (  
    "fmt"
)
 
func rectProps(length, width float64) (float64, float64) {  
    var area = length * width
    var perimeter = (length + width) * 2
    return area, perimeter
}
func main() {  
    area, _ := rectProps(10.8, 5.6) // 返回值周长被丢弃
    fmt.Printf("Area %f ", area)
}

运行这个程序

在程序的 area, _ := rectProps(10.8, 5.6) 这一行,我们看到空白符 _ 用来跳过不要的计算结果。

本章教程到此告一段落了,感谢您的阅读,欢迎您的任何评论和反馈。

 

2.     包

这是 Golang 系列教程的第 7 个教程。

什么是包,为什么使用包?

到目前为止,我们看到的 Go 程序都只有一个文件,文件里包含一个 main 函数和几个其他的函数。在实际中,这种把所有源代码编写在一个文件的方法并不好用。以这种方式编写,代码的重用和维护都会很困难。而包(Package)解决了这样的问题。

包用于组织 Go 源代码,提供了更好的可重用性与可读性。由于包提供了代码的封装,因此使得 Go 应用程序易于维护。

例如,假如我们正在开发一个 Go 图像处理程序,它提供了图像的裁剪、锐化、模糊和彩色增强等功能。一种组织程序的方式就是根据不同的特性,把代码放到不同的包中。比如裁剪可以是一个单独的包,而锐化是另一个包。这种方式的优点是,由于彩色增强可能需要一些锐化的功能,因此彩色增强的代码只需要简单地导入(我们会在随后讨论)锐化功能的包,就可以使用锐化的功能了。这样的方式使得代码易于重用。

我们会逐步构建一个计算矩形的面积和对角线的应用程序。

通过这个程序,我们会更好地理解包。

main 函数和 main 包

所有可执行的 Go 程序都必须包含一个 main 函数。这个函数是程序运行的入口。main 函数应该放置于 main 包中。

package packagename 这行代码指定了某一源文件属于一个包。它应该放在每一个源文件的第一行。

下面开始为我们的程序创建一个 main 函数和 main 包。 Go 工作区内的 src 文件夹中创建一个文件夹,命名为 geometry。在 geometry 文件夹中创建一个 geometry.go 文件。

在 geometry.go 中编写下面代码。

// geometry.go

package main

 

import "fmt"

 

func main() { 

    fmt.Println("Geometrical shape properties")

}

package main 这一行指定该文件属于 main 包。import "packagename" 语句用于导入一个已存在的包。在这里我们导入了 fmt 包,包内含有 Println 方法。接下来是 main 函数,它会打印 Geometrical shape properties。

键入 go install geometry,编译上述程序。该命令会在 geometry 文件夹内搜索拥有 main 函数的文件。在这里,它找到了 geometry.go。接下来,它编译并产生一个名为 geometry (在 windows 下是 geometry.exe)的二进制文件,该二进制文件放置于工作区的 bin 文件夹。现在,工作区的目录结构会是这样:

src

    geometry

        gemometry.go

bin

    geometry

键入 workspacepath/bin/geometry,运行该程序。请用你自己的 Go 工作区来替换 workspacepath。这个命令会执行 bin 文件夹里的 geometry 二进制文件。你应该会输出 Geometrical shape properties。

创建自定义的包

我们将组织代码,使得所有与矩形有关的功能都放入 rectangle 包中。

我们会创建一个自定义包 rectangle,它有一个计算矩形的面积和对角线的函数。

属于某一个包的源文件都应该放置于一个单独命名的文件夹里。按照 Go 的惯例,应该用包名命名该文件夹。

因此,我们在 geometry 文件夹中,创建一个命名为 rectangle 的文件夹。在 rectangle 文件夹中,所有文件都会以 package rectangle 作为开头,因为它们都属于 rectangle 包。

在我们之前创建的 rectangle 文件夹中,再创建一个名为 rectprops.go 的文件,添加下列代码。

// rectprops.go

package rectangle

 

import "math"

 

func Area(len, wid float64) float64 { 

    area := len * wid

    return area

}

 

func Diagonal(len, wid float64) float64 { 

    diagonal := math.Sqrt((len * len) + (wid * wid))

    return diagonal

}

在上面的代码中,我们创建了两个函数用于计算 Area 和 Diagonal。矩形的面积是长和宽的乘积。矩形的对角线是长与宽平方和的平方根。math 包下面的 Sqrt 函数用于计算平方根。

注意到函数 Area 和 Diagonal 都是以大写字母开头的。这是有必要的,我们将会很快解释为什么需要这样做。

导入自定义包

为了使用自定义包,我们必须要先导入它。导入自定义包的语法为 import path。我们必须指定自定义包相对于工作区内 src 文件夹的相对路径。我们目前的文件夹结构是:

src

    geometry

        geometry.go

        rectangle

            rectprops.go

import "geometry/rectangle" 这一行会导入 rectangle 包。

在 geometry.go 里面添加下面的代码:

// geometry.go

package main

 

import ( 

    "fmt"

    "geometry/rectangle" // 导入自定义包

)

 

func main() { 

    var rectLen, rectWidth float64 = 6, 7

    fmt.Println("Geometrical shape properties")

    /*Area function of rectangle package used*/

    fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth))

    /*Diagonal function of rectangle package used*/

    fmt.Printf("diagonal of the rectangle %.2f ", rectangle.Diagonal(rectLen, rectWidth))

}

上面的代码导入了 rectangle 包,并调用了里面的 Area 和 Diagonal 函数,得到矩形的面积和对角线。Printf 内的格式说明符 %.2f 会将浮点数截断到小数点两位。应用程序的输出为:

Geometrical shape properties 

area of rectangle 42.00 

diagonal of the rectangle 9.22

导出名字(Exported Names)

       我们将 rectangle 包中的函数 Area 和 Diagonal 首字母大写。在 Go 中这具有特殊意义。在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。其它包只能访问被导出的函数和变量。在这里,我们需要在 main 包中访问 Area 和 Diagonal 函数,因此会将它们的首字母大写。

在 rectprops.go 中,如果函数名从 Area(len, wid float64) 变为 area(len, wid float64),并且在 geometry.go 中, rectangle.Area(rectLen, rectWidth) 变为 rectangle.area(rectLen, rectWidth), 则该程序运行时,编译器会抛出错误 geometry.go:11: cannot refer to unexported name rectangle.area。因为如果想在包外访问一个函数,它应该首字母大写。

       golang中根据首字母的大小写来确定可以访问的权限。无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用,可以简单的理解成,首字母大写是公有的,首字母小写是私有的。

 

init 函数

所有包都可以包含一个 init 函数。init 函数不应该有任何返回值类型和参数,在我们的代码中也不能显式地调用它。init 函数的形式如下:

func init() { 

}

init 函数可用于执行初始化任务,也可用于在开始执行之前验证程序的正确性。

包的初始化顺序如下:

  1. 首先初始化包级别(Package Level)的变量
  2. 紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。

如果一个包导入了另一个包,会先初始化被导入的包。

尽管一个包可能会被导入多次,但是它只会被初始化一次。

为了理解 init 函数,我们接下来对程序做了一些修改。

首先在 rectprops.go 文件中添加了一个 init 函数。

// rectprops.go

package rectangle

 

import "math" 

import "fmt"

 

/*

 * init function added

 */

func init() { 

    fmt.Println("rectangle package initialized")

}

func Area(len, wid float64) float64 { 

    area := len * wid

    return area

}

 

func Diagonal(len, wid float64) float64 { 

    diagonal := math.Sqrt((len * len) + (wid * wid))

    return diagonal

}

我们添加了一个简单的 init 函数,它仅打印 rectangle package initialized。

现在我们来修改 main 包。我们知道矩形的长和宽都应该大于 0,我们将在 geometry.go 中使用 init 函数和包级别的变量来检查矩形的长和宽。

修改 geometry.go 文件如下所示:

// geometry.go

package main

 

import ( 

    "fmt"

    "geometry/rectangle" // 导入自定义包

    "log"

)

/*

 * 1. 包级别变量

*/

var rectLen, rectWidth float64 = 6, 7

 

/*

*2. init 函数会检查长和宽是否大于0

*/

func init() { 

    println("main package initialized")

    if rectLen < 0 {

        log.Fatal("length is less than zero")

    }

    if rectWidth < 0 {

        log.Fatal("width is less than zero")

    }

}

 

func main() { 

    fmt.Println("Geometrical shape properties")

    fmt.Printf("area of rectangle %.2f\n", rectangle.Area(rectLen, rectWidth))

    fmt.Printf("diagonal of the rectangle %.2f ",rectangle.Diagonal(rectLen, rectWidth))

}

我们对 geometry.go 做了如下修改:

  1. 变量 rectLen 和 rectWidth 从 main 函数级别移到了包级别。
  2. 添加了 init 函数。当 rectLen 或 rectWidth 小于 0 时,init 函数使用 log.Fatal 函数打印一条日志,并终止了程序。

main 包的初始化顺序为:

  1. 首先初始化被导入的包。因此,首先初始化了 rectangle 包。
  2. 接着初始化了包级别的变量 rectLen 和 rectWidth
  3. 调用 init 函数。
  4. 最后调用 main 函数。

当运行该程序时,会有如下输出。

rectangle package initialized 

main package initialized 

Geometrical shape properties 

area of rectangle 42.00 

diagonal of the rectangle 9.22

果然,程序会首先调用 rectangle 包的 init 函数,然后,会初始化包级别的变量 rectLen 和 rectWidth。接着调用 main 包里的 init 函数,该函数检查 rectLen 和 rectWidth 是否小于 0,如果条件为真,则终止程序。我们会在单独的教程里深入学习 if 语句。现在你可以认为 if rectLen < 0 能够检查 rectLen 是否小于 0,并且如果是,则终止程序。rectWidth 条件的编写也是类似的。在这里两个条件都为假,因此程序继续执行。最后调用了 main 函数。

让我们接着稍微修改这个程序来学习使用 init 函数。

将 geometry.go 中的 var rectLen, rectWidth float64 = 6, 7 改为 var rectLen, rectWidth float64 = -6, 7。我们把 rectLen初始化为负数。

现在当运行程序时,会得到:

rectangle package initialized 

main package initialized 

2017/04/04 00:28:20 length is less than zero

像往常一样, 会首先初始化 rectangle 包,然后是 main 包中的包级别的变量 rectLen 和 rectWidth。rectLen 为负数,因此当运行 init 函数时,程序在打印 length is less than zero 后终止。

本代码可以在 github 下载。

使用空白标识符(Blank Identifier)

导入了包,却不在代码中使用它,这在 Go 中是非法的。当这么做时,编译器是会报错的。其原因是为了避免导入过多未使用的包,从而导致编译时间显著增加。将 geometry.go 中的代码替换为如下代码:

// geometry.go

package main

 

import (

    "geometry/rectangle" // 导入自定的包

)

func main() {

 

}

上面的程序将会抛出错误 geometry.go:6: imported and not used: "geometry/rectangle"。

然而,在程序开发的活跃阶段,又常常会先导入包,而暂不使用它。遇到这种情况就可以使用空白标识符 _。

下面的代码可以避免上述程序的错误:

package main

 

import ( 

    "geometry/rectangle"

)

 

var _ = rectangle.Area // 错误屏蔽器

 

func main() {

 

}

var _ = rectangle.Area 这一行屏蔽了错误。我们应该了解这些错误屏蔽器(Error Silencer)的动态,在程序开发结束时就移除它们,包括那些还没有使用过的包。由此建议在 import 语句下面的包级别范围中写上错误屏蔽器。

有时候我们导入一个包,只是为了确保它进行了初始化,而无需使用包中的任何函数或变量。例如,我们或许需要确保调用了 rectangle 包的 init 函数,而不需要在代码中使用它。这种情况也可以使用空白标识符,如下所示。

package main

 

import (

    _ "geometry/rectangle"

)

func main() {

 

}

运行上面的程序,会输出 rectangle package initialized。尽管在所有代码里,我们都没有使用这个包,但还是成功初始化了它。

包的介绍到此结束。希望您喜欢本篇,请留下您的宝贵的反馈和评论。

 

3.     if-else 语句

这是我们 Golang 系列教程的第 8 篇。

if 是条件语句

if 语句的语法是

if condition {  
}

如果 condition 为真,则执行 { 和 } 之间的代码。

不同于其他语言,例如 C 语言,Go 语言里的 { } 是必要的,即使在 { } 之间只有一条语句。

if 语句还有可选的 else if 和 else 部分。

if condition {  
} else if condition {
} else {
}

if-else 语句之间可以有任意数量的 else if。条件判断顺序是从上到下。如果 if 或 else if 条件判断的结果为真,则执行相应的代码块。 如果没有条件为真,则 else 代码块被执行。

让我们编写一个简单的程序来检测一个数字是奇数还是偶数。

package main
 
import (  
    "fmt"
)
 
func main() {  
    num := 10
    if num % 2 == 0 { //checks if number is even
        fmt.Println("the number is even") 
    }  else {
        fmt.Println("the number is odd")
    }
}

在线运行程序

if num2 == 0 语句检测 num 取 2 的余数是否为零。 如果是为零则打印输出 "the number is even",如果不为零则打印输出 "the number is odd"。在上面的这个程序中,打印输出的是 the number is even

if 还有另外一种形式,它包含一个 statement 可选语句部分,该组件在条件判断之前运行。它的语法是

if statement; condition {  
}

让我们重写程序,使用上面的语法来查找数字是偶数还是奇数。

package main
 
import (  
    "fmt"
)
 
func main() {  
    if num := 10; num % 2 == 0 { //checks if number is even
        fmt.Println(num,"is even") 
    }  else {
        fmt.Println(num,"is odd")
    }
}

在线运行程序

在上面的程序中,num 在 if 语句中进行初始化,num 只能从 if 和 else 中访问。也就是说 num 的范围仅限于 if else 代码块。如果我们试图从其他外部的 if 或者 else 访问 num,编译器会不通过。

让我们再写一个使用 else if 的程序。

package main
 
import (  
    "fmt"
)
 
func main() {  
    num := 99
    if num <= 50 {
        fmt.Println("number is less than or equal to 50")
    } else if num >= 51 && num <= 100 {
        fmt.Println("number is between 51 and 100")
    } else {
        fmt.Println("number is greater than 100")
    }
 
}

在线运行程序

在上面的程序中,如果 else if num >= 51 && num <= 100 为真,程序将输出 number is between 51 and 100

获取免费的 Golang 工具

一个注意点

else 语句应该在 if 语句的大括号 } 之后的同一行中。如果不是,编译器会不通过。

让我们通过以下程序来理解它。

package main
 
import (  
    "fmt"
)
 
func main() {  
    num := 10
    if num % 2 == 0 { //checks if number is even
        fmt.Println("the number is even") 
    }  
    else {
        fmt.Println("the number is odd")
    }
}

在线运行程序

在上面的程序中,else 语句不是从 if 语句结束后的 } 同一行开始。而是从下一行开始。这是不允许的。如果运行这个程序,编译器会输出错误,

main.go:12:5: syntax error: unexpected else, expecting }

出错的原因是 Go 语言的分号是自动插入。你可以在这里阅读分号插入规则 https://golang.org/ref/spec#Semicolons

在 Go 语言规则中,它指定在 } 之后插入一个分号,如果这是该行的最终标记。因此,在if语句后面的 } 会自动插入一个分号。

实际上我们的程序变成了

if num%2 == 0 {  
      fmt.Println("the number is even") 
};  //semicolon inserted by Go
else {  
      fmt.Println("the number is odd")
}

分号插入之后。从上面代码片段可以看出第三行插入了分号。

由于 if{…} else {…} 是一个单独的语句,它的中间不应该出现分号。因此,需要将 else 语句放置在 } 之后处于同一行中。

我已经重写了程序,将 else 语句移动到 if 语句结束后 } 的后面,以防止分号的自动插入。

package main
 
import (  
    "fmt"
)
 
func main() {  
    num := 10
    if num%2 == 0 { //checks if number is even
        fmt.Println("the number is even") 
    } else {
        fmt.Println("the number is odd")
    }
}

在线运行程序

现在编译器会很开心,我们也一样 ?。

 

4.     循环

这是 Golang 系列教程的第 9 部分。

循环语句是用来重复执行某一段代码。

for 是 Go 语言唯一的循环语句。Go 语言中并没有其他语言比如 C 语言中的 while 和 do while 循环。

for 循环语法

for initialisation; condition; post {  
}

初始化语句只执行一次。循环初始化后,将检查循环条件。如果条件的计算结果为 true ,则 {} 内的循环体将执行,接着执行 post 语句。post 语句将在每次成功循环迭代后执行。在执行 post 语句后,条件将被再次检查。如果为 true, 则循环将继续执行,否则 for 循环将终止。(译注:这是典型的 for 循环三个表达式,第一个为初始化表达式或赋值语句;第二个为循环条件判定表达式;第三个为循环变量修正表达式,即此处的 post )

这三个组成部分,即初始化,条件和 post 都是可选的。让我们看一个例子来更好地理解循环。

例子

让我们用 for 循环写一个打印出从 1 到 10 的程序。

package main
 
import (  
    "fmt"
)
 
func main() {  
    for i := 1; i <= 10; i++ {
        fmt.Printf(" %d",i)
    }
}

Run in playground

在上面的程序中,i 变量被初始化为 1。条件语句会检查 i 是否小于 10。如果条件成立,i 就会被打印出来,否则循环就会终止。循环语句会在每一次循环完成后自增 1。一旦 i 变得比 10 要大,循环中止。

上面的程序会打印出 1 2 3 4 5 6 7 8 9 10 。

在 for 循环中声明的变量只能在循环体内访问,因此 i 不能够在循环体外访问。

break

break 语句用于在完成正常执行之前突然终止 for 循环,之后程序将会在 for 循环下一行代码开始执行。

让我们写一个从 1 打印到 5 并且使用 break 跳出循环的程序。

package main
 
import (  
    "fmt"
)
 
func main() {  
    for i := 1; i <= 10; i++ {
        if i > 5 {
            break //loop is terminated if i > 5
        }
        fmt.Printf("%d ", i)
    }
    fmt.Printf("\nline after for loop")
}

Run in playground

在上面的程序中,在循环过程中 i 的值会被判断。如果 i 的值大于 5 然后 break 语句就会执行,循环就会被终止。打印语句会在 for循环结束后执行,上面程序会输出为

1 2 3 4 5  
line after for loop

continue

continue 语句用来跳出 for 循环中当前循环。在 continue 语句后的所有的 for 循环语句都不会在本次循环中执行。循环体会在一下次循环中继续执行。

让我们写一个打印出 1 到 10 并且使用 continue 的程序。

package main
 
import (  
    "fmt"
)
 
func main() {  
    for i := 1; i <= 10; i++ {
        if i%2 == 0 {
            continue
        }
        fmt.Printf("%d ", i)
    }
}

Run in playground

在上面的程序中,这行代码 if i%2==0 会判断 i 除以 2 的余数是不是 0,如果是 0,这个数字就是偶数然后执行 continue 语句,从而控制程序进入下一个循环。因此在 continue 后面的打印语句不会被调用而程序会进入一下个循环。上面程序会输出 1 3 5 7 9

更多例子

让我们写更多的代码来演示 for 循环的多样性吧

下面这个程序打印出从 0 到 10 所有的偶数。

package main
 
import (  
    "fmt"
)
 
func main() {  
    i := 0
    for ;i <= 10; { // initialisation and post are omitted
        fmt.Printf("%d ", i)
        i += 2
    }
}

Run in playground

正如我们已经知道的那样,for 循环的三部分,初始化语句、条件语句、post 语句都是可选的。在上面的程序中,初始化语句和 post 语句都被省略了。i 在 for 循环外被初始化成了 0。只要 i<=10 循环就会被执行。在循环中,i 以 2 的增量自增。上面的程序会输出 0 2 4 6 8 10

上面程序中 for 循环中的分号也可以省略。这个格式的 for 循环可以看作是二选一的 for while 循环。上面的程序可以被重写成:

package main
 
import (  
    "fmt"
)
 
func main() {  
    i := 0
    for i <= 10 { //semicolons are ommitted and only condition is present
        fmt.Printf("%d ", i)
        i += 2
    }
}

Run in playground

在 for 循环中可以声明和操作多个变量。让我们写一个使用声明多个变量来打印下面序列的程序。

10 * 1 = 10  
11 * 2 = 22  
12 * 3 = 36  
13 * 4 = 52  
14 * 5 = 70  
15 * 6 = 90  
16 * 7 = 112  
17 * 8 = 136  
18 * 9 = 162  
19 * 10 = 190
package main
 
import (  
    "fmt"
)
 
func main() {  
    for no, i := 10, 1; i <= 10 && no <= 19; i, no = i+1, no+1 { //multiple initialisation and increment
        fmt.Printf("%d * %d = %d\n", no, i, no*i)
    }
 
}

Run in playground

在上面的程序中 no 和 i 被声明然后分别被初始化为 10 和 1 。在每一次循环结束后 no 和 i 都自增 1 。布尔型操作符 && 被用来确保 i 小于等于 10 并且 no 小于等于 19 。

无限循环

无限循环的语法是:

for {  
}

下一个程序就会一直打印Hello World不会停止。

package main
 
import "fmt"
 
func main() {  
    for {
        fmt.Println("Hello World")
    }
}

如果你打算在 go playground 里尝试上面的程序,你会得到一个“过程耗时太长”的错误。请尝试在你本地系统上运行,来无限的打印 “Hello World” 。

这里还有一个 range 结构,它可以被用来在 for 循环中操作数组对象。当我们学习数组时我们会补充这方面内容。

这就是 for 循环的全部内容。希望您能享受本次阅读。请留下您宝贵的意见和建议。

5.     switch语句

这是 Golang 系列教程中的第 10 篇。

switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。它可以被认为是替代多个 if else 子句的常用方式。

看代码比文字更容易理解。让我们从一个简单的例子开始,它将把一个手指的编号作为输入,然后输出该手指对应的名字。比如 0 是拇指,1 是食指等等。

package main
 
import (
    "fmt"
)
 
func main() {
    finger := 4
    switch finger {
    case 1:
        fmt.Println("Thumb")
    case 2:
        fmt.Println("Index")
    case 3:
        fmt.Println("Middle")
    case 4:
        fmt.Println("Ring")
    case 5:
        fmt.Println("Pinky")
 
    }
}

在线运行程序

在上述程序中,switch finger 将 finger 的值与每个 case 语句进行比较。通过从上到下对每一个值进行对比,并执行与选项值匹配的第一个逻辑。在上述样例中, finger 值为 4,因此打印的结果是 Ring 。

在选项列表中,case 不允许出现重复项。如果您尝试运行下面的程序,编译器会报这样的错误: main.go182:在tmp / sandbox887814166 / main.go167

package main
 
import (
    "fmt"
)
 
func main() {
    finger := 4
    switch finger {
    case 1:
        fmt.Println("Thumb")
    case 2:
        fmt.Println("Index")
    case 3:
        fmt.Println("Middle")
    case 4:
        fmt.Println("Ring")
    case 4://重复项
        fmt.Println("Another Ring")
    case 5:
        fmt.Println("Pinky")
 
    }
}

在线运行程序

默认情况(Default Case)

我们每个人一只手只有 5 个手指。如果我们输入了不正确的手指编号会发生什么?这个时候就应该是属于默认情况。当其他情况都不匹配时,将运行默认情况。

package main
 
import (
    "fmt"
)
 
func main() {
    switch finger := 8; finger {
    case 1:
        fmt.Println("Thumb")
    case 2:
        fmt.Println("Index")
    case 3:
        fmt.Println("Middle")
    case 4:
        fmt.Println("Ring")
    case 5:
        fmt.Println("Pinky")
    default: // 默认情况
        fmt.Println("incorrect finger number")
    }
}

在线运行程序

在上述程序中 finger 的值是 8,它不符合其中任何情况,因此会打印 incorrect finger numberdefault 不一定只能出现在 switch 语句的最后,它可以放在 switch 语句的任何地方

您可能也注意到我们稍微改变了 finger 变量的声明方式。finger 声明在了 switch 语句内。在表达式求值之前,switch 可以选择先执行一个语句。在这行 switch finger= 8; finger 中, 先声明了finger 变量,随即在表达式中使用了它。在这里,finger 变量的作用域仅限于这个 switch 内。

多表达式判断

通过用逗号分隔,可以在一个 case 中包含多个表达式。

package main
 
import (
    "fmt"
)
 
func main() {
    letter := "i"
    switch letter {
    case "a", "e", "i", "o", "u": // 一个选项多个表达式
        fmt.Println("vowel")
    default:
        fmt.Println("not a vowel")
    }
}

在线运行程序

在 case "a","e","i","o","u": 这一行中,列举了所有的元音。只要匹配该项,则将输出 vowel

无表达式的 switch

在 switch 语句中,表达式是可选的,可以被省略。如果省略表达式,则表示这个 switch 语句等同于 switch true,并且每个 case 表达式都被认定为有效,相应的代码块也会被执行。

package main
 
import (
    "fmt"
)
 
func main() {
    num := 75
    switch { // 表达式被省略了
    case num >= 0 && num <= 50:
        fmt.Println("num is greater than 0 and less than 50")
    case num >= 51 && num <= 100:
        fmt.Println("num is greater than 51 and less than 100")
    case num >= 101:
        fmt.Println("num is greater than 100")
    }
 
}

在线运行程序

在上述代码中,switch 中缺少表达式,因此默认它为 true,true 值会和每一个 case 的求值结果进行匹配。case num >= 51 && <= 100:为 true,所以程序输出 num is greater than 51 and less than 100。这种类型的 switch 语句可以替代多个 if else 子句。

Fallthrough 语句

在 Go 中,每执行完一个 case 后,会从 switch 语句中跳出来,不再做后续 case 的判断和执行。使用 fallthrough 语句可以在已经执行完成的 case 之后,把控制权转移到下一个 case 的执行代码中

让我们写一个程序来理解 fallthrough。我们的程序将检查输入的数字是否小于 50、100 或 200。例如我们输入 75,程序将输出75 is lesser than 100 和 75 is lesser than 200。我们用 fallthrough 来实现了这个功能。

package main
 
import (
    "fmt"
)
 
func number() int {
    num := 15 * 5 
    return num
}
 
func main() {
 
    switch num := number(); { // num is not a constant
    case num < 50:
        fmt.Printf("%d is lesser than 50\n", num)
        fallthrough
    case num < 100:
        fmt.Printf("%d is lesser than 100\n", num)
        fallthrough
    case num < 200:
        fmt.Printf("%d is lesser than 200", num)
    }
 
}

在线运行程序

switch 和 case 的表达式不一定是常量。它们也可以在运行过程中通过计算得到。在上面的程序中,num 被初始化为函数 number() 的返回值。程序运行到 switch 中时,会计算出 case 的值。case num < 100 的结果为 true,所以程序输出 75 is lesser than 100。当执行到下一句 fallthrough 时,程序控制直接跳转到下一个 case 的第一个执行逻辑中,所以打印出 75 is lesser than 200。最后这个程序的输出会是

75 is lesser than 100  
75 is lesser than 200

fallthrough 语句应该是 case 子句的最后一个语句。如果它出现在了 case 语句的中间,编译器将会报错:fallthrough statement out of place

这也是我们本教程的最后内容。还有一种 switch 类型称为 type switch 。我们会在学习接口的时候再研究这个。

希望您能享受本次阅读。请留下您宝贵的意见和建议。

posted @ 2024-01-23 10:28  易先讯  阅读(2)  评论(0编辑  收藏  举报