什么是函数

函数,简单来讲就是一段将输入数据转换为输出数据公用代码块。当然有的时候函数的返回值为空,那么就是说输出数据为空。而真正的处理过程在函数内部已经完成了。

想一想我们为什么需要函数,最直接的需求就是代码中有太多的重复代码了,为了代码的可读性和可维护性,将这些重复代码重构为函数也是必要的。

函数定义

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

先看一个例子

package main
import (
"fmt"
)
func slice_sum(arr []int) int {
sum :=
for _, elem := range arr {
sum += elem
}
return sum
}
func main() {
var arr1 = []int{, , , , }
var arr2 = []int{, , , , , , , }
fmt.Println(slice_sum(arr1))
fmt.Println(slice_sum(arr2))
}

在上面的例子中,我们需要分别计算两个切片的元素和。如果我们把计算切片元素的和的代码分别为两个切片展开,那么代码就失去了简洁性和一致性。假设你预想实现同样功能的代码在拷贝粘贴的过程中发生了错误,比如忘记改变量名之类的,到时候debug到崩溃吧。因为这时很有可能你就先入为主了,因为模板代码没有错啊,是不是。所以函数就是这个用处。

我们再仔细看一下上面的函数定义:

首先是关键字func,然后后面是函数名称参数列表,最后是返回值列表。当然如果函数没有参数列表或者返回值,那么这两项都是可选的。其中返回值两边的括号在只声明一个返回值类型的时候可以省略。

命名返回值

Go的函数很有趣,你甚至可以为返回值预先定义一个名称,在函数结束的时候,直接一个return就可以返回所有的预定义返回值。例如上面的例子,我们将sum作为命名返回值。

package main
import (
"fmt"
)
func slice_sum(arr []int) (sum int) {
sum =
for _, elem := range arr {
sum += elem
}
return
}
func main() {
var arr1 = []int{, , , , }
var arr2 = []int{, , , , , , , }
fmt.Println(slice_sum(arr1))
fmt.Println(slice_sum(arr2))
}

这里要注意的是,如果你定义了命名返回值,那么在函数内部你将不能再重复定义一个同样名称的变量。比如第一个例子中我们用sum:=0来定义和初始化变量sum,而在第二个例子中,我们只能用sum=0初始化这个变量了。因为:=表示的是定义并且初始化变量。

实参数和虚参数

可能你听说过函数的实参数和虚参数。其实所谓的实参数就是函数调用的时候传入的参数。在上面的例子中,实参就是arr1arr2,而虚参数就是函数定义的时候表示函数需要传入哪些参数的占位参数。在上面的例子中,虚参就是arr实参和虚参的名字不必是一样的。即使是一样的,也互不影响。因为虚参是函数的内部变量。而实参则是另一个函数的内部变量或者是全局变量。它们的作用域不同。如果一个函数的虚参碰巧和一个全局变量名称相同,那么函数使用的也是虚参。例如我们再修改一下上面的例子。

package main
import (
"fmt"
)
var arr = []int{, , , , }
func slice_sum(arr []int) (sum int) {
sum =
for _, elem := range arr {
sum += elem
}
return
}
func main() {
var arr2 = []int{, , , , , , , }
fmt.Println(slice_sum(arr))
fmt.Println(slice_sum(arr2))
}

在上面的例子中,我们定义了全局变量arr并且初始化值,而我们的slice_sum函数的虚参也是arr,但是程序同样正常工作。

函数多返回值

记不记得你在java或者c里面需要返回多个值时还得去定义一个对象或者结构体的呢?在Go里面,你不需要这么做了。Go函数支持你返回多个值。

其实函数的多返回值,我们在上面遇见过很多次了。那就是range函数。这个函数用来迭代数组或者切片的时候返回的是两个值,一个是数组或切片元素的索引,另外一个是数组或切片元素。在上面的例子中,因为我们不需要元素的索引,所以我们用一个特殊的忽略返回值符号下划线(_)来忽略索引。

假设上面的例子我们除了返回切片的元素和,还想返回切片元素的平均值,那么我们修改一下代码。

package main
import (
"fmt"
)
func slice_sum(arr []int) (int, float64) {
sum :=
avg := 0.0
for _, elem := range arr {
sum += elem
}
avg = float64(sum) / float64(len(arr))
return sum, avg
}
func main() {
var arr1 = []int{, , , , , , , }
fmt.Println(slice_sum(arr1))
}

