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

  • Post author:
  • Post category:其他


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

关键字

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)
}



版权声明:本文为weixin_45477086原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。