Golang基础3-函数、nil相关

news2024/11/18 5:42:54

函数

    • 需要声明原型
    • 支持不定参数 func sum(numbers ...int)int
    • 支持返回多值
    • 支持递归
    • 支持命名返回参数
// 命名返回参数
func add(a, b int) (sum int) {
    sum = a + b
    return // 这里不需要显式地写出返回值,因为已经在函数签名中声明了命名返回参数
}
    • 支持匿名函数、闭包
    • 函数也是一种类型,函数可以赋值给变量(本质函数指针)
    • 一个包中能有名字一样的函数
    • 不支持:重载(==,!=等等均不支持),默认参数

简单demo

package main

import "fmt"

//测试函数
func test(x, y int, s string) (int, string) {
    n := x + y
    return n, fmt.Sprintf("%s,%d\n", s, n)
}
func main() {
    a, b := test(1, 2, "你好")
    // _可以忽略某些值的返回
    fmt.Println(a)
    fmt.Println(b)

}

回调函数demo

回调函数本质其实就是函数指针作为形参,传递给了函数,增加了代码的灵活度。

package main

import "fmt"

// 回调函数1
func testFunc(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 formatHelper(s string, x, y int) string {
    return fmt.Sprintf(s, x, y)
}

func main() {
    s1 := testFunc(func() int {
        return 100
    })
    fmt.Println(s1) //100

    //匿名函数,回调进行格式化返回string
    //s2 := format(func(s string, x, y int) string {
    //    return fmt.Sprintf(s, x, y)
    //}, "%d %d", 10, 20)
    s2 := format(formatHelper, "%d %d", 10, 20)
    fmt.Println(s2)
}

闭包demo

闭包很简单,可以理解为返回值是一个函数指针,其他的再看就很好理解了。

https://juejin.cn/post/6844903793771937805

package main

import (
    "fmt"
)

//返回函数指针 func()int
func a() func() int {
    i := 0
    b := func() int {
        i++
        fmt.Println(i)
        return i
    }
    return b
}

func main() {
    //a执行完返回func() int 的这个函数指针,其中赋值给c,
    //那么这里面保存有这个b匿名函数的所有信息,实现了自增,可以不用定义全局变量
    c := a()
    c()
    c()
    c()
    //因为这个是函数指针
    a() //不会输出i
}

递归demo

package main

import "fmt"

// 递归1,求阶乘
func Factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * Factorial(n-1)
}

// 递归2,斐波那契数列
func Fibonaci(n int) int {
    if n == 0 {
        return 0
    }
    if n == 1 {
        return 1
    }
    return Fibonaci(n-1) + Fibonaci(n-2)
}
func main() {
    fmt.Println("5!=", Factorial(5))

    fmt.Println("前10项斐波那契数列:")
    for i := 0; i < 10; i++ {
        fmt.Printf("%d\t", Fibonaci(i))
    }
}

异常处理demo

参考文档:异常处理 · Go语言中文文档

使用panic抛出错误,defer捕获错误,一般panic中抛出异常,defer中捕获异常,之后正常处理。

panic:

内置函数,panic后的代码不执行, interface{},直到goroutine整个退出并报告错误,

recover:

1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。

2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

painc处理demo

painc会导致程序直接退出,平时开发中尽量不随便使用。

一般场景:我的服务想要启动,必须依赖某些服务、日志、mysql能联通,配置文件没问题,那么才能启动的时候,直接使用panic

一旦服务启动,这时你的某行代码不小心触发panic,那么这就是重大事故(比如别人请求,你直接挂了)

但是架不住有些地方被动触发panic,这时就引入了recover来捕获panic

package main

import "fmt"

// painc部分后面代码不执行
func test() {
    defer func() {
        if err := recover(); err != nil {
            println("recover panic:", err.(string))
        }
    }()
    panic("panic错误测试!")
    //panic后的代码不执行
    //fmt.Println("panic后代码")
}

func main() {
    test()
    fmt.Println("main")
}

error处理 demo
package main

import (
    "errors"
    "fmt"
)

func A() (int, error) {
    return 2, errors.New("this is an error")

}

func main() {
    if _, err := A(); err != nil {
        fmt.Println(err)
    }
}

recover捕获panic的demo

recover需要延迟调用,也就是必须在defer的函数内部,否则返回nil

package main

