函数
函数作用域
在 Go 中,定义在函数外的变量是全局的,那些定义在函数内部的变量,对于函数来说是局部的。
局部 变量 仅仅 在执行定义它的函数时有效。
package main
import "fmt"
var a int
func main() {
a = 5
println(a)
f()
}
func f() {
a := 6
println(a)
g()
}
func g() {
println(a)
}
//565
延迟代码(defer )
//在这里有许多重复的代码。为了解决这些,Go 有了 defer 语句。在 defer 后指定的函数会在函数退出 前 调用。
func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close() //←重复代码
return false
}
if failureY {
file.Close() //←重复代码
return false
}
file.Close() //←重复代码
return true
}
//====================
//用defer改进
func ReadWrite() bool {
file.Open("file")
defer file.Close() //← file.Close() 被添加到了 defer 列表
// 做一些工作
if failureX {
return false //← Close() 现在自动调用
}
if failureY {
return false //← 这里也是
}
return true// ← And here
}
可以将多个函数放入 “延迟列表”中
package main
import "fmt"
var a int
func main() {
for i := 0 ; i < 5 ; i++ {
//fmt.Printf("%d ", i)//0 1 2 3 4
//延迟的函数是按照后进先出(LIFO)的顺序执行
defer fmt.Printf("%d ", i)//4 3 2 1 0
}
}
利用 defer 甚至可以修改返回值
package main
// import "fmt"
var a int
func main() {
println(f())
}
//在这个(匿名)函数中,可以访问任何命名返回参数:
//在 defer 中访问返回值
func f() (ret int ) { //← ret 初始化为零
defer func () {
ret++ //← ret 增加为 1
} ()
return 0 //← 返回的是 1 而 不是 0 !
}
defer func () {
/* ... */
} () // ← () 在这里是必须的
//或者这个例子,更加容易了解为什么,以及在哪里需要括号:
//带参数的函数符号
defer func (x int ) {
/* ... */
} (5) //← 为输入参数 x 赋值 5
变参
接受不定数量的参数的函数叫做变参函数。定义函数使其接受变参:
arg ... int
告诉 Go 这个函数接受不定数量的参数。注意,这些参数的类型全部是 int 。
如果不指定变参的类型,默认是空的接口 interface{}
func myfunc(arg ... int ) {}
恐慌(Panic)和恢复(Recover)
Go 没有像 Java 那样的异常机制,例如你无法像在 Java 中那样抛出一个异常。作为替代,它使用了恐慌和恢复(panic-and-recover)机制。一定要记得,这应当作为最后的手段被使用,你的代码中应当没有,或者很少的令人恐慌的东西。这是个强大的工具,明智的使用它。那么,应该如何使用它呢。
Panic
是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数 F 调用 panic ,函数 F 的执行被中断,并且 F 中的延迟函数会正常执行,然后 F 返回到调用它的地方。在调用的地方, F 的行为就像调用了 panic 。这一过程继续向上,直到程序崩溃时的所有 goroutine 返回。
恐慌可以直接调用 panic 产生。也可以由 运行时错误 产生,例如访问越界的数组。
Recover
是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟 函数中有效。
在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果。如果
当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
函数练习
// Q5. (0) 平均值
// 1. 编写一个函数用于计算一个 float64 类型的 slice 的平均值。
package main
import "fmt"
func main() {
s := [] float64{1.25,0.98,3.96}
fmt.Printf("%v\n",average(s))
}
func average(xs []float64) (avg float64) {
sum := 0.0
switch len(xs){
case 0:
avg = 0 //如果长度是零,返回 0;
default :
for _, v := range xs {
sum += v
}
avg = sum /float64(len(xs)) //为了使除法能正常计算,必须将值转换为 float64 ;
}
return //得到平均值,返回它
}
// Q6. (0) 整数顺序
// 1. 编写函数,返回其(两个)参数正确的(自然)数字顺序:
// f(7,2) → 2,7
// f(2,7) → 2,7
package main
import "fmt"
func main() {
s := [] float64{1.25,0.98,3.96}
fmt.Printf("%v\n",average(s))
}
func order(a,b int) (int , int) {
if a > b
return b,a
return a,b
}
// 1. 创建一个固定大小保存整数的栈。它无须超出限制的增长。定义 push 函数——
// 将数据放入栈,和 pop 函数——从栈中取得内容。栈应当是先进后出,后进先出(LIFO)
// 的。
package main
import "fmt"
type stack struct { //← 栈 不应该被导出
i int //指向最后一个元素的索引
data [10] int
}
//向函数 push 提供一个指向栈的指针
//这里不用指针不会有真正的结果,因为func (s stack) push(k int ) push 函数得到的是s的副本
func (s *stack) push(k int ) { //← 工作于参数的副本
if s.i+1 > 9 {
return
}
// println(k)
// println(s.i)
s.data[s.i] = k
s.i++
}
func (s *stack) pop() int {
s.i--
return s.data[s.i]
}
func main() {
var s stack //← 让 s 是一个 stack 变量
s.push(25)
fmt.Printf("stack %v\n", s) ;
s.push(14)
fmt.Printf("stack %v\n", s) ;
}
// 更进一步。编写一个 String 方法将栈转化为字符串形式的表达。可以这样的
// 方式打印整个栈: fmt.Printf("My stack %v\n", stack)
// 栈可以被输出成这样的形式: [0:m] [1:l] [2:k]
package main
import (
"fmt"
"strconv"
)
type stack struct { //← 栈 不应该被导出
i int //指向最后一个元素的索引
data [10] int
}
func (s *stack) push(k int ) { //← 工作于参数的副本
if s.i+1 > 9 {
return
}
// println(k)
// println(s.i)
s.data[s.i] = k
s.i++
}
func (s stack) String() string {
var str string
for i := 0 ; i <= s.i ; i++ {
str = str + "[" +
strconv.Itoa(i) + ":" + strconv.Itoa(s.data[i]) + "]"
}
return str
}
func main() {
var s stack
s.push(25)
s.push(14)
s.push(88)
fmt.Printf("%v\n",s)
fmt.Printf("%v\n",s.String())
}
// Q9. (1) 变参
// 1. 编写函数接受整数类型变参,并且每行打印一个数字。
package main
import "fmt"
func main() {
printthem(1,4,5,7,8)
//printthem(1,6,9)
}
func printthem(numbers ... int ) {//← numbers 现在是整数类型的 slice
for _,d := range numbers{
fmt.Printf("%d\n",d)
}
}
// Q10. (1) 斐波那契
// 1. 斐波那契数列以:1,1,2,3,5,8,13,... 开始。或者用数学形式表达:x 1 = 1;x 2 =
// 1;x n = x n−1 + x n−2 ∀n > 2。
// 编写一个接受 int 值的函数,并给出这个值得到的斐波那契数列。
package main
// import "fmt"
func fibonacci( value int ) []int {
x := make([]int , value)//创建一个用于保存函数执行结果的 array ;
x[0],x[1] = 1 ,1
for n := 2; n < value ; n++{
x[n] = x[n-1] + x[n-2]
}
return x //返回 整个 array;
}
func main() {
//使用关键字 range 可以 “遍历” 数字得到斐波那契函数返回的序列。这有 10 个,且打印了出来
for _, term := range fibonacci(10){
println(term)
//fmt.Printf("%v,",term)
}
}
// map() 函数是一个接受一个函数和一个列表作为参数的函数。函
// 数应用于列表中的每个元素,而一个新的包含有计算结果的列表被返回。因此:
// map(f(),(a 1 ,a 2 ,...,a n−1 ,a n )) = (f(a 1 ),f(a 2 ),...,f(a n−1 ),f(a n ))
// 1. 编写 Go 中的简单的 map() 函数。它能工作于操作整数的函数就可以了。
// 2. 扩展代码使其工作于字符串列表
package main
import "fmt"
func main() {
m := []int {1,2,3}
f := func (i int) int {
return i*i
}
fmt.Printf("%v",Map(f,m))
}
func Map(f func(int) int,l []int) []int {
j := make([]int,len(l))
for k,v:=range l{
j[k] = f(v)
}
return j
}
// Q12. (0) 最小值和最大值
// 1. 编写一个函数,找到 int slice ( []int ) 中的最大值。
// 2. 编写一个函数,找到 int slice ( []int ) 中的最小值。
package main
func main() {
l := []int{0,2,6,9,10}
println(max(l))
println(min(l))
}
func max(l []int) (max int) {//使用了命名返回参数;
max = l[0]
for _, v:=range l{//对 l 循环。元素的序号不重要;
if v > max {
max = v // 如果找到了新的最大值,记住它;
}
}
return //当前的 max 值被返回
}
func min(l []int)(min int) {
min = l[0]
for _,v := range l{
if v < min {
min = v
}
}
return
}
// Q13. (1) 冒泡排序
// 1. 编写一个针对 int 类型的 slice 冒泡排序的函数。
// 它在一个列表上重复步骤来排序,比较每个相邻的元素,并且顺序错
// 误的时候,交换它们。一遍一遍扫描列表,直到没有交换为止,这意
// 味着列表排序完成。算法得名于更小的元素就像 “ 泡泡 ” 一样冒到列表
// 的顶端。
// 1. 冒泡排序并不是最有效率的,对于 n 个元素它的算法复杂度是 O(n 2 )。快速排
// 序 是更好的排序算法。
// 但是冒泡排序容易实现。
package main
import "fmt"
func main() {
n := []int {5,-1,12,0,3,6} //定义一个引用类型slice
fmt.Printf("unsorted %v\n",n)
bubbleSort(n)
fmt.Printf("sorted %v\n",n)
}
//由于 slice 是一个引用类型, bubblesort 函数可以工作,并且无须返回排序后的 slice。
func bubbleSort(n []int) {
for i:=0;i < len(n) - 1; i++{
for j:= i+1; j<len(n);j++{
if n[j] < n[i] {
n[i],n[j] = n[j],n[i]
}
}
}
}
// Q14. (1) 函数返回一个函数
// 1. 编写一个函数返回另一个函数,返回的函数的作用是对一个整数 +2。函数的名
// 称叫做 plusTwo 。然后可以像下面这样使用:
// p := plusTwo()
// fmt.Printf("%v\n", p(2))
// 应该打印 4。
// 2. 使 1 中的函数更加通用化,创建一个 plusX(x) 函数,返回一个函数用于对整
// 数加上 x 。
package main
import "fmt"
func main() {
p2 := plusTwo()
fmt.Printf("%v\n",p2(2))
p3 := plusX(3)
fmt.Printf("%v\n",p3(5))
}
func plusTwo() func(int) int {//定义新的函数返回一个函数
return func(x int) int{ return x + 2} //函数符号,在返回语句中定义了一个 +2 的 函数
}
//这里使用了闭包
func plusX(x int ) func( int ) int {
return func (y int ) int { return x + y }
}