Go_秒懂函数、参数、可变参数、匿名函数、内置函数

2023-10-30

函数是将具有独立功能的代码块组成一个整体,使其具有特殊功能的代码集。它将复杂的算法过程分解为若干个小任务,使程序结构更加清晰、易于维护。通过调用完成一段算法指令,输出或存储相关结果。因此,函数还是代码复用和测试的基本单元。

关键字func用于定义函数

  • 函数必须先定义,后调用,定义的过程为函数定义

  • 函数定义后需要调用才能使用,该过程为函数调用

函数定义:

func 函数名(参数列表)(返回值列表){
        语句体
        return 返回值
}

函数调用:

函数调用时,参数的数量与数据类型必须与函数定义中的相匹配。

普通格式调用:

 函数名(形参列表)

函数值格式调用:

 变量 := 函数名(形参列表)

函数表达式格式调用:

 变量 := 类名.函数名(形参列表)

函数的返回值通常会使用变量接收,否则该返回值无意义。

package function


import "fmt"

// 定义函数Function,形参分别是a、b,返回值类型是int,可以省略返回值名
func Function(a int, b int) int {
	return a + b
}

func main() {
	// 普通格式调用
	fmt.Println(Function(1, 2))

	// 函数值格式调用
	f := Function(1, 2)
	fmt.Println(f)
}

————————————————————————————分界线————————————————————————————

package main

import (
        function "go_basics/func"
)

func main() {
	// 函数表达式格式调用
	function.Function(1, 2)
}

函数只能判断是否为nil,不支持其他比较操作。

func main() {
        fmt.Println(function01 == nil)
        fmt.Println(function01 == function02) // 无效运算: function01 == function02 (在 func() 中未定义运算符 ==)
}
func function01() {}
func function02() {}

函数中的变量是局部的,函数外不生效。

func main() {
	var num = 1
	fmt.Println(num)
}

num // 报错,找不到num

形参列表可视为已定义的局部变量

func Function(x, y int) int { // 这里已经定义了局部变量x、y
	x := 100  // 错误:':=' 的左侧没有新变量
	x = 100   // 可以修改

	// 当定义多个变量时,只要左侧有新的变量,即可成立
	a, x := 1, 2
	fmt.Println(a, x, y)
	return x + y
}

参数:

基本类型和数组默认都是值传递,实参将自己的地址值拷贝一份给形参。

形参和实参:

形参是指函数中定义的参数,实参则是函数调用时所传递的参数。形参相当于函数局部变量,而实参则是函数外部对象,可以是常量、变量、表达式或函数等。

形参(形式参数):

顾名思义形参就是只有一个形式,没有赋值

// num只是一个形式并没有赋值
func function(num int) {}

实参(实际参数):

顾名思义实参就是有实际的参数数值

func main() {
  num := 10
  function(num) // 这里的num就已经赋值了
}

func function(num int) {}

基本类型作为形参不会被修改原数据

func main() {
	var a = 1
	var b = 2

	Function(a, b)
	fmt.Println("main函数:", "a=", a, "b=", b)
}

func Function(a, b int) {
	a, b = b, a
	fmt.Println("Function函数:", "a=", a, "b=", b)
}

输出:

function函数: a= 2 b= 1
main函数: a= 1 b= 2

无论是基本类型、引用类型都是值拷贝传递,无非是拷贝目标对象,还是拷贝地址值在函数调用时,会为形参和返回值分配内存空间,并将实参数据拷贝到形参内存。

func main() {
	num := 20
	Function(&num)
	fmt.Println("main函数中 num= ", num)
}

func Function(num *int) {
	*num = *num + 10
	fmt.Println("function函数 num= ", *num)
}

输出:

function() num: 30
main() num: 30

形参列表中相邻的同数据类型可以合并数据类型,调用时必须按参数顺序传递指定类型的实参,哪怕使用_也不能忽略实参。

func main() {
	Function(1, 2, "abc",) // 报错;'function' 调用中的实参不足
	Function(1, 2, "abc", false) // 给bool变量赋值就可以了
}

func Function(x, y int, s string, _ bool) int {
	return x + y
}

Go不支持函数重载。

