diff --git a/README_gc.md b/README_gc.md index a1b43d6..879ac92 100644 --- a/README_gc.md +++ b/README_gc.md @@ -15,4 +15,4 @@ Golang 编程:245386165 |更新日期 |更新内容 |----------|------------------ -|2015-07-19|5.6 标签与 goto \ No newline at end of file +|2015-07-20|6.2 函数参数与返回值 \ No newline at end of file diff --git a/eBook/06.2.md b/eBook/06.2.md index d449eb9..0a9ec99 100644 --- a/eBook/06.2.md +++ b/eBook/06.2.md @@ -1,32 +1,34 @@ # 6.2 函数参数与返回值 -函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与C、C++、Java和C#,多值返回是Go的一大特性,为我们判断一个函数是否正常执行(参考[第 5.2 节](05.2.md))提供了方便。 +函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与 C、C++、Java 和 C#,多值返回是 Go 的一大特性,为我们判断一个函数是否正常执行(参考 [第 5.2 节](05.2.md))提供了方便。 -我们通过 `return `关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 `return `或 `panic`(参考[第 13 章](13.0.md))结尾。 +我们通过 `return` 关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以 `return ` 或 `panic`(参考 [第 13 章](13.0.md))结尾。 -在函数块里面,`return`之后的语句都不会执行。如果一个函数需要返回值,那么这个函数里面的每一个代码分支(code-path)都要有`return`语句。 +在函数块里面,`return` 之后的语句都不会执行。如果一个函数需要返回值,那么这个函数里面的每一个代码分支(code-path)都要有 `return` 语句。 -问题6.1:下面的函数将不会被编译,为什么呢?大家可以试着纠正过来。 +问题 6.1:下面的函数将不会被编译,为什么呢?大家可以试着纠正过来。 - func (st *Stack) Pop() int { - v := 0 - for ix := len(st) - 1; ix >= 0; ix-- { - if v = st[ix]; v != 0 { - st[ix] = 0 - return v - } +```go +func (st *Stack) Pop() int { + v := 0 + for ix := len(st) - 1; ix >= 0; ix-- { + if v = st[ix]; v != 0 { + st[ix] = 0 + return v } - } + } +} +``` -函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`func f(int, int, float64)` +函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`func f(int, int, float64)`。 -没有参数的函数通常被称为 *niladic* 函数(niladic function),就像 `main.main()`。 +没有参数的函数通常被称为 **niladic** 函数(niladic function),就像 `main.main()`。 -# #6.2.1 按值传递(call by value) 按引用传递(call by reference) +## 6.2.1 按值传递(call by value) 按引用传递(call by reference) -Go默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 `Function(arg1)`。 +Go 默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量,比如 `Function(arg1)`。 -如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如&variable)传递给函数,这就是按引用传递,比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(** 译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。 **) +如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable)传递给函数,这就是按引用传递,比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(** 译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。 **) 几乎在任何情况下,传递指针(一个32位或者64位的值)的消耗都比传递副本来得少。 @@ -36,25 +38,27 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 但是绝大部分的函数还是带有返回值的。 -如下,simple_function.go里的 `MultiPly3Nums` 函数带有三个形参,分别是 `a`、`b`、`c`,还有一个 `int` 类型的返回值(被注释的代码具有和未注释部分同样的功能,只是多引入了一个本地变量): +如下,simple_function.go 里的 `MultiPly3Nums` 函数带有三个形参,分别是 `a`、`b`、`c`,还有一个 `int` 类型的返回值(被注释的代码具有和未注释部分同样的功能,只是多引入了一个本地变量): 示例 6.2 [simple_function.go](examples/chapter_6/simple_function.go) - package main - - import "fmt" - - func main() { - fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6)) - // var i1 int = MultiPly3Nums(2, 5, 6) - // fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1) - } - - func MultiPly3Nums(a int, b int, c int) int { - // var product int = a * b * c - // return product - return a * b * c - } +```go +package main + +import "fmt" + +func main() { + fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6)) + // var i1 int = MultiPly3Nums(2, 5, 6) + // fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1) +} + +func MultiPly3Nums(a int, b int, c int) int { + // var product int = a * b * c + // return product + return a * b * c +} +``` 输出显示: @@ -76,42 +80,44 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 ## 6.2.2 命名的返回值(named return variables) -如下,multiple_return.go里的函数带有一个`int`参数,返回两个`int`值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值(the return values are filled in at the calling function in a parallel assignment)。 +如下,multiple_return.go 里的函数带有一个 `int` 参数,返回两个 `int` 值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值。 -`getX2AandX3` 与 `getX2AndX3_2 `两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`。 +`getX2AandX3` 与 `getX2AndX3_2` 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`。 -命名返回值作为结果形参(result parameters)被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的return语句。需要注意的是,即使只有一个命名返回值,也需要使用`()`括起来(参考[第 6. 6节](06.6.md)的fibonacci.go函数)。 +命名返回值作为结果形参(result parameters)被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的return语句。需要注意的是,即使只有一个命名返回值,也需要使用 `()` 括起来(参考 [第 6.6 节](06.6.md)的 fibonacci.go 函数)。 示例 6.3 [multiple_return.go](examples/chapter_6/multiple_return.go) - package main - - import "fmt" - - var num int = 10 - var numx2, numx3 int - - func main() { - numx2, numx3 = getX2AndX3(num) - PrintValues() - numx2, numx3 = getX2AndX3_2(num) - PrintValues() - } - - func PrintValues() { - fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3) - } - - func getX2AndX3(input int) (int, int) { - return 2 * input, 3 * input - } - - func getX2AndX3_2(input int) (x2 int, x3 int) { - x2 = 2 * input - x3 = 3 * input - // return x2, x3 - return - } +```go +package main + +import "fmt" + +var num int = 10 +var numx2, numx3 int + +func main() { + numx2, numx3 = getX2AndX3(num) + PrintValues() + numx2, numx3 = getX2AndX3_2(num) + PrintValues() +} + +func PrintValues() { + fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3) +} + +func getX2AndX3(input int) (int, int) { + return 2 * input, 3 * input +} + +func getX2AndX3_2(input int) (x2 int, x3 int) { + x2 = 2 * input + x3 = 3 * input + // return x2, x3 + return +} +``` 输出结果: @@ -120,15 +126,14 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 警告: - return or return var 是可以的。不过, - return var = expression(表达式) 会引发一个编译错误: - syntax error: unexpected =, expecting semicolon or newline or } +- return 或 return var 都是可以的。 +- 不过 `return var = expression`(表达式) 会引发一个编译错误:`syntax error: unexpected =, expecting semicolon or newline or }`。 即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。 -任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在`return`语句里面都要明确指出包含返回值的变量或是一个可计算的值(就像上面警告所指出的那样)。 +任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在 `return` 语句里面都要明确指出包含返回值的变量或是一个可计算的值(就像上面警告所指出的那样)。 -**!!尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂!!** +** 尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂 ** 练习 6.1 [mult_returnval.go](exercises/chapter_6/mult_returnval.go) @@ -136,30 +141,32 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 练习 6.2 [error_returnval.go](exercises/chapter_6/error_returnval.go) -编写一个名字为MySqrt的函数,计算一个float64类型浮点数的平方根,如果参数是一个负数的话将返回一个错误。编写两个版本,一个是非命名返回值,一个是命名返回值。 +编写一个名字为 MySqrt 的函数,计算一个 float64 类型浮点数的平方根,如果参数是一个负数的话将返回一个错误。编写两个版本,一个是非命名返回值,一个是命名返回值。 ## 6.2.3 空白符(blank identifier) -空白符用来匹配一些不需要的值,然后丢弃掉,下面的blank_identifier.go就是很好的例子。 +空白符用来匹配一些不需要的值,然后丢弃掉,下面的 blank_identifier.go 就是很好的例子。 -`ThreeValues `是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 `i1` 与 `f1`。第二个返回值赋给了空白符 `_`,然后自动丢弃掉。 +`ThreeValues` 是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 `i1` 与 `f1`。第二个返回值赋给了空白符 `_`,然后自动丢弃掉。 示例 6.4 [blank_identifier.go](examples/chapter_6/blank_identifier.go) - package main - - import "fmt" - - func main() { - var i1 int - var f1 float32 - i1, _, f1 = ThreeValues() - fmt.Printf("The int: %d, the float: %f \n", i1, f1) - } - - func ThreeValues() (int, int, float32) { - return 5, 6, 7.5 - } +```go +package main + +import "fmt" + +func main() { + var i1 int + var f1 float32 + i1, _, f1 = ThreeValues() + fmt.Printf("The int: %d, the float: %f \n", i1, f1) +} + +func ThreeValues() (int, int, float32) { + return 5, 6, 7.5 +} +``` 输出结果: @@ -169,26 +176,28 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 示例 6.5 [minmax.go](examples/chapter_6/minmax.go) - package main - - import "fmt" - - func main() { - var min, max int - min, max = MinMax(78, 65) - fmt.Printf("Minmium is: %d, Maximum is: %d\n", min, max) - } - - func MinMax(a int, b int) (min int, max int) { - if a < b { - min = a - max = b - } else { // a = b or a < b - min = b - max = a - } - return +```go +package main + +import "fmt" + +func main() { + var min, max int + min, max = MinMax(78, 65) + fmt.Printf("Minmium is: %d, Maximum is: %d\n", min, max) +} + +func MinMax(a int, b int) (min int, max int) { + if a < b { + min = a + max = b + } else { // a = b or a < b + min = b + max = a } + return +} +``` 输出结果: @@ -196,27 +205,29 @@ Go默认使用按值传递来传递参数,也就是传递参数的副本。函 ## 6.2.4 改变外部变量(outside variable) -传递指针给函数不但可以节省内存(因为没有复制变量的值),而且赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用`return`返回。如下的例子,`reply`是一个指向`int`变量的指针,通过这个指针,我们在函数内修改了这个`int`变量的数值。 +传递指针给函数不但可以节省内存(因为没有复制变量的值),而且赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用 `return` 返回。如下的例子,`reply` 是一个指向 `int` 变量的指针,通过这个指针,我们在函数内修改了这个 `int` 变量的数值。 示例 6.6 [side_effect.go](examples/chapter_6/side_effect.go) - package main - - import ( - "fmt" - ) - - // this function changes reply: - func Multiply(a, b int, reply *int) { - *reply = a * b - } - - func main() { - n := 0 - reply := &n - Multiply(10, 5, reply) - fmt.Println("Multiply:", *reply) // Multiply: 50 - } +```go +package main + +import ( + "fmt" +) + +// this function changes reply: +func Multiply(a, b int, reply *int) { + *reply = a * b +} + +func main() { + n := 0 + reply := &n + Multiply(10, 5, reply) + fmt.Println("Multiply:", *reply) // Multiply: 50 +} +``` 这仅仅是个指导性的例子,当需要在函数内改变一个占用内存比较大的变量时,性能优势就更加明显了。然而,如果不小心使用的话,传递一个指针很容易引发一些不确定的事,所以,我们要十分小心那些可以改变外部变量的函数,在必要时,需要添加注释以便其他人能够更加清楚的知道函数里面到底发生了什么。