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

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

关键字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)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzhuzhu.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值