func Function(n1 int)         {}
func Function(n1 int, n2 int) {} // 此包中重新声明的 'Function'

函数也是一种数据类型,可以赋值给一个变量,那么这个变量就是一个函数类型的变量,通过该变量可以对函数调用。

func main() {
	z := Function // 直接把函数赋值给一个变量
	fmt.Printf("变量z的数据类型为:%T\nFunction的数据类型为:%T\n", z, Function)

	// 因为是赋值给变量了,所以可以直接使用变量调用相当于原函数名本身
	fmt.Println(z(1, 2))
}

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

输出:

变量z的数据类型为:func(int, int) int
Function的数据类型为:func(int, int) int
3

既然函数是一种数据类型,那么函数也可以作为形参使用

func main() {
	fmt.Println(Function(GetSum, 10, 20))
}

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

/*
	参数1:GetSum func(x, y int) int
	GetSum:参数名
	func:函数类型
	(x, y int) int:GetSum的参数及返回值
*/
func Function(GetSum func(x, y int) int, num1, num2 int) int {
	return GetSum(num1, num2)
}

可变参数:

顾名思义函数中参个数是可以变化的,如果函数不确定形参长度,可以使用可变参数传递,变参本质上是一个切片,它只能接收相同类型的参数值,且必须放在参数列表的最后。

可变参数的使用:

func main() {
	Function("abc", 1, 2, 3, 4, 5)
}

func Function(s string, a ...int) {
	fmt.Printf("可变参数a的数据类型为:%T\n值为:%v", a, a)
}

输出:

可变参数a的数据类型为:[]int
值为:[1 2 3 4 5]

变参是切片,可以修改原数据。

func main() {
	a := []int{10, 20, 30}
	Function(a...)
	fmt.Println(a)
}

func Function(a ...int) {
	a[0] = 100
}

输出:

[100 20 30]

返回值

  • Go支持多个返回值,如果没有定义返回值,但是写了return,相当于终止函数。
  • 返回值不想接收时候可以使用下划线忽略_
  • 返回值只有一个时可以不写括号,有多个时必须写括号。

没定义返回值但写了return就会终止,return后面的代码是不会执行的。

func main() {
	Function(1, 2) // 结果为空
}

func Function(x, y int) {
	return
	z := x + y
	fmt.Println("会走我吗", z)
}

函数后面只有返回值类型没有给返回值命名可以返回任意指定变量

func main() {
	f := function(1, 2)
	fmt.Println(f)
}

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

命名返回值

func main() {
	f := Function(1, 2)
	fmt.Println(f) // 3
}

func Function(x, y int) (sum int) {
	sum = x + y
	return // 函数返回值那里已经定义了,在函数中可以省略返回值名,直接return,相当于return sum
}

有返回值的函数,必须有明确的return终止语句。

func main() {
	f := Function(1, 2)
	fmt.Println(f) // 3
}

func Function(x, y int) (sum int) {
	sum = x + y
} // 函数末尾缺少 'return' 语句

相同类型的多返回值可用作调用实参,或直接返回

func main() {
	log(test()) //多返回值直接用作实参。
}

func log(x int, err error) {
	fmt.Println(x, err)
}

func test() (int, error) {
	return div(5, 0) //多返回值直接用作return结果。
}

func div(x, y int) (int, error) {
	if y == 0 {
		return 0, errors.New("error...")
	}
	return x / y, nil
}

匿名函数:

匿名函数就是没有名字的函数,如果函数只使用一次,就可以使用匿名函数,匿名函数也可以实现多次调用。

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

作用:

  1. 匿名函数只有在被调用的时候才会开辟空间,执行完毕就会被销毁,可以节省内存
  2. 减少重名的风险
  3. 可以实现闭包

格式:

func(形参)(返回值) {
		函数体
}(实参) // 在定义的时候就已经传入了参数

无返回值匿名函数

func main() {
	func(s string) {
		fmt.Println(s)
	}("我是实参,上面的s是形参,我会被打印不")
}

输出:

我是实参,上面的s是形参,我会被打印不

有返回值匿名函数:把匿名函数赋值给一个变量,再通过变量调用函数

