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踩坑——no required module provides package go.mod file not found in current directory or any parent

    背景 准备运行下面代码 package main import github com gin gonic gin func main 创建一个默认的路由引擎 r gin Default GET 请求方式 hello 请求的路径 当客户端以G
  • Golang适合高并发场景的原因分析

    典型的两个现实案例 我们先看两个用Go做消息推送的案例实际处理能力 360消息推送的数据 16台机器 标配 24个硬件线程 64GB内存 Linux Kernel 2 6 32 x86 64 单机80万并发连接 load 0 2 0 4 C
  • golang sleep

    golang的休眠可以使用time包中的sleep 函数原型为 func Sleep d Duration 其中的Duration定义为 type Duration int64 Duration的单位为 nanosecond 为了便于使用
  • Go切片排序

    Go 语言标准库提供了sort包 用于对切片和用户定义的集合进行排序 具体示例如下 基本排序 package main import fmt sort func main float 从小到大排序 f float64 5 2 1 3 0 7
  • 权重实现随机抽奖

    一般抽奖是怎么实现的 在实习期间学会了一种通用的写法 在这里记录一下 最近在学Golang语法基础 这里就用Golang来写 package main import fmt time math rand func main r rand N
  • golang:环境变量GOPROXY和GO111MODULE设置

    我们安装完golang后 我们在windows的cmd命令下就可以直接查看和使用go命令和环境变量了 同样的在linux下可以在控制台使用 如下图所示 C Users lijie1 gt go env set GO111MODULE set
  • beego+goAdmin+mysql+docker+natapp作为微信小程序地服务器“伪部署”

    写在前面的话 1 为什么我要叫伪部署 答 因为我把它们放在服务器运行 都是开发模式 生产模式实在不会弄 所以就这样了 2 系统环境 答 腾讯云服务器 系统为 ubuntu 版本不记得 应该是比较高的 3 前提假设 答 假设你的服务器已经安装
  • go 进阶 gin实战相关: 五. gin_scaffold 企业脚手架

    目录 一 gin scaffold 企业级脚手架 二 gin scaffold 脚手架安装及使用演示 文件分层解释 开始使用 1 配置开启go mod 功能 2 下载 安装 gin scaffold 3 整合 golang common 4
  • Go Web编程实战(10)----模板引擎库text/template包的使用

    目录 前言 模板引擎 定义模板文件 解析模板文件 渲染模板 实战使用模板 创建 tmpl文件 创建文件用于解析与渲染模板 前言 在Go语言中 模板引擎库text template包主要用于处理任意格式的文本内容 同时还提供了html tem
  • goland环境配置

    goland modules环境配置 下载和安装goland 环境配置 配置环境变量GOPATH 配置go modules GOPROXY代理的系统变量 工程目录中新建三个工作目录 goland中启用go modules 新建一个go程序
  • Go 语言注释教程

    注释是在执行时被忽略的文本 注释可用于解释代码 使其更易读 注释还可用于在测试替代代码时防止代码执行 Go支持单行或多行注释 Go单行注释 单行注释以两个正斜杠 开头 在 和行尾之间的任何文本都将被编译器忽略 不会被执行 示例 This i
  • 基于Go语言实现简易Web应用

    目录 前言 Go语言特点 写在使用Go语言实现Web应用前面 创建Web服务器 声明一个结构体操作 加入中间件的使用 使用静态文件服务器 最后 前言 在编程语言中 近几年问世的几个新语言都是非常不错的 比如Go Python Rust等等
  • 【go语言开发】编写单元测试

    本文主要介绍使用go语言编写单元测试用例 首先介绍如何编写单元测试 然后介绍基本命令的使用 最后给出demo示例 文章目录 前言 命令 示例 前言 在go语言中编写单元测试时 使用说明 测试文件命名 在 Go 语言中 测试文件的命名应与被测
  • go-zero开发入门之网关往rpc服务传递数据2

    go zero 的网关服务实际是个 go zero 的 API 服务 也就是一个 http 服务 或者说 rest 服务 http 转 grpc 使用了开源的 grpcurl 库 当网关需要往 rpc 服务传递额外的数据 比如鉴权数据的时候
  • go-zero 的 etcd 配置

    实现代码在 core discov config go 文件中 type EtcdConf struct Hosts string Key string ID int64 json optional User string json opt
  • Go 语言中切片的使用和理解

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

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • go开发--操作mysql数据库

    在 Go 中访问 MySQL 数据库并进行读写操作通常需要使用第三方的 MySQL 驱动 Go 中常用的 MySQL 驱动有 github com go sql driver mysql 和 github com go xorm xorm
  • [每周一更]-(第55期):Go的interface

    参考地址 https juejin cn post 6978322067775029261 https gobyexample com interfaces https go dev tour methods 9 介绍下Go的interfa
  • 【go语言】读取toml文件

    一 简介 TOML 全称为Tom s Obvious Minimal Language 是一种易读的配置文件格式 旨在成为一个极简的数据序列化语言 TOML的设计原则之一是保持简洁性 易读性 同时提供足够的灵活性以满足各种应用场景 TOML

随机推荐