函数特性:java
- Go 函数 不支持 嵌套、重载和默认参数
- 函数无需声明原型、支持不定长度变参、多返回值、命名返回值参数
- 支持匿名函数、闭包
- 函数也能够做为一种类型使用
- 函数是一等公民,可做为参数传递
- 函数传递是值的拷贝或者是指针的拷贝,区别在于会不会影响原始值
- 不定长变参,若是传入slice,必定要用 ...展开传入
定义函数使用关键字 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) }
实参:在调用有参函数时,函数名后面括号中的参数称为“实际参数”,实参能够是常量、变量或表达式。闭包
形参:自定义函数中的“形参”全称为"形式参数" 因为它不是实际存在变量,因此又称虚拟变量。实参和形参能够重名。形参的做用域是整个函数体就像定义在函数体内的局部变量函数式编程
变参函数
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...展开了 }
参数传递:code
(1)值传递:orm
传递的是值得拷贝,函数中对传入的变量修改,不会影响到实际参数。
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("helloworld", addperfix)//将对应类型的参数传入 }
小结:
一、Go语言函数中的参数不支持默认值。
二、不管是值传递,仍是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,通常来讲,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。
三、map、slice、chan、指针、interface默认以引用的方式传递。
四、函数的可变参数只能有一个,且必须是最后一个。
五、在参数赋值时能够不用用一个一个的赋值,能够直接传递一个数组或者切片,特别注意的是在参数后加上“…”便可
(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 }
defer、return、返回值三者的执行顺序应该是:return最早给返回值赋值;接着defer开始执行一些收尾工做;最后RET指令携带返回值退出函数。
匿名函数可赋值给变量,作为结构字段,或者在 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)())
闭包的概念:
闭包是能够包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(因为自由变量包含在代码块中,因此这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(做用域)。
我的理解
也就是,函数返回值是一个函数,返回的函数能作一些事情,经过主函数获得这个返回值函数,而后就能够屡次调用去作这个函数能够作的事情。这个函数应用改的全部的局部变量都将成为闭包中的变量,持续引用。
能够把 闭包理解成,返回值是一个对象,能够作一些操做。 咱们传值获得这个对象(实际上是函数),而后就能够传值作这个函数能作的事情了。函数式编程的概念这里不深究,大概了解闭包的使用。
注意:闭包引用的原局部变量也是拷贝,是对指针的拷贝。
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 } }
递归,就是在运行的过程当中调用本身。一个函数调用本身,就叫作递归函数。
构成递归需具有的条件:
一、子问题须与原始问题为一样的事,且更为简单。
二、不能无限制地调用自己,须有个出口,化简为非递归情况处理。
递归函数能够解决许多数学问题,如计算给定数字阶乘、产生斐波系列等;
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)) } }
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
这里简单介绍下go语言错误处理,(会有一篇详解错误处理的)。
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