函数由关键字 func、函数名、参数列表、返回值、函数体和返回语句组成,每一个程序都包含很多的函数,函数是基本的代码块。另外有一点需要注意的是在 Go 里面函数重载是不被允许的。
func 函数名(形式参数列表)(返回值列表){
函数体
}
-
函数的参数及返回值
-
正常的参数列表
func add(x int, y int) int { x = x - 10 y = y - 10 return x + y } -
参数类型省略
// x和y都是int类型 func add(x, y int) int { x = x - 10 y = y - 10 return x + y } -
可变参数
func add(x int, y ...int) int { x = x - 10 fmt.Println("%T\n",y) // y的类型是int类型的切片 []int return x } -
省略参数名
函数定义时,它的形参一般是有名字的,不过我们也可以定义没有形参名的函数,只有相应的形参类型,这种方法可以在接口中使用,在继承接口实现这个方法的时候就参数名可以随便命名
func f(int, int, float64)注意:在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的引用传递被修改。如果函数的返回值太多,那么此时可以通过传递地址的方式将实参的地址传递到函数中,那么此时函数修改的就是真实参数的值了,就不需要再进行返回值 -
go语言支持多个返回值返回
func test(x, y int) (int, int) { return x, y } -
带有变量名的返回值
func test1(x, y int) (c int, d int) { c = x d = y return // 如果返回值带有变量名那么return之后可以不指定返回的参数,默认返回同名的参数 }注意:同一种类型返回值和命名返回值两种形式只能二选一,混用时将会发生编译错误
-
-
函数的类型
Go语言里面拥有三种类型的函数:
-
普通的带有名字的函数
-
匿名函数或者 lambda 函数
-
方法
第一个普通的带有名字的函数上边已经讲了,下边我们说一下后边两种:
-
匿名函数或者lambda函数
匿名函数的定义如下
func(参数列表)(返回参数列表){ 函数体 }-
匿名函数的调用
匿名函数可以在声明后调用,例如:
func(data int) { fmt.Println("hello", data) }(100)在最后的小括号就是对于这个函数的调用,参数为100
-
赋值再调用
// 将匿名函数体保存到f()中 f := func(data int) { fmt.Println("hello", data) } // 使用f()调用 f(100) -
匿名函数作为回调函数使用
func test2(f func(int) int) int { i := f(100) return i } func main() { i := test2(func(i int) int { return i }) fmt.Println(i) }如果你使用过java的lambda表达式你可以想到这就是lambda表达式的形式,只不过lambda表达式更加的简略,直接传递参数和方法体,并不是一个完整的函数定义。
-
-
方法
方法也是一种函数,但是这是一种特殊的函数。对于面向对象,go中的方法就像java中的成员函数一样,他是用来描述对象的一类行为。但是在go中并没有class这一说法,所以go就是用了另类的函数(方法)用来描述一个某个类型的行为。他就是接受者类型。这个唯一的接受者被称为方法声明的接受者参数。接受者参数必须被()括住,并且声明在func和函数名称之间。
比如我们定义一个Person结构体
type Person struct { Age int Name string } // 方法 func (p *Person) GetName() string { return p.Name }其中GetName就是方法(在go中开头大写表示共有,小写表示私有),他只属于Person这个结构体,其他的类型不能调用。所以我们可以明确的给类型T和*T声明一个方法。
但是要注意的是如果要定义一个方法有以下几个需要注意的地方:
-
T必须是定义的类型
-
T的定义必须跟方法定义的在同一个包中。
-
T不能是指针类型
-
T不能是interface类型。
-
指针接收器和非指针接收器
上面我们使用了 *Person作为接收器,其实我们还可以使用Person作为接收器,二者的效果是一样的。
type Person struct { Age int Name string } // 方法 func (p Person) GetName() string { return p.Name }
-
-
-
defer
关键字 defer 的用法类似于Java的 finally 语句块,它一般用于释放某些已分配的资源,典型的例子就是对互斥解锁,或者关闭一个文件。
-
defer的执行顺序
当有多个 defer 行为被注册时,它们会以逆序执行(即后进先出),有如下程序
func main() { defer test3() defer test4() defer test5() defer test6() } func test3() { fmt.Println("我是test3") } func test4() { fmt.Println("我是test4") } func test5() { fmt.Println("我是test5") } func test6() { fmt.Println("我是test6") }我们运行后打印出来的结果为
我是test6 我是test5 我是test4 我是test3 -
defer和返回值
有如下两个方法
func test7() int { var result int defer func() { result++ }() return result } func test8() (result int) { defer func() { result++ }() return result }我们在接受这两个方法的返回值并打印后发现test7返回的是0,而test8返回的是1,那这两个看似一样的方法为什么返回值却不一样呢?
如果要解决这个问题必须要了解defer的作用原理:
- 将result赋值给返回值(可以理解成Go自动创建了一个返回值变量retVlaue,相当于执行retValue = result)
- 然后检查是否有defer,如果有则执行
- 返回刚才创建的返回值(retValue)
所以在使用匿名返回值的时候,go返回的真正变量并不是我们return的result,所以当defer修改result的值时并不会影响返回值。但是如果我们指定了返回值的名称,那么go就只能返回这个参数,所以此时defer就可以影响返回值的内容了
-