首页 > 其他分享 >Go-函数的那些事儿

Go-函数的那些事儿

时间:2024-09-07 13:53:45浏览次数:11  
标签:函数 err int test func Go println main 事儿

Go-函数的那些事儿

在这里插入图片描述

定义

函数是结构化编程的最小模块单元。它将复杂的算法过程分解为若干较小任务,隐藏相关细节,使得程序结构更加清晰,易于维护。
函数被设计成相对独立,通过接收输入参数完成一段算法指令,输出或存储相关结果。因此,函数还是代码复用和测试的基本单元。

Go函数借鉴了动态语言

无须前置声明。
不支持命名嵌套定义(nested)。
不支持同名函数重载(overload)。
不支持默认参数。
支持不定长变参。
支持多返回值。
支持命名返回值。
支持匿名函数和闭包。

第一类对象(first-class object)指可在运行期创建,可用作函数参数或返回值,可存入变量的实体。最常见的用法就是匿名函数。

package main

func helloworld(){
	println("hello world ")
}
func exec(f func()){
	f()
}
func main(){
	exec(helloworld)
}

返回指针

func test() *int{
		a := 0x100
		return &a
}
func main(){
	var a *int = test()
	println(a,*a)
}
go build -gcflags="-l -m" main.go # 分析堆渣
 go tool objdump -s "main/.main" test # 反编译

在这里插入图片描述

命名规则

通常是动词和介词加上名词,例如writeHello。
避免不必要的缩写,printError要比printErr更好一些。
避免使用类型关键字,比如buildUserStruct看上去会很别扭。
避免歧义,不能有多种用途的解释造成误解。
避免只能通过大小写区分的同名函数。
避免与内置函数同名,这会导致误用。
避免使用数字,除非是特定专有名词,例如UTF8。
避免添加作用域提示前缀。
使用相同术语,保持一致性。
使用习惯用语,比如init表示初始化,is/has返回布尔值结果。
使用反义词组命名行为相反的函数,比如get/set、min/max等。

参数

不管是指针、引用类型,还是其他类型参数,都是值拷贝传递(pass-by-value)。区别无非是拷贝目标对象,还是拷贝指针而已。在函数调用前,会为形参和返回值分配内存空间,并将实参拷贝到形参内存。

package main
import "fmt"
func test(x *int){
	fmt.println("pointer:%p,target:%v",&x,x)
}
func main(){
	a := 0x100
	p := &a
	fmt.Printf("point:%p,target:%v",&a,p)
}

在这里插入图片描述

尽管实参和形参都指向同一目标,但传递指针时依然被复制。开辟新的内存去存储指针值

二级指针

	//不用返回参数,可使用二级指针传出值
	func 	test(p **int){
		x := 100
		*p = &x
	}
	
	func main(){
		var p *int
		test(&p)
		println(*p)
	}

复合参数

type serverOption struct{
	address string
	port int
	path string
	timeout time.Duration
	log *log.Logger

}

func newOption() *serverOption{
	return &serverOption{
		address: "127.0.0.1",
    port: 80,
    path: "/",
		timeout: time.Second * 5,
    log: nil
	}
}

func server(option *serverOption){
	fmt.Printf("%#v",*option)
}

func main(){
	opt1 := newOption()
	opt1.port=8080
	server(opt1)

}

在这里插入图片描述

变参

//变参就是一次性接受多个参数
func test(s string,a ...int){
	fmt.Printf("%T,%v\n",a,a)
}

func main(){
	test("string",1,2,3)
}

在这里插入图片描述

//当切片与数组成为参数时,需要进行展开操作
func test(s string,a ...int){
	fmt.Printf("%T,%v\n",a,a)
}

func main(){
	v1 := [3]int{1,2,3}
	test("ss",v1[:]...)   //...放在后面意为展开的意思,如果是数组会转成切片再传去
}

在这里插入图片描述

//切片是引用类型,可以直接修改值
func test(s string,a ...int){
	for i := range a {
		a[i] +=100
	} 
}

func main(){
	v1 := [3]int{1,2,3}
	test("ss",v1[:]...)   //...放在后面意为展开的意思,如果是数组会转成切片再传去
	fmt.println(v1)
}

在这里插入图片描述

需要有return

func test(a,b int) (int,error){
	if b == 0 {
		return 0,errors.new("b not's zero")
	}
	return a/b,nil
}

func main(){
	var a,b int = 2,2
  result,err := test(a,b)
  fmt.Println(result,err)
}

在这里插入图片描述

func test(a,b int) (int,error){
	if b == 0 {
		return 0,errors.new("b not's zero")
	}
	return a/b,nil
}

func main(){
	var a,b int = 2,0
  result,err := test(a,b)
  fmt.Println(result,err)
}

在这里插入图片描述

匿名函数

匿名函数是指没有定义名字符号的函数。
除没有名字外,匿名函数和普通函数完全相同。最大区别是,我们可在函数内部定义匿名函数,形成类似嵌套效果。匿名函数可直接调用,保存到变量,作为参数或返回值。直接执行:

