Files
build-web-application-with-…/2.3.md
2012-08-28 17:51:38 +08:00

445 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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里面的核心设计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那么第一个可以省略默认是后面的类型。同时我们注意到它的返回值就是一个类型这个就是省略写法。
###多个返回值
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"
}
看到了吗?上面例子中`x`变量没有任何的变化虽然我们调用了add1函数并且在函数里面`a=a+1`
理由很简单因为当我们调用add1的时候add1接收的参数其实是x的copy而不是x本身
那你也许会问了我如果真的需要传这个x本身自己怎么办呢
这就牵扯到了所谓的指针我们需要修改相应的函数参数int改为*int改为接收类型是指针类型这样接收的就是参数本身就可以修改相应的值了请看下面的例子
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上面就会花费大量的内存。所以记住了当你要操作大数据量的时候用引用是一个明智的选择。
###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)
}
###函数作为值、类型
我们可以通过类型来操作,他的类型就是所有拥有相同的参数,相同的返回值得一种类型
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这个类型是一个函数然后这个函数参数和返回值是一样的但是我们可以实现很多种的逻辑这样使得我们的程序变得非常的灵活。
###Panic和Recover
Go没有例如像Java那样的异常机制不能抛出一个异常。作为代替它使用了panic和recover机制。一定要记得这应当作为最后的手段被使用你的代码中应当没有或者很少的令人恐慌的东西。这是个强大的工具明智的使用它。那么应该如何使用它。
Panic
>是一个内建函数可以中断原有的控制流程进入一个令人恐慌的流程中。当函数F调用panic函数F的执行被中断但是F中的延迟函数会正常执行然后F返回到调用它的地方。在调用的地方F的行为就像调用了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
}
## links
* [目录](<preface.md>)
* 上一章: [GO基础](<2.2.md>)
* 下一节: [高级类型](<2.4.md>)
## LastModified
* $Id$