首页 > 其他分享 >go基础-函数

go基础-函数

时间:2023-08-07 19:36:56浏览次数:32  
标签:函数 int fmt 基础 func go main sum

概述

在任何语言中函数都是极其重要的内容,业务功能都是由一个或多个函数组合完成。go语言是函数式编程语言,函数是一等公民,可以被传递、有函数类型,go语言有三种类型的函数,普通函数、匿名函数(Lambda函数)、方法函数。go语言函数有独特属性,可以有多个返回值,需要使用多个变量接收、函数也是一种类型,函数签名是函数类型、函数不能被重载

 

基本使用

声明函数时必须包含参数类型、返回值类型。单个返回值可省略括号

fun sum(x int, y int) int {
    return x+y
}

调用函数

n := sum(10, 20)    // 30

 

两个返回值,多个返回值必须使用括号。

fun sum(x int, y int) (int, int) {
    return x+y, x-y
}

使用两个变量接收

n, m := sum(x, y)

 

返回值变量也可以在声明中定义

func sum(x int, y int) (a int, b int) {
    a = x + y
    b = x - y
    return
}

在函数声明定义返回类型和返回值变量,使用return时可省略返回对象。

 

两个返回值一般用于错误处理,一个表示结果,一个表示错误。很多库函数都有类似的应用

func Open(name string) (file *File, err error)

 

第一个返回值file表示文件指针,第二个返回值err表示打开文件异常,所以一般先判断是否有异常

file, err := open("test.txt")
if err != nil {
    fmt.Pringln("打开文件失败, ", err)
    return
}

 

使用_表示忽略返回值

file, _ := open("test.txt")

 

go语言函数也支持可变参数,注意可变参数必须在最后位置,固定是切片类型。

func sum(x int, args... int) int {
    for _, arg := range args {
        x += arg
    }
    return x
}

// 使用
i := sum(10, 20, 30, 40)

 

当可变参数是一个空接口类型时,调用者是否解包可变参数会导致不同的结果:

func Print(a ...interface{}) {
    fmt.Println(a...)
}

func main() {
    var a = []interface{}{123, "abc"}

    Print(a...) // 123 abc
    Print(a)    // [123 abc]
}

第一个 Print 调用时传入的参数是 a...,等价于直接调用 Print(123, "abc")。第二个 Print 调用传入的是未解包的 a,等价于直接调用 Print([]interface{}{123, "abc"})

 

