Go语言基础之函数

本文详细介绍了Go语言中的函数,包括为何需要函数、函数的作用、如何定义和调用函数、函数参数和返回值的处理,以及defer、panic和recover的使用。此外,还探讨了高阶函数、匿名函数和闭包的概念。通过实例展示了如何利用函数提高代码的复用性和组织性。
摘要由CSDN通过智能技术生成

Go语言基础之函数

前言

Hey,大家好呀,我是星期八,这次咱们继续学习Go基础之函数叭。

为什么需要函数

函数,在所有编程语言中都叫函数,Java,PHP,Python,JS等,统一都叫函数。

函数的作用

一般是这样形容的:函数可以将重复的,或者特定功能的,封装成一个方便调用的东西。

注:在Go中,函数是支持闭包的。

在没有使用函数时

代码

package main

import "fmt"

func main() {
	//模拟一个打开文件,写入一行内容进入文件,在关闭文件的功能
	var file_name = "a.txt" //文件名
	var w_content = "爱我中华"  //写入的内容
	fmt.Println(fmt.Sprintf("打开 %s 文件",file_name))
	fmt.Println(fmt.Sprintf("向 %s 文件写入了 %s ", file_name, w_content))
	fmt.Println(fmt.Sprintf("关闭 %s 文件",file_name))

	//如果再再向其他文件写入内容,还需要复制一次

	var file_name2 = "b.txt" //文件名
	var w_content2 = "中国威武"  //写入的内容
	fmt.Println(fmt.Sprintf("打开 %s 文件",file_name2))
	fmt.Println(fmt.Sprintf("向 %s 文件写入了 %s ", file_name2, w_content2))
	fmt.Println(fmt.Sprintf("关闭 %s 文件",file_name2))
}

使用函数后

将相同功能封装成函数。

package main

import "fmt"

func w_file(filename string, w_content string) {
	fmt.Println(fmt.Sprintf("打开 %s 文件", filename))
	fmt.Println(fmt.Sprintf("向 %s 文件写入了 %s ", filename, w_content))
	fmt.Println(fmt.Sprintf("关闭 %s 文件", filename))
}
func main() {
	//将相同功能封装成函数
	w_file("a.txt""爱我中华")
	w_file("b.txt""中国威武")
}

上述代码执行的结果都如下

在这里插入图片描述

**ps:**但是可以明显看到,使用函数将相同功能抽出来,代码会变的简单,整洁。

函数使用

函数名命名规则

函数命名尽量以驼峰命名,例如:getNameconnectData等。

语法

在Go中,定义函数语言用到func关键字。

func 函数名([参数1 参数类型1,参数2 参数类型2...]) [(返回值 返回值类型,...)]{
	逻辑代码
}
//中括号表示可选参数

无参数,无返回值

package main

import "fmt"

func say1() {
	fmt.Println("我终于会说话了...")
}

有参数,无返回值

func say2(c string) {
	fmt.Println("我终于会说" + c + "了")
}

有或者无参数,有返回值

func say3(c string) (string) {
	fmt.Println("我终于会说" + c + "了")
	return "哦耶"
}

main函数

func main() {
	say1()
	say2("你好哇")
	result := say3("你好哇")
	fmt.Printf(result)
}

结果

在这里插入图片描述

调用函数

函数名+括号调用函数,如果有参数传入相关参数即可。

package main

import "fmt"

func say() string{
	fmt.Println("我终于会说话了...")
	return ""
}

func main() {
	//函数名+括号调用函数
	say() //结果:我终于会说话了...
}

**注:**如果函数有返回值,可以不接收。

函数参数特性

在Go中,如果函数参数都是统一类型,可以这样写。

//arg1, arg2, arg3, arg4参数类型都是string
func say(arg1, arg2, arg3, arg4 string) {
	fmt.Println("我终于会说话了...")
}

//arg1,arg2参数是int类型,arg4,arg4是string类型,
func say(arg1, arg2, int, arg3, arg4 string) {
	//表示arg1, arg2, arg3, arg4参数类型都是string
	fmt.Println("我终于会说话了...")
}

大概意思就是,如果参数不写类型,会以后面碰到的类型为准。

函数的…参数

…参数,也叫可变长参数,有点像Python中的*args

功能是当不知道接收多少个参数时,接收多的参数会放在…中。

…参数需要放在最后面。

代码

package main

import "fmt"

func say(name string, content ...string) {
    fmt.Println(content)        //结果:[666 双击 ok 哦耶]
	fmt.Printf("%T\n", content) //结果:[]string,是切片类型
	fmt.Println("我是"+name, "我说了:")
	//循环切片
	for _, v := range content {
		fmt.Println(v)
	}

}

