# 2.3 流程和函数
这小节我们要介绍Go里面的流程控制以及函数操作
## 流程控制
流程控制在编程语言中是最伟大的发明了，因为有了它，你可以通过很简单的流程描述来表达很复杂的逻辑。流程控制包含分三大类：条件判断，循环控制和无条件跳转。
### if
`if`也许是各种编程语言中最常见的了，它的语法概括起来就是:如果满足条件就做某事，否则做另一件事。

Go里面`if`条件判断语句中不需要括号，如下代码所示

	if x > 10 {
		fmt.Println("x is greater than 10")
	} else {
		fmt.Println("x is less than 10")
	}

Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量，这个变量的作用域只能在该条件逻辑块内，其他地方就不起作用了，如下所示

	// 计算获取值x,然后根据x返回的大小，判断是否大于10。
	if x := computedValue(); x > 10 {
		fmt.Println("x is greater than 10")
	} else {
		fmt.Println("x is less than 10")
	}

	//这个地方如果这样调用就编译出错了，因为x是条件里面的变量
	fmt.Println(x)

多个条件的时候如下所示：

	if integer == 3 {
		fmt.Println("The integer is equal to 3")
	} else if integer < 3 {
		fmt.Println("The integer is less than 3")
	} else {
		fmt.Println("The integer is greater than 3")
	}

### goto

Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环：

	func myFunc() {
		i := 0
	Here:   //这行的第一个词，以冒号结束作为标签
		println(i)
		i++
		goto Here   //跳转到Here去
	}

>标签名是大小写敏感的。

### for
Go里面最强大的一个控制逻辑就是`for`，它即可以用来循环读取数据，又可以当作`while`来控制逻辑，还能迭代操作。它的语法如下：

	for expression1; expression2; expression3 {
		//...
	}

`expression1`、`expression2`和`expression3`都是表达式，其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的，`expression2`是用来条件判断，`expression1`在循环开始之前调用，`expression3`在每轮循环结束之时调用。

一个例子比上面讲那么多更有用，那么我们看看下面的例子吧：

	package main
	import "fmt"

	func main(){
		sum := 0;
		for index:=0; index < 10 ; index++ {
			sum += index
		}
		fmt.Println("sum is equal to ", sum)
	}
	// 输出：sum is equal to 45

有些时候需要进行多个赋值操作，由于Go里面没有`,`操作，那么可以使用平行赋值`i, j = i+1, j-1`


有些时候如果我们忽略`expression1`和`expression3`：

	sum := 1
	for ; sum < 1000;  {
		sum += sum
	}

其中`;`也可以省略，那么就变成如下的代码了，是不是似曾相识？对，这就是`while`的功能。

	sum := 1
	for sum < 1000 {
		sum += sum
	}

在循环里面有两个关键操作`break`和`continue`	,`break`操作是跳出当前循环，`continue`是跳过本次循环。当嵌套过深的时候，`break`可以配合标签使用，即跳转至标签所指定的位置，详细参考如下例子：

	for index := 10; index>0; index-- {
		if index == 5{
			break // 或者continue
		}
		fmt.Println(index)
	}
	// break打印出来10、9、8、7、6
	// continue打印出来10、9、8、7、6、4、3、2、1

`break`和`continue`还可以跟着标号，用来跳到多重循环中的外层循环

`for`配合`range`可以用于读取`slice`和`map`的数据：

	for k,v:=range map {
		fmt.Println("map's key:",k)
		fmt.Println("map's val:",v)
	}

由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值
例如

	for _, v := range map{
		fmt.Println("map's val:", v)
	}


### switch
有些时候你需要写很多的`if-else`来实现一些逻辑处理，这个时候代码看上去就很丑很冗长，而且也不易于以后的维护，这个时候`switch`就能很好的解决这个问题。它的语法如下

	switch sExpr {
	case expr1:
		some instructions
	case expr2:
		some other instructions
	case expr3:
		some other instructions
	default:
		other code
	}