func main() {
	num := func(x, y int) int {
		return x + y
	}
	fmt.Println(num(1, 2))
}

全局匿名函数:把匿名函数赋值一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。

var num = func(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(num(1, 2))
}

匿名函数没有传参会报错,在结尾传入参数就可以

func main() {
	func(x int) { // 报错:func 已评估但未使用
		fmt.Println(x)
	}
}

闭包

  • 闭包(closure)是函数和其引用环境的组合体(匿名函数引用了匿名函数外部的数据,如变量、常量、函数等。)
  • 闭包让我们不用传递参数就可读取或修改环境状态,传入一次就可以反复使用

ClosePackage返回的匿名函数会引用匿名函数外部的变量x,这种现象就称作闭包,不管是变量,还是其它数据,只要是匿名函数引用了外部的数据,那么就会称为闭包,因为变量x只初始化一次,所以连续调用时候结果就会累计

func main() {
	num := ClosePackage()
  // num里传的形参是给匿名函数的
	fmt.Println(num(1)) // 传入一个值为1,这个1会赋给匿名函数中的y
	fmt.Println(num(2))
	fmt.Println(num(3))
}

func ClosePackage() func(int) int { // 定义一个函数,无形参,返回值是一个匿名函数
	var x int = 1
	return func(y int) int {
		x = y + 1 // 在这里使用匿名函数外的变量x
		return x
	}
}

输出:

2
3
4

闭包应用:

func main() {
	f := FileTest(".pdf")
	fmt.Println(f("Go语言学习笔记"))
	fmt.Println(f("Go语言学习笔记.韩顺平"))
	fmt.Println(f(".pdf"))
}

func FileTest(FileName string) func(string) string {

	return func(name string) string {
		// 判断传入的name开头是否有指定的后缀(FileName),不等于就加上后缀,如果等于就返回name
		if !strings.HasPrefix(name, FileName) {
			return name + FileName
		}
		return name
	}
}

输出:

Go语言学习笔记.pdf
Go语言学习笔记.韩顺平.pdf
.pdf

内置函数

函数 作用
make 为切片,map、通道类型分配内存并初始化对象
len 计算数组、切片、map、通道的长度
cap 计算数组、切片、通道的容量
delete 删除 map 中对应的键值对
append 将数据添加到切片的末尾
copy 将原切片的数据复制到新切片中
new 除切片、map、通道类型以外的类型分配内存并初始化对象,返回的类型为指针
complex 生成一个复数
real 获取复数的实部
imag 获取复数的虚部
print 将信息打印到标准输出,没有换行
println 将信息打印到标准输出并换行
close 关闭通道,释放资源
panic 触发程序异常
recover 捕捉 panic 的异常信息

len:用来计算长度的,string、arr、slice、map、channel都可以

func main() {
	s := "itzhuhzu"
	fmt.Println("长度为:",len(s))
}

new:用来分配值内存的,int、float32、struct返回值是指针

func main() {
	num := 100
	fmt.Printf("num的类型:%T,num的值:%v,num的内存地址:%v\n", num, num, &num)

	num2 := new(int)
	*num2 = 100
	fmt.Printf("num2的类型:%T,num2的值:%v,num2的内存地址:%v,num2指向地址存储的数据:%v", num2, num2, &num2, *num2)
}

输出:

num的类型:int,num的值:100,num的内存地址:0x1400012c008
num2的类型:*int,num2的值:0x1400012c020,num2的内存地址:0x14000126020,num2指向地址存储的数据:100

直接定义变量的流程是:

开辟内存空间 -> 将数据存储到内存空间

适用new定义变量的流程是:

开启指针内存空间 -> 指向数据的内存地址

defer

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

func main() {
	defer fmt.Println("第1个defer")
	defer fmt.Println("第2个defer")
	defer fmt.Println("第3个defer")

	fmt.Println("第1个输出")
	fmt.Println("第2个输出")
	fmt.Println("第3个输出")
}

输出:defer的结果是倒叙的,原因是:进入main函数发现了defer,就把defer抓走放在了一个独立的栈中等待执行(压栈),然后继续执行下面的,直到所有的程序执行完,才执行defer(弹栈),而栈内存是先进后出(就像弹夹一样,先放的子弹是最后才打出去的),所以是先输出了第3个defer

