Go起步:8、Go的函数
函数是基本的代码块,用于履行1个任务,是构成代码履行的逻辑结构。 函数定义函数其实在之前已见过了,第1次履行hello world程序的main()其实就是1个函数,而且是1个比较特殊的函数。每一个go程序都是从名为main的package包的main()函数开始履行包的概念不是这里的重点,以后做单独说明。同时main()函数是无参数,无返回值的。 func function_name( [parameter list] ) [return_types] {
函数体
} 定义解析:
函数调用Go的函数调用只要通过函数名然后向函数传递参数,函数就会履行并返回值回来。就像之前调用Println()输出信息1样。
同时这个规则也适用于变量的可见性,即首字母大写的变量才是全局的。 package main
import "fmt"
/* 函数返回两个数的较大值 */
func max(num1 int,num2 int) int {
/* 定义局部变量 */
var result int
if num1 > num2 {
result = num1
} else {
result = num2
}
return result
}
func main() {
var a int = 100
var b int = 200
var ret int
/* 调用函数并返回较大值 */
ret = max(a,b)
fmt.Printf("最大值是 : %dn",ret)
}
上面定义了1个函数max(),用于比较两个数,并返回其中较大的1个。终究通过main() 函数中调用 max()函数履行。 这里关于函数的参数列表有1个简便写法,当连续两个或多个函数的已命名形参类型相同时,除最后1个类型之外,其它都可以省略。 就像上面的func max(num1 int,num2 int) int {}定义,可以简写成 func max(num1,num2 int) int {} 多返回值前面定义函数时说过,Go的函数支持多返回值,这与C、C++和Java等开发语言极大不同。这个特性能够使我们写出比其他语言更优雅、更简洁的代码,比如File.Read()函 数就能够同时返回读取的字节数和毛病信息。如果读取文件成功,则返回值中的n为读取的字节 数,err为nil,否则err为具体的出错信息: func (file *File) Read(b []byte) (n int,err Error) 1个简单的例子以下: package main
import "fmt"
func swap(x,y string) (string,string) {
return y,x
}
func main() {
a,b := swap("hello","world")
fmt.Println(a,b)
}
上面实现了简单的字符串交换功能,代码实现上10分的简洁,由于支持多返回值,所以不需要想Java需要构建1个可以保存多个值得数据结构。 而且可以发现,对返回值如果是同1类型,可以不定义变量名称,虽然代码看上去是简洁了很多,但是命名后的返回值可让代码更清晰,可读性更强。 如果调用方调用了1个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单 地用1个下划线“_”来跳过这个返回值。就像上面的例子,如果我们只关注第1个返回值则可以写成: a,_ := swap("hello","world") 若值关注第2返回值则可以写成: _,b := swap("hello","world") 函数参数函数定义时指出,函数定义时有参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。
在默许情况下,Go 语言使用的是值传递,即在调用进程中不会影响到实际参数。 值传递Go中int类型保存的的是1个数字类型,下面定义1个交换函数swap(),用于交换两个参数的值。 package main
import "fmt"
func main() {
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %dn",a)
fmt.Printf("交换前 b 的值为 : %dn",b)
/* 通过调用函数来交换值 */
swap(a,b)
fmt.Printf("交换后 a 的值 : %dn",a)
fmt.Printf("交换后 b 的值 : %dn",b)
}
/* 定义相互交换值的函数 */
func swap(x,y int) int {
var temp int
temp = x /* 保存 x 的值 */
x = y /* 将 y 值赋给 x */
y = temp /* 将 temp 值赋给 y*/
return temp
}
援用参数通过前面的介绍可以知道,Go的指针类型是对变量地址的援用。 package main
import "fmt"
func main() {
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %dn",b)
/* 调用 swap() 函数
* &a 指向 a 指针,a 变量的地址
* &b 指向 b 指针,b 变量的地址
*/
swap(&a,&b)
fmt.Printf("交换后 a 的值 : %dn",y *int) {
var temp int
temp = *x /* 保存 x 的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y*/
}
可以发现,终究传进来的参数指在履行交换函数swap()后也被修改了,这是由于参数终究指向的都是地址的援用,所有援用被修改了,值也就相应的变了。 不定参数顾名思义,不定参数就是函数的参数不是固定的。这个在C和Java里都有。在之前的代码中,包fmt下面的 fmt.Println()函数也是参数不定的。 不定参数类型先看1个函数的定义: func myfunc(args ...int) {
} 可以看出,上面的定义和之前的函数定义最大的不同就是,他的参数是以“…type”的方式定义的,这和Java的语法有些类似,也是用”…”实现。需要说明的是“…type”在Go中只能作为参数的情势出现,而且只能作为函数的最后1个参数。 package main
import "fmt"
func main() {
var a int = 100
var b int = 200
myfunc(a,b)
}
func myfunc(args ...int) {
fmt.Println(args)
for _,arg := range args {
fmt.Println(arg)
}
}
从上面的结果可以看出,类型“…type“本质上是1个数组切片,也就是[]type,所以参数args可以用for循环来取得每一个传入的参数。 这是Go的1 个语法糖(syntactic sugar),即这类语法对语言的功能并没有影响,但是更方便程序员使用。通常来讲,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。 不定参数的传递一样是上面的myfunc(args …int)函数为例,在参数赋值时可以不用用1个1个的赋值,可以直接传递1个数组或切片,特别注意的是在参数后加上“…”便可。 package main
import "fmt"
func main() {
arr := []int{100,200,300}
myfunc(arr...)
myfunc(arr[:2]...)
}
func myfunc(args ...int) {
fmt.Println(args)
for _,arg := range args {
fmt.Println(arg)
}
}
任意类型的不定参数上面的例子在定义不定参数时,都有1个要求,参数的类型是1致的。那末如果函数的参数类型不1致,如何使用不定参数方式来定义。在Go中,要实现这个需求需要引入1个新的类型–interface{}。看名字可以看出,这类类型实际上就是接口。关于Go的接口这里只做引出。 func Println(a ...interface{}) (n int,err error) {
return Fprintln(os.Stdout,a...)
} 其实用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。 package main
import (
"fmt"
"reflect"
)
func main() {
arr := []int{100, 200, 300}
myfunc(100,"abc",arr)
}
func myfunc(args ...interface{}) {
fmt.Println(args)
for _,arg := range args {
fmt.Println(arg)
fmt.Println(reflect.TypeOf(arg))
fmt.Println("=======")
}
}
匿名函数匿名函数是指不需要定义函数名的1种函数实现方式。1958年LISP首先采取匿名函数。 package main
import (
"fmt"
"math"
)
func main() {
getSqrt := func(a float64) float64 {
return math.Sqrt(a)
}
fmt.Println(getSqrt(4))
}
上面先定义了1个名为getSqrt 的变量,初始化该变量时和之前的变量初始化有些不同,使用了func,func是定义函数的,可是这个函数和上面说的函数最大不同就是没有函数名,也就是匿名函数。这里将1个函数当作1个变量1样的操作。 闭包理解闭包闭包的应当都听过,但到底甚么是闭包呢? <!DOCTYPE html>
<html lang="zh">
<head>
<title></title>
</head>
<body>
</body>
</html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript"></script>
<script>
function a(){
var i=0;
function b(){
console.log(++i);
document.write("<h1>"+i+"</h1>");
}
return b;
}
$(function(){
var c=a();
c();
c();
c();
//a(); //不会有信息输出
document.write("<h1>=============</h1>");
var c2=a();
c2();
c2();
});
</script> 这段代码有两个特点:
这样在履行完var c=a()后,变量c实际上是指向了函数b(),再履行函数c()后就会显示i的值,第1次为1,第2次为2,第3次为3,以此类推。
在上面的例子中,由于闭包的存在使得函数a()返回后,a中的i始终存在,这样每次履行c(),i都是自加1后的值。
下面来想象另外一种情况,如果a()返回的不是函数b(),情况就完全不同了。由于a()履行完后,b()没有被返回给a()的外界,只是被a()所援用,而此时a()也只会被b()引 用,因此函数a()和b()相互援用但又不被外界打扰(被外界援用),函数a和b就会被GC回收。所以直接调用a();是页面并没有信息输出。 下面来讲闭包的另外一要素援用环境。c()跟c2()援用的是不同的环境,在调用i++时修改的不是同1个i,因此两次的输出都是1。函数a()每进入1次,就构成了1个新的环境,对应的闭包中,函数都是同1个函数,环境却是援用不同的环境。这和c()和c()的调用顺序都是无关的。 以上就是对闭包作用的非常直白的描写,不专业也不严谨,但大概意思就是这样,理解闭包需要按部就班的进程。 Go的闭包Go语言是支持闭包的,这里只是简单地讲1下在Go语言中闭包是如何实现的。 package main
import (
"fmt"
)
func a() func() int {
i := 0
b := func() int {
i++
fmt.Println(i)
return i
}
return b
}
func main() {
c := a()
c()
c()
c()
//a() //不会输出i
}
可以发现,输出和之前的JavaScript的代码是1致的。具体的缘由和上面的也是1样的,这里就不在赘述了。 这页说明Go语言是支持闭包的,至于具体是如何支持的目前就先做讨论了。 关于闭包这里也只讲到了基本的概念,至于更深入的东西我目前能力有限只能靠以后渐渐摸索。就像上面讲到的,理解闭包需要按部就班的进程。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |