diff --git a/zh/02.3.md b/zh/02.3.md index c642b983..43f51102 100644 --- a/zh/02.3.md +++ b/zh/02.3.md @@ -8,48 +8,48 @@ Go里面`if`条件判断语句中不需要括号,如下代码所示 ```Go - if x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } +if x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} ``` Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示 ```Go - // 计算获取值x,然后根据x返回的大小,判断是否大于10。 - if x := computedValue(); x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } +// 计算获取值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) +//这个地方如果这样调用就编译出错了,因为x是条件里面的变量 +fmt.Println(x) ``` 多个条件的时候如下所示: ```Go - 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") - } +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`跳转到必须在当前函数内定义的标签。例如假设这样一个循环: ```Go - func myFunc() { - i := 0 - Here: //这行的第一个词,以冒号结束作为标签 - println(i) - i++ - goto Here //跳转到Here去 - } +func myFunc() { + i := 0 +Here: //这行的第一个词,以冒号结束作为标签 + println(i) + i++ + goto Here //跳转到Here去 +} ``` >标签名是大小写敏感的。 @@ -57,27 +57,27 @@ Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前 Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: ```Go - for expression1; expression2; expression3 { - //... - } +for expression1; expression2; expression3 { + //... +} ``` `expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 一个例子比上面讲那么多更有用,那么我们看看下面的例子吧: ```Go - package main +package main - import "fmt" +import "fmt" - func main(){ - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) +func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index } - // 输出:sum is equal to 45 + fmt.Println("sum is equal to ", sum) +} +// 输出:sum is equal to 45 ``` 有些时候需要进行多个赋值操作,由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1` @@ -85,122 +85,122 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 有些时候如果我们忽略`expression1`和`expression3`: ```Go - sum := 1 - for ; sum < 1000; { - sum += sum - } +sum := 1 +for ; sum < 1000; { + sum += sum +} ``` 其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。 ```Go - sum := 1 - for sum < 1000 { - sum += sum - } +sum := 1 +for sum < 1000 { + sum += sum +} ``` 在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子: ```Go - for index := 10; index>0; index-- { - if index == 5{ - break // 或者continue - } - fmt.Println(index) +for index := 10; index>0; index-- { + if index == 5{ + break // 或者continue } - // break打印出来10、9、8、7、6 - // continue打印出来10、9、8、7、6、4、3、2、1 + 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`的数据: ```Go - for k,v:=range map { - fmt.Println("map's key:",k) - fmt.Println("map's val:",v) - } +for k,v:=range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) +} ``` 由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值 例如 ```Go - for _, v := range map{ - fmt.Println("map's val:", v) - } +for _, v := range map{ + fmt.Println("map's val:", v) +} ``` ### switch 有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下 ```Go - switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code - } +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`。 ```Go - 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") - } +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代码。 ```Go - 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") - } +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") +} ``` 上面的程序将输出 ```Go - The integer was <= 6 - The integer was <= 7 - The integer was <= 8 - default case +The integer was <= 6 +The integer was <= 7 +The integer was <= 8 +default case ``` ## 函数 函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下: ```Go - func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - //这里是处理逻辑代码 - //返回多个值 - return value1, value2 - } +func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + //这里是处理逻辑代码 + //返回多个值 + return value1, value2 +} ``` 上面的代码我们看出 @@ -215,30 +215,30 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 下面我们来看一个实际应用函数的例子(用来计算Max值) ```Go - package main +package main - import "fmt" +import "fmt" - // 返回a、b中最大值. - func max(a, b int) int { - if a > b { - return a - } - return b +// 返回a、b中最大值. +func max(a, b int) int { + if a > b { + return a } + return b +} - func main() { - x := 3 - y := 4 - z := 5 +func main() { + x := 3 + y := 4 + z := 5 - max_xy := max(x, y) //调用函数max(x, y) - max_xz := max(x, z) //调用函数max(x, z) + 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)) // 也可在这直接调用它 - } + 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个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。 @@ -248,46 +248,46 @@ Go语言比C更先进的特性,其中一点就是函数能够返回多个值 我们直接上代码看例子 ```Go - package main +package main - import "fmt" +import "fmt" - //返回 A+B 和 A*B - func SumAndProduct(A, B int) (int, int) { - return A+B, A*B - } +//返回 A+B 和 A*B +func SumAndProduct(A, B int) (int, int) { + return A+B, A*B +} - func main() { - x := 3 - y := 4 +func main() { + x := 3 + y := 4 - xPLUSy, xTIMESy := SumAndProduct(x, y) + xPLUSy, xTIMESy := SumAndProduct(x, y) - fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) - fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) - } + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} ``` 上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 ```Go - func SumAndProduct(A, B int) (add int, Multiplied int) { - add = A+B - Multiplied = A*B - return - } +func SumAndProduct(A, B int) (add int, Multiplied int) { + add = A+B + Multiplied = A*B + return +} ``` ### 变参 Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参: ```Go - func myfunc(arg ...int) {} +func myfunc(arg ...int) {} ``` `arg ...int`告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int`的`slice`: ```Go - for _, n := range arg { - fmt.Printf("And the number is: %d\n", n) - } +for _, n := range arg { + fmt.Printf("And the number is: %d\n", n) +} ``` ### 传值与传指针 当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。 @@ -295,26 +295,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 为了验证我们上面的说法,我们来看一个例子 ```Go - package main +package main - import "fmt" +import "fmt" - //简单的一个函数,实现了参数+1的操作 - func add1(a int) int { - a = a+1 // 我们改变了a的值 - return a //返回一个新值 - } +//简单的一个函数,实现了参数+1的操作 +func add1(a int) int { + a = a+1 // 我们改变了a的值 + return a //返回一个新值 +} - func main() { - x := 3 +func main() { + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(x) //调用add1(x) + x1 := add1(x) //调用add1(x) - fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" - fmt.Println("x = ", x) // 应该输出"x = 3" - } + fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" + fmt.Println("x = ", x) // 应该输出"x = 3" +} ``` 看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化 @@ -325,26 +325,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。请看下面的例子 ```Go - package main +package main - import "fmt" +import "fmt" - //简单的一个函数,实现了参数+1的操作 - func add1(a *int) int { // 请注意, - *a = *a+1 // 修改了a的值 - return *a // 返回新值 - } +//简单的一个函数,实现了参数+1的操作 +func add1(a *int) int { // 请注意, + *a = *a+1 // 修改了a的值 + return *a // 返回新值 +} - func main() { - x := 3 +func main() { + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(&x) // 调用 add1(&x) 传x的地址 + x1 := add1(&x) // 调用 add1(&x) 传x的地址 - fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" - fmt.Println("x = ", x) // 应该输出 "x = 4" - } + fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" + fmt.Println("x = ", x) // 应该输出 "x = 4" +} ``` 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? @@ -356,44 +356,44 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的: ```Go - func ReadWrite() bool { - file.Open("file") - // 做一些工作 - if failureX { - file.Close() - return false - } - - if failureY { - file.Close() - return false - } - +func ReadWrite() bool { + file.Open("file") +// 做一些工作 + if failureX { file.Close() - return true + return false } + + if failureY { + file.Close() + return false + } + + file.Close() + return true +} ``` 我们看到上面有很多重复的代码,Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。 ```Go - func ReadWrite() bool { - file.Open("file") - defer file.Close() - if failureX { - return false - } - if failureY { - return false - } - return true +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` ```Go - for i := 0; i < 5; i++ { - defer fmt.Printf("%d ", i) - } +for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) +} ``` ### 函数作为值、类型 @@ -404,46 +404,46 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子 ```Go - package main - - import "fmt" +package main - type testInt func(int) bool // 声明了一个函数类型 +import "fmt" - func isOdd(integer int) bool { - if integer%2 == 0 { - return false - } - return true - } +type testInt func(int) bool // 声明了一个函数类型 - func isEven(integer int) bool { - if integer%2 == 0 { - return true - } +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) - } +// 声明的函数类型在这个地方当做了一个参数 + +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) } - return result } + 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) - } +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`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。 @@ -460,26 +460,26 @@ Recover 下面这个函数演示了如何在过程中使用`panic` ```Go - var user = os.Getenv("USER") +var user = os.Getenv("USER") - func init() { - if user == "" { - panic("no value for $USER") - } +func init() { + if user == "" { + panic("no value for $USER") } +} ``` 下面这个函数检查作为其参数的函数在执行时是否会产生`panic`: ```Go - func throwsPanic(f func()) (b bool) { - defer func() { - if x := recover(); x != nil { - b = true - } - }() - f() //执行函数f,如果f中出现了panic,那么就可以恢复回来 - return - } +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() //执行函数f,如果f中出现了panic,那么就可以恢复回来 + return +} ``` ### `main`函数和`init`函数 @@ -497,14 +497,14 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下: ```Go - import( - "fmt" - ) +import( + "fmt" +) ``` 然后我们代码里面可以通过如下的方式调用 ```Go - fmt.Println("hello world") +fmt.Println("hello world") ``` 上面这个fmt是Go语言的标准库,其实是去`GOROOT`环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块: @@ -545,10 +545,10 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 这个操作经常是让很多人费解的一个操作符,请看下面这个import ```Go - import ( - "database/sql" - _ "github.com/ziutek/mymysql/godrv" - ) + import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" + ) ``` _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。