首页 > 其他分享 >【go从入门到精通】函数详解

【go从入门到精通】函数详解

时间:2024-03-27 11:01:57浏览次数:23  
标签:return 入门 int 详解 func go main panic 函数

作者简介:

        高科先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C++,go等语言开发经验,mysql,mongo,redis等数据库,设计模式和网络库开发经验,对战棋类,回合制,moba类页游,手游有丰富的架构设计和开发经验。 (谢谢你的关注)

--------------------------------------------------------------------------------------------------------------------------------

函数定义

        Go语言函数基本组成:关键字func、函数名、参数列表、返回值、函数体和返回语句。语法如下:

func 函数名(参数列表) (返回值列表) {
    // 函数体
     return
}

        函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。

        有点简单,很多做C,C++开发的都知道声明函数原型是一件有点冗余的事情,而转到go之后,你会发现go函数用起来便轻松简洁很多,至少不需要你声明函数原型。那么我们接下来一起先看看go函数都有哪些特点:  

    • 无需声明原型。
    • 支持不定 变参。
    • 支持多返回值。
    • 支持命名返回参数。 
    • 支持匿名函数和闭包。
    • 函数也是一种类型,一个函数可以赋值给变量。

    • 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
    • 不支持 重载 (overload) 
    • 不支持 默认参数 (default parameter)。 

"func" 为定义函数的关键字,函数这一行的最末尾需要一个左大括号,注意左大括号依旧不能另起一行。

这里我写一个简单的两整数相加的函数

package main
import (
	"fmt"
)
func Sum(a int, b int) int {
	return a + b
}
func main() {
	fmt.Println("sum(1,2):", Sum(1, 2))

}

运行下结果

sum(1,2): 3

这里实际上如果类型相同的相邻参数,我们可以吧参数类型合并一下,所以我们可以这样写Sum函数:

func Sum(a, b int) int {
	return a + b
}

所以接下来我们接下来要说下函数的参数

函数参数

       函数定义时有参数,该参数变量可称为函数的形参。形参就像定义在函数体内的局部变量。

但当调用函数,传递过来的变量就是函数的实参。

值传递和引用传递

函数可以通过两种方式来传递参数:

值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

func swap(x, y int) int {
       ... ...
  }

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

package main

import (
    "fmt"
)

/* 定义相互交换值的函数 */
func swap(x, y *int) {
    var temp int

    temp = *x /* 保存 x 的值 */
    *x = *y   /* 将 y 值赋给 x */
    *y = temp /* 将 temp 值赋给 y*/

}

func main() {
    var a, b int = 1, 2
    /*
        调用 swap() 函数
        &a 指向 a 指针,a 变量的地址
        &b 指向 b 指针,b 变量的地址
    */
    swap(&a, &b)

    fmt.Println(a, b)
}

输出结果:

    2 1

再来一个map的传参例子:

package main

import (
    "fmt"
)

func Mod(a []int) {
	for i, v := range a {
		a[i] = 100 + v
	}
}

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	Mod(numbers)
	fmt.Println(numbers)
}

输出结果:

[101 102 103 104 105 106 107 108 109 110]

可见,虽然我们的Mod函数传参过来并非指针,表面上好像是值传递,从结果上来看似乎又间接的修改了slice的值,因此slice   实际上是以引用的方式传递。

总结

在默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

注意2:map、slice、chan、指针、interface默认以引用的方式传递。

不定参数传值

 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)

Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。

  func myfunc(args ...int) {    //0个或多个参数
  }

  func add(a int, args…int) int {    //1个或多个参数
  }

  func add(a int, b int, args…int) int {    //2个或多个参数
  }

注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.

其实说到这里你可能会想到我们最开始写helloworld的时候,用到的fmt.Println函数。接下来我用一个简单的例子来调用不定参数的函数:

package main

import (
    "fmt"
)

func Sum(a int, args ...int) (sum int) {
    sum = a
    for _, v := range args {
        sum += v
    }
    return sum
}

func main() {
    fmt.Println("sum(1-4):", Sum(1, 2, 3, 4))
}

输出:sum(1-4): 

由于这种不定参的函数可以接受某种类型的切片 slice 为参数,因此我们的代码还可以这样调用起来:

package main