Go 语言函数还可以直接或间接地调用自己,也就是支持递归调用。相比其他语言 Go 语言函数的递归调用深度逻辑上没有限制,函数调用的栈不会出现溢出错误,因为 Go 语言运行时会根据需要动态地调整函数栈的大小。每个 goroutine 刚启动时只会分配很小的栈(4 或 8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以达到 GB 级(依赖具体实现,在目前的实现中,32 位体系结构为 250MB,64 位体系结构为 1GB)

func f(x int) {
    if x > 1 {
        f(x-1)
    } else {
        fmt.Println(x)
    }
}
 

因为Go 语言函数的栈会自动调整大小,所以程序员很少需要关心栈的运行机制的,在 Go 语言规范中甚至故意没有讲到栈和堆的概念。我们无法知道函数参数或局部变量到底是保存在栈中还是堆中,只需要知道它们能够正常工作就可以了

func f(x int) *int {
    return &x
}

func g() *int {
    i := 20
    return i
}

有C/C++经验的程序员会惊讶这两个函数有bug,因为参数变量在栈上维护,函数返回之后栈变量就失效了,返回的地址自然也应该失效了,返回的是野指针。在Go语言中可以正常工作,Go 编译器会保证指针指向的变量在合适的地方,不用关心 Go 语言中函数栈和堆的问题,编译器和运行时会帮我们搞定;同样不要假设变量在内存中的位置是固定不变的,指针随时可能会变化,特别是在你不期望它变化的时候。

 

函数类型

函数也是一种类型,即函数类型,也称为函数签名。

func sum(x int, y int) int {
    return x + y
}

func main() {
    fmt.Printf("%T\n", sum)    // 输出:func(int, int) int
}

注意,签名中不包括函数名称

 

C语言一样,go的函数名是只读指针,指向函数的首地址,所以go函数是引用类型。

把函数当参数传递时,复制出来的新指针,也指向相同的函数地址。注意不是复制函数,是复制函数指针,还有多种引用类型,如slice、map、chan、interface等。

func main() {
    fmt.Println(sum)            // 0x108ef60
    fmt.Printf("%v\n", sum)        // 0x108ef60
    fmt.Printf("%v\n", &sum)    // err
}

两条语句结果一样,都打印函数的起始内存地址。注意无法使用地址符获取函数地址,这也说明不存在函数指针,更无法通过指针调用函数,这与C语言有区别,C语言回调函数经常使用这招儿。

 

go语言中函数是一等公民,可以被传递、调用,这时就依赖函数类型

func handler(sum func(int, int, ) int, x int, y int) int {
    return sum(x, y)
}

第一个参数是函数类型,接收函数做为参数,使用函数签名定义。

 

也可自定义类型,简单理解就是定义别名,简化写法

// 自定义类型
type sum func(int, int) int

// 接收自定义类型
func handler(plus sum, x int, y int) int {
    return plus(x, y)
}

 

匿名函数

JavaScript一样go也支持匿名函数,声明函数时不写名称

func main() {
    f := func(x int, y int) int {
        return x + y
    }

    fmt.Printf("%T\n", f) // func(int, int) int
    fmt.Println(f(1, 2)) // 3
}

 

与普通函数一样使用、传递,匿名函数也有函数签名,也可自定义类型

// 自定义类型
type sum func(int, int) int

func main() {
    // 声明变量
    var f sum = func(x int, y int) int {
        return x + y
    }

    fmt.Printf("%T\n", f)
    fmt.Println(f(1, 2))
}

 

函数闭包

闭包是函数式编程语言的招牌功能之一,go当然也支持闭包。简单来说就是函数可记住诞生时的环境信息,也称为记忆效应。

var str string = "hello"

func func1() {
    fmt.Println(str)
}

函数func1应用引用了外部变量str,注意是“引用”,而非值传递,两者会相互影响,这与函数调用传参有本质区别。

func1()         // hello
str = "world"
func1()         // world

 

更多使用动态生成匿名函数的方式,如下

func Accumulate(value int) func() int {
    // 返回一个闭包
    return func() int {
        // 引入外部变量value并累加
        value++
        // 返回一个累加值
        return value
    }
}

 

返回值是一个函数,并且引用了外部变量value,该变量被会记录在函数内部,外部环境被销毁也不受影响,有点类似Python的装饰器。

// 创建一个累加器, 初始值为1
accumulator := Accumulate(1)


fmt.Println(accumulator())    // 2
fmt.Println(accumulator())    // 3

每次调用value都会被累加,有点Java中lombda的感觉,

 

延迟执行

这是go特有的技能,函数内部被defer修饰的语句总是最后执行,有点类似java中finally的特性。

func main() {
    fmt.Println("defer begin")
    defer fmt.Println(1)
    defer fmt.Println(2)
    defer fmt.Println(3)
    fmt.Println("defer end")
}

 

可以有多条defer修饰,与栈的数据结构一致,先进后出,输出如下

defer begin
defer end
3
2
1

 

注意,被defer修饰的预计总是最后执行,不再是顺序执行,与语句所在的位置无关。

func main() {
    fmt.Println("start time: ", time.Now())
    defer fmt.Println("end time: ", time.Now())

    time.Sleep(time.Duration(rand.Intn(1000)))
    fmt.Println("ok")
    return
} 

不受return影响,end time预计总是在最后打印,输出如下

start time:  2023-07-19 23:45:51.551994 +0800 CST m=+0.000133126
ok
end time:  2023-07-19 23:45:51.552634 +0800 CST m=+0.000773430

也不受panic的影响,程序崩溃前也会执行被defer修饰的语句

 

主要使用场景是异常捕获、回收资源、释放互斥锁等,因为被defer修饰的语句一定会在最后执行

func main() {
    fp, err := os.Open(filename)
    if err != nil {
        fmt.Println("open file error", err)
        return 
    }
    
    // 最后一定会关闭文件
    defer f.Close()

    // 对文件指针fp进行操作
    ...
}

 

也可以用于申请和释放锁

func main(key string) int {
    // 申请锁
    sync.Mutex.Lock()
    // 释放锁,延迟到函数结束时执行
    defer sync.Mutex.Unlock()

    // 业务逻辑
    ...
}

 

特殊函数

go语言中有两个比较特殊的函数,在固定场景下使用

 

main函数,同C一样是程序的入口函数,只能有一个main函数,程序从这里开始执行,注意main函数只能属于main包。只有当代码包含有main函数时才可编译出可执行文件。

package main    // main包
import "fmt"

func main() {    // main函数
    fmt.Println("hello world")
}

 

init函数,也称为初始化函数,会在main函数之前被自动调用,只要被import导入,该包所有init函数都被会自动执行,多次导入只执行一次。import是链式导入,init执行也是链式执行,如下图。

 

go执行顺序是:常量定义(const) -> 全局变量定义(var) -> 初始化函数(init) -> 程序入口(main)

要注意的是,在 main 函数执行之前所有代码都运行在同一个 Goroutine 中,也是运行在程序的主系统线程中。如果某个 init 函数内部用 go 关键字启动了新的 Goroutine 的话,新的 Goroutine 和 main.main 函数是并发执行的

 

 

标签:函数,int,fmt,基础,func,go,main,sum
From: https://www.cnblogs.com/asdfzxv/p/17612487.html

相关文章

  • 华为VRP-OSPF基础配置命令
             ......
  • [golang]使用mTLS双向加密认证http通信
    前言假设一个场景,服务端部署在内网,客户端需要通过暴露在公网的nginx与服务端进行通信。为了避免在公网进行http明文通信造成的信息泄露,nginx与客户端之间的通信应当使用https协议,并且nginx也要验证客户端的身份,也就是mTLS双向加密认证通信。这条通信链路有三个角色:服务端、N......
  • Go / Golang JSON 一些心得
    自定义序列化和反序列化可以实现json.Marshaler和json.Unmarshaler自定义json的序列化和反序列化typeTags[]stringfunc(tTags)MarshalJSON()([]byte,error){return[]byte(strconv.Quote(strings.Join(t,","))),nil}func(t*Tags)UnmarshalJSON(b[]b......
  • UDS诊断服务基础篇之22
      应用场景:读取当前ECU的序列号,版本号等;标定成功后读取内部标定结果等;读取当前ECU所处在的Session,内部状态,SnapshotData等;其他需要读取内部相关参数的场合;服务请求:服务请求是Client发送给到Server的诊断服务指令。其中Client可以理解为Tester,Server可以理解......
  • RabbitMQ从入门到精通零基础进阶学习路线?
    RabbitMQ从入门到精通零基础进阶学习路线?学习RabbitMQ可以遵循以下路线,从入门到精通:步骤1:理解消息队列和RabbitMQ基础知识-学习什么是消息队列和为什么要使用它们。-了解RabbitMQ的概念和术语,如生产者、消费者、队列、交换器和绑定等。步骤2:安装和设置RabbitMQ-下载和安装Ra......
  • 性能测试-基础篇
    前言:性能是什么 每个人眼里对性能理解不一样,但是我们如果从一个App的维度来看: 用户眼中的性能:1、App使用崩溃,卡顿,延迟2、App反应慢,使用页面无反应 那开发眼中的性能:1、数据库设计是否合理2、代码逻辑、算法是否可以优化 运维眼中的性能:1、服务器资源使用是否合理......
  • 支持多数据源联合查询的SQL运行引擎sycnany-SQL添加使用自定义函数
    在微服务和云原生愈发流行的今天,数据的分布也愈发脱离单库单机而更加复杂,使用的数据库类型也会更多,但业务的复杂依然会带来了大量的数据查询和导出需求,而很多时候我们很难为数据量的大部分系统创建完整的BI数仓系统,这时候你是不是觉得为这些需求查询和导出数据就会是一个十分困难且......
  • MySQL和MongoDB如何JOIN查询?一个直接在本地运行的SQL执行引擎
    在微服务和云原生愈发流行的今天,数据的分布也愈发脱离单库单机而更加复杂,使用的数据库类型也会更多,但业务的复杂依然会带来了大量的数据查询和导出需求,而很多时候我们很难为数据量的大部分系统创建完整的BI数仓系统,这时候你是不是觉得为这些需求查询和导出数据就会是一个十分困难且......
  • JavaOpenCV相似度计算基础教程
    JavaOpenCV相似度计算基础教程JavaOpenCV是一个基于开放源代码的计算机视觉库,它可以实现许多计算机视觉任务,如图像处理、物体识别和图像相似度计算等。本教程旨在向您介绍JavaOpenCV中的相似度计算基础,帮助您理解如何使用该库计算图像之间的相似度。JavaOpenCV相似度计算基础教程图......
  • 编程范式 --- 函数式编程
    定义函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambdacalculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。--百度百科简单说,"函数式编程"是一种"编程范式"(programmingparadigm),也就是如何编写程序的方法论。它属于"结......