第六章修改 (#843)

Co-authored-by: Joe Chen <jc@unknwon.io>
This commit is contained in:
Haigang Zhou
2022-05-09 22:19:53 +08:00
committed by GitHub
parent f5dae8f559
commit c42d8a340a
13 changed files with 101 additions and 99 deletions

View File

@@ -2,9 +2,9 @@
函数能够接收参数供自己使用,也可以返回零个或多个值(我们通常把返回多个值称为返回一组值)。相比与 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:下面的函数将不会被编译,为什么呢?大家可以试着纠正过来。
@@ -22,19 +22,19 @@ func (st *Stack) Pop() int {
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,就像这样:`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)`
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加&符号,比如 &variable传递给函数这就是按引用传递比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(**译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。**
如果你希望函数可以直接修改参数的值,而不是对参数的副本进行操作,你需要将参数的地址(变量名前面添加 `&` 符号,比如 `&variable`)传递给函数,这就是按引用传递,比如 `Function(&arg1)`,此时传递给函数的是一个指针。如果传递给函数的是一个指针,指针的值(一个地址)会被复制,但指针的值所指向的地址上的值不会被复制;我们可以通过这个指针的值来修改这个值所指向的地址上的值。(**译者注:指针也是变量类型,有自己的地址和值,通常指针的值指向一个变量的地址。所以,按引用传递也是按值传递。**
几乎在任何情况下传递指针一个32位或者64位的值的消耗都比传递副本来得少。
在函数调用时,像切片slice、字典map、接口interface、通道channel这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)。
在函数调用时,像切片 (slice)、字典 (map)、接口 (interface)、通道 (channel) 这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)。
有些函数只是完成一个任务,并没有返回值。我们仅仅是利用了这种函数的副作用(side-effect),就像输出文本到终端,发送一个邮件或者是记录一个错误等。
有些函数只是完成一个任务,并没有返回值。我们仅仅是利用了这种函数的副作用 (side-effect),就像输出文本到终端,发送一个邮件或者是记录一个错误等。
但是绝大部分的函数还是带有返回值的。
@@ -59,11 +59,11 @@ func MultiPly3Nums(a int, b int, c int) int {
return a * b * c
}
```
输出显示:
Multiply 2 * 5 * 6 = 60
如果一个函数需要返回四到五个值,我们可以传递一个切片给函数(如果返回值具有相同类型)或者是传递一个结构体(如果返回值具有不同的类型)。因为传递一个指针允许直接修改变量的值,消耗也更少。
问题 6.2
@@ -77,14 +77,14 @@ func MultiPly3Nums(a int, b int, c int) int {
(B) func DoSomething(a A) {
b = &a
}
## 6.2.2 命名的返回值named return variables
如下multiple_return.go 里的函数带有一个 `int` 参数,返回两个 `int` 值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值。
## 6.2.2 命名的返回值 (named return variables)
如下 multiple_return.go 里的函数带有一个 `int` 参数,返回两个 `int` 值;其中一个函数的返回值在函数调用时就已经被赋予了一个初始零值。
`getX2AndX3``getX2AndX3_2` 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 `()` 把它们括起来,比如 `(int, int)`
命名返回值作为结果形参result parameters被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的 `return` 语句。需要注意的是,即使只有一个命名返回值,也需要使用 `()` 括起来(参考 [第 6.6 节](06.6.md)的 fibonacci.go 函数)。
命名返回值作为结果形参 (result parameters) 被初始化为相应类型的零值,当需要返回的时候,我们只需要一条简单的不带参数的 `return` 语句。需要注意的是,即使只有一个命名返回值,也需要使用 `()` 括起来(参考[第 6.6 节](06.6.md) [fibonacci.go](.\examples\chapter_6\fibonacci.go) 函数)。
示例 6.3 [multiple_return.go](examples/chapter_6/multiple_return.go)
@@ -118,7 +118,7 @@ func getX2AndX3_2(input int) (x2 int, x3 int) {
return
}
```
输出结果:
num = 10, 2x num = 20, 3x num = 30
@@ -142,9 +142,9 @@ func getX2AndX3_2(input int) (x2 int, x3 int) {
练习 6.2 [error_returnval.go](exercises/chapter_6/error_returnval.go)
编写一个名字为 MySqrt 的函数,计算一个 float64 类型浮点数的平方根,如果参数是一个负数的话将返回一个错误。编写两个版本,一个是非命名返回值,一个是命名返回值。
编写一个名字为 `MySqrt()` 的函数,计算一个 `float64` 类型浮点数的平方根,如果参数是一个负数的话将返回一个错误。编写两个版本,一个是非命名返回值,一个是命名返回值。
## 6.2.3 空白符blank identifier
## 6.2.3 空白符 (blank identifier)
空白符用来匹配一些不需要的值,然后丢弃掉,下面的 blank_identifier.go 就是很好的例子。
@@ -168,12 +168,12 @@ func ThreeValues() (int, int, float32) {
return 5, 6, 7.5
}
```
输出结果:
The int: 5, the float: 7.500000
另外一个示例,函数接收两个参数,比较它们的大小,然后按小-大的顺序返回这两个数示例代码为minmax.go。
另外一个示例,函数接收两个参数,比较它们的大小,然后按小-大的顺序返回这两个数,示例代码为 minmax.go。
示例 6.5 [minmax.go](examples/chapter_6/minmax.go)
@@ -199,12 +199,12 @@ func MinMax(a int, b int) (min int, max int) {
return
}
```
输出结果:
Minimum is: 65, Maximum is 78
## 6.2.4 改变外部变量outside variable
## 6.2.4 改变外部变量 (outside variable)
传递指针给函数不但可以节省内存(因为没有复制变量的值),而且赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用 `return` 返回。如下的例子,`reply` 是一个指向 `int` 变量的指针,通过这个指针,我们在函数内修改了这个 `int` 变量的数值。
@@ -229,7 +229,7 @@ func main() {
fmt.Println("Multiply:", *reply) // Multiply: 50
}
```
这仅仅是个指导性的例子,当需要在函数内改变一个占用内存比较大的变量时,性能优势就更加明显了。然而,如果不小心使用的话,传递一个指针很容易引发一些不确定的事,所以,我们要十分小心那些可以改变外部变量的函数,在必要时,需要添加注释以便其他人能够更加清楚的知道函数里面到底发生了什么。
## 链接