1个输出
第2个输出
第3个输出
第3defer2defer1defer

return后的defer不生效,输出结果为空,因为defer还没来得及注册,遇到return后整个test函数就结束了

func main() {
   test()
}

func test() {
   return
   defer fmt.Println("test函数")
}

init

init 函数最主要的作用,就是完成一些初始化的工作,每一个源文件都可以包含一个init函数,该函数会在main函数执行前被调用

var name = "itzhuzhu"
var age = 24

func main() {
	fmt.Println("main方法执行")
}

func init() {
	fmt.Println("init方法执行")
	fmt.Println("name=", name, "age=", age)
}

输出:

init方法执行
name= itzhuzhu age= 24
main方法执行

如果一个文件同时包含全局变量定义init函数main函数,则执行的流程是全局变量定义 > init > main

var num = test()

func test() int {
	fmt.Println("test方法执行")
	return 2022
}
func init() {
	fmt.Println("init方法执行")
}
func main() {
	fmt.Println("main方法执行")
}

输出:

test方法执行
init方法执行
main方法执行

如果 main.go引用了utils.go,但是两个文件都含有定义变量、init、main,执行的流程是怎么样的?

  1. 先执行utils.go
  2. 再执行utils.go下的变量 > init > main
  3. 再回去执行main.go下的变量 > init > main

如果是mian.go文件中的一个函数引用了utils.go下的函数,则流程是

  1. 先执行mian.go,然后走到引用utils.go的代码才会进入utils.go文件中执行

递归

  • 递归指的是一个函数在函数体内调用了自己
  • 当一个函数执行完毕或者遇到 return,就会返回给调用者,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁

递归注意事项:

  1. 递归一定要有出口。否则内存溢出(出口:什么时候不再调用自己)
  2. 递归虽然有出口,但是递归的次数也不宜过多, 否则内存溢出
func main() {
	test(4)
}

func test(n int) {
	if n > 2 {
		n--
		test(n)
	}
	fmt.Println(n)
}

输出:

2
2
3

递归案例过程分析:

// main调用test,现在N=4
func test(4 int) {
     if 4 > 2 {
          4--
          test(3)
     }
     fmt.Println(3)
}

func test(3 int) {
     if 3 > 2 {
          3--
          test(2)
     }
     fmt.Println(2) 
}

func test(2 int) {
     if 2 > 2 {
        不成立,if执行完以后,就会把n的值返回给调用者,会往上面传
     }
     fmt.Println(2)
}

// 这段代码是在栈中完成的,栈的特点是先进后出,所以打印的结果是2、2、3

斐波那契数

给你一个整数n,请使用递归的方式,求出它的斐波那契数是多少?

斐波那契数:1,1,2,3,5,8,13…,从第三个数开始是前两个的和

func main() {
	res := test(6)
	fmt.Println(res)
}

func test(n int) (result int) {
	if n == 1 || n == 2 {
		return 1
	} else {
		return test(n-1) + test(n-2)
	}
}

递归求阶乘:

var s = 1

func main() {
	recursion(5)
	fmt.Println(s)
}