func main(){
	v1 := func(a,b int) int {
		return a + b
	}
	v1(1,2)
}

可以通过通道进行传输

func testStruct(){
	type cale type {
		mul func(x,y int) int
	}
		x := cale{ 
		mul: func(x,y int) int{
			return x + y
		},
	}
	println(x.mul(2,3))
}


func testChannel(){
	c := make(chan func(int,int)int,2)
	c <- func(x,y int) int {
		return x+y
	}
	println((<-c)(1,2))    //如果(<-c)后面不接参数,可以不要()
}

func main(){
	testChanel()
	testStruct()
}

在这里插入图片描述

闭包

闭包(closure)是在其词法上下文中引用了自由变量的函数,或者说是函数和其引用的环境的组合体。这种说明太学术范儿了,很难理解,我们先看一个例子。

func test(x int) func(){
	return func(){
		println(x)
	}
}

func main(){
	f := test(123)
	f()
}


///

func test(x int) func() {
	return func() {
		println(x)
	}
}

func main() {
	var f func() = test(123)
	f()
}

在这里插入图片描述

那具体什么叫闭包呢?

func test(x int) func() {
	println(&x)
	return func() {
		println(&x, x)
	}
}

func main() {
	v1 := 10
	f := test(v1)
	f()
}

在这里插入图片描述

通过输出指针,我们注意到闭包直接引用了原环境变量。分析汇编代码,你会看到返回的不仅仅是匿名函数,还包括所引用的环境变量指针。所以说,闭包是函数和引用环境的组合体更加确切。

defer

语句defer向当前函数注册稍后执行的函数调用。这些调用被称作延迟调用,因为它们直到当前函数执行结束前才被执行,常用于资源释放、解除锁定,以及错误处理等操作。

func main() {
	file, err := os.Open("./text.go")
	if err != nil {
		println(errors.New("this is error").Error())
	}
	defer file.Close()
}

在这里插入图片描述

defer顺序是倒排的

func main() {
	defer println(1)
	defer println(2)
	defer println(3)
	defer println(4)
	defer println(5)
}

在这里插入图片描述

在函数中既有return 又有 defer

func test()(z int){
	defer func(){
		println("defer:",z)
		z+=100
	}()
	return z+=100
}

func main(){
	println(test())
}

在这里插入图片描述

对defer的性能进行分析

相比直接用CALL汇编指令调用函数,延迟调用则须花费更大代价。这其中包括注册、调用等操作,还有额外的缓存开销。
以最常用的mutex为例,我们简单对比一下两者的性能差异。

Main_test.go

var m sync.Mutex
func call(){
	m.Lock()
	m.Unlock
}

func defercall(){
	m.Lock()
	defer m.Unlock
}

func BenchmarkCall(b *testing.B){
	for i:=0;i<=b.N;i++{
		call()
	}
}

func BenchmarkDefercall(b *testing.B){
for i:=;i<=b.N;i++{
	defercall()
}

在这里插入图片描述

错误处理

标准库将error定义为借口类型

type error interfance{
		Error() string
}

当我们自定义错误时,我们自定义的值返回给error时,error本身是interface类型,可以接受任意值,我们需要书写自定义类型的Error犯法,从而复合要求,不然程序会报错

练习

type divError struct{
	x,y int
}

func(divError) Error(){
	return "division by zero"
}

func div(x,y) (int,error){
	if y==0{
		return 0,divError{x,y}
	}
	return x/y,nil
}

func main(){
	z,err := div(5,0)
	if err != nil{
	switch e:=err.(type){
	case divError:
		fmt.Println(e.x,e.y)
	default:
			fmt.Println(e)
	}
	log.Fatalln(err)
	}
println(z)
}

在这里插入图片描述

我们需要自定义错误类型,以容纳更多上下文状态信息。这样的话,还可基于类型做出判断。

在正式代码中,我们不能忽略error返回值,应严格检查,否则可能会导致错误的逻辑状态。调用多返回值函数时,除error外,其他返回值同样需要关注。
以os.File.Read方法为例,它会同时返回剩余内容和EOF。

大量函数和方法返回error,使得调用代码变得很难看,一堆堆的检查语句充斥在代码行间。解决思路有:

使用专门的检查函数处理错误逻辑(比如记录日志),简化检查代码。
在不影响逻辑的情况下,使用defer延后处理错误状态(err退化赋值)。
在不中断逻辑的情况下,将错误作为内部状态保存,等最终“提交”时再处理。

panic与recover

与error相比,panic/recover在使用方法上更接近try/catch结构化异常。

func panic(v interfance{})
func recover() interfance{}

它们是内置函数而非语句。panic会立即中断当前函数流程,执行延迟调用。而在延迟调用函数中,recover可捕获并返回panic提交的错误对象。

//panic and recover

 func main() {
     defer func() {
         if err := recover(); err != nil {
              log.Fatalln(err)
          }
      }()
     panic(" this is errors")
      println("exit")

  } 

在这里插入图片描述

因为panic参数是空接口类型,因此可使用任何对象作为错误状态。而recover返回结果同样要做转型才能获得具体信息。
无论是否执行recover,所有延迟调用都会被执行。但中断性错误会沿调用堆栈向外传递,要么被外层捕获,要么导致进程崩溃。

//连续调用panic,只有最后一个panic会被执行

func main() {
	defer func() {
		for {
			if err := recover(); err != nil {
				log.Println(err)
			} else {
				log.Fatalln(err)
			}

		}
	}()
	defer func() {
		panic("this is defer func error")
	}()

	panic("this is main error")
}

在这里插入图片描述

通过recover去保护代码

func test(x, y int) {
	z := 0
	func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println(err)
				z = 0
			}
		}()
		z = x / y //保护此行代码
	}()
	println("x / y =", z)
}
func main() {
	test(5, 0)
}

