文章目录

  • 01-HelloWorld
    • 一 建立 Go 工作区
    • 二 运行 Go 程序
      • 2.1 hello world 程序代码介绍
  • 02-开发环境搭建
    • 一 下载地址
    • 二 安装
      • Linux安装
      • Windows安装
      • Mac安装
    • 三 测试安装
    • 四 命令介绍
      • 基本介绍
      • build 和 run 命令
    • get 命令
  • 02-命名规范
    • 一 变量定义规范
    • 二 关键字
    • 三 保留字
    • 四 注意
  • 03-变量
    • 变量是什么
    • 声明单个变量
    • 声明变量并初始化
    • 类型推断(Type Inference)
    • 声明多个变量
    • 简短声明
  • 04-类型
    • bool
    • 有符号整型
    • 无符号整型
    • 浮点型
    • 复数类型
    • 其他数字类型
    • string 类型
    • 类型转换
  • 05-常量
    • 定义
    • 字符串常量
    • 布尔常量
    • 数字常量
    • 数字表达式
  • 06-函数(Function)
    • 函数是什么?
    • 函数的声明
    • 示例函数
    • 多返回值
    • 命名返回值
    • 空白符
  • 07-包
    • 什么是包,为什么使用包?
    • main 函数和 main 包
    • 创建自定义的包
    • 导入自定义包
    • 导出名字(Exported Names)
    • init 函数
    • 使用空白标识符(Blank Identifier)
  • 08-if-else语句
    • 一个注意点
  • 09-循环
    • for 循环语法
    • 例子
    • break
    • continue
    • 更多例子
    • 无限循环
  • 10-switch语句
    • 默认情况(Default Case)
    • 多表达式判断
    • 无表达式的 switch
    • Fallthrough 语句

01-HelloWorld

一 建立 Go 工作区

在编写代码之前,我们首先应该建立 Go 的工作区(Workspace),环境搭建一节,我们已经讲过了。
Mac 或 Linux 操作系统下,Go 工作区应该设置在 HOME/go∗∗。所以我们要在∗∗HOME/go**。所以我们要在 **HOME/go∗∗。所以我们要在∗∗HOME 目录下创建 go 目录。
而在 Windows 下,工作区应该设置在 C:\Users\YourName\go。所以请将 go 目录放置在 C:\Users\YourName
其实也可以通过设置 GOPATH 环境变量,用其他目录来作为工作区。但为了简单起见,我们采用上面提到的放置方法。
所有 Go 源文件都应该放置在工作区里的 src 目录下。请在刚添加的 go 目录下面创建目录 src
所有 Go 项目都应该依次在 src 里面设置自己的子目录。我们在 src 里面创建一个目录 hello 来放置整个 hello world 项目。
创建上述目录之后,其目录结构如下:

gosrchello

在我们刚刚创建的 hello 目录下,在 helloworld.go 文件里保存下面的程序。

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

创建该程序之后,其目录结构如下:

gosrchellohelloworld.go

二 运行 Go 程序

运行 Go 程序有多种方式,我们下面依次介绍。
1.使用 go run 命令 - 在命令提示符旁,输入 go run workspacepath/src/hello/helloworld.go
上述命令中的 workspacepath 应该替换为你自己的工作区路径(Windows 下的 C:/Users/YourName/go,Linux 或 Mac 下的 $HOME/go)。
在控制台上会看见 Hello World 的输出。
2.使用 go install 命令 - 运行 go install hello,接着可以用 workspacepath/bin/hello 来运行该程序。
上述命令中的 workspacepath 应该替换为你自己的工作区路径(Windows 下的 C:/Users/YourName/go,Linux 或 Mac 下的 $HOME/go)。
当你输入 go install hello 时,go 工具会在工作区中搜索 hello 包(hello 称之为包)。接下来它会在工作区的 bin 目录下,创建一个名为 hello(Windows 下名为 hello.exe)的二进制文件。运行 go install hello 后,其目录结构如下所示:

gobinhellosrchellohelloworld.go

2.1 hello world 程序代码介绍

package main //程序首行,必须指明是哪个包 这里表示是main包import "fmt" //示导入fmt包 因为Println函数是在fmt包下//***在函数外只能声明变量,常量,类型定义等,不能写逻辑代码func main() { //定义一个main函数 fmt.Println("Hello World") //输出 Hello World
}/*
package main - 每一个 Go 文件都应该在开头进行 package name 的声明。包(Packages)用于代码的封装与重用,这里的包名称是`main`。import "fmt" - 我们引入了 fmt 包,用于在 main 函数里面打印文本到标准输出。func main() - main 是一个特殊的函数。整个程序就是从 main 函数开始运行的。main 函数必须放置在 main 包中。{和} 分别表示 main 函数的开始和结束部分。fmt.Println("Hello World") - fmt 包中的 Println 函数用于把文本写入标准输出。*/

02-开发环境搭建

[TOC]

一 下载地址

安装包下载地址为:https://golang.org/dl/。
如果打不开可以使用这个地址:https://golang.google.cn/dl/。
各个系统对应的包名:

操作系统 包名
Windows go1.13.3.windows-amd64.msi
Linux go1.13.3.linux-amd64.tar.gz
Mac go1.13.3.darwin-amd64.pkg
FreeBSD go1.13.3.freebsd-amd64.tar.gz

二 安装

Linux安装

1、下载二进制包:go1.13.3.linux-amd64.tar.gz
2、将下载的二进制包解压至 /usr/local目录。

tar -C /usr/local -xzf go1.13.3.linux-amd64.tar.gz