`sExpr`和`expr1`、`expr2`、`expr3`的类型必须一致。Go的`switch`非常灵活，表达式不必是常量或整数，执行的过程从上至下，直到找到匹配项；而如果`switch`没有表达式，它会匹配`true`。

	i := 10
	switch i {
	case 1:
		fmt.Println("i is equal to 1")
	case 2, 3, 4:
		fmt.Println("i is equal to 2, 3 or 4")
	case 10:
		fmt.Println("i is equal to 10")
	default:
		fmt.Println("All I know is that i is an integer")
	}

在第5行中，我们把很多值聚合在了一个`case`里面，同时，Go里面`switch`默认相当于每个`case`最后带有`break`，匹配成功后不会自动向下执行其他case，而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。

	integer := 6
	switch integer {
		case 4:
		fmt.Println("The integer was <= 4")
		fallthrough
		case 5:
		fmt.Println("The integer was <= 5")
		fallthrough
		case 6:
		fmt.Println("The integer was <= 6")
		fallthrough
		case 7:
		fmt.Println("The integer was <= 7")
		fallthrough
		case 8:
		fmt.Println("The integer was <= 8")
		fallthrough
		default:
		fmt.Println("default case")
	}

上面的程序将输出

	The integer was <= 6
	The integer was <= 7
	The integer was <= 8
	default case


## 函数
函数是Go里面的核心设计，它通过关键字`func`来声明，它的格式如下：

	func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
		//这里是处理逻辑代码
		//返回多个值
		return value1, value2
	}

上面的代码我们看出

- 关键字`func`用来声明一个函数`funcName`
- 函数可以有一个或者多个参数，每个参数后面带有类型，通过`,`分隔
- 函数可以返回多个值
- 上面返回值声明了两个变量`output1`和`output2`，如果你不想声明也可以，直接就两个类型
- 如果只有一个返回值且不声明返回值变量，那么你可以省略 包括返回值 的括号
- 如果没有返回值，那么就直接省略最后的返回信息
- 如果有返回值， 那么必须在函数的外层添加return语句

下面我们来看一个实际应用函数的例子（用来计算Max值）

	package main
	import "fmt"

	// 返回a、b中最大值.
	func max(a, b int) int {
		if a > b {
			return a
		}
		return b
	}

	func main() {
		x := 3
		y := 4
		z := 5

		max_xy := max(x, y) //调用函数max(x, y)
		max_xz := max(x, z) //调用函数max(x, z)

		fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
		fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
		fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它
	}

上面这个里面我们可以看到`max`函数有两个参数，它们的类型都是`int`，那么第一个变量的类型可以省略（即 a,b int,而非 a int, b int)，默认为离它最近的类型，同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型，这个就是省略写法。

### 多个返回值
Go语言比C更先进的特性，其中一点就是函数能够返回多个值。

我们直接上代码看例子

	package main
	import "fmt"

	//返回 A+B 和 A*B
	func SumAndProduct(A, B int) (int, int) {
		return A+B, A*B
	}

	func main() {
		x := 3
		y := 4

		xPLUSy, xTIMESy := SumAndProduct(x, y)

		fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
		fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
	}

上面的例子我们可以看到直接返回了两个参数，当然我们也可以命名返回参数的变量，这个例子里面只是用了两个类型，我们也可以改成如下这样的定义，然后返回的时候不用带上变量名，因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写)，官方建议：最好命名返回值，因为不命名返回值，虽然使得代码更加简洁了，但是会造成生成的文档可读性差。

	func SumAndProduct(A, B int) (add int, Multiplied int) {
		add = A+B
		Multiplied = A*B
		return
	}

### 变参
Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点，首先需要定义函数使其接受变参：

	func myfunc(arg ...int) {}
`arg ...int`告诉Go这个函数接受不定数量的参数。注意，这些参数的类型全部是`int`。在函数体中，变量`arg`是一个`int`的`slice`：

	for _, n := range arg {
		fmt.Printf("And the number is: %d\n", n)
	}