在这里插入图片描述

除非是不可恢复性、导致系统无法正常工作的错误,否则不建议使用panic。(例如:文件系统没有操作权限,服务端口被占用,数据库未启动等情况。)

package main

import "runtime/debug"

func test() {
	panic("this test error")
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			debug.PrintStack()
		}
	}()
	test()
}

在这里插入图片描述

标签:函数,err,int,test,func,Go,println,main,事儿
From: https://blog.csdn.net/l47ronin/article/details/141873769

相关文章

  • 结合回调函数处理异步任务结果
    结合回调函数处理异步任务结果的过程可以比作在等待一份重要的快递时安排一个通知服务。这个通知服务就是回调函数,它会在快递送达时通知你,或者在处理完成后执行特定的操作。在Java的CompletableFuture中,这种模式可以通过supplyAsync()、thenApply()、thenAccept()和handle(......
  • SQL 自定义函数 生成网卡地址,MES开发中经常会用到的
    SQL自定义函数生成网卡地址,MES开发中经常会用到的ALTERFunction[dbo].[Fun_ReleaseMACadd]( @CurrentSeqNovarchar(6))Returnsvarchar(18)-------------------------------------------------------------------------------------------------As--------------......
  • DataX实战之MongoDB导入数据到mysql——打包jar包时出现Could not find goal assembly
    使用idea打开我们本地的datax源码或者下载的源码下载地址:https://github.com/alibaba/DataX/blob/master/mongodbreader/doc/mongodbreader.md进行编译,打包上传:指定mongodbreader模块以及它所依赖的模块进行打包【推荐使用,大约只运行3分钟左右】mvn-Ucleanpac......
  • Study Plan For Algorithms - Part24
    1.全排列II题目链接:https://leetcode.cn/problems/permutations-ii/给定一个可包含重复数字的序列nums,按任意顺序返回所有不重复的全排列。classSolution:defpermuteUnique(self,nums:List[int])->List[List[int]]:defbacktrack(nums,path,res,us......
  • Study Plan For Algorithms - Part25
    1.栈的压入、弹出序列输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。方法一:defvalidateStackSequences(pushed,popped):stack=[]len_pushed=len(pushed)stack_index=0i......
  • 汇总10个Google搜索国内版
    汇总https://ai-to.cn/https://google.cloudnative.lovehttps://gsearch.g.shellten.top/https://g.savalone.com/https://xueshu.lanfanshu.cn/https://xue.glgoo.net/https://so.cljtscd.com/http://scholar.hedasudi.com/https://ac.scmor.com/https://so3.cljtscd.com/......
  • 字符串查找函数strchr 、 strrchr和strstr的简介
    目录一、函数简介1.1. strchr 函数1.2.strrchr函数1.3. strstr 函数二、函数原型2.1. strchr 函数参数返回值2.1. strchr 函数参数返回值2.2. strstr 函数参数返回值三、函数实现(伪代码)3.1.strchr实现3.2.strrchr实现3.3. strstr实现四、......
  • go 使用grpc和grpcurl
    安装依赖和工具#ubuntu安装protobufaptinstalllibprotobuf-devprotobuf-compilerprotoc-gen-goprotoc-gen-go-grpc-y#查看protobuf版本protoc--version#安装grpcurlwgethttps://github.com/fullstorydev/grpcurl/releases/download/v1.8.7/grpcurl_1.8.7_linux_......
  • Django
    Django1.创建项目1.1终端创建进入终端想将项目放在哪个目录,就进入哪个目录创建django项目(用django-admin.exe工具)#scripts已经配置好环境变量django-adminstartproject项目名1.2Pycharm创建注:项目文件位置不是解释器位置1.2.1说明:终端执行命令行得......
  • Go - Web Application 9
    UsingrequestcontextAtthemomentourlogicforauthenticatingauserconsistsofsimplycheckingwhethera "authenticatedUserID"valueexistsintheirsessiondata,likeso:func(app*application)isAuthenticated(r*http.Request)bool{......