3、将 /usr/local/go/bin 目录添加至PATH环境变量:

export PATH=$PATH:/usr/local/go/bin

Windows安装

Windows 下可以使用 .msi 后缀(在下载列表中可以找到该文件,如go1.13.3.windows-amd64.msi)的安装包来安装。
默认情况下 .msi 文件会安装在 c:\Go 目录下。你可以将 c:\Go\bin 目录添加到 Path 环境变量中。添加后你需要重启命令窗口才能生效

Mac安装

Mac下直接双击go1.13.3.darwin-amd64.pkg,一路下一步安装即可

三 测试安装

1、创建工作目录 C:>Go_Project
2、 创建文件test.go,写入如下代码

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

3、打开cmd,切换目录到C:>Go_Project下,执行如下命令:

go run test.go

看到打印结果:

Hello, World!

四 命令介绍

基本介绍

直接在终端中输入 go help 即可显示所有的 go 命令以及相应命令功能简介,主要有下面这些:

  • build: 编译包和依赖
  • clean: 移除对象文件
  • doc: 显示包或者符号的文档
  • env: 打印go的环境信息
  • bug: 启动错误报告
  • fix: 运行go tool fix
  • fmt: 运行gofmt进行格式化
  • generate: 从processing source生成go文件
  • get: 下载并安装包和依赖
  • install: 编译并安装包和依赖
  • list: 列出包
  • run: 编译并运行go程序
  • test: 运行测试
  • tool: 运行go提供的工具
  • version: 显示go的版本
  • vet: 运行go tool vet

build 和 run 命令

就像其他静态类型语言一样,要执行 go 程序,需要先编译,然后在执行产生的可执行文件。go build 命令就是用来编译 go程序生成可执行文件的。但并不是所以的 go 程序都可以编译生成可执行文件的, 要生成可执行文件,go程序需要满足两个条件:

  • 该go程序需要属于main包
  • 在main包中必须还得包含main函数

也就是说go程序的入口就是 main.main, 即main包下的main函数, 例子(test.go):
编译hello.go,然后运行可执行程序:

$ go run test.go   # 将会生成可执行文件 test
$ ./test           # 运行可执行文件
Hello, World!

上面就是 go build 的基本用法,另外如果使用 go build 编译的不是一个可执行程序,而是一个包,那么将不会生成可执行文件。
go run 命令可以将上面两步并为一步执行(不会产生中间文件)。

$ go run test.go
Hello, World!

上面两个命令都是在开发中非常常用的。
此外 go clean 命令,可以用于将清除产生的可执行程序:

$ go clean    # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean hello.go  # 会删除对应的可执行文件

get 命令

这个命令同样也是很常用的,我们可以使用它来下载并安装第三方包, 使用方式:

go get src

从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装,例如我们想使用 beego 来开发web应用,我们首先就需要获取 beego:

go get github.com/astaxie/beego

这条命令将会自动下载安装 beego 以及它的依赖,然后我们就可以使用下面的方式使用:

package mainimport "github.com/astaxie/beego"   # 这里需要使用 src 下的完整路径func main() {beego.Run()
}

02-命名规范

一 变量定义规范

Go语言中的函数名、变量名、常量名、类型名、语句标号和包名等所有的命名,都遵循一个简单的命名规则:
1 一个名字必须以一个字母(Unicode字母)或下划线开头,后面可以跟任意数量的字母、数字或下划线
2 大写字母和小写字母是不同的:Name和name是两个不同的变量
3 关键字和保留字都不建议用作变量名

二 关键字

Go语言中关键字有25个;关键字不能用于自定义名字,只能在特定语法结构中使用。

break      default       func     interface   select
case       defer         go       map         struct
chan       else          goto     package     switch
const      fallthrough   if       range       type
continue   for           import   return      var

三 保留字

go语言中有37个保留字,主要对应内建的常量、类型和函数

内建常量: true false iota nil内建类型:  int int8 int16 int32 int64uint uint8 uint16 uint32 uint64 uintptrfloat32 float64 complex128 complex64bool byte rune string error内建函数: make len cap new append copy close deletecomplex real imagpanic recover

四 注意

  1. 这些保留字并不是关键字,可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱
  2. 如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的(必须是在函数外部定义的包级名字;包级函数名本身也是包级名字),那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母
  3. 名字的长度没有逻辑限制,但是Go语言的风格是尽量使用短小的名字,对于局部变量尤其是这样;你会经常看到i之类的短名字,而不是冗长的theLoopIndex命名。通常来说,如果一个名字的作用域比较大,生命周期也比较长,那么用长的名字将会更有意义
  4. 在习惯上,Go语言程序员推荐使用 驼峰式 命名,当名字由几个单词组成时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml
  5. go文件的名字,建议用下划线的方式命名(参见go源码)

03-变量

变量是什么

变量指定了某存储单元(Memory Location)的名称,该存储单元会存储特定类型的值。在 Go 中,有多种语法用于声明变量。

声明单个变量

var name type 是声明单个变量的语法。

package mainimport "fmt"func main() {var age int // 变量声明fmt.Println("my age is", age)
}

语句 var age int 声明了一个 int 类型的变量,名字为 age。我们还没有给该变量赋值。如果变量未被赋值,Go 会自动地将其初始化,赋值该变量类型的零值(Zero Value)。本例中 age 就被赋值为 0。如果你运行该程序,你会看到如下输出:

my age is 0

变量可以赋值为本类型的任何值。上一程序中的 age 可以赋值为任何整型值(Integer Value)。