### 传值与传指针
当我们传一个参数值到被调用函数里面时，实际上是传了这个值的一份copy，当在被调用函数中修改参数值的时候，调用函数中相应实参不会发生任何变化，因为数值变化只作用在copy上。

为了验证我们上面的说法，我们来看一个例子

	package main
	import "fmt"

	//简单的一个函数，实现了参数+1的操作
	func add1(a int) int {
		a = a+1 // 我们改变了a的值
		return a //返回一个新值
	}

	func main() {
		x := 3

		fmt.Println("x = ", x)  // 应该输出 "x = 3"

		x1 := add1(x)  //调用add1(x)

		fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4"
		fmt.Println("x = ", x)    // 应该输出"x = 3"
	}

看到了吗？虽然我们调用了`add1`函数，并且在`add1`中执行`a = a+1`操作，但是上面例子中`x`变量的值没有发生变化

理由很简单：因为当我们调用`add1`的时候，`add1`接收的参数其实是`x`的copy，而不是`x`本身。

那你也许会问了，如果真的需要传这个`x`本身,该怎么办呢？

这就牵扯到了所谓的指针。我们知道，变量在内存中是存放于一定地址上的，修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址，才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数，并将函数的参数的类型由`int`改为`*int`，即改为指针类型，才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的，只是copy的是一个指针。请看下面的例子

	package main
	import "fmt"

	//简单的一个函数，实现了参数+1的操作
	func add1(a *int) int { // 请注意，
		*a = *a+1 // 修改了a的值
		return *a // 返回新值
	}

	func main() {
		x := 3

		fmt.Println("x = ", x)  // 应该输出 "x = 3"

		x1 := add1(&x)  // 调用 add1(&x) 传x的地址

		fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
		fmt.Println("x = ", x)    // 应该输出 "x = 4"
	}

这样，我们就达到了修改`x`的目的。那么到底传指针有什么好处呢？

- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级 (8bytes),只是传内存地址，我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销（内存和时间）。所以当你要传递大的结构体的时候，用指针是一个明智的选择。
- Go语言中`string`，`slice`，`map`这三种类型的实现机制类似指针，所以可以直接传递，而不用取地址后传递指针。（注：若函数需改变`slice`的长度，则仍需要取地址传递指针）

### defer
Go里面有一个不错的设计，就是回调函数，有点类似面向对象语言里面的析构函数，当函数执行完之后再执行。特别是当你在进行一些打开资源的操作时，遇到错误需要提前返回，在返回前你需要关闭相应的资源，不然很容易造成资源泄露等问题。如下代码所示，我们一般写打开一个资源是这样操作的：

	func ReadWrite() bool {
		file.Open("file")
	// 做一些工作
		if failureX {
			file.Close()
			return false
		}

		if failureY {
			file.Close()
			return false
		}

		file.Close()
		return true
	}

我们看到上面有很多重复的代码，Go的`defer`有效解决了这个问题。使用它后，不但代码量减少了很多，而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。

	func ReadWrite() bool {
		file.Open("file")
		defer file.Close()
		if failureX {
			return false
		}
		if failureY {
			return false
		}
		return true
	}

如果有很多调用`defer`，那么`defer`是采用后进先出模式，所以如下代码会输出`4 3 2 1 0`

	for i := 0; i < 5; i++ {
		defer fmt.Printf("%d ", i)
	}

### 函数作为值、类型

在Go中函数也是一种变量，我们可以通过`type`来定义它，它的类型就是所有拥有相同的参数，相同的返回值的一种类型

	type typeName func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])