import "fmt"

func except() {
    fmt.Println("except延迟函数调用!")
    fmt.Println("except延迟函数recover:", recover())
}

func recoveryDemo() {

    //等效于下面的匿名延迟函数
    defer except()

    //延迟调用,recover在函数内部
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("有效", err.(string))
        }
    }()

    //defer recover()              //无效,不是延迟调用,nil
    defer fmt.Println(recover()) //无效,空

    defer func() {
        func() {
            fmt.Println("defer inner")
            fmt.Println("defer inner", recover()) //无效
        }()

    }()

    panic("panic错误测试!")
    //不会执行
    fmt.Println("End of test!")
}

func main() {
    recoveryDemo()
    fmt.Println("main")
}

总结:需要recover捕获panic时defer延迟函数进行接受,并且第一个有效的recover只能捕获最后一个painc(如果多个panic),之后有效的recover也返回nil。

defer的使用

defer延迟调用,一般释放资源和连接、关闭文件、释放锁等等。类似于java的finally和c++中析构函数,不过defer一般跟在函数或方法中。

参考博客:【Golang】Go语言defer用法大总结(含return返回机制)_golang defer return-CSDN博客

多个defer满足后进先出

defer跟无参、有参函数、方法
package main

import "fmt"
//无返回值
func test(a int) {
    defer fmt.Println("defer1 = ", a)//方法
    defer func(v int) {
        fmt.Println("defer2 = ", v)
    }(a)//有参函数
    defer func() {
        fmt.Println("defer3 = ", a)
    }()//无参函数
    a += 100
}

func main() {
    test(0)
}

defer满足后进先出,其次,有参情况下a会先传递进入,最后等a+=100之后执行完了再输出。

可读取函数返回值(return返回机制)

先return结构写入返回值,后defer收尾,最后携带返回值退出.

无名返回值,有名返回值的区别,见下面demo

无名有名返回值defer的demo

函数返回值可以无名、有名,这个是方便理解的不全代码,有名res的话本质局部变量,因此defer后会可能会影响res的返回值。而int这个返回值就直接定了。这个很容易引起bug,因此看下面例子:

package main

import "fmt"

// 无名返回值
func UnNamed(n int) int {
    n += 100

    defer func() {
        n += 100
        fmt.Println("UnNamed defer1", n)
    }()

    return n
}

// 有名返回值
func Named(n int) (res int) {
    res = n + 100

    defer func() {
        res += 100
        fmt.Println("UnNamed defer1", res)
    }()
    // 返回res局部变量,因此受defer中的res的逻辑影响
    return res
}

func main() {
    n := 0
    fmt.Println("main UnNamed return:", UnNamed(n))

    fmt.Println("main Named return:", Named(n))
}

对于第一无名返回值,先执行return保存返回值100,之后defer输出200,最后返回到main函数为100.

第二有名返回值,先执行return知道返回的是res(此时100),之后defer修改输出res200,最终返回到main为200.

同理可以更复杂,defer可以传入形参的无名有名函数,可以进行分析。

package main

import "fmt"

// 无名返回值
func UnNamed(n int) int {
    n += 100

    defer func(n int) { //传入100,输出110
        n += 10
        fmt.Println("UnNamed defer2", n)
    }(n)

    defer func() { //200
        n += 100
        fmt.Println("UnNamed defer1", n)
    }()

    return n //100
}

// 有名返回值
func Named(n int) (res int) {
    res = n + 100 //100

    defer func(res int) { //传入100并且注意是值拷贝,并且入栈,110
        res += 10
        fmt.Println("UnNamed defer2", res)
    }(res)

    defer func() { //入栈
        res += 100
        fmt.Println("UnNamed defer1", res)
    }()

    return res //100->200
}

func main() {
    n := 0
    fmt.Println("main UnNamed return:", UnNamed(n)) //100

    fmt.Println()
    fmt.Println("main Named return:", Named(n)) //200
}

因此传入指针等等,defer函数,有无名返回值均会影响main函数中接收到的最终return的值,请注意。

    • 调用os.Exit时defer不会被执行defer
    • 与panic进行配合处理异常

nil相关

nil代表某些数据类型的零值

不同类型0值

bool false

number 0

string ""

slice、map、channel、pointer、interface{} nil

如果是结构体,那么它的零值是,内部所有属性的零值的集合

