首页 > 其他分享 >GO语言中的函数

GO语言中的函数

时间:2023-10-16 10:11:07浏览次数:22  
标签:defer 函数 int fmt Println func GO 语言

函数的基本形式

//函数定义。a,b是形参
func argf(a int, b int) { 
	a = a + b 
}
var x, y int = 3, 6
argf(x, y) //函数调用。x,y是实参
  • 形参是函数内部的局部变量,实参的值会拷贝给形参。
  • 函数定义时的第一个的大括号不能另起一行。
  • 形参可以有0个或多个。
  • 参数类型相同时可以只写一次,比如argf(a,b int)。
  • 在函数内部修改形参的值,实参的值不受影响。
  • 如果想通过函数修改实参,就需要指针类型。
func argf(a, b *int) { 
    *a = *a + *b
    *b = 888
}
var x, y int = 3, 6
argf(&x, &y)

slice、map、channel都是引用类型,它们作为函数参数时其实跟普通struct没什么区别,都是对struct内部的各个字段做一次拷贝传到函数内部。

package main

import "fmt"

func slice_arg_1(arr []int) { //slice作为参数,实际上是把slice的arrayPointer、len、cap拷贝了一份传进来
	arr[0] = 1           //修改底层数据里的首元素
	arr = append(arr, 1) //arr的len和cap发生了变化,不会影响实参
}

func main() {
	arr := []int{8}
	slice_arg_1(arr)
	fmt.Println(arr[0])   //1
	fmt.Println(len(arr)) //1
}

关于函数返回值

  • 可以返回0个或多个参数。
  • 可以在func行直接声明要返回的变量。
  • return后面的语句不会执行。
  • 无返回参数时return可以不写。
func returnf(a, b int) (c int) { //返回变量c已经声明好了
    a = a + b
    c = a //直接使用c
    return //由于函数要求有返回值,即使给c赋过值了,也需要显式写return
}

不定长参数

不定长参数实际上是slice类型。

不定长参数需要在类型前面加上三个点...,以切片的形式存储不定长参数

取出参数需要使用循环遍历

func variable_ength_arg(a int, other ...int) int { 
    sum := a
    for _, ele := range other {//不定长参数实际上是slice类型
        sum += ele
    }
    fmt.Printf("len %d cap %d\n", len(other), cap(other))
    return sum
}
variable_ength_arg(1)
variable_ength_arg(1,2,3,4)

把切片转换为不定长参数

最典型的例子就是append函数

append函数接收的就是不定长参数。

在切片的后面加上...,就可以把切片转换为不定长参数

arr = append(arr, 1, 2, 3)
arr = append(arr, 7)
arr = append(arr)
slice := append([]byte("hello "), "world"...) //...自动把"world"转成byte切片,等价于[]byte("world")...
slice2 := append([]rune("hello "), []rune("world")...) //需要显式把"world"转成rune切片

在很多场景下string都隐式的转换成了byte切片,而非rune切片,比如"a中"[1]是228而非"中"。

递归函数

func Fibonacci(n int) int {
    if n == 0 || n == 1 {
        return n //凡是递归,一定要有终止条件,否则会进入无限循环
    }
    return Fibonacci(n-1) + Fibonacci(n-2) //递归调用函数自身
}

匿名函数

函数也是一种数据类型。

func function_arg1(f func(a, b int) int, b int) int { //f参数是一种函数类型 f的函数类型就是一个匿名函数
	a := 2 * b
	return f(a, b)
}

type foo func(a, b int) int //foo是一种函数类型
func function_arg2(f foo, b int) int { //参数类型看上去简洁多了
    a := 2 * b
    return f(a, b)
}

type User struct {
    Name string
    bye foo //bye的类型是foo,而foo代表一种函数类型
    hello func(name string) string //使用匿名函数来声明struct字段的类型
}

ch := make(chan func(string) string, 10)//声明一个管道,管道的值是就是匿名函数
ch <- func(name string) string {  //使用匿名函数
	return "hello " + name
}

闭包

闭包(Closure)是引用了自由变量的函数,自由变量将和函数一同存在,即使已经离开了创造它的环境。闭包复制的是原对象的指针。

package main

import "fmt"

//闭包(Closure)是引用了自由变量的函数。自由变量将和函数一同存在,即使已经离开了创造它的环境。
func sub() func() {
	i := 10
	fmt.Printf("%p\n", &i)
	b := func() {
		fmt.Printf("i addr %p\n", &i) //闭包复制的是原对象的指针
		i--                           //b函数内部引用了变量i
		fmt.Println(i)
	}
	return b //返回了b函数,变量i和b函数将一起存在,即使已经离开函数sub()
}