函数作为类型到底有什么好处呢？那就是可以把这个类型的函数当做值来传递，请看下面的例子

	package main
	import "fmt"

	type testInt func(int) bool // 声明了一个函数类型

	func isOdd(integer int) bool {
		if integer%2 == 0 {
			return false
		}
		return true
	}

	func isEven(integer int) bool {
		if integer%2 == 0 {
			return true
		}
		return false
	}

	// 声明的函数类型在这个地方当做了一个参数

	func filter(slice []int, f testInt) []int {
		var result []int
		for _, value := range slice {
			if f(value) {
				result = append(result, value)
			}
		}
		return result
	}

	func main(){
		slice := []int {1, 2, 3, 4, 5, 7}
		fmt.Println("slice = ", slice)
		odd := filter(slice, isOdd)    // 函数当做值来传递了
		fmt.Println("Odd elements of slice are: ", odd)
		even := filter(slice, isEven)  // 函数当做值来传递了
		fmt.Println("Even elements of slice are: ", even)
	}

函数当做值和类型在我们写一些通用接口的时候非常有用，通过上面例子我们看到`testInt`这个类型是一个函数类型，然后两个`filter`函数的参数和返回值与`testInt`类型是一样的，但是我们可以实现很多种的逻辑，这样使得我们的程序变得非常的灵活。

### Panic和Recover

Go没有像Java那样的异常机制，它不能抛出异常，而是使用了`panic`和`recover`机制。一定要记住，你应当把它作为最后的手段来使用，也就是说，你的代码中应当没有，或者很少有`panic`的东西。这是个强大的工具，请明智地使用它。那么，我们应该如何使用它呢？

Panic
>是一个内建函数，可以中断原有的控制流程，进入一个令人恐慌的流程中。当函数`F`调用`panic`，函数F的执行被中断，但是`F`中的延迟函数会正常执行，然后F返回到调用它的地方。在调用的地方，`F`的行为就像调用了`panic`。这一过程继续向上，直到发生`panic`的`goroutine`中所有调用的函数返回，此时程序退出。恐慌可以直接调用`panic`产生。也可以由运行时错误产生，例如访问越界的数组。

Recover
>是一个内建的函数，可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中，调用`recover`会返回`nil`，并且没有其它任何效果。如果当前的`goroutine`陷入恐慌，调用`recover`可以捕获到`panic`的输入值，并且恢复正常的执行。

下面这个函数演示了如何在过程中使用`panic`

	var user = os.Getenv("USER")

	func init() {
		if user == "" {
			panic("no value for $USER")
		}
	}

下面这个函数检查作为其参数的函数在执行时是否会产生`panic`：

	func throwsPanic(f func()) (b bool) {
		defer func() {
			if x := recover(); x != nil {
				b = true
			}
		}()
		f() //执行函数f，如果f中出现了panic，那么就可以恢复回来
		return
	}

### `main`函数和`init`函数

Go里面有两个保留的函数：`init`函数（能够应用于所有的`package`）和`main`函数（只能应用于`package main`）。这两个函数在定义时不能有任何的参数和返回值。虽然一个`package`里面可以写任意多个`init`函数，但这无论是对于可读性还是以后的可维护性来说，我们都强烈建议用户在一个`package`中只写一个`init`函数。

Go程序会自动调用`init()`和`main()`，所以你不需要在任何地方调用这两个函数。每个`package`中的`init`函数都是可选的，但`package main`就必须包含一个`main`函数。

程序的初始化和执行都起始于`main`包。如果`main`包还导入了其它的包，那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入，那么它只会被导入一次（例如很多包可能都会用到`fmt`包，但它只会被导入一次，因为没有必要导入多次）。当一个包被导入时，如果该包还导入了其它的包，那么会先将其它包导入进来，然后再对这些包中的包级常量和变量进行初始化，接着执行`init`函数（如果有的话），依次类推。等所有被导入的包都加载完毕了，就会开始对`main`包中的包级常量和变量进行初始化，然后执行`main`包中的`init`函数（如果存在的话），最后执行`main`函数。下图详细地解释了整个执行过程：

![](images/2.3.init.png?raw=true)


## links
   * [目录](<preface.md>)
   * 上一章: [Go基础](<2.2.md>)
   * 下一节: [struct类型](<2.4.md>)