很简单吧,当然我们还可以将上面的参数定义为命名参数

package main
import (
"fmt"
)
func slice_sum(arr []int) (sum int, avg float64) {
sum =
avg = 0.0
for _, elem := range arr {
sum += elem
}
avg = float64(sum) / float64(len(arr))
//return sum, avg
return
}
func main() {
var arr1 = []int{, , , , , , , }
sumvalue,avgvalue:=slice_sum(arr1)
fmt.Println(sumvalue,avgvalue)
}

在上面的代码里面,将return sum, avg给注释了而直接使用return。其实这两种返回方式都可以。

变长参数

想一想我们的fmt包里面的Println函数,它怎么知道你传入的参数个数呢?

package main
import (
"fmt"
)
func main() {
fmt.Println()
fmt.Println(, )
fmt.Println(, , )
}

这个要归功于Go的一大特性,支持可变长参数列表。

首先我们来看一个例子

package main
import (
"fmt"
)
func sum(arr ...int) int {
sum :=
for _, val := range arr {
sum += val
}
return sum
}
func main() {
fmt.Println(sum())
fmt.Println(sum(, ))
fmt.Println(sum(, , ))
}

在上面的例子中,我们将原来的切片参数修改为可变长参数,然后使用range函数迭代这些参数,并求和。
从这里我们可以看出至少一点那就是可变长参数列表里面的参数类型都是相同的如果你对这句话表示怀疑,可能是因为你看到Println函数恰恰可以输出不同类型的可变参数,这个问题的答案要等到我们介绍完Go的接口后才行)。

另外还有一点需要注意,那就是可变长参数定义只能是函数的最后一个参数。比如下面的例子:

package main
import (
"fmt"
)
func sum(base int, arr ...int) int {
sum := base
for _, val := range arr {
sum += val
}
return sum
}
func main() {
fmt.Println(sum(, ))
fmt.Println(sum(, , ))
fmt.Println(sum(, , , ))
}

这里不知道你是否觉得这个例子其实和那个切片的例子很像啊,在哪里呢?

package main
import (
"fmt"
)
func sum(base int, arr ...int) int {
sum := base
for _, val := range arr {
sum += val
}
return sum
}
func main() {
var arr1 = []int{, , , , }
fmt.Println(sum(, arr1...))
}

呵呵,就是把切片“啪,啪,啪”三个耳光打碎了,传递过去啊!:-P

闭包函数

曾经使用python和javascript的时候就在想,如果有一天可以把这两种语言的特性做个并集该有多好。

这一天终于来了,Go支持闭包函数。

首先看一个闭包函数的例子。所谓闭包函数就是将整个函数的定义一气呵成写好并赋值给一个变量。然后用这个变量名作为函数名去调用函数体。

我们将刚刚的例子修改一下:

package main
import (
"fmt"
)
func main() {
var arr1 = []int{, , , , }
var sum = func(arr ...int) int {
total_sum :=
for _, val := range arr {
total_sum += val
}
return total_sum
}
fmt.Println(sum(arr1...))
}

从这里我们可以看出,其实闭包函数也没有什么特别之处。因为Go不支持在一个函数的内部再定义一个嵌套函数,所以使用闭包函数能够实现在一个函数内部定义另一个函数的目的。

这里我们需要注意的一个问题是,闭包函数对它外层的函数中的变量具有访问修改的权限。例如:

package main
import (
"fmt"
)
func main() {
var arr1 = []int{, , , , }
var base =
var sum = func(arr ...int) int {
total_sum :=
total_sum += base
for _, val := range arr {
total_sum += val
}
return total_sum
}
fmt.Println(sum(arr1...))
}

这个例子,输出315,因为total_sum加上了base的值。

package main
import (
"fmt"
)
func main() {
var base =
inc := func() {
base +=
}
fmt.Println(base)
inc()
fmt.Println(base)
}

在上面的例子中,闭包函数修改了main函数的局部变量base。

最后我们来看一个闭包的示例,生成偶数序列。

package main
import (
"fmt"
)
func createEvenGenerator() func() uint {
i := uint()
return func() (retVal uint) {
retVal = i
i +=
return
}
}
func main() {
nextEven := createEvenGenerator()
fmt.Printf("nextEven变量类型为 = %T 值为%d\n", nextEven,nextEven() );
fmt.Printf("nextEven变量类型为 = %T 值为%d\n", nextEven,nextEven() );
fmt.Printf("nextEven变量类型为 = %T 值为%d\n", nextEven,nextEven() );
}

