Merge pull request #24 from eXthen/master

添加06.2.md和相关的例子与问题解答
This commit is contained in:
Joe Chen
2013-07-26 11:48:05 -07:00
9 changed files with 382 additions and 2 deletions

View File

@@ -38,7 +38,7 @@ Function是pack1包里面的一个函数括号里的是被调用函数的*实
一个简单的函数调用其他函数的例子:
Example 6.1 [greeting.go](examples/chapter_6/greeting.go)
示例 6.1 [greeting.go](examples/chapter_6/greeting.go)
package main

View File

@@ -1,7 +1,221 @@
#6.2 函数参数与返回值
函数能够接收参数供自己使用也可以返回零个或多个值我们通常把返回多个值称为返回一组值。相比与C、C++、Java和C#多值返回是Go的一大特性为我们判断一个函数是否正常执行参考[5.2节](05.2.md))提供了方便。
我们通过`return`关键字返回一组值。事实上,任何一个有返回值(单个或多个)的函数都必须以`return``panic`(参考[第13章](13.0.md))结尾。
在函数块里面,`return`之后的语句都不会执行。如果一个函数需要返回值那么这个函数里面的每一个代码分支code-path都要有`return`语句。
问题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
}
}
}
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`func f(int, int, float64)`
没有参数的函数通常被称为*niladic*函数niladic function就像`main.main()`
##6.2.1 按值传递call by value 按引用传递call by reference
Go默认使用按值传递来传递参数也就是传递参数的副本。函数接收参数副本之后在使用变量的过程中可能对副本的值进行更改但不会影响到原来的变量比如`Function(arg1)`
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如&variable传递给函数这就是按引用传递比如`Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(**译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。**
几乎在任何情况下传递指针一个32位或者64位的值的消耗都比传递副本来得少。
在函数调用时像切片slice、字典map、接口interface、通道channel这样的引用类型都是默认使用引用传递即使没有显示的指出指针
有些函数只是完成一个任务,并没有返回值。我们仅仅是利用了这种函数的副作用,就像输出文本到终端,发送一个邮件或者是记录一个错误等。
但是绝大部分的函数还是带有返回值的。
如下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
}
输出显示:
Multiply 2 * 5 * 6 = 60
如果一个函数需要返回四到五个值,我们可以传递一个切片给函数(如果返回值具有相同类型)或者是传递一个结构体(如果返回值具有不同的类型)。因为传递一个指针允许直接修改变量的值,消耗也更少。
问题 6.2
如下的两个函数调用有什么不同:
(A) func DoSomething(a *A) {
b = a
}
(B) func DoSomething(a A) {
b = &a
}
##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
`getX2AandX3``getX2AndX3_2`两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用`()`把它们括起来,比如`(int, int)`
命名返回值作为结果形参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
}
输出结果:
num = 10, 2x num = 20, 3x num = 30
num = 10, 2x num = 20, 3x num = 30
警告:
return or return var 是可以的。不过,
return var = expression表达式 会引发一个编译错误:
syntax error: unexpected =, expecting semicolon or newline or }
即使函数使用了命名返回值,你依旧可以无视它而返回明确的值。
任何一个非命名返回值(使用非命名返回值是很糟的编程习惯)在`return`语句里面都要明确指出包含返回值的变量或是一个可计算的值(就像上面警告所指出的那样)。
**!!尽量使用命名返回值:会使代码更清晰、更简短,同时更加容易读懂!!**
练习 6.1 [mult_returnval.go](exercises/chapter_6/mult_returnval.go)
编写一个函数,接收两个整数,然后返回它们的和、积与差。编写两个版本,一个是非命名返回值,一个是命名返回值。
练习 6.2 [error_returnval.go](exercises/chapter_6/error_returnval.go)
编写一个名字为MySqrt的函数计算一个float64类型浮点数的平方根如果参数是一个负数的话将返回一个错误。编写两个版本一个是非命名返回值一个是命名返回值。
##6.2.3 空白符blank identifier
空白符用来匹配一些不需要的值然后丢弃掉下面的blank_identifier.go就是很好的例子。
`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
}
输出结果:
The int: 5, the float: 7.500000
另外一个示例,函数接收两个参数,比较它们的大小,然后按小-大的顺序返回这两个数示例代码为minmax.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
}
输出结果
Minimum is: 65, Maximum is 78
##6.2.4 改变外部变量outside variable
传递指针给函数不但可以节省内存因为没有复制变量的值而且赋予了函数直接修改外部变量的能力所以被修改的变量不再需要使用`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
}
这仅仅是个指导性的例子当需要在函数内改变一个占用内存比较大的变量时性能优势就更加明显了然而如果不小心使用的话传递一个指针很容易引发一些不确定的事所以我们要十分小心那些可以改变外部变量的函数在必要时需要添加注释以便其他人能够更加清楚的知道函数里面到底发生了什么
##链接
- [目录](directory.md)
- 上一节[函数介绍](06.1.md)
- 下一节:[传递不定参数](06.3.md)
- 下一节[传递不定参数](06.3.md)

View File

@@ -0,0 +1,15 @@
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
}

View File

@@ -0,0 +1,20 @@
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
}

View File

@@ -0,0 +1,28 @@
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
}

View File

@@ -0,0 +1,17 @@
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
}

View File

@@ -0,0 +1,15 @@
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
}

View File

@@ -0,0 +1,44 @@
package main
import (
"fmt"
"errors"
"math"
)
func MySqrt1(a float64) (Sqrt float64, Err error) {
if a < 0 {
Err = errors.New("Fuck!")
Sqrt = 0
return
}
Sqrt = math.Sqrt(a)
return
}
func MySqrt2(a float64) (float64, error) {
var Sqrt float64
var Err error
if a < 0 {
Err = errors.New("Fuck!")
Sqrt = 0
return Sqrt, Err
}
Sqrt = math.Sqrt(a)
return Sqrt, Err
}
func main() {
var a float64 = 99.99
var b float64 = -99.99
Sqrt11, Err11 := MySqrt1(a)
Sqrt12, Err12 := MySqrt1(b)
Sqrt21, Err21 := MySqrt1(a)
Sqrt22, Err22 := MySqrt1(b)
fmt.Println(Sqrt11, Err11)
fmt.Println(Sqrt12, Err12)
fmt.Println(Sqrt21, Err21)
fmt.Println(Sqrt22, Err22)
}

View File

@@ -0,0 +1,27 @@
package main
import "fmt"
func MultReturn1(a, b int) (Sum, Mul, Dif int) {
Sum = a + b
Mul = a * b
Dif = a - b
return
}
func MultReturn2(a, b int) (int, int, int) {
Sum := a + b
Mul := a * b
Dif := a - b
return Sum, Mul, Dif
}
func main() {
a := 100
b := 78
Sum1, Mul1, Dif1 := MultReturn1(a, b)
Sum2, Mul2, Dif2 := MultReturn2(a, b)
fmt.Println("Named:", Sum1, Mul1, Dif1)
fmt.Println("Unnamed:", Sum2, Mul2, Dif2)
}