func recursion(num int) {
	if num == 1 {
		return // 终止函数的意思
	}
	s *= num
	recursion(num - 1)
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go_秒懂函数、参数、可变参数、匿名函数、内置函数 的相关文章

  • go struct{} 空结构体的特点和作用

    空结构体的特点和作用 参考代码 package main import fmt unsafe func main empStruct 空结构体的实例和作用 func empStruct 空结构体的特点 1 不占用内存 2 地址不变 var
  • Golang教程:(十六)结构体

    原文 https golangbot com structs 欢迎来到Golang系列教程的第十六篇 什么是结构体 结构体 struct 是用户自定义的类型 它代表若干字段的集合 有些时候将多个数据看做一个整体要比单独使用这些数据更有意义
  • Go的并发的退出

    有时候我们需要通知goroutine停止它正在干的事情 比如一个正在执行计算的web服务 然而它的客户端已经断开了和服务端的连接 Go语言并没有提供在一个goroutine中终止另一个goroutine的方法 由于这样会导致goroutin
  • golang基础教程

    目录 golang基础教程 一 环境搭建 golang基础教程 二 开发规范及API golang基础教程 三 变量与数据类型概述 golang基础教程 四 基本数据类型 golang基础教程 五 基本数据类型的转换 golang基础教程
  • Jenkins系列:3、wsl/ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序

    Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 文章目录 Jenkins系列 3 wsl ubuntu安装Jenkins及Jenkins构建可交叉编译的go程序 1 前言 2 wsl
  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • Go语言里面的各种疑难杂症

    什么是闭包 闭包有什么缺陷 func AddUpper func int int var n int 10 return func x int int n n x return n func main f AddUpper fmt Prin
  • 48.Go简要实现令牌桶限流与熔断器并集成到Gin框架中

    文章目录 一 简介 二 限流器与熔断器在微服务中的作用 1 限流器 对某个接口单位时间内的访问量做限制 2 熔断器 当服务连续报错 超过一定阈值时 打开熔断器使得服务不可用 三 具体实现 1 限流器实现逻辑 以令牌桶算法为例 2 限流器集成
  • 【go语言开发】编写单元测试

    本文主要介绍使用go语言编写单元测试用例 首先介绍如何编写单元测试 然后介绍基本命令的使用 最后给出demo示例 文章目录 前言 命令 示例 前言 在go语言中编写单元测试时 使用说明 测试文件命名 在 Go 语言中 测试文件的命名应与被测
  • 【go语言开发】loglus日志框架的使用

    本文将简单介绍loglus框架的基本使用 并给出demo 文章目录 前言 Loglus常见用法 自定义日志级别 使用字段钩子 输出到多个位置 使用钩子实现自定义日志处理 demo
  • go-zero目录结构和说明

    code of conduct md 行为准则 CONTRIBUTING md 贡献指南 core 框架的核心组件 bloom 布隆过滤器 用于检测一个元素是否在一个集合中 breaker 熔断器 用于防止过多的请求导致系统崩溃 cmdli
  • Go 语言中切片的使用和理解

    切片与数组类似 但更强大和灵活 与数组一样 切片也用于在单个变量中存储相同类型的多个值 然而 与数组不同的是 切片的长度可以根据需要增长和缩小 在 Go 中 有几种创建切片的方法 使用 datatype values 格式 从数组创建切片
  • 协程-单线程内的异步执行

    1 仿协程实例 不同事件依次顺序执行 coding utf 8 import time def calculate 1 step event name for index in range step print This is s even
  • 【go语言】error错误机制及自定义错误返回类型

    简介 Go 语言通过内置的 error 接口来处理错误 该接口定义如下 type error interface Error string 这意味着任何实现了 Error 方法的类型都可以作为错误类型 在 Go 中 通常使用 errors
  • [每周一更]-(第55期):Go的interface

    参考地址 https juejin cn post 6978322067775029261 https gobyexample com interfaces https go dev tour methods 9 介绍下Go的interfa
  • Golang拼接字符串性能对比

    g o l a n g golang g o l an g
  • Golang拼接字符串性能对比

    g o l a n g golang g o l an g
  • 这套Go语言开发框架组合真的非常高效

    我尝试过很多框架 从Django Flask和Laravel到NextJS和SvelteKit 到目前为止 这是我唯一可以使用的不会让我感到疯狂或者放弃项目的堆栈 框架 我喜欢所有这些框架 但我只是不太适应它们的设计方式 实际上 我是一个弱
  • golang 生成一年的周数

    GetWeekTimeCycleForGBT74082005 获取星期周期 中华人民共和国国家标准 GB T 7408 2005 参数 year 年份 GB T 7408 2005 func GetWeekTimeCycleForGBT74
  • go cannot find package “github.com/gorilla/websocket“解读

    Go无法找到包 github com gorilla websocket 的解决方案 在Go开发过程中 我们经常会依赖第三方库来简化开发工作 而使用 go get 命令安装这些库时 有时候我们可能会遇到类似于以下错误的情况 plaintex

随机推荐