这个例子很有意思的,因为我们定义了一个返回函数定义的函数。而所返回的函数定义就是在这个函数的内部定义的闭包函数。这个闭包函数在外层函数调用的时候,每次都生成一个新的偶数(加2操作)然后返回闭包函数定义。

其中func() uint就是函数createEvenGenerator的返回值。在createEvenGenerator中,这个返回值是return返回的闭包函数定义。

func() (retVal uint) {
retVal = i
i +=
return
}

因为createEvenGenerator函数返回的是一个函数定义,所以我们再把它赋值给一个代表函数的变量,然后用这个代表闭包函数的变量去调用函数执行。

递归函数

每次谈到递归函数,必然绕不开阶乘和斐波拉切数列。

阶乘

package main
/**
n!=1*2*3*...*n
*/
import (
"fmt"
)
func factorial(x uint) uint {
if x == {
return
}
return x * factorial(x-)
}
func main() {
fmt.Println(factorial())
}

如果x为0,那么返回1,因为0!=1。如果x是1,那么f(1)=1f(0),如果x是2,那么f(2)=2f(1)=21f(0),依次推断f(x)=x(x-1)21*f(0)。

从上面看出所谓递归,就是在函数的内部重复调用一个函数的过程。需要注意的是这个函数必须能够一层一层分解,并且有出口。上面的例子出口就是0。

斐波拉切数列 Fibonacci

求第N个斐波拉切元素

package main
/**
f(1)=1
f(2)=2
f(n)=f(n-2)+f(n-1)
*/
import (
"fmt"
)
func fibonacci(n int) int {
var retVal =
if n == {
retVal =
} else if n == {
retVal =
} else {
retVal = fibonacci(n-) + fibonacci(n-)
}
return retVal
}
func main() {
fmt.Println(fibonacci())
}

斐波拉切第一个元素是1,第二个元素是2,后面的元素依次是前两个元素的和。

其实对于递归函数来讲,只要知道了函数的出口,后面的不过是让计算机去不断地推断,一直推断到这个出口。理解了这一点,递归就很好理解了。

异常处理

当你读取文件失败而退出的时候是否担心文件句柄是否已经关闭?抑或是你对于try…catch…finally的结构中finally里面的代码和try里面的return代码那个先执行这样的问题痛苦不已?

一切都结束了。一门完美的语言必须有一个清晰的无歧义的执行逻辑。

好,来看看Go提供的异常处理。

defer

package main
import (
"fmt"
)
func first() {
fmt.Println("first func run")
}
func second() {
fmt.Println("second func run")
}
func main() {
defer second()
first()
}

Go语言提供了关键字defer来在函数运行结束的时候运行一段代码或调用一个清理函数。上面的例子中,虽然second()函数写在first()函数前面,但是由于使用了defer标注,所以它是在main函数执行结束的时候才调用的。

所以输出结果

first func run
second func run

defer用途最多的在于释放各种资源。比如我们读取一个文件,读完之后需要释放文件句柄。

package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
fname := "D:\\Temp\\test.txt"
f, err := os.Open(fname)
defer f.Close()
if err != nil {
os.Exit()
}
bReader := bufio.NewReader(f)
for {
line, ok := bReader.ReadString('\n')
if ok != nil {
break
}
fmt.Println(strings.Trim(line, "\r\n"))
}
}

在上面的例子中,我们按行读取文件,并且输出。从代码中,我们可以看到在使用os包中的Open方法打开文件后,立马跟着一个defer语句用来关闭文件句柄。这样就保证了该文件句柄在main函数运行结束的时候或者异常终止的时候一定能够被释放。而且由于紧跟着Open语句,一旦养成了习惯,就不会忘记去关闭文件句柄了。

panic & recover

当你周末走在林荫道上,听着小歌,哼着小曲,很是惬意。突然之间,从天而降瓢泼大雨,你顿时慌张(panic)起来,没有带伞啊,淋着雨感冒就不好了。于是你四下张望,忽然发现自己离地铁站很近,那里有很多卖伞的,心中顿时又安定了下来(recover),于是你飞奔过去买了一把伞(defer)。

好了,panic和recover是Go语言提供的用以处理异常的关键字。panic用来触发异常,而recover用来终止异常并且返回传递给panic的值。(注意recover并不能处理异常,而且recover只能在defer里面使用,否则无效。)