func main() {
	//函数名+括号调用函数
	say("张三""666""双击""ok""哦耶") //结果:我终于会说话了...
}

结果如图所示

在这里插入图片描述

注:参数是…类型的,他的值是一个切片类型。

函数的返回值

返回值是一个的

package main

import "fmt"

//返回值是一个
func say1() string {
	return "ok"
}

返回值是多个的,需要用括号括起来

//返回值是多个的,需要用括号括起来
func say2() (intstring) {
	return 1"ok"
}

返回值是命名的

//返回值是命名的,不管是多个返回值还是一个返回值,都需要括号
//如果是命名返回值,需要在逻辑代码中,将变量赋值
func say3() (a int, b string) {
	//逻辑代码
	a = 18
	b = "666"
	/*
		直接return即可,不需要retrun a,b
		return的默认就是 a 和 b
		不用跟上述返回一样,返回具体值
	*/
	return
}

main函数

func main() {
	s := say1()
	fmt.Println(s)
	a1, b1 := say2()
	fmt.Println(a1, b1)
	a2, b2 := say3()
	fmt.Println(a2, b2)
}

结果

在这里插入图片描述

Go函数内存分配图

Go的函数内存分配,有点像堆分配,有点像,但是本质不是。

在这里插入图片描述

可以理解像堆内存一样,栈中保存的是堆的地址。

验证

代码

package main

import "fmt"


func say() string {
	return "ok"
}

func main() {
	fmt.Printf("say栈上的内容:%p\n",say)
}

结果

在这里插入图片描述

本质

在这里插入图片描述

函数的作用域

作用域这个问题,以前可能或多或少提过,再来复习一下叭。

全局变量

全局变量就是在所有函数外部定义的变量,程序不结束,变量就一直存在。

当然,任何函数都可以访问全局变量。

**注:**全局变量尽量全部用大写。

小试牛刀
package main

import "fmt"


var NAME = "张三"
func say() string {
	fmt.Println(NAME)
	return "ok"
}

func main() {
    say()
	fmt.Println(NAME)
}

结果:

在这里插入图片描述

上述可能会有个问题,全局变量,全局变量,大家共用一个,要是谁傻不拉几修改了不就完蛋了,整个程序都凉了。

var引发的问题

就像这样。

package main

import "fmt"

var NAME = "张三"

func say() string {
	fmt.Println(NAME)
	NAME = "李四"
	return "ok"
}

func main() {
	say()
	fmt.Println(NAME)
}

结果:

在这里插入图片描述

这不就完犊子了吗???所以,一定要有解决办法。

使用const解决问题

解决办法:使用常量定义全局变量。

package main

import "fmt"

const NAME = "张三"

func say() string {
	fmt.Println(NAME)
	//NAME = "李四"//会报错:cannot assign to NAME
	return "ok"
}

func main() {
	say()
	fmt.Println(NAME)

}
总结

在定义全局变量时,需要用const修饰,并且变量名全部大写。

局部变量

局部变量,局部变量就是在某个函数内定义的变量,只能在自己函数内使用。

更专业点,在{}内定义的,只能在{}内使用,for同理。

代码

package main

import (
	"fmt"
)

func say() string {
	var name = "张三"
	fmt.Println(name)
	return "ok"
}

func main() {
	say()
	//fmt.Println(name)//会报错:undefined: name
    //for同理
	for i := 0; i <= 1; i++ {
		var c = "66"
		fmt.Println(c) //66
	}
	//fmt.Println(c)//会报错:undefined: c
}

defer

在Go中,defer语句,可以理解为在return之前执行的一个语句。

如果函数没有return,会有一个默认的return,只是看不见而已。

一个defer

代码

package main

import "fmt"

func say() {
	//defer尽量往前放
	defer fmt.Println("我是666")
	fmt.Println("你们都是最棒的")
}

func main() {
	say()
}

执行结果

在这里插入图片描述

多个defer

代码

package main

import "fmt"

func say() {
	//defer尽量往前放
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println("你们都是最棒的")
}

func main() {
	say()
}

执行结果

在这里插入图片描述

可以发现,defer的执行结果是反着的。

结论:先执行的defer,会最后执行,最后执行的defer,会最先执行,有点像栈,先进后出

defer的作用

通常来说,defer会用在释放数据库连接,关闭文件等需要在函数结束时处理的操作。

这里暂时先不举例子。

panic和recover

这俩,可以理解为Python中的tryraise,因为在Go中,是没有try的,是不能像其他语言一样,try所有异常。

应用场景:比如某个web,在启动时,数据库都没连接成功,必定要启动失败,就像电脑,没有电源必不能开机一样。

panic

先看一下语法吧

package main

import "fmt"

func say() {
	var flag = true
	if flag{
        //引发错误,直接中断程序的错误
		panic("OMG,撤了撤了,必须撤了")
	}
}