// 外部引用函数参数局部变量
func add(base int) func(int) int {
	return func(i int) int {
		fmt.Printf("base addr %p\n", &base)
		base += i
		return base
	}
}

func main() {
	b := sub()
	b()
	b()
	fmt.Println()

	tmp1 := add(10)
	fmt.Println(tmp1(1), tmp1(2)) //11,13
	// 此时tmp1和tmp2不是一个实体了
	tmp2 := add(100)
	fmt.Println(tmp2(1), tmp2(2)) //101,103
}

延迟调用defer

  • defer用于注册一个延迟调用(在函数返回之前调用)。
  • defer典型的应用场景是释放资源,比如关闭文件句柄,释放数据库连接等。
  • 如果同一个函数里有多个defer,则后注册的先执行
  • defer后可以跟一个func,func内部如果发生panic,会把panic暂时搁置,当把其他defer执行完之后再来执行这个。
  • defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算。

defer执行顺序

后注册先执行

func fn() {
	fmt.Println("A")
    //如果同一个函数里有多个defer,则后注册的先执行
	defer fmt.Println("E")
	fmt.Println("B")
	defer fmt.Println("D")
	fmt.Println("C")
}
func main() {
	fn()
}
/*
A
B
C
D
E
*/

defer中的变量

func defer_exe_time() (i int) {
	i = 9
	defer func() { //defer后可以跟一个func
		fmt.Printf("first i=%d\n", i) //打印5,而非9。充分理解“defer在函数返回前执行”的含义,不是在“return语句前执行defer”
	}()
	defer func(i int) {
		fmt.Printf("second i=%d\n", i) //打印9,注册时就被赋了值
	}(i)
	defer fmt.Printf("third i=%d\n", i) //defer后不是跟func,而直接跟一条执行语句,则相关变量在注册defer时被拷贝或计算
	return 5 //会将5赋值给i
}
/*
third i=9
second i=9
first i=5
*/

defer内部有异常

不会影响其他defer,会把异常放到最后处理

func fn() {
	n := 0
	defer fmt.Println("AAAAA")
	defer func() {
		fmt.Println(2 / n)
	}()
	defer fmt.Println("BBBBB")
}
func main() {
	fn()
}
/*
BBBBB
AAAAA
panic: runtime error: integer divide by zero

goroutine 1 [running]:
main.fn.func1()
        E:/Codes/GO/test.go:9 +0x86
main.fn()
        E:/Codes/GO/test.go:12 +0xe9
main.main()
        E:/Codes/GO/test.go:14 +0x17
exit status 2
*/

异常处理

go语言没有try catch,它提倡返回error。

func divide(a, b int) (int, error) {
    if b == 0 {
        return -1, errors.New("divide by zero")
    }
    return a / b, nil
}
if res, err := divide(3, 0); err != nil {//函数调用方判断error是否为nil
    fmt.Println(err.Error())
}

创建异常的方法:

  • errors.New(错误信息的字符串)
  • fmt.Errorf("hahhahh%d", 20) 可以是格式化字符串

自定义异常

Go语言定义了error这个接口,自定义的error一定要实现Error()方法

type diyerror struct {    //自定义error
    name string
    message string
}
func (err diyerror) Error() string {    //error接口要求实现Error() string方法
	return e.name + "=========" + e.msg + time.Now().Format("2006-01-02 15:04:05")
}
//调用
func newerror() diyerror {
	return diyerror{"一个错误", "这是消息"}
}

//抛出异常
func main() {
	panic(newerror())
}

何时会发生panic:

  • 运行时错误会导致panic,比如数组越界、除0。
  • 程序主动调用panic(error)。

panic会执行什么:

  1. 逆序执行当前goroutine的defer链(recover()从这里介入)。
  2. 打印错误信息和调用堆栈。
  3. 调用exit(2)结束整个进程。

recover()会使程序从panic中恢复(即不会执行panic的第2步和第3步),并返回panic value

recover()所在的函数后续的代码不会执行,但函数可以正常返回。

在未发生panic时调用recover(),会返回nil。

recover()必须在defer中才能生效。

处理异常

func soo() {
	fmt.Println("enter soo")

	defer func() { //去掉这个defer试试,看看panic的流程。把这个defer放到soo函数末尾试试。把这个defer移到main()里试试。
		//recover必须在defer中才能生效
		if err := recover(); err != nil {
            fmt.Printf("soo函数中发生了panic:%v\n", err.Error())
		}
	}()
	fmt.Println("regist recover")

	defer fmt.Println("hello")
	defer func() {
		n := 0
		_ = 3 / n //除0异常,发生panic,下一行的defer没有注册成功
		defer fmt.Println("how are you")
	}()
}
package main

import (
	"fmt"
)