import (
    "fmt"
)
func Sum(args ...int) (sum int) {
	sum = 0
	for _, v := range args {
		sum += v
	}
	return sum
}

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	fmt.Println("sum(1-4):", Sum(numbers ...)) // slice时使用
}

在调用的时候我们需要给slice参数后边加上...即可。

任意类型的不定参数


就是函数的参数和每个参数的类型都不是固定的。

用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。

func myfunc(args ...interface{}) {
  }

 函数重载

         函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参或者不同的返回值,在 Go 语言里面函数重载是不被允许的。

         当你这样写同名函数的时候如下:

func Mod(a []int) {
	for i, v := range a {
		a[i] = 100 + v
	}
}
func Mod(a int)  {
	a = a+1
}

    编译报错,信息如下:

  Mod redeclared in this block    
  other declaration of Mod

函数签名        

        函数也可以作为函数类型被使用。函数类型也就是函数签名,函数类型表示具有相同参数和结果类型的所有函数的集合。函数类型的未初始化变量的值为nil。就像下面:

type  funcType func (int, int) int

上面通过type关键字,定义了一个新类型,函数类型 funcType 。

        函数签名由函数参数、返回值以及它们的类型组成,如果两个函数的参数列表和返回值列表的变量类型能一一对应,那么这两个函数就有相同的签名,下面testa与testb具有相同的函数签名。

func testa  (a, b int, z float32) bool
func testb  (a, b int, z float32) (bool)

函数调用传入的参数必须按照参数声明的顺序。而且Go语言没有默认参数值的说法。

那么我们如何来显示的调用对应的函数呢?我们可以这样来:

package main

import (
    "fmt"
)

func testa(a int, b int, z float32) bool {
	fmt.Println("testa")
	return a+b > int(z)
}
func testb(a, b int, z float32) bool {
	fmt.Println("testb")
	return a+b > int(z)
}

type FuncType func(int, int, float32) bool

func main() {
	var z float32
	z = 10.345
	FuncType(testa)(1, 2, z)
	FuncType(testb)(1, 2, z)
}

函数也可以在表达式中赋值给变量,这样作为表达式中右值出现,我们称之为函数值字面量(function literal),函数值字面量是一种表达式,它的值被称为匿名函数,就像下面一样:

f := func() int { return 7 }

下面代码对以上2种情况都做了定义和调用:

package main

import (
    "fmt"
    "time"
)

type funcType func(time.Time)     // 定义函数类型funcType

func main() {
    f := func(t time.Time) time.Time { return t } // 方式一:直接赋值给变量
    fmt.Println(f(time.Now()))

    var timer funcType = CurrentTime // 方式二:定义函数类型funcType变量timer
    timer(time.Now())

    funcType(CurrentTime)(time.Now())  // 先把CurrentTime函数转为funcType类型,然后传入参数调用
// 这种处理方式在Go 中比较常见
}

func CurrentTime(start time.Time) {
    fmt.Println(start)
}

函数返回值

"_"标识符,用来忽略函数的某个返回值

Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。

返回值的名称应当具有一定的意义,可以作为文档使用。

没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。

直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。

package main

import (
    "fmt"
)

func add(a, b int) (c int) {
    c = a + b
    return
}

func calc(a, b int) (sum int, avg int) {
    sum = a + b
    avg = (a + b) / 2

    return
}

func main() {
    var a, b int = 1, 2
    c := add(a, b)
    sum, avg := calc(a, b)
    fmt.Println(a, b, c, sum, avg)
}

输出结果:

    1 2 3 3 1 

Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 "_" 忽略。

package main

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)
}

输出结果:

    1 

多返回值可直接作为其他函数调用实参。

package main

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()))
}

输出结果:

    3
    3

命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。

package main

func add(x, y int) (z int) {
    z = x + y
    return
}

func main() {
    println(add(1, 2))
}

输出结果: 3

命名返回参数可被同名局部变量遮蔽,此时需要显式返回。

func add(x, y int) (z int) {
    { // 不能在一个级别,引发 "z redeclared in this block" 错误。
        var z = x + y
        // return   // Error: z is shadowed during return
        return z // 必须显式返回。
    }
}

命名返回参数允许 defer 延迟调用通过闭包读取和修改。

package main

func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()

    z = x + y
    return
}