先瞧个小例子

package main
import (
"fmt"
)
func main() {
fmt.Println("I am walking and singing...")
panic("It starts to rain cats and dogs")
msg := recover()
fmt.Println(msg)
}

咦?怎么没有输出recover获取的错误信息呢?

这是因为在运行到panic语句的时候,程序已经异常终止了,后面的代码就不运行了。

那么如何才能阻止程序异常终止呢?这个时候要使用defer。因为defer一定是在函数执行结束的时候运行的。不管是正常结束还是异常终止

修改一下代码

package main
import (
"fmt"
)
func main() {
defer func() {
msg := recover()
fmt.Println(msg)
}()
fmt.Println("I am walking and singing...")
panic("It starts to rain cats and dogs")
}

好了,看下输出

I am walking and singing...
It starts to rain cats and dogs

小结:

panic触发的异常通常是运行时错误。比如试图访问的索引超出了数组边界,忘记初始化字典或者任何无法轻易恢复到正常执行的错误。




第六节 使用函数

是时候讨论一下Go的函数定义了。

什么是函数

函数,简单来讲就是一段将输入数据转换为输出数据公用代码块。当然有的时候函数的返回值为空,那么就是说输出数据为空。而真正的处理过程在函数内部已经完成了。

想一想我们为什么需要函数,最直接的需求就是代码中有太多的重复代码了,为了代码的可读性和可维护性,将这些重复代码重构为函数也是必要的。

函数定义

先看一个例子

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func slice_sum(arr []int)int{
  6. sum :=0
  7. for _, elem := range arr {
  8. sum += elem
  9. }
  10. return sum
  11. }
  12. func main(){
  13. var arr1 =[]int{1,3,2,3,2}
  14. var arr2 =[]int{3,2,3,1,6,4,8,9}
  15. fmt.Println(slice_sum(arr1))
  16. fmt.Println(slice_sum(arr2))
  17. }

在上面的例子中,我们需要分别计算两个切片的元素和。如果我们把计算切片元素的和的代码分别为两个切片展开,那么代码就失去了简洁性和一致性。假设你预想实现同样功能的代码在拷贝粘贴的过程中发生了错误,比如忘记改变量名之类的,到时候debug到崩溃吧。因为这时很有可能你就先入为主了,因为模板代码没有错啊,是不是。所以函数就是这个用处。

我们再仔细看一下上面的函数定义:

首先是关键字func,然后后面是函数名称参数列表,最后是返回值列表。当然如果函数没有参数列表或者返回值,那么这两项都是可选的。其中返回值两边的括号在只声明一个返回值类型的时候可以省略。

命名返回值

Go的函数很有趣,你甚至可以为返回值预先定义一个名称,在函数结束的时候,直接一个return就可以返回所有的预定义返回值。例如上面的例子,我们将sum作为命名返回值。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func slice_sum(arr []int)(sum int){
  6. sum =0
  7. for _, elem := range arr {
  8. sum += elem
  9. }
  10. return
  11. }
  12. func main(){
  13. var arr1 =[]int{1,3,2,3,2}
  14. var arr2 =[]int{3,2,3,1,6,4,8,9}
  15. fmt.Println(slice_sum(arr1))
  16. fmt.Println(slice_sum(arr2))
  17. }

这里要注意的是,如果你定义了命名返回值,那么在函数内部你将不能再重复定义一个同样名称的变量。比如第一个例子中我们用sum:=0来定义和初始化变量sum,而在第二个例子中,我们只能用sum=0初始化这个变量了。因为:=表示的是定义并且初始化变量。

实参数和虚参数

可能你听说过函数的实参数和虚参数。其实所谓的实参数就是函数调用的时候传入的参数。在上面的例子中,实参就是arr1arr2,而虚参数就是函数定义的时候表示函数需要传入哪些参数的占位参数。在上面的例子中,虚参就是arr实参和虚参的名字不必是一样的。即使是一样的,也互不影响。因为虚参是函数的内部变量。而实参则是另一个函数的内部变量或者是全局变量。它们的作用域不同。如果一个函数的虚参碰巧和一个全局变量名称相同,那么函数使用的也是虚参。例如我们再修改一下上面的例子。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. var arr =[]int{1,3,2,3,2}
  6. func slice_sum(arr []int)(sum int){
  7. sum =0
  8. for _, elem := range arr {
  9. sum += elem
  10. }
  11. return
  12. }
  13. func main(){
  14. var arr2 =[]int{3,2,3,1,6,4,8,9}
  15. fmt.Println(slice_sum(arr))
  16. fmt.Println(slice_sum(arr2))
  17. }