func main() {
	say()
	fmt.Println("继续呀...")//不会执行,程序挂了
}

执行效果

在这里插入图片描述

可以看淡,继续呀就没打印,程序直接挂了,但是上述好像并没有解决这个问题。

recover

尝试捕捉

代码

package main

import "fmt"

func say() {
	//匿名函数,defer执行的是一个匿名函数
	defer func() {
		var err = recover()
		//如果有panic错误,err!=nil,在此处步骤,尝试恢复
		if err != nil {
			fmt.Println("尝试恢复...")
		}
	}()
	var flag = true
	if flag {
		panic("OMG,撤了撤了,必须撤了")
	}
}

func main() {
	say()
	fmt.Println("继续呀...")
}

执行结果

在这里插入图片描述

可以看到,如果recover捕捉了,并且没有panic,程序就会继续正常执行。

注意

defer必须在panic语句之前。

recover必须配合defer使用。

上次主要回顾

上述我们知道,定义一个函数,可以将函数内存分配理解如下。

在这里插入图片描述

同时我们也知道,无论进行什么操作,只会操作上面的

函数和变量

函数名即变量

不知道你想过没,定义一个变量,接收一个函数,就像这样。

package main

import "fmt"

func say() {
	fmt.Println("say")
}

func main() {
	var s1 = say
	s1()
}

执行结果如下。

在这里插入图片描述

可以发现,通过一个变量接收一个函数名,在通过变量名+括号执行,是没有问题的。

那么,这个变量是什么类型的呢???

fmt.Printf("%T\n",s1)

执行结果

在这里插入图片描述

如果我将say函数改一下呢?

func say(s int) int{
	fmt.Println("say")
	return 1
}
fmt.Printf("%T\n",s1)

在这里插入图片描述

可以发现,如果函数参数返回值不一样,打印出来的类型也是不一样的。

定义函数类型

上述我们知道,可以通过变量接收一个函数名

通过变量接收函数名没有约束的,不管函数几个参数,几个返回值,都可以接收,真是活出了动态语言的样子。

定义函数类型就是限制变量接收函数,只能接收指定格式函数

主要用到type关键字。

格式

type 变量名 func([参数类型,参数类型]) [返回值类型]
中括号表示可选参数

例如

type a func()
type b func(int)
type a func(intint) int

具体代码

package main

import "fmt"

/*
	定义一个函数类型的变量
	接收的函数参数必须是两个int类型
	函数的返回值也必须是int类型
*/
type cType func(intint) int

func say1(a, b int) int {
	fmt.Println("say",a+b)
	return 1
}
func say2(a, b int) {
	fmt.Println("say")

}
func main() {
	var s1 cType
	s1 = say1//调用s1其实调用的就是say1
	s1(11)
	
	//var s2 cType
	//s2 = say2//报错,cannot use say2 (type func(int, int)) as type cType in assignment
}

高阶函数

千万不要被这个名字唬住了。

简单点说,高阶函数就是把函数当作参数或者把函数当作返回值

函数当作参数

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}
func calc(x int, y int, other func(intint) int) int {
	return other(x, y)
}
func main() {
	//将add函数传入第三个参数
	var result = calc(3412, add)
	fmt.Println(result)
}

函数当作返回值

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}
func test() (func(intint) int) {
	return add
}
func main() {
	var a = test()
	fmt.Println(a(12))
}

至于上述两个的功能,恕小生不才,至今用到的场景不多。

匿名函数

匿名函数顾名思义,就是没有名字的函数。

语法如下

func([参数,参数...])[(返回值,返回值)]{
	代码
}()
//匿名函数后面必须跟括号,直接执行

例如

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

代码

package main

import "fmt"

func main() {
    //s1等于一个匿名函数,并且直接执行
	var s1 = func(x int, y int) (int) {
		return x + y
	}(12)
	fmt.Println(s1)
}

闭包

闭包,这个有点不太理解,简单点说就是函数里面套了一个函数里面函数引用的外面函数变量

示例

package main

import "fmt"

func other() func() {
    //返回的是一个函数类型
	var a = 666
	return func() {
        //内部函数使用的是外面函数的a
		fmt.Println(a)
	}
}
func main() {
	var o = other()
	o()
}

执行结果。

在这里插入图片描述

结果是没有问题的。

虽然我们以前学过,函数执行完毕后,里面的变量会回收。

但是在用到闭包时,可以这样理解,里面函数使用了外面函数的变量,那这个变量就不会被回收。

总结

上述我们学习了Go基础之函数。

如果在操作过程中有任何问题,记得下面留言,我们看到会第一时间解决问题。

我是码农星期八,如果觉得还不错,记得动手点赞一下哈。

感谢你的观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值