func
func关键字用来定义函数,函数是golang中非常重要的一块。定义一个函数的语法格式是:
func fnctionName(arg1 dataType,arg2 dataType)(dataType,dataType){
fmt.Println("func body")
return "a","b"
}
函数多返回值是golang的特性之一,在声明返回值时,可以创建带变量名的返回值,语法如下:
func functionName(arg1 dataType,arg2 dataType)(retName dataType){
fmt.Println("func body")
retName = "hello world"
return
}
上边的retName就是函数返回值的变量名,在函数的body内,可以直接给retName这个返回值变量赋值,效果等同于return retName
函数调用方法:
如果是同一个包中的函数,直接使用函数名加上括号即可调用,如果函数需要参数,则在括号内传入参数即可。如下代码所示:
package main
import (
"fmt"
)
func functionName(str string) {
fmt.Println(str)
}
func main() {
functionName("hello world")
}
如果要引用另一个包中的函数,首先使用import导入想要引用的包,然后以包名为前缀访问包中可导出函数。上面示例代码中Println函数在fmt包中,在调用Println函数时,首先导入了fmt包,然后通过fmt为前缀,调用Println函数。
函数的几个特点
* 参数
* 返回值
1.函数参数
golang函数支持两种形式的参数,第一是:固定个数参数,第二是可变个数参数。固定个数参数,就是函数接收固定个数的参数,如创建一个2个参数且没有返回值的函数
// 第一种写法
func World(str1, str2 string) {
fmt.Println(str1, str2)
}
// 第二种写法
func World(str1 string, str2 string) {
fmt.Println(str1, str2)
}
当参数类型相同时,可以简写成第一种写法。如果参数类型不同,只能采用第二种写法,依次指定每一个参数的类型。
上边介绍了固定参数的函数,下边介绍可变参数函数。可变参数只能是函数的最后一个参数,定义可变参数,只需要将参数的类型前边加上三点(…)即可。定义可变参数语法格式是:
func functionName(arg ...dataType){
fmt.Println("func body")
}
下边来一段示例代码,详细的介绍函数可变参数情形:
package main
import (
"fmt"
)
func VarParameter(str1 string, num int, other ...string) {
fmt.Println("第一个参数:", str1)
fmt.Println("第二个参数:", num)
fmt.Println("可变参数:", other, "参数类型是:", reflect.ValueOf(other).Kind())
for index, val := range other {
fmt.Println("可变参数第", index, "个值是:", val)
}
}
func main() {
VarParameter("var", 100, "a", "b", "c")
}
输出信息:
第一个参数: var
第二个参数: 100
可变参数: [a b c] 参数类型是:slice
可变参数第 0 个值是: a
可变参数第 1 个值是: b
可变参数第 2 个值是: c
other是一个可变参数,那么在golang中,可变参数是一个什么类型呢?答案是:slice。获取可变参数中的值,只需要按照slice类型变量的操作方法即可。
如果可变参数不是函数最后一个参数,那么在编译时会提示如下错误信息:
can only use ... with final parameter in list
如果在创建函数时,可变参数存在多种数据类型,则可以将可变参数类型设置成interface{},示例代码如下:
package main
import (
"fmt"
"reflect"
)
func VarParameter(str1 string, num int, other ...interface{}) {
fmt.Println("第一个参数:", str1)
fmt.Println("第二个参数:", num)
fmt.Println("可变参数:", other, "参数类型是:", reflect.ValueOf(other).Kind())
for index, val := range other {
fmt.Println("可变参数第", index, "个值是:", val, ",参数类型是:", reflect.ValueOf(val).Kind())
}
}
func main() {
VarParameter("var", 100, 1, "b", 3.234)
}
输出信息是:
第一个参数: var
第二个参数: 100
可变参数: [1 b 3.234] 参数类型是: slice
可变参数第 0 个值是: 1 ,参数类型是: int
可变参数第 1 个值是: b ,参数类型是: string
可变参数第 2 个值是: 3.234 ,参数类型是: float64
参数传递时,是值传递,还是指针传递呢?
当变量被当做参数传入调用函数时,是值传递,也称变量的一个拷贝传递。如果传递过来的值是指针,就相当于把变量的地址作为参数传递到函数内,那么在函数内对这个指针所指向的内容进行修改,将会改变这个变量的值。如下边示例代码:
package main
import (
"fmt"
)
func PtrTest(str *string) {
*str = "world"
}
func main() {
var str = "hello"
PtrTest(&str)
fmt.Println("str value is:", str)
}
输出信息是:
str value is: world
从上边的输出信息可知,str变量地址当做参数传入函数后,在函数中对地址所指向内容进行了修改,导致了变量str值发生了变化。
这个过程能否说明函数调用传递的是指针,而不是变量的拷贝呢?下边通过另一个例子来进行说明:
package main
import (
"fmt"
)
var workd = "hello wolrd"
func PtrTest(str *string) {
str = &workd
}
func main() {
var str = "hello"
PtrTest(&str)
fmt.Println("str value is:", str)
}
输出信息是:
str value is: hello
上边示例中,str变量地址被作为参数传入到了函数PtrTest中,在函数中对参数进行重新赋值,将world变量地址赋值给了参数,函数调用结束后,重新打印变量str值,发现值没有被修改。所以,在函数调用中,变量被拷贝了一份传入函数,函数调用结束后,拷贝的值被丢弃。如果拷贝的是变量的地址,那么在函数内,其实是通过修改这个地址所指向内存中内容,从而达到修改变量值的目的,但是函数内并不能修改这个变量的地址,也就是str变量虽然将地址当做参数传入到PtrTest函数中,PtrTest函数中虽然对这个地址进行了修改,但是在函数调用结束后,拷贝传递进去并被修改的参数被丢弃,str变量地址未发生变化。
2.多返回值
golang中一个函数可以有多个返回值,多返回值函数定义如下:
package main
import (
"fmt"
)
func functionName(str string) (string, int) {
return "hello" + str, 200
}
func main() {
msg, status := functionName("hello world")
fmt.Println("msg is:", msg)
fmt.Println("status is :", status)
}
输出信息是:
msg is: hellohello world
status is : 200
golang语言允许给返回值设置变量名,写法如下:
package main
import (
"fmt"
)
func functionName(str string) (msg string, status int) {
msg = "hello " + str
status = 200
return
}
func main() {
msg, status := functionName("hello world")
fmt.Println("msg is:", msg)
fmt.Println("status is :", status)
}
给返回值设置变量名后,在函数体内,可以直接使用返回值变量,无需在函数体内定义。
函数类型变量
golang可以定义函数类型的变量,函数类型的变量可以被调用。定义函数类型变量示例代码:
package main
import (
"fmt"
"reflect"
)
func main() {
var f = func(str string) {
fmt.Println("hello", str)
}
fmt.Println("类型是:", reflect.ValueOf(f).Kind())
f("func type")
}
输出信息是:
类型是: func
hello func type
函数类型变量也可以当做参数传递给另一个函数,然后在另一个函数中执行,示例代码如下:
package main
import (
"fmt"
"reflect"
)
func exec(f func(str string)) {
f("func type")
}
func main() {
var f = func(str string) {
fmt.Println("hello", str)
}
fmt.Println("类型是:", reflect.ValueOf(f).Kind())
exec(f)
}
输出信息是:
类型是: func
hello func type
exec函数接收一个函数类型的参数,那么是不是只要是函数,就可以被当做参数传入到exec中吗?答案是:不行。 从exec函数的参数列表可知,exec接收一个函数类型参数,这个函数类型参数接收一个字符串类型的参数。
匿名函数与函数闭包
匿名函数,就是定义函数的时候没有给函数取名字。定义函数类型变量,其实就是匿名函数的一种形式。而匿名函数又是闭包的一种形式,闭包示例如下:
package main
import (
"fmt"
)
func main() {
var str = "hello"
var num = 200
// 闭包,没有函数名也可称为匿名函数
func() {
fmt.Println(str, num)
}()
}
输出信息是:
hello 200
嵌套层级稍微复杂一些的闭包示例如下:
package main
import (
"fmt"
)
func main() {
var num = 200
var p = func() func() {
var s = 100
return func() {
fmt.Println(s + num)
}
}()
p()
num = 400
p()
}
输出结果是:
300
500
闭包在程序设计中有着诸多的价值,闭包中可以直接使用外部的变量,闭包内的变量又可以不被闭包外访问,保证闭包内变量的安全性。