42

07GO语言函数

 5 years ago
source link: https://studygolang.com/articles/18587?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK