内容简介:定义函数使用关键字 func,且左大括号不能另起一行实参:在调用有参函数时,函数名后面括号中的参数称为“实际参数”,实参可以是常量、变量或表达式。形参:自定义函数中的“形参”全称为"形式参数" 由于它不是实际存在变量,所以又称虚拟变量。实参和形参可以重名。形参的作用域是整个函数体就像定义在函数体内的局部变量
go语言函数
- 1、函数定义
- 2、参数
- 3、返回值
- 4、匿名函数
- 5、闭包
- 6、递归
- 7、延迟调用
- 8、错误处理
函数特性:
- Go 函数 不支持 嵌套、重载和默认参数
- 函数无需声明原型、支持不定长度变参、多返回值、命名返回值参数
- 支持匿名函数、闭包
- 函数也可以作为一种类型使用
- 函数是一等公民,可作为参数传递
- 函数传递是值的拷贝或者是指针的拷贝,区别在于会不会影响原始值
- 不定长变参,如果传入slice,一定要用 ...展开传入
1、函数定义
定义函数使用关键字 func,且左大括号不能另起一行
func A(a int,b string) int{ // 参数 a, b 名在前,类型在后 // 如果只有一个返回值,直接写个int就行。表示返回值是一个int型 } func A(a int,b string) (int,string){ //有多个返回值的时候,小括号括起来,用逗号隔开 } func A(a,b,c int){ //如果 多个参数类型一样,可以这样写 } func A()(a,b,c int){ //对于返回值也可以这样写,简写一定要命名返回的是谁,如a,b,c a,b,c=1,2,3 //在返回值的时候已经命名了a,b,c 已经分配好内存地址, //所以在这里直接进行赋值就可以, 不需要加 := return a,b,c // 返回的时候不用说明返回的是谁,因为函数已经命名返回的名称类型。 //可以直接写一个return 就可以,但是为了代码可读性,还是写全 } func B()(int,int,int){ // 这样写不命名返回值,但是return时要注意 a,b,c :=1,2,3 //没有开辟内存控件,所以要用 := return a,b,c //由于没有命名返回值,所以必须要把a,b,c返回 }
函数是一种类型,建议将复杂签名定义为函数类型,以便于阅读。
func test(fn func() int) int { return fn() } type FormatFunc func(s string, x, y int) string // 定义函数类型。 func format(fn FormatFunc, s string, x, y int) string { return fn(s, x, y) } func main() { s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。 s2 := format(func(s string, x, y int) string { return fmt.Sprintf(s, x, y) }, "%d, %d", 10, 20) println(s1, s2) }
2、参数
实参:在调用有参函数时,函数名后面括号中的参数称为“实际参数”,实参可以是常量、变量或表达式。
形参:自定义函数中的“形参”全称为"形式参数" 由于它不是实际存在变量,所以又称虚拟变量。实参和形参可以重名。形参的作用域是整个函数体就像定义在函数体内的局部变量
变参
go语言的变参本质上就是 slice。只能有⼀个,且必须是最后⼀个。
func test(s string, n ...int) string {//使用 变量名 ...类型声明可变参,只能是最后一个 var x int for _, i := range n { x += i } return fmt.Sprintf(s, x) } func main() { println(test("sum: %d", 1, 2, 3)) }
注意:在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”。即:给可变参数传一个slice时,必须用...展开
func main() { s := []int{1, 2, 3} println(test("sum: %d", s...))//test函数接收int型可变参,这里传入int型slice, s...展开了 }
参数传递:
(1)值传递:
传递的是值得拷贝,函数中对传入的变量修改,不会影响到实际参数。
func sum(x, y int) int { //接收int型 的x,y 其实是出入实参的拷贝 return x + y }
(2)引用传递
将实际参数的地址传递到函数中,比如传入指针,在函数中修改会改变实际参数。
func swap(x, y *string) {//接受的是指针类型,通过指针直接操作内存 var temp string temp = *x *x = *y *y = temp }
(3)固定类型可变参数
就是最后一个参数是可变参数,可以传入不固定的参数,但是类型相同。
func variable(str string, source ...string){//可变参数,固定类型 fmt.Println("可变参数的长度:",len(source)) }
(4)任意类型的不定参数
函数和每个参数的类型都不固定。 形参用 interface{}类型的可变参数声明
func variable(values ...interface{}) {//接收一个可变参数,空接口类型(任何类型都实现了空接口) for _, val := range values { switch v := val.(type) { case int: fmt.Println("val type is int ", v) case float64: fmt.Println("val type is float ", v) case string: fmt.Println("val type is string ", v) case bool: fmt.Println("val type is bool ", v) default: fmt.Println("val type is unknow ", v) } } }
(5)函数类型参数
函数类型赋值给变量,作为参数传递引用
// 定义函数类型--这个函数接收两个string的参数 type myfunc func(string, string) func addperfix(perfix, name string) {//这个函数正好符合myfunc这个函数类型 fmt.Println(perfix, name) } // 第二个参数接收的是一个 myfunc类型的参数 func sayhello(name string, f myfunc) { f("hello", name) } func main() { sayhello("oldboy", addperfix)//将对应类型的参数传入 }
小结:
1、 Go 语言函数中的参数不支持默认值。
2、无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
3、map、slice、chan、指针、interface默认以引用的方式传递。
4、函数的可变参数只能有一个,且必须是最后一个。
5、在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可
3、返回值
(1)有返回值的函数必须写return,否则报错
(2)可以返回多个返回值,声明的时候用括号括起来
(3)返回值可以被命名,命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。 命名返回参数
可被同名局部变量遮蔽,此时需要显式返回
(4)不能⽤容器对象接收多返回值。只能⽤多个变量,或 "_" 忽略。
func test() (int, int) { return 1, 2 } func main() { // s := make([]int, 2) // s = test() // Error: multiple-value test() in single-value context x, _ := test() //有返回值必须用多个变量接。 println(x) }
(5)多返回值可直接作为其他函数调⽤实参。
func test() (int, int) { return 1, 2 } func add(x, y int) int { return x + y } func sum(n ...int) int { var x int for _, i := range n { x += i } return x } func main() { println(add(test())) println(sum(test())) }
(6)命名返回参数允许 defer 延迟调⽤通过闭包读取和修改。
func add(x, y int) (z int) { defer func() { z += 100 }() z = x + y return } func main() { println(add(1, 2)) // 输出: 103 }
显式 return 返回前,会先修改命名返回参数。
//这段代码,命名返回值z,就相当于局部变量,返回z+200,即z=z+200 func add(x, y int) (z int) { defer func() { println(z) // 输出: 203 }() z = x + y return z + 200 // 执⾏顺序: (z = z + 200) -> (call defer) -> (ret) } func main() { println(add(1, 2)) // 输出: 203 }
4、匿名函数
匿名函数可赋值给变量,做为结构字段,或者在 channel ⾥传送。
匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必声明
fn := func() { println("Hello, World!") } fn() // --- function collection --- fns := [](func(x int) int){ func(x int) int { return x + 1 }, func(x int) int { return x + 2 }, } println(fns[0](100)) // --- function as field --- d := struct { fn func() string }{ fn: func() string { return "Hello, World!" }, } println(d.fn()) // --- channel of function --- fc := make(chan func() string, 2) fc <- func() string { return "Hello, World!" } println((<-fc)())
5、闭包
闭包的概念:
闭包是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。
个人理解
也就是,函数返回值是一个函数,返回的函数能做一些事情,通过主函数得到这个返回值函数,然后就可以多次调用去做这个函数可以做的事情。这个函数应用改的所有的局部变量都将成为闭包中的变量,持续引用。
可以把 闭包理解成,返回值是一个对象,可以做一些操作。 我们传值得到这个对象(其实是函数),然后就可以传值做这个函数能做的事情了。函数式编程的概念这里不深究,大概了解闭包的使用。
注意:闭包引用的原局部变量也是拷贝,是对指针的拷贝。
func main(){ f:=closure(10) fmt.Println(f(1)) fmt.Println(f(2)) fmt.Println(f(3)) fmt.Println(f(4)) } func closure(x int) func(int)int{ return func(y int) int{ return x+y } }
6、递归
递归,就是在运行的过程中调用自己。一个函数调用自己,就叫做递归函数。
构成递归需具备的条件:
1、子问题须与原始问题为同样的事,且更为简单。
2、不能无限制地调用本身,须有个出口,化简为非递归状况处理。
递归函数可以解决许多数学问题,如计算给定数字阶乘、产生斐波系列等;
package main import "fmt" //菲波那切数列 func fibonaci(i int) int { if i == 0 { return 0 } if i == 1 { return 1 } return fibonaci(i-1) + fibonaci(i-2) } func main() { var i int for i = 0; i < 10; i++ { fmt.Printf("%d\n", fibonaci(i)) } }
7、延迟调用-defer
- defer的执行方式类似其它语言中的析构函数,在函数体执行结束后按照调用顺序的相反顺序逐个执行
- 即使函数发生严重错误也会执行
- 支持匿名函数的调用
- 常用于资源清理、文件关闭、解锁以及记录时间等操作
- 通过与匿名函数配合可在return之后修改函数计算结果
- 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址
- Go 没有异常机制,但有 panic/recover 模式来处理错误
- Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
func main(){ //defer 逆序调用 for i:=0;i<10;i++{ defer fmt.Println(i) } } //执行结果是: 从9 到0
看一个有趣的例子:
func main(){ for i:=0;i<10;i++{ defer func(){ fmt.Println(i) }() } } //执行结果 全部都是10
当defer 的是个匿名函数的时候,这里就会出现闭包。每次执行循环体,都只是 对i的引用,可以理解成 把defer 存入栈,但是存的是func这个匿名函数并没有赋值i,等到执行完循环体开始执行defer 语句的时候,就会给i赋值,这个时候的i就是10,所以输出了10个10
8、错误处理
这里简单介绍下go语言错误处理,(会有一篇详解错误处理的)。
- defer 在函数发声严重错误的时候也会执行,类似 java 中 try..catch 中的finally 。
- go 语言没有异常机制,但有 panic/recover 模式来处理错误
- Panic 可以在任何地方引发,但recover只有在defer调用的函数中有效
func main() { A() B() C() } func A(){ fmt.Println("func A") } func B() { defer func() { //注意defer 要在panic之前注册函数 if err:=recover();err!=nil{ //调用recover(),返回一个panic的信息, // 如果返回nil,说明没错,如果返回不是nil,说明引发了panic,在进行recover操作 fmt.Println("Recover in B") } }() panic("Panic in B") } func C(){ fmt.Println("func C") } //打印结果 //func A //Recover in B //func C
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。