在上面的例子中,我们定义了全局变量arr并且初始化值,而我们的slice_sum函数的虚参也是arr,但是程序同样正常工作。

函数多返回值

记不记得你在java或者c里面需要返回多个值时还得去定义一个对象或者结构体的呢?在Go里面,你不需要这么做了。Go函数支持你返回多个值。

其实函数的多返回值,我们在上面遇见过很多次了。那就是range函数。这个函数用来迭代数组或者切片的时候返回的是两个值,一个是数组或切片元素的索引,另外一个是数组或切片元素。在上面的例子中,因为我们不需要元素的索引,所以我们用一个特殊的忽略返回值符号下划线(_)来忽略索引。

假设上面的例子我们除了返回切片的元素和,还想返回切片元素的平均值,那么我们修改一下代码。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func slice_sum(arr []int)(int, float64){
  6. sum :=0
  7. avg :=0.0
  8. for _, elem := range arr {
  9. sum += elem
  10. }
  11. avg = float64(sum)/ float64(len(arr))
  12. return sum, avg
  13. }
  14. func main(){
  15. var arr1 =[]int{3,2,3,1,6,4,8,9}
  16. fmt.Println(slice_sum(arr1))
  17. }

很简单吧,当然我们还可以将上面的参数定义为命名参数

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func slice_sum(arr []int)(sum int, avg float64){
  6. sum =0
  7. avg =0.0
  8. for _, elem := range arr {
  9. sum += elem
  10. }
  11. avg = float64(sum)/ float64(len(arr))
  12. //return sum, avg
  13. return
  14. }
  15. func main(){
  16. var arr1 =[]int{3,2,3,1,6,4,8,9}
  17. fmt.Println(slice_sum(arr1))
  18. }

在上面的代码里面,将return sum, avg给注释了而直接使用return。其实这两种返回方式都可以。

变长参数

想一想我们的fmt包里面的Println函数,它怎么知道你传入的参数个数呢?

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func main(){
  6. fmt.Println(1)
  7. fmt.Println(1,2)
  8. fmt.Println(1,2,3)
  9. }

这个要归功于Go的一大特性,支持可变长参数列表。

首先我们来看一个例子

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func sum(arr ...int)int{
  6. sum :=0
  7. for _, val := range arr {
  8. sum += val
  9. }
  10. return sum
  11. }
  12. func main(){
  13. fmt.Println(sum(1))
  14. fmt.Println(sum(1,2))
  15. fmt.Println(sum(1,2,3))
  16. }

在上面的例子中,我们将原来的切片参数修改为可变长参数,然后使用range函数迭代这些参数,并求和。
从这里我们可以看出至少一点那就是可变长参数列表里面的参数类型都是相同的如果你对这句话表示怀疑,可能是因为你看到Println函数恰恰可以输出不同类型的可变参数,这个问题的答案要等到我们介绍完Go的接口后才行)。

另外还有一点需要注意,那就是可变长参数定义只能是函数的最后一个参数。比如下面的例子:

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func sum(baseint, arr ...int)int{
  6. sum :=base
  7. for _, val := range arr {
  8. sum += val
  9. }
  10. return sum
  11. }
  12. func main(){
  13. fmt.Println(sum(100,1))
  14. fmt.Println(sum(200,1,2))
  15. fmt.Println(sum(300,1,2,3))
  16. }

这里不知道你是否觉得这个例子其实和那个切片的例子很像啊,在哪里呢?

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func sum(baseint, arr ...int)int{
  6. sum :=base
  7. for _, val := range arr {
  8. sum += val
  9. }
  10. return sum
  11. }
  12. func main(){
  13. var arr1 =[]int{1,2,3,4,5}
  14. fmt.Println(sum(300, arr1...))
  15. }

呵呵,就是把切片“啪,啪,啪”三个耳光打碎了,传递过去啊!:-P

闭包函数

曾经使用python和javascript的时候就在想,如果有一天可以把这两种语言的特性做个并集该有多好。

这一天终于来了,Go支持闭包函数。

首先看一个闭包函数的例子。所谓闭包函数就是将整个函数的定义一气呵成写好并赋值给一个变量。然后用这个变量名作为函数名去调用函数体。