package mainimport "fmt"func main() {var age int // 变量声明fmt.Println("my age is", age)age = 29 // 赋值fmt.Println("my age is", age)age = 54 // 赋值fmt.Println("my new age is", age)
}

上面的程序会有如下输出:

my age is  0
my age is 29
my new age is 54

声明变量并初始化

声明变量的同时可以给定初始值。 var name type = initialvalue 的语法用于声明变量并初始化。

package mainimport "fmt"func main() {var age int = 29 // 声明变量并初始化fmt.Println("my age is", age)
}

在上面的程序中,age 是具有初始值 29 的 int 类型变量。如果你运行上面的程序,你可以看见下面的输出,证实 age 已经被初始化为 29。

my age is 29

类型推断(Type Inference)

如果变量有初始值,那么 Go 能够自动推断具有初始值的变量的类型。因此,如果变量有初始值,就可以在变量声明中省略 type
如果变量声明的语法是 var name = initialvalue,Go 能够根据初始值自动推断变量的类型。
在下面的例子中,你可以看到在第 6 行,我们省略了变量 ageint 类型,Go 依然推断出了它是 int 类型。

package mainimport "fmt"func main() {var age = 29 // 可以推断类型fmt.Println("my age is", age)
}

声明多个变量

Go 能够通过一条语句声明多个变量。
声明多个变量的语法是 var name1, name2 type = initialvalue1, initialvalue2

package mainimport "fmt"func main() {var width, height int = 100, 50 // 声明多个变量fmt.Println("width is", width, "height is", heigh)
}

上述程序将在标准输出打印 width is 100 height is 50
你可能已经想到,如果 width 和 height 省略了初始化,它们的初始值将赋值为 0。

package mainimport "fmt"func main() {  var width, height intfmt.Println("width is", width, "height is", height)width = 100height = 50fmt.Println("new width is", width, "new height is ", height)
}

上面的程序将会打印:

width is 0 height is 0
new width is 100 new height is  50

在有些情况下,我们可能会想要在一个语句中声明不同类型的变量。其语法如下:

var (  name1 = initialvalue1,name2 = initialvalue2
)

使用上述语法,下面的程序声明不同类型的变量。

package mainimport "fmt"func main() {var (name   = "naveen"age    = 29height int)fmt.Println("my name is", name, ", age is", age, "and height is", height)
}

这里我们声明了 string 类型的 name、int 类型的 age 和 height(我们将会在下一教程中讨论 golang 所支持的变量类型)。运行上面的程序会产生输出 my name is naveen , age is 29 and height is 0

简短声明

Go 也支持一种声明变量的简洁形式,称为简短声明(Short Hand Declaration),该声明使用了 := 操作符。
声明变量的简短语法是 name := initialvalue

package mainimport "fmt"func main() {  name, age := "naveen", 29 // 简短声明fmt.Println("my name is", name, "age is", age)
}

运行上面的程序,可以看到输出为 my name is naveen age is 29
简短声明要求 := 操作符左边的所有变量都有初始值。下面程序将会抛出错误 cannot assign 1 values to 2 variables,这是因为 age 没有被赋值

package mainimport "fmt"func main() {  name, age := "naveen" //errorfmt.Println("my name is", name, "age is", age)
}

简短声明的语法要求 := 操作符的左边至少有一个变量是尚未声明的。考虑下面的程序:

package mainimport "fmt"func main() {a, b := 20, 30 // 声明变量a和bfmt.Println("a is", a, "b is", b)b, c := 40, 50 // b已经声明,但c尚未声明fmt.Println("b is", b, "c is", c)b, c = 80, 90 // 给已经声明的变量b和c赋新值fmt.Println("changed b is", b, "c is", c)
}

在上面程序中的第 8 行,由于 b 已经被声明,而 c 尚未声明,因此运行成功并且输出:

a is 20 b is 30
b is 40 c is 50
changed b is 80 c is 90

但是如果我们运行下面的程序:

package mainimport "fmt"func main() {  a, b := 20, 30 // 声明a和bfmt.Println("a is", a, "b is", b)a, b := 40, 50 // 错误,没有尚未声明的变量
}

上面运行后会抛出 no new variables on left side of := 的错误,这是因为 a 和 b 的变量已经声明过了,:= 的左边并没有尚未声明的变量。
变量也可以在运行时进行赋值。考虑下面的程序:

package mainimport (  "fmt""math"
)func main() {  a, b := 145.8, 543.8c := math.Min(a, b)fmt.Println("minimum value is ", c)
}

在上面的程序中,c 的值是运行过程中计算得到的,即 a 和 b 的最小值。上述程序会打印:

minimum value is  145.8

由于 Go 是强类型(Strongly Typed)语言,因此不允许某一类型的变量赋值为其他类型的值。下面的程序会抛出错误 cannot use "naveen" (type string) as type int in assignment,这是因为 age 本来声明为 int 类型,而我们却尝试给它赋字符串类型的值。

package mainfunc main() {  age := 29      // age是int类型age = "naveen" // 错误,尝试赋值一个字符串给int类型变量
}

04-类型

下面是 Go 支持的基本类型:

  • bool
  • 数字类型
    int8, int16, int32, int64, int
    uint8, uint16, uint32, uint64, uint
    float32, float64
    complex64, complex128
    byte
    rune
  • string

bool

bool 类型表示一个布尔值,值为 true 或者 false。

package mainimport "fmt"func main() {  a := trueb := falsefmt.Println("a:", a, "b:", b)c := a && bfmt.Println("c:", c)d := a || bfmt.Println("d:", d)
}