nil 和empty的区别

这里分析了slice、map中的nil和empty的区别。

package main

import "fmt"

type Student struct {
    name string
    age  int
}

func main() {
    // nil slice,其实可以创建,append对nil slice进行了处理,但是map就不行
    var s1 []Student
    //s1 = append(s1, Student{"Bob", 19})
    fmt.Println(s1)
    if s1 == nil {
        fmt.Println("s1==nil")
    }

    // 不是nil slice,其实本质上是创建了,内部ptr指向一个空间为0的数组
    var s2 = make([]Student, 0)
    if s2 == nil {
        fmt.Println("s2==nil")
    }

    // nil map
    var m1 map[int]Student
    if m1 == nil {
        fmt.Println("m1==nil")
    }
    //可以查询,但是无法添加键值对,panic(assignment to entry in nil map)
    //m1[1] = Student{"hhh", 123}
    //if val, ok := m1[1]; ok {
    //    fmt.Println("ok", val)
    //} else {
    //    fmt.Println("not ok")
    //}
    fmt.Println(m1)

    //不是nil map,已经初始化了,0个空间的map
    var m2 = make(map[int]Student, 0)
    //可以查询,插入数据了
    m2[1] = Student{"hhh", 123}
    if val, ok := m2[1]; ok {
        fmt.Println("ok", val)
    } else {
        fmt.Println("not ok")
    }
    fmt.Println(m2)

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1621268.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

redis基于Stream类型实现消息队列,命令操作,术语概念,个人总结等

个人大白话总结 1 在Redis Stream中&#xff0c;即使消息被消费者确认&#xff08;acknowledged, ACK&#xff09;&#xff0c;消息也不会自动从Stream数据结构中删除。这与Kafka或RabbitMQ等传统消息队列系统的做法不同&#xff0c;在那些系统中&#xff0c;一旦消息被消费并…

Linux 服务器硬件及RAID配置实战

服务器详解 服务器分类 可以分为&#xff1a;塔式服务器、机架服务器、刀片服务器、机柜服务器等。 其中以机架式居多 服务器架构 服务器品牌&#xff1a; 戴尔、AMD、英特尔、惠普、华为、华3&#xff08;H3C&#xff09;、联想、浪潮、长城 服务器规格&#xff1a; 规格…

贵州大学计算机840初试

本人是24考研的一名考生&#xff0c;现在已经上岸啦。有想考贵州大学计算机的同学需要资料可以找我喔&#xff5e; #希望大家都可以如愿以偿&#x1f60e;&#x1f60e;

Java web应用性能分析之客户端慢

客户端慢的原因包括&#xff1a; 终端设备老化&#xff08;手机、PAD、电脑年限久远、运行期间产生了很多垃圾未清除&#xff09;终端网络设备老化&#xff08;路由器、交换机老化&#xff09;跟我们使用的手机一样&#xff0c;路由器也需要及时更新换代&#xff0c;否则硬件跟…

11.泛型

文章目录 1 泛型概念2. 自定义泛型结构3 泛型方法4 泛型在继承上的体现5 通配符的使用 1 泛型概念 所谓泛型就是用标识符标识不确定的类型&#xff0c;详细说就是&#xff1a;定义类或接口时用标识符表示类中某个属性的类型或者是某个方法的返回值及参数类型。泛型将在使用时&a…

oracle 12c+ max_string_size参数

一个客户的数据库版本是19.3,在做数据库复制的时候,目标端报错了,查看了一下问题发现表的字段长度有不对,在12c以前我们都知道varchar的长度最大是4000,但是客户这里居然有32767: 把客户的建表语句弄出来,放到我的一个19c的测试环境进行测试: 发现报错了: 这里报错很明显了,是M…

力扣面试 150二叉搜索树迭代器 中序遍历 栈模拟递归 步骤拆分

Problem: 173. 二叉搜索树迭代器 思路 &#x1f469;‍&#x1f3eb; 三叶 复杂度 时间复杂度: O ( 1 ) O(1) O(1) 空间复杂度: O ( h ) O(h) O(h) Code class BSTIterator { Stack<TreeNode> d new Stack<>();public BSTIterator(TreeNode root){dfsLe…

thsi指针用法总结

1 c类对象中的变量和函数是分开存储的 2 所以对象共用一份成员函数&#xff0c;类的大小是指非静态的成员变量&#xff1b; this 完成链式操作 const 修饰成员函数

IDEA启动项目弹框提示:Lombok requires enabled annotation processing

问题现象 IDEA启动项目弹框提示如下图&#xff1a; 原因分析 由弹窗内容分析&#xff0c;首先确认我的IDEA中已经安装了Lombok插件&#xff0c;其次去settings中查找annotation processing配置&#xff0c;发现确实有这个配置并且未勾选启动 如何解决 修改配置

Linux:动静态库介绍

动静态库 库的介绍开发环境 & 编译器库存在的意义库的实现库的命名静态库制作和使用总结 动态库的制作和使用动态库的使用方法方法一方法二方法三 库加载问题静态库加载问题动态库的加载问题与位置无关码 C/C静态库下载方式 库的介绍 静态库&#xff1a;程序在编译链接的时…

[MoeCTF-2022]Sqlmap_boy

title:[MoeCTF 2022]Sqlmap_boy 查看网页源代码&#xff0c;得到提示 <!-- $sql select username,password from users where username".$username." && password".$password.";; --> 用万能密码绕过&#xff0c;用’"闭合 爆数据库…

Lambda表达式特点

Lambda 表达式是 Java 8 引入的一项重要特性&#xff0c;它们提供了一种更简洁的方式来表达匿名函数。Lambda 表达式允许你将一段代码传递给方法&#xff0c;而不是显式创建一个实现了接口的匿名内部类。Lambda 表达式通常用于实现单个抽象方法的接口&#xff08;即函数式接口&…

用友NC Cloud importhttpscer接口任意文件上传漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、漏洞描述 用友NC Cloud的importhttpscer接口如果存在任意文件上传…

SpringBoot+Vue开发记录(四)

说明&#xff1a; 本篇文章的主要内容是软件架构以及项目的前端Vue创建 一、软件架构 我道听途说的&#xff0c;听说这个东西很关键很重要什么的。 软件架构&#xff08;software architecture&#xff09;是一个系统的草图,是一系列相关的抽象模式&#xff0c;用于指导大型软…

安装Selenium库的方法最终解答!_Python库

安装Python库Selenium 我的环境&#xff1a;Window10&#xff0c;Python3.7&#xff0c;Anaconda3&#xff0c;Pycharm2023.1.3 Selenium Selenium是一个开源的自动化测试工具&#xff0c;它支持多种编程语言&#xff08;如Python、Java等&#xff09;&#xff0c;能够在不同…

代码托管基础操作

在待上传代码文件夹中右键&#xff0c;打开Git Bash Here依次输入以下命令&#xff1a; git init(在本地初始化一个代码仓库&#xff0c;具体表现为会在你的文件夹里出现一个隐藏的.git文件夹) git add .&#xff08;先把代码放到本地的一个缓冲区&#xff09;添加当前目录下的…

基于达梦数据库开发-Java篇

文章目录 前言一、示例展示1.环境准备2.采用基础的jdbc调用3.采用扩展的mybatis调用 二、注意事项1.使用路径注解2.数据库对象的准确引用 三、可能异常1.无效的表或视图名2.无效的表或视图名3.网络通信异常 总结 前言 达梦提供了JDBC方式的驱动以便进行Java开发。默认情况下相…

3个比较不错的Linux云音乐应用程序整理

在现代音乐流媒体时代&#xff0c;基于云的音乐应用程序因其便利性和可访问性而变得非常流行。Linux 用户尤其寻求可靠且功能丰富的音乐播放器来无缝地享受他们喜爱的音乐。 在这里&#xff0c;我们探讨了三个最好的基于云的音乐应用程序&#xff0c;每个应用程序都提供专为 L…

函数的查询

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 在实际使用中经常会需要查询数据库中已有的函数或者某一个函数的内容&#xff0c;下面就介绍一下如何查询函数。 和存储过程类似&#xff0c;这也需要使用到数据字典user_s…

污水处理设备运维注意事项有哪些

污水处理设备的运维是确保污水处理效率和处理质量的关键环节。良好的运维不仅可以延长设备的使用寿命&#xff0c;还能确保污水处理过程的稳定性和可靠性。以下是一些污水处理设备运维的重要注意事项&#xff1a; 1. 定期检查和维护 设备检查&#xff1a;定期对污水处理设备进…