func main() {
    println(add(1, 2)) 
}

输出结果:    103

显式 return 返回前,会先修改命名返回参数。

package main

func add(x, y int) (z int) {
    defer func() {
        println(z) // 输出: 203
    }()

    z = x + y
    return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
}

func main() {
    println(add(1, 2)) // 输出: 203
}

输出结果:

203

203

 函数是第一类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读。

package main

import "fmt"

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)
}

输出结果:    100 10, 20

有返回值的函数,必须有明确的终止语句,否则会引发编译错误。

panic和recover函数简要说明

 使用场景

       Go 语言拥有一些内置函数,内置函数是预先声明的,它们像任何其他函数一样被调用,内置函数没有标准的类型,因此它们只能出现在调用表达式中,它们不能用作函数值。这里我们简要的说明下常用的panic和recover函数(细节我们将会在其他文章里专门作为一个专题来分析):

内置函数说明
panic用来表示非常严重的不可恢复的异常错误
recover用于从 panic 或 错误场景中恢复
func panic(interface{})
func recover() interface{}

panic和recover两个内置函数,协助报告和处理运行时异常和程序定义的错误。

在执行函数时,显式调用panic或者运行时发生panic都会终止函数的执行。然后,由函数延迟(defer)的任何函数都照常执行。 依此类推,直到执行goroutine中的顶级函数延迟。 此时,程序终止并报告错误条件,包括panic参数的值。

panic(42)
panic("unreachable")
panic(Error("cannot parse"))

因此当我们需要终止一个异常,我们就可以直接使用panic,比如下面的代码:

if false == LoadCfg() {
	panic("加载配置文件失败")
}

当进入到if条件判断里边之后,panic会将此时的堆栈信息打印输出:

这段代码里我们会看到panic的原因有输出,堆栈信息终止在InitModels函数体内,GameConfig.go的第63行,这样你能很方便的定位到问题。

recover函数用于在发生panic异常时恢复程序的控制流。它只能在defer函数中调用,并且可以捕获并处理发生的panic异常。

当程序发生panic异常时,它会中断当前的控制流程,并且开始执行所有在当前函数中定义的defer函数。在defer函数中调用recover函数,可以捕获到panic异常,并且程序可以继续正常执行。

recover函数的签名如下:

func recover() interface{}

它没有任何参数,返回一个interface{}类型的值。

当调用recover函数时,它会返回panic函数传递过来的值,如果没有发生panic异常或者recover函数不是在defer函数中调用,那么它会返回nil。

通常情况下,我们会在defer函数中使用recover函数来捕获和处理panic异常。示例代码如下:

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered:", r)
        }
    }()

    panic("Something went wrong!")
}

在上面的示例中,我们使用defer函数在程序发生panic异常时调用recover函数。在defer函数中,我们通过检查recover函数的返回值是否为nil来判断是否发生了panic异常,并进行相应的处理。

需要注意的是,recover函数只能在defer函数中调用,否则它不会起作用。另外,一旦recover函数被调用并且成功恢复了程序的控制流,后续的defer函数中的代码将不会执行。

和try-catch的区别

实际上有很多人在练习刚才的例子的时候很容易和try catch联系起来(如果你有其他语言开发基础),作为初学者的时候,我也会这样联想。的确,它与其他语言中的 try-catch 机制相似recover 充当了异常处理器的角色,可以在出现异常的函数中使用 defer 语句来捕获和处理 panic 异常。

然而,需要注意的是,在 Go 语言中的 panic-recover 机制与传统的 try-catch 不完全一样。在 Go 中,panic 是一种特殊的异常情况,它表示发生了不可恢复的错误。当 panic 异常发生时,程序会终止当前的执行流程,并从调用栈中逐层寻找 defer 函数,直到找到 recover 函数为止。

与 try-catch 不同的是,Go 中的 recover 函数只能在 defer 函数中使用,并且只能在同一个 goroutine 中的调用中生效。这意味着 recover 并不能用来处理跨协程的异常。另外,由于 recover 只能在 defer 中使用,它也不能用来捕获和处理其他非 panic 的异常。

总结来说,Go 语言中的 recover 机制可以起到类似于 try-catch 的异常处理作用,但有一些细节上的差异。在使用 recover 时需要注意其在 defer 函数中的限制和局限性。