在上面的程序中,a 赋值为 true,b 赋值为 false。
c 赋值为 a && b。仅当 a 和 b 都为 true 时,操作符 && 才返回 true。因此,在这里 c 为 false。
当 a 或者 b 为 true 时,操作符 || 返回 true。在这里,由于 a 为 true,因此 d 也为 true。我们将得到程序的输出如下。

a: true b: false
c: false
d: true

有符号整型

int8:表示 8 位有符号整型大小:8 位范围:-128~127
int16:表示 16 位有符号整型大小:16 位范围:-32768~32767
int32:表示 32 位有符号整型大小:32 位范围:-2147483648~2147483647
int64:表示 64 位有符号整型大小:64 位范围:-9223372036854775808~9223372036854775807
int:根据不同的底层平台(Underlying Platform),表示 32 或 64 位整型。除非对整型的大小有特定的需求,否则你通常应该使用 int 表示整型。大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。范围:在 32 位系统下是 -2147483648~2147483647,而在 64 位系统是 -9223372036854775808~9223372036854775807。

package mainimport "fmt"func main() {  var a int = 89b := 95fmt.Println("value of a is", a, "and b is", b)
}

上面程序会输出 value of a is 89 and b is 95
在上述程序中,a 是 int 类型,而 b 的类型通过赋值(95)推断得出。上面我们提到,int 类型的大小在 32 位系统下是 32 位,而在 64 位系统下是 64 位。接下来我们会证实这种说法。
在 Printf 方法中,使用 %T 格式说明符(Format Specifier),可以打印出变量的类型。Go 的 unsafe 包提供了一个 Sizeof 函数,该函数接收变量并返回它的字节大小。unsafe 包应该小心使用,因为使用 unsafe 包可能会带来可移植性问题。不过出于本教程的目的,我们是可以使用的。
下面程序会输出变量 a 和 b 的类型和大小。格式说明符 %T 用于打印类型,而 %d 用于打印字节大小。

package mainimport (  "fmt""unsafe"
)func main() {  var a int = 89b := 95fmt.Println("value of a is", a, "and b is", b)fmt.Printf("type of a is %T, size of a is %d", a, unsafe.Sizeof(a)) // a 的类型和大小fmt.Printf("\ntype of b is %T, size of b is %d", b, unsafe.Sizeof(b)) // b 的类型和大小
}

以上程序会输出:

value of a is 89 and b is 95
type of a is int, size of a is 4
type of b is int, size of b is 4

从上面的输出,我们可以推断出 a 和 b 为 int 类型,且大小都是 32 位(4 字节)。如果你在 64 位系统上运行上面的代码,会有不同的输出。在 64 位系统下,a 和 b 会占用 64 位(8 字节)的大小。

无符号整型

uint8:表示 8 位无符号整型大小:8 位范围:0~255
uint16:表示 16 位无符号整型大小:16 位范围:0~65535
uint32:表示 32 位无符号整型大小:32 位范围:0~4294967295
uint64:表示 64 位无符号整型大小:64 位范围:0~18446744073709551615
uint:根据不同的底层平台,表示 32 或 64 位无符号整型。大小:在 32 位系统下是 32 位,而在 64 位系统下是 64 位。范围:在 32 位系统下是 0~4294967295,而在 64 位系统是 0~18446744073709551615。

浮点型

float32:32 位浮点数float64:64 位浮点数
下面一个简单程序演示了整型和浮点型的运用。

package mainimport (  "fmt"
)func main() {  a, b := 5.67, 8.97fmt.Printf("type of a %T b %T\n", a, b)sum := a + bdiff := a - bfmt.Println("sum", sum, "diff", diff)no1, no2 := 56, 89fmt.Println("sum", no1+no2, "diff", no1-no2)
}

a 和 b 的类型根据赋值推断得出。在这里,a 和 b 的类型为 float64(float64 是浮点数的默认类型)。我们把 a 和 b 的和赋值给变量 sum,把 b 和 a 的差赋值给 diff,接下来打印 sum 和 diff。no1 和 no2 也进行了相同的计算。上述程序将会输出:

type of a float64 b float64
sum 14.64 diff -3.3000000000000007
sum 145 diff -33

复数类型

complex64:实部和虚部都是 float32 类型的的复数。complex128:实部和虚部都是 float64 类型的的复数。
内建函数 complex用于创建一个包含实部和虚部的复数。complex 函数的定义如下:

func complex(r, i FloatType) ComplexType

该函数的参数分别是实部和虚部,并返回一个复数类型。实部和虚部应该是相同类型,也就是 float32 或 float64。如果实部和虚部都是 float32 类型,则函数会返回一个 complex64 类型的复数。如果实部和虚部都是 float64 类型,则函数会返回一个 complex128 类型的复数。
还可以使用简短语法来创建复数:

c := 6 + 7i

下面我们编写一个简单的程序来理解复数。

package mainimport (  "fmt"
)func main() {  c1 := complex(5, 7)c2 := 8 + 27icadd := c1 + c2fmt.Println("sum:", cadd)cmul := c1 * c2fmt.Println("product:", cmul)
}

在上面的程序里,c1 和 c2 是两个复数。c1的实部为 5,虚部为 7。c2 的实部为8,虚部为 27。c1 和 c2 的和赋值给 cadd ,而 c1 和 c2 的乘积赋值给 cmul。该程序将输出:

sum: (13+34i)
product: (-149+191i)

其他数字类型

byte 是 uint8 的别名。rune 是 int32 的别名。
在学习字符串的时候,我们会详细讨论 byte 和 rune。

string 类型