我们将刚刚的例子修改一下:

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func main(){
  6. var arr1 =[]int{1,2,3,4,5}
  7. var sum = func(arr ...int)int{
  8. total_sum :=0
  9. for _, val := range arr {
  10. total_sum += val
  11. }
  12. return total_sum
  13. }
  14. fmt.Println(sum(arr1...))
  15. }

从这里我们可以看出,其实闭包函数也没有什么特别之处。因为Go不支持在一个函数的内部再定义一个嵌套函数,所以使用闭包函数能够实现在一个函数内部定义另一个函数的目的。

这里我们需要注意的一个问题是,闭包函数对它外层的函数中的变量具有访问修改的权限。例如:

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func main(){
  6. var arr1 =[]int{1,2,3,4,5}
  7. varbase=300
  8. var sum = func(arr ...int)int{
  9. total_sum :=0
  10. total_sum +=base
  11. for _, val := range arr {
  12. total_sum += val
  13. }
  14. return total_sum
  15. }
  16. fmt.Println(sum(arr1...))
  17. }

这个例子,输出315,因为total_sum加上了base的值。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func main(){
  6. varbase=0
  7. inc := func(){
  8. base+=1
  9. }
  10. fmt.Println(base)
  11. inc()
  12. fmt.Println(base)
  13. }

在上面的例子中,闭包函数修改了main函数的局部变量base。

最后我们来看一个闭包的示例,生成偶数序列。

  1. package main
  2. import(
  3. "fmt"
  4. )
  5. func createEvenGenerator() func()uint{
  6. i :=uint(0)
  7. return func()(retVal uint){
  8. retVal = i
  9. i +=2
  10. return
  11. }
  12. }
  13. func main(){
  14. nextEven := createEvenGenerator()
  15. fmt.Println(nextEven())
  16. fmt.Println(nextEven())
  17. fmt.Println(nextEven())
  18. }

这个例子很有意思的,因为我们定义了一个返回函数定义的函数。而所返回的函数定义就是在这个函数的内部定义的闭包函数。这个闭包函数在外层函数调用的时候,每次都生成一个新的偶数(加2操作)然后返回闭包函数定义。

其中func() uint就是函数createEvenGenerator的返回值。在createEvenGenerator中,这个返回值是return返回的闭包函数定义。

  1. func()(retVal uint){
  2. retVal = i
  3. i +=2
  4. return
  5. }

因为createEvenGenerator函数返回的是一个函数定义,所以我们再把它赋值给一个代表函数的变量,然后用这个代表闭包函数的变量去调用函数执行。

递归函数

每次谈到递归函数,必然绕不开阶乘和斐波拉切数列。

阶乘

  1. package main
  2. /**
  3. n!=1*2*3*...*n
  4. */
  5. import(
  6. "fmt"
  7. )
  8. func factorial(x uint)uint{
  9. if x ==0{
  10. return1
  11. }
  12. return x * factorial(x-1)
  13. }
  14. func main(){
  15. fmt.Println(factorial(5))
  16. }

如果x为0,那么返回1,因为0!=1。如果x是1,那么f(1)=1f(0),如果x是2,那么f(2)=2f(1)=21f(0),依次推断f(x)=x(x-1)21*f(0)。

从上面看出所谓递归,就是在函数的内部重复调用一个函数的过程。需要注意的是这个函数必须能够一层一层分解,并且有出口。上面的例子出口就是0。

斐波拉切数列

求第N个斐波拉切元素

