07GO语言函数

杨旭 · · 517 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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

有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:杨旭

查看原文:07GO语言函数

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

517 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传