func B() {
	// defer func() { //方式一,recover()在B()函数里,则在B()函数中panic后面的代码不会执行。不影响BBBBBBB的打印
	// 	if panicValue := recover(); panicValue != nil {
	// 		fmt.Printf("panic info %v\n", panicValue)
	// 	}
	// }()
	panic(5)
}

func main() {
	defer func() { //方式二,recover()在main()函数里,则在main()函数中panic后面的代码不会执行。BBBBBBB不会打印出来
		if panicValue := recover(); panicValue != nil {
			fmt.Printf("panic info %v\n", panicValue)
		}
	}()

	B()
	fmt.Println("BBBBBBB")
}

取出error结构体的内容

使用error.Error()

标签:defer,函数,int,fmt,Println,func,GO,语言
From: https://www.cnblogs.com/guangdelw/p/17766770.html

相关文章

  • 树叶识别系统python+Django网页界面+TensorFlow+算法模型+数据集+图像识别分类
    一、介绍树叶识别系统。使用Python作为主要编程语言开发,通过收集常见的6中树叶('广玉兰','杜鹃','梧桐','樟叶','芭蕉','银杏')图片作为数据集,然后使用TensorFlow搭建ResNet50算法网络模型,通过对数据集进行处理后进行模型迭代训练,得到一个识别精度较高的H5模型文件。并基于Dja......
  • 使用链表而不是 stdarg 实现可变参数函数
    Qidi2023.10.150.需要使用可变参数函数的场景常见的场景是类似于printf(char*fmt,...)函数,输入的参数个数和类型都是未知的,此时除了需要...表示可变参数列表,还需要用fmt参数说明参数的个数和类型。还有另一种场景,假设我们要实现一个音频控制功能的程序。在初始设计......
  • ARC167D Good Permutation 题解
    题意给定一个长度为\(N\)的排列\((P_1,P_2,\cdots,P_N)\)。称一个排列\(P\)为“好排列”当且仅当对于所有\(1\leqx\leqN\),都能通过不停地使\(x\leftarrowP_x\)将\(x\)变成\(1\)。通过最小次数操作将\(P\)变成“好排列”,每次操作为交换\(P\)中的两个数\(P_i\)......
  • 为什么Google在JSON响应中添加了`while(1);`?
    内容来自DOChttps://q.houxu6.top/?s=为什么Google在JSON响应中添加了while(1);?为什么Google在(私有)JSON响应前加上while(1);?例如,这是在Google日历中打开和关闭日历时的响应:while(1);[['u',[['smsSentFlag','false'],['hideInvitations','false......
  • mongoDB​二
    MongoDB中的索引索引就是为了加速查询的,MongoDB的索引几乎与传统的关系型数据库一模一样,这其中也包括一些基本的优化技巧。下面是创建索引的命令:创建索引:db.集合名称.ensureIndex({"name":1})1代表索引升序存储-1代表索引降序存储_id默认自动创建索引注意:该索引被创建后,基......
  • golang值接收者与指针接收者(一)
    golang方法的接收者有两种:值接收者与指针接收者。平时使用中两种接收者的主要区别就是能不能修改接收者的内部值。先说结论:值接收者方法不能修改结构体内部的值,指针接收者方法可以修改结构体内部的值。做个测试:typeStudentstruct{ ageint}func(sStudent)SetAge(ag......
  • 关于django中间件执行过程
    众所周知,django的中间件一般有五个方法,常用的仅三个,示例如下fromdjango.utils.deprecationimportMiddlewareMixinfromdjango.httpimportHttpResponse中间件示例classMyMiddleWare(MiddlewareMixin): defprocess_request(self,request): pass defprocess_view(se......
  • 20231407陈原《计算机科学与概论》及《C语言程序设计》第三周学习情况
    [2022-2023-1-计算机基础与程序设计]2023-2024-1计算机基础与程序设计第三周作业https://edu.cnblogs.com/campus/besti/2022-2023-1-CFAP[2022-2023-1计算机基础与程序设计第一周作业](https://www.cnblogs.com/rocedu/p/9577842.html#WEEK03)作业目标学习《计算机科......
  • Go 匿名函数与闭包
    Go匿名函数与闭包匿名函数和闭包是一些编程语言中的重要概念,它们在Go语言中也有重要的应用。让我们来详细介绍这两个概念,并提供示例代码来帮助理解。目录Go匿名函数与闭包一、匿名函数(AnonymousFunction)二、闭包函数(Closure)一、匿名函数(AnonymousFunction)匿名函数,也称为无......
  • python字典把函数作为字典的值
    defadd(x,y):returnx+ysum=add(3,5)#print(sum)dict={"add":add}sum1=dict.get("add")(4,6)通过传参把列表list传进去,在调用的方法中添加元素,原来的列表list也就成功添加了元素defaddItem(list):print(type(list))list.append("aaas")......