换个语言学一下 Golang (7)——使用函数的更多相关文章

  1. 换个语言学一下 Golang (12)——Web基础

    一.web工作方式 我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容.在这个看似简单的用户行为背后,到底隐藏了些什么呢?对于普通的上网过程,系统其实是这样做的 ...

  2. 换个语言学一下 Golang (3)——数据类型

    在 Go 编程语言中,数据类型用于声明函数和变量. 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存. Go 语言按类别有以下几种 ...

  3. 换个语言学一下 Golang (1)

    做技术的总是有些拗.这么多年一直在.net的框框里打转转.直到现在市场上.net工作越来越难找,项目越来越老才发现不做出改变不行了.就从学习Go开始吧. Go语言的特点 简洁.快速.安全 并行.有趣. ...

  4. 换个语言学一下 Golang (13)——Web表单处理

    介绍 表单是我们平常编写Web应用常用的工具,通过表单我们可以方便的让客户端和服务器进 行数据的交互.对于以前开发过Web的用户来说表单都非常熟悉.表单是一个包含表单元素的区域.表单元素是允许用户在表 ...

  5. 换个语言学一下 Golang (11)——使用包和测试

    Go天生就是为了支持良好的项目管理体验而设计的. 包 在软件工程的实践中,我们会遇到很多功能重复的代码,比如去除字符串首尾的空格.高质量软件产品的特点就是它的部分代码是可以重用的,比如你不必每次写个函 ...

  6. 换个语言学一下 Golang (4)——变量与常量

    一.变量定义 所谓的变量就是一个拥有指定名称和类型的数据存储位置. //看一个例子 package main import ( "fmt" ) func main() { var ...

  7. 换个语言学一下 Golang (2)——基础语法

    Go 标记 Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号.比如下面的hello world就是由 6 个标记组成: 行分隔符 在 Go 程序中,一行代表一个语句结束.每个语句 ...

  8. 换个语言学一下 Golang(14) ——fmt包

    Print() 函数将参数列表 a 中的各个参数转换为字符串并写入到标准输出中. 非字符串参数之间会添加空格,返回写入的字节数. func Print(a ...interface{}) (n int ...

  9. 换个语言学一下 Golang (10)——并行计算

    如果说Go有什么让人一见钟情的特性,那大概就是并行计算了吧. 做个题目 如果我们列出10以下所有能够被3或者5整除的自然数,那么我们得到的是3,5,6和9.这四个数的和是23.那么请计算1000以下( ...

随机推荐

  1. java.lang.IllegalStateException: No instances www.xxxx.com available for localhost

    在SpringCloud的项目中,我们使用了自动配置的OAuth2RestTemplate,RestTemplate,但是在使用这些restTemplate的时候,url必须是服务的名称,如果要调用真 ...

  2. Spark2.x(六十一):在Spark2.4 Structured Streaming中Dataset是如何执行加载数据源的?

    本章主要讨论,在Spark2.4 Structured Streaming读取kafka数据源时,kafka的topic数据是如何被执行的过程进行分析. 以下边例子展开分析: SparkSession ...

  3. 原创:【ajax | axios跨域简单请求+复杂请求】自定义header头Token请求Laravel5后台【亲测可用】

    如标题:我想在ajax的header头增加自定义Token进行跨域api认证并调用,api使用laravel5编写,如何实现? 首先,了解下CORS简单请求和复杂请求.  -- CORS简单请求 -- ...

  4. Armbian编译以及定制

    Armbian项目地址 Github: https://github.com/armbian/build Armbian for TV Box 项目地址 Github: https://github. ...

  5. java中为什么notify()可能会导致死锁,而notifyAll()则不会

    简单的说,notify()只唤醒一个正在等待的线程,当该线程执行完以后施放该对象的锁,而没有再次执行notify()方法,则其它正在等待的线程 则一直处于等待状态,不会被唤醒而进入该对象的锁的竞争池, ...

  6. GOROOT、GOPATH、GOBIN、project目录 _(转)

    前言:我觉得java程序员学golang很容易上手.关于GOROOT.GOPATH.GOBIN这些环境变量的设置,我隐约感觉到了java的影子(尽管我是一个C++程序员),唯一和java不同的是不能设 ...

  7. BIO,NIO,AIO到NETTY

    NIO 近期接触了几个产品都触及NIO,要么应用,要么改造项目,听多了也有些了解,但仍然不能真正理解,工期比较赶,还是要潜心下来看看. NIO是什么呢,应该是NOT-BLOCKING IO的意思,不阻 ...

  8. Qt开发经验小技巧71-80

    在我们使用QList.QStringList.QByteArray等链表或者数组的过程中,如果只需要取值,而不是赋值,强烈建议使用 at() 取值而不是 [] 操作符,在官方书籍<C++ GUI ...

  9. Ajax方式导出Excel,浏览器显示下载Excel表

    以前实现导出Excel,都是用form表单提交,因为jquery封装的ajax请求导出Excel,浏览器不显示文件. 但是这次的需求要带着header,form表单不能带header,百度了下,原生a ...

  10. ABP .NETCore更新数据库时一直连接的之前数据库

    使用Update-Database -Verbose更新数据库时,在appsettings.json配置文件中已修改为新的连接字符串,但是使用命令更新数据库时仍然连接的是之前的数据库. 后来把代码移至 ...