标签:return,入门,int,详解,func,go,main,panic,函数
From: https://blog.csdn.net/pbymw8iwm/article/details/136878240

相关文章

  • 使用K8S集群运行MongoDB7.0
    参考:https://hub.docker.com/_/mongo创建PVC创建PVC用于数据持久化#catmongodb-pvc.yamlapiVersion:v1kind:PersistentVolumeClaimmetadata:name:mongodb-pvcspec:accessModes:-ReadWriteOnceresources:requests:storage:22Gistorag......
  • webpack 入门笔记1
    webpack是一个综合性平台1为npm环境-packjson->依赖->依赖的编译器环境bale-->esj->程序.构建一个综合平台。2开发目录到生产目录;3打包优化将上百个依赖整合为若干chunk.提升下载速度.综合总线打通步骤1(node环境已下载)建立npm环境-与本地的链接npminit指令......
  • 详解SSL证书系列(7)HTTP的三大缺点
    我们已经了解到HTTP协议具有相当优秀和方便的一面,然而HTTP并非只有好的一面,事物皆具有两面性,它也是有不足之处的,那么HTTP有哪些缺点呢?窃听风险由于HTTP本身不具备加密的功能,所以也无法做到对通信内容进行加密,即HTTP报文是使用明文方式发送的。如果要问为什么通信时不加密是一......
  • iperf详解 ---- 灌包
    摘自:https://zhuanlan.zhihu.com/p/585534080我们前面介绍过(我今天学习了一下3个perf:iperf、netperf和qperf),iperf3是用于执行网络吞吐量测量的工具,可以测试TCP、UDP或SCTP的吞吐量。本文以iperf3.12为例,介绍一下iperf3命令的详细用法。  快速用例  要执行iperf3测试......
  • Python-VBA编程500例-020-02(入门级)
    第k个组合(ThekthCombination)的问题在实际应用中具有广泛的用途,它涉及从n个不同元素中选出k个元素的所有可能组合。这种组合的概念在许多领域都有重要的应用,常见的一些具体应用有:1、彩票与赌博:在某些彩票或赌博游戏中,参与者需要选择特定数量的号码或符号。这些号码或符号的......
  • 【蓝桥杯3.23小白赛】(详解)
    第一题签到题不多说【二进制王国】#include<iostream>#include<vector>#include<algorithm>usingnamespacestd;//intCmp(strings1,strings2)测试了一下时间差确实很明显,还是用下面的内个intCmp(conststring&s1,conststring&s2)//const修饰表示在函......
  • 人工智能深度学习入门指南
    人工智能深度学习是一个涉及复杂算法和技术的领域,主要目的是让机器能够模仿人脑的学习过程,从而具备理解、分析、预测等能力。下面将详细描述深度学习的工作原理、学习过程,并给出一些建议。深度学习的工作原理基于神经网络,这是一种模拟人脑神经元连接方式的计算模型。神经网络......
  • linux入门
    组管理usermod-grootws#将ws的主组(gid)改为root组usermod-Grootws#将用户ws添加到root组当中idws#查看用户信息gid是主组uid是身份group是其他组#在ugo例g是指与创建用户相同主组的组群shellname='cxk'#shell变量不能有空格$path#是全局变量$?#若返回的......
  • 中国国内怎么使用订阅升级Duolingo多邻国plus?多邻国使用教程
    多邻国Duolingo合租平台,环球巴士首单9折优惠码:110072多邻国Duolingo是什么,能用来做什么?众所周知,多邻国考试的性价比非常高,不仅考试时间短,出分也很快,国际认可度越来越高。不过要注意的是,虽然相比托福和雅思,多邻国的考试难度有所降低,但依旧需要同学们投入大量时间和精力备考,想要......
  • 市场数据和金融数据API的获取步骤,支持Python、Java、Go等接入方式,轻松实现量化数据交
    今天我想分享一个非常实用的技术内容,即如何通过接口API来实现订阅并接入实时行情数据源的报价信息。这个技术可以帮助你获取最新的市场数据,为你的应用程序或交易策略提供及时的信息支持。接入实时行情数据源可以让你了解市场动态并快速作出决策,非常有助于优化你的交易策略和投资决......