在 Golang 中,字符串是字节的集合。如果你现在还不理解这个定义,也没有关系。我们可以暂且认为一个字符串就是由很多字符组成的。我们后面会在一个教程中深入学习字符串。 下面编写一个使用字符串的程序。

package mainimport (  "fmt"
)func main() {  first := "Naveen"last := "Ramanathan"name := first +" "+ lastfmt.Println("My name is",name)
}

上面程序中,first 赋值为字符串 “Naveen”,last 赋值为字符串 “Ramanathan”。+ 操作符可以用于拼接字符串。我们拼接了 first、空格和 last,并将其赋值给 name。上述程序将打印输出 My name is Naveen Ramanathan
还有许多应用于字符串上面的操作,我们将会在一个单独的教程里看见它们。

类型转换

Go 有着非常严格的强类型特征。Go 没有自动类型提升或类型转换。我们通过一个例子说明这意味着什么。

package mainimport (  "fmt"
)func main() {  i := 55      //intj := 67.8    //float64sum := i + j //不允许 int + float64fmt.Println(sum)
}

上面的代码在 C 语言中是完全合法的,然而在 Go 中,却是行不通的。i 的类型是 int ,而 j 的类型是 float64 ,我们正试图把两个不同类型的数相加,Go 不允许这样的操作。如果运行程序,你会得到 main.go:10: invalid operation: i + j (mismatched types int and float64)
要修复这个错误,i 和 j 应该是相同的类型。在这里,我们把 j 转换为 int 类型。把 v 转换为 T 类型的语法是 T(v)。

package mainimport (  "fmt"
)func main() {  i := 55      //intj := 67.8    //float64sum := i + int(j) //j is converted to intfmt.Println(sum)
}

现在,当你运行上面的程序时,会看见输出 122
赋值的情况也是如此。把一个变量赋值给另一个不同类型的变量,需要显式的类型转换。下面程序说明了这一点。

package mainimport (  "fmt"
)func main() {  i := 10var j float64 = float64(i) // 若没有显式转换,该语句会报错fmt.Println("j", j)
}

在第 9 行,i 转换为 float64 类型,接下来赋值给 j。如果不进行类型转换,当你试图把 i 赋值给 j 时,编译器会抛出错误。

05-常量

定义

在 Go 语言中,术语”常量”用于表示固定的值。比如 5-89I love Go67.89 等等。
看看下面的代码:

var a int = 50
var b string = "I love Go"

在上面的代码中,变量 a 和 b 分别被赋值为常量 50 和 I love GO。关键字 const 被用于表示常量,比如 50I love Go。即使在上面的代码中我们没有明确的使用关键字 const,但是在 Go 的内部,它们是常量。
顾名思义,常量不能再重新赋值为其他的值。因此下面的程序将不能正常工作,它将出现一个编译错误: cannot assign to a.

package mainfunc main() {  const a = 55 // 允许a = 89       // 不允许重新赋值
}

常量的值会在编译的时候确定。因为函数调用发生在运行时,所以不能将函数的返回值赋值给常量。

package mainimport (  "fmt""math"
)func main() {  fmt.Println("Hello, playground")var a = math.Sqrt(4)   // 允许const b = math.Sqrt(4) // 不允许
}

在上面的程序中,因为 a 是变量,因此我们可以将函数 math.Sqrt(4) 的返回值赋值给它(我们将在单独的地方详细讨论函数)。
b 是一个常量,它的值需要在编译的时候就确定。函数 math.Sqrt(4) 只会在运行的时候计算,因此 const b = math.Sqrt(4) 将会抛出错误 error main.go:11: const initializer math.Sqrt(4) is not a constant)

字符串常量

双引号中的任何值都是 Go 中的字符串常量。例如像 Hello WorldSam 等字符串在 Go 中都是常量。
什么类型的字符串属于常量?答案是他们是无类型的。
Hello World 这样的字符串常量没有任何类型。

const hello = "Hello World"

上面的例子,我们把 Hello World 分配给常量 hello。现在常量 hello 有类型吗?答案是没有。常量仍然没有类型。
Go 是一门强类型语言,所有的变量必须有明确的类型。那么, 下面的程序是如何将无类型的常量 Sam 赋值给变量 name 的呢?

package mainimport (  "fmt"
)func main() {  var name = "Sam"fmt.Printf("type %T value %v", name, name)}

答案是无类型的常量有一个与它们相关联的默认类型,并且当且仅当一行代码需要时才提供它。在声明中 var name = “Sam” , name 需要一个类型,它从字符串常量 Sam 的默认类型中获取。
有没有办法创建一个带类型的常量?答案是可以的。以下代码创建一个有类型常量。

const typedhello string = "Hello World"

上面代码中, typedhello 就是一个 string 类型的常量。
Go 是一个强类型的语言,在分配过程中混合类型是不允许的。让我们通过以下程序看看这句话是什么意思。

