#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 := computed_value(); 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可以用于读取slice和map的数据，配合range

	for k,v:=range map {
		fmt.Println("map's key:",k)
		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")
	}

我们看第六行，我们把很多值聚合在了一个case里面，同时，Go里面switch默认相当于每个case后面带有break，匹配成功后不会自动向下尝试，而是跳出整个switch了，但是可以使用fallthrough使其这样做。

	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，如果你不想申明也可以，直接就两个类型
- 如果只有一个返回值且不申明返回值变量，那么你可以省略用以包括返回值的括号
- 如果没有返回值，那么就直接省略最后的返回信息

下面我们来看一个实际应用函数的例子(用来计算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)) //just call it here
	}

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

###多个返回值
Go语言和C相比，更先进的地方，其中一点就是能够返回多个值，也许这个思想来源于python。

我们直接上代码看例子

	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的目的。那么到底传指针有什么好处呢？

- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级，只是传内存地址，我们可以通过指针高效的传递大的结构体。如果传值的话，那么每次传递, 在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 type_name func(input1 inputType1 [, input2 inputType2 [, ...]) (result1 resultType1 [, ...])
函数作为类型到底有什么好处呢？那就是可以把这个类型的函数当做值来传递，请看下面的例子

	package main
	import "fmt"

	type test_int 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 test_int) []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)
	}

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

###Panic和Recover

Go没有例如像Java那样的异常机制：不能抛出一个异常。作为代替，它使用了panic和recover机制。一定要记得，这应当作为最后的手段被使用，你的代码中应当没有，或者很少的令人恐慌的东西。这是个强大的工具，明智的使用它。那么，应该如何使用它。

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()，所以你不需要在任何地方调用这两个函数。init函数每个package里面是可选的，但是对于package main就必须包含一个main函数

初始化和执行程序都是开始于main包，如果有import其他包，那么依次import进来。虽然有很多个包里面import了同一个包，但是这个包只会import一次(例如fmt包，可能很多package里面都会用到，但是只import了一次，因为没有必要包含多次)。当一个package被import进来的时候，如果他本身还import了其他包，那么先执行import其他包，然后执行这个包里面的包级别的常量和变量，然后执行init函数(如果包里面有的话)。最后所有的被main包import的包都加载完毕了，然后开始执行main包里面的常量和变量的初始化，然后执行main包里面的init函数(如果存在的话)，最后执行到了main函数。下图详细的解释了整个的执行过程。

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


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

## LastModified 
   * $Id$