package mainfunc main() {  var defaultName = "Sam" // 允许type myString stringvar customName myString = "Sam" // 允许customName = defaultName // 不允许}

在上面的代码中,我们首先创建一个变量 defaultName 并分配一个常量 Sam常量 Sam 的默认类型是 string ,所以在赋值后 defaultName 是 string 类型的。
下一行,我们将创建一个新类型 myString,它是 string 的别名。
然后我们创建一个 myString 的变量 customName 并且给他赋值一个常量 Sam 。因为常量 Sam 是无类型的,它可以分配给任何字符串变量。因此这个赋值是允许的,customName 的类型是 myString
现在,我们有一个类型为 string 的变量 defaultName 和另一个类型为 myString 的变量 customName。即使我们知道这个 myStringstring 类型的别名。Go 的类型策略不允许将一种类型的变量赋值给另一种类型的变量。因此将 defaultName 赋值给 customName 是不允许的,编译器会抛出一个错误 main.go:7:20: cannot use defaultName (type string) as type myString in assignmen

布尔常量

布尔常量和字符串常量没有什么不同。他们是两个无类型的常量 truefalse。字符串常量的规则适用于布尔常量,所以在这里我们不再重复。以下是解释布尔常量的简单程序。

package mainfunc main() {  const trueConst = truetype myBool boolvar defaultBool = trueConst // 允许var customBool myBool = trueConst // 允许defaultBool = customBool // 不允许
}

上面的程序是自我解释的。

数字常量

数字常量包含整数、浮点数和复数的常量。数字常量中有一些微妙之处。
让我们看一些例子来说清楚。

package mainimport (  "fmt"
)func main() {  const a = 5var intVar int = avar int32Var int32 = avar float64Var float64 = avar complex64Var complex64 = afmt.Println("intVar",intVar, "\nint32Var", int32Var, "\nfloat64Var", float64Var, "\ncomplex64Var",complex64Var)
}

上面的程序,常量 a 是没有类型的,它的值是 5 。您可能想知道 a 的默认类型是什么,如果它确实有一个的话, 那么我们如何将它分配给不同类型的变量。答案在于 a 的语法。下面的程序将使事情更加清晰。

package mainimport (  "fmt"
)func main() {  var i = 5var f = 5.6var c = 5 + 6ifmt.Printf("i's type %T, f's type %T, c's type %T", i, f, c)}

在上面的程序中,每个变量的类型由数字常量的语法决定。5 在语法中是整数, 5.6 是浮点数,5+6i 的语法是复数。当我们运行上面的程序,它会打印出 i's type int, f's type float64, c's type complex128
现在我希望下面的程序能够正确的工作。

package mainimport (  "fmt"
)func main() {  const a = 5var intVar int = avar int32Var int32 = avar float64Var float64 = avar complex64Var complex64 = afmt.Println("intVar",intVar, "\nint32Var", int32Var, "\nfloat64Var", float64Var, "\ncomplex64Var",complex64Var)
}

在这个程序中, a 的值是 5a 的语法是通用的(它可以代表一个浮点数、整数甚至是一个没有虚部的复数),因此可以将其分配给任何兼容的类型。这些常量的默认类型可以被认为是根据上下文在运行中生成的。 var intVar int = a 要求 aint,所以它变成一个 int 常量。 var complex64Var complex64 = a 要求 acomplex64,因此它变成一个复数类型。很简单的:)。

数字表达式

数字常量可以在表达式中自由混合和匹配,只有当它们被分配给变量或者在需要类型的代码中的任何地方使用时,才需要类型。

package mainimport (  "fmt"
)func main() {  var a = 5.9/8fmt.Printf("a's type %T value %v",a, a)
}

在上面的程序中, 5.9 在语法中是浮点型,8 是整型,5.9/8 是允许的,因为两个都是数字常量。除法的结果是 0.7375 是一个浮点型,所以 a 的类型是浮点型。这个程序的输出结果是: a's type float64 value 0.7375

06-函数(Function)

函数是什么?

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

函数的声明

在 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 // 返回总价
}

上述函数有两个整型的输入 priceno,返回值 totalPricepriceno 的乘积,也是整数类型。
如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。 例如,price int, no int 可以简写为 price, no int,所以示例函数也可写成

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

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

calculateBill(10, 5)

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

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

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

Total price is 540

多返回值

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

  • 面积 = 长 * 宽
  • 周长 = 2 * ( 长 + 宽 )
package mainimport (  "fmt"
)func rectProps(length, width float64)(float64, float64) {  var area = length * widthvar perimeter = (length + width) * 2return 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 类型的输入参数 lengthwidth,并返回两个 float64 类型的值。该程序在控制台上打印结果为

Area 60.480000 Perimeter 32.800000

命名返回值

从函数中可以返回一个命名值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。
上面的 rectProps 函数也可用这个方式写成:

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

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

空白符

_ 在 Go 中被用作空白符,可以用作表示任何类型的任何值。
我们继续以 rectProps 函数为例,该函数计算的是面积和周长。假使我们只需要计算面积,而并不关心周长的计算结果,该怎么调用这个函数呢?这时,空白符 _ 就上场了。
下面的程序我们只用到了函数 rectProps 的一个返回值 area

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

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

07-包

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

到目前为止,我们看到的 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 文件夹。现在,工作区的目录结构会是这样:

srcgeometrygemometry.go
bingeometry

键入 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 rectangleimport "math"func Area(len, wid float64) float64 {  area := len * widreturn area
}func Diagonal(len, wid float64) float64 {  diagonal := math.Sqrt((len * len) + (wid * wid))return diagonal
}

在上面的代码中,我们创建了两个函数用于计算 AreaDiagonal。矩形的面积是长和宽的乘积。矩形的对角线是长与宽平方和的平方根。math 包下面的 Sqrt 函数用于计算平方根。
注意到函数 Area 和 Diagonal 都是以大写字母开头的。这是有必要的,我们将会很快解释为什么需要这样做。

导入自定义包

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

srcgeometrygeometry.gorectanglerectprops.go

import "geometry/rectangle" 这一行会导入 rectangle 包。
geometry.go 里面添加下面的代码:

// geometry.go
package main import (  "fmt""geometry/rectangle" // 导入自定义包
)func main() {  var rectLen, rectWidth float64 = 6, 7fmt.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。因为如果想在包外访问一个函数,它应该首字母大写。

init 函数

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

func init() {
}

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

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

如果一个包导入了另一个包,会先初始化被导入的包。
尽管一个包可能会被导入多次,但是它只会被初始化一次。
为了理解 init 函数,我们接下来对程序做了一些修改。
首先在 rectprops.go 文件中添加了一个 init 函数。

// rectprops.go
package rectangleimport "math"
import "fmt"/** init function added*/
func init() {  fmt.Println("rectangle package initialized")
}
func Area(len, wid float64) float64 {  area := len * widreturn 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. 变量 rectLenrectWidth 从 main 函数级别移到了包级别。
  2. 添加了 init 函数。当 rectLen 或 rectWidth 小于 0 时,init 函数使用 log.Fatal 函数打印一条日志,并终止了程序。

main 包的初始化顺序为:

  1. 首先初始化被导入的包。因此,首先初始化了 rectangle 包。
  2. 接着初始化了包级别的变量 rectLenrectWidth
  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 函数,然后,会初始化包级别的变量 rectLenrectWidth。接着调用 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 后终止。

使用空白标识符(Blank Identifier)

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

// geometry.go
package main import ("geometry/rectangle" // 导入自定的包
)
func main() {}

上面的程序将会抛出错误 geometry.go:6: imported and not used: "geometry/rectangle"
然而,在程序开发的活跃阶段,又常常会先导入包,而暂不使用它。遇到这种情况就可以使用空白标识符 _
下面的代码可以避免上述程序的错误:

package mainimport (  "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。尽管在所有代码里,我们都没有使用这个包,但还是成功初始化了它。

08-if-else语句

if 是条件语句。if 语句的语法是

if condition {
}

如果 condition 为真,则执行 {} 之间的代码。
不同于其他语言,例如 C 语言,Go 语言里的 { } 是必要的,即使在 { } 之间只有一条语句。
if 语句还有可选的 else ifelse 部分。

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

if-else 语句之间可以有任意数量的 else if。条件判断顺序是从上到下。如果 ifelse if 条件判断的结果为真,则执行相应的代码块。 如果没有条件为真,则 else 代码块被执行。
让我们编写一个简单的程序来检测一个数字是奇数还是偶数。

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

if num%2 == 0 语句检测 num 取 2 的余数是否为零。 如果是为零则打印输出 “the number is even”,如果不为零则打印输出 “the number is odd”。在上面的这个程序中,打印输出的是 the number is even
if 还有另外一种形式,它包含一个 statement 可选语句部分,该组件在条件判断之前运行。它的语法是

if statement; condition {
}

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

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

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

package mainimport (  "fmt"
)func main() {  num := 99if 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

一个注意点

else 语句应该在 if 语句的大括号 } 之后的同一行中。如果不是,编译器会不通过。
让我们通过以下程序来理解它。

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

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

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

出错的原因是 Go 语言的分号是自动插入。
在 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 mainimport (  "fmt"
)func main() {  num := 10if num%2 == 0 { //checks if number is evenfmt.Println("the number is even") } else {fmt.Println("the number is odd")}
}

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

09-循环

循环语句是用来重复执行某一段代码。
for 是 Go 语言唯一的循环语句。Go 语言中并没有其他语言比如 C 语言中的 whiledo while 循环。

for 循环语法

for initialisation; condition; post {
}

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

例子

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

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

在上面的程序中,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 mainimport (  "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")
}

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

1 2 3 4 5
line after for loop

continue

continue 语句用来跳出 for 循环中当前循环。在 continue 语句后的所有的 for 循环语句都不会在本次循环中执行。循环体会在一下次循环中继续执行。
让我们写一个打印出 1 到 10 并且使用 continue 的程序。

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

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

更多例子

让我们写更多的代码来演示 for 循环的多样性吧
下面这个程序打印出从 0 到 10 所有的偶数。

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

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

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

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 mainimport (  "fmt"
)func main() {  for no, i := 10, 1; i <= 10 && no <= 19; i, no = i+1, no+1 { //multiple initialisation and incrementfmt.Printf("%d * %d = %d\n", no, i, no*i)}}

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

无限循环

无限循环的语法是:

for {
}

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

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

在你本地系统上运行,来无限的打印 “Hello World” 。
这里还有一个 range 结构,它可以被用来在 for 循环中操作数组对象。当我们学习数组时我们会补充这方面内容。

10-switch语句

switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块。它可以被认为是替代多个 if else 子句的常用方式。
看代码比文字更容易理解。让我们从一个简单的例子开始,它将把一个手指的编号作为输入,然后输出该手指对应的名字。比如 0 是拇指,1 是食指等等。

package mainimport ("fmt"
)func main() {finger := 4switch 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 fingerfinger 的值与每个 case 语句进行比较。通过从上到下对每一个值进行对比,并执行与选项值匹配的第一个逻辑。在上述样例中, finger 值为 4,因此打印的结果是 Ring
在选项列表中,case 不允许出现重复项。如果您尝试运行下面的程序,编译器会报这样的错误: main.go:18:2:在tmp / sandbox887814166 / main.go:16:7

package mainimport ("fmt"
)func main() {finger := 4switch 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 mainimport ("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 number。default 不一定只能出现在 switch 语句的最后,它可以放在 switch 语句的任何地方。
您可能也注意到我们稍微改变了 finger 变量的声明方式。finger 声明在了 switch 语句内。在表达式求值之前,switch 可以选择先执行一个语句。在这行 switch finger:= 8; finger 中, 先声明了finger 变量,随即在表达式中使用了它。在这里,finger 变量的作用域仅限于这个 switch 内。

多表达式判断

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

package mainimport ("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 mainimport ("fmt"
)func main() {num := 75switch { // 表达式被省略了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 10075 is lesser than 200。我们用 fallthrough 来实现了这个功能。

package mainimport ("fmt"
)func number() int {num := 15 * 5 return num
}func main() {switch num := number(); { // num is not a constantcase num < 50:fmt.Printf("%d is lesser than 50\n", num)fallthroughcase num < 100:fmt.Printf("%d is lesser than 100\n", num)fallthroughcase 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

Go语言系列——01-HelloWorld、02-命名规范、03-变量、04-类型、05-常量、06-函数(Function)、07-包、08-if-else语句、09-循环、10-switch语句相关推荐

  1. 【R语言系列01】烦人的拼贴操作 详述 paste and paste0

    R语言系列01 烦人的拼贴操作 paste 与 paste0 相信很多人在一开始使用R语言的paste, paste0的时候,总是拿捏不准,感到有些迷糊. 本期文章中,我将记录对比一些操作以及表现,加 ...

  2. html 变量命名规范,JavaScript 变量命名规则

    匈牙利命名法 匈牙利命名法匈牙利命名法是电脑程序设计中的一种变量命名规则,此命名法又可细分为:系统匈牙利命名法和匈牙利应用命名法. 匈牙利命名法具备语言独立的特性,并且首次在BCPL语言中被大量使用. ...

  3. html语言书写注意事项,CSS命名规范参考及书写注意事项

    CSS书写顺序 *{ /*显示属性*/ display position float clear cursor - /*盒模型*/ margin padding width height /*排版*/ ...

  4. BizTalk开发系列(十九) BizTalk命名规范

    更多内容请查看:BizTalk动手实验系列目录                       BizTalk 开发系列 目前BizTalk项目的开发人员比较少,但是在开发过程中还是需要命名规范的约束.根 ...

  5. Java 源程序的良好书写规范有哪些_使用Java作为程序语言时,好的命名规范有哪些...

    变量名 普通变量命名应该采用首字母小写,其他字母首字母大写的方式. final static变量的名字应该都大写,并且指出完整含义.如果一个常量名称由多个单词组成,则应该用下划线来分割这些单词如. N ...

  6. JavaScript命名规范与变量声明的注意事项

    (一)JavaScript命名规范: 1.严格区分大小写. 2.变量的命名必须以字母或_或$开头,余下部分可以是任意的字幕,数字,或者是_或者是$. 3.不能用关键字或者是保留字命名. 4.JavaS ...

  7. delphi switch语句例子_「GCTT 出品」Go 系列教程——10. switch 语句

    Go语言中文网,致力于每日分享编码知识,欢迎关注我,会有意想不到的收获! Go 系列教程是非常棒的一套初学者教程,入门就它了. 「GCTT 出品」Go 系列教程--1. 介绍与安装 「GCTT 出品」 ...

  8. C语言case后语句省略不写,switch语句基础理解与分析

    switch语句和if语句最为相似,语法接近C语言. switch(expression) { case   value:  statement break; case   value:  state ...

  9. c语言case语句块,JavaScript使用Switch语句来选择将要执行的代码块

    JavaScript Switch 语句 switch 语句用于基于不同的条件来执行不同的动作. 使用 switch 语句来选择要执行的多个代码块之一.语法如下: switch(n) { case 1 ...

最新文章

  1. 基于脑电图的情绪识别BCI应用于DOC患者
  2. Web应用验证码方面总结(ASP.NET版)
  3. php中的函数调简单 传入参数即可,php函数与传递参数的简单示例
  4. CC 攻击检测研究现状
  5. cocos2D(九)---- CCAction
  6. 源码篇:Python 实战案例----银行系统
  7. Vue2.3.0+使用.sync修饰符对prop进行双向绑定/子组件同步prop到父组件绑定的值
  8. YBTOJ 特殊数列(哈希表)
  9. @Autowired所有的东西!
  10. LeetCode 1560. 圆形赛道上经过次数最多的扇区
  11. Linux实战教学笔记13:定时任务补充
  12. Docker学习总结(20)——Docker 容器实践精华问答集锦
  13. HQL 如何 count(*) 分页查询出来 group by 的总数?
  14. 两强格局初定,网易云能拿什么跟腾讯音乐打
  15. JS 动态添加的元素 绑定事件
  16. JQuery冒泡(选择并上传多张图片)
  17. Java实现调用百度AI开放云平台(人脸识别API)
  18. 计算机基础课程-书籍和视频教程资源
  19. 辗转相除法——求最大公约数(易懂详解)
  20. 再预告:DIY大宝剑,大宝剑二号 ——名字还没想好

热门文章

  1. (计算机视觉笔记)1、初入计算机视觉
  2. RS485的EMC防雷保护方案
  3. 新编《守株待兔》—C语言版—兼聊为什么不应该用%d格式转换输出指针
  4. win10开机右下角网络图标突然变成小地球,显示无internet连接,但是可以正常上网
  5. 12AU7+6V6GT 耳放的设计
  6. newmultipartentity php,使用MultipartEntity图片上传
  7. 数据交换协议--JSON、XML、YAML、TOML、TLV
  8. 什么是反向链接?如何获得更多反向链接?
  9. 【Foobar 2000】如何为本地歌曲音乐评级、打分、评分、打小星星?喜爱程度分级-playcount、quick tagger
  10. 【原创】Visio软件绘图功能极佳----记我的第一次Visio软件绘制时序图