函数使用
💥本文章所有相关go代码参考自go 1.18+版本
函数定义
- 函数基本组成:关键字
func
、函数名、参数列表、返回值列表、函数体、返回语句。
|
|
- 除了
main()
、init()
函数外,其他所有类型的函数都可以有【参数】与【返回值】。 - 函数一般也可以这么写:
func FunctionName Signature [FunctionBody]
func
定义函数关键字。FunctionName
函数名。Signature
函数签名,包括函数参数和函数返回值,函数签名是识别一个函数的依据。FunctionBody
函数体。
func FunctionName (a typea, b typed) (t1 type1, t2 type2)
- 函数签名由函数参数、返回值以及它们的类型组成。
(a typea, b typed) (t1 type1, t2 type2)
- 如果两个函数的参数列表和返回值列表的变量类型能一一对应,那么这两个函数就有相同得签名。
- 下面
testa
与testb
具有相同得函数签名。
func testa (a, b int, z float32) bool
func testb (a, b int, z float32) (bool)
- 函数调用传入的参数必须按照参数声明的顺序。
Go
语言【没有默认参数值】。 - 函数签名中的最后传入参数可以具有前缀为
...
的类型(...int
),这样的参数称为【可变参数】。- 在接收这种(
...
)参数的时候,当做【切片】处理即可。 - 注意(
s…
)这种形式的s
只能是切片或者是字符串(只能在append()
函数或可变参数函数中使用)【不能是数组】。
- 在接收这种(
- 可以使用零个或多个参数来调用该函数,这样的函数称为【变参函数】。
// 其实values就是 []int 切片
func doFix(prefix string, values ...int)
使用
- 函数的参数和返回值列表始终带括号,但只有一个未命名的参数值,可以将其写为未加括号的类型。
- 一个函数也可以拥有多个返回值,返回类型之间需要使用逗号分隔,并使用小括号()将它们括起来。
func testa (a, b int, z float32) bool
func swap (a int, b int) (t1 int, t2 int)
- 在函数体中,参数是局部变量,被初始化为调用者传入的值。
- 函数的参数和命名返回值是函数最外层的局部变量,它们的作用域就是整个函数。
- 如果函数的签名声明了返回值,则函数体的语句列表必须以终止语句结束。
- 但是如果函数没有声明返回值也是可以使用
return
结束函数后面代码。
|
|
-
Go
语言函数重载是不被允许的。- 函数重载:可以编写多个同名函数,只要它们拥有不同的形参或者不同的返回值。
- 官方不支持重载原因,让Go保存简单。
-
函数可以作为函数类型被使用。函数类型就是函数签名。函数类型的未初始化变量的值为
nil
(函数是引用类型)。 -
函数作为参数被使用,这种是回调。其实就是
funcval
指针。 -
函数作为返回值被使用,这种是闭包。其实就是
funcval
指针。
|
|
|
|
- 函数可以在表达式中赋值给变量,这样作为表达式中的右值出现,称为函数值字面量。
- 函数值字面量是一种表达式,它的值被称为匿名函数。
|
|
|
|
...int
- 函数只能是最后一个参数是
...int
形式。
|
|
main.main
相关汇编:
|
|
...int
传递参数:
|
|
main.main
相关汇编:
|
|
main.X
相关汇编:
|
|
main.main
和main.X
函数的栈布局情况:
|
|
s...
形式传参:
|
|
main.main
相关汇编代码:
|
|
main.X
函数相关汇编:
|
|
main.main
和main.X
函数的栈布局情况:
|
|
函数调用
Go
语言中函数默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响原来的变量。- 如果希望函数可以直接修改参数的值,而不是对参数的副本进行操作。需要将参数的地址传递给函数,这就是按引用传值。如
Function(&arg1)
,此时传递给函数的是一个指针。如果传递给函数的是一个指针,则可以通过这个指针来修改对应地址上的变量值。 - 在进行函数调用时,像切片(
slice
)、字典(map
)、函数(func
)、通道(channel
)等这样的引用类型都是默认使用引用传递。 - 命名返回值被初始化为相应类型的零值,当需要返回的时候,只需要一条简单的不带参数的
return
语句。即使只有一个命名返回值,也需要使用()
括起来。如type funcType func() (b bool)
、type funcType1 func() bool
。
|
|
内置函数
- 内置函数是预先声明的,它们像任何其他函数一样被调用。
- 内置函数没有标准的类型,因此只能出现在调用表达式中,不能用作函数值。
- 它们有时候可以针对不同的类型进行操作。
- 内置函数
make()
和new()
都和内存分配相关,但也有差异。
内置函数 | 说明 |
---|---|
make(T) |
make 只用于slice 、map 以及channel 这三种引用数据的内存分配和初始化,make(T) 返回类型T 的值(不是*T) |
new(T) |
new 用于值类型的内存分配,并且置为零值,new(T) 分配类型T 的零值并返回其地址,也就是指向类型T 的指针 |
- 内置函数
make()
用作于slice
、map
和channel
三种数据类型时,参数及作用有些区别。 - make 函数原型:
func make(t Type, size ...IntegerType) Type
- new 函数原型:
func new(Type) *Type
T 的类型 | 参数 | 说明 |
---|---|---|
slice |
make(T, n) |
T 为切片类型,长度和容量都为n |
slice |
make(T, n, m) |
T 为切片类型,长度n ,容量m (n <=m ,否则错误 ) |
map |
make(T) |
T 为字典类型 |
map |
make(T, n) |
T 为字典类型,分配n 个元素的空间 |
channel |
make(T) |
T 为通道类型,无缓冲区 |
channel |
make(T, n) |
T 为通道类型,缓冲区容量为n |
- 内置函数
make()
的实际使用举例
|
|
new(T)
内置函数在运行时为该类型的变量分配内存,返回指向它的类型*T
的值,并对变量初始化
|
|
new(S)
为S
类型的变量分配内存,并初始化(a = 0, b = 0.0
),返回包含该位置地址的类型*S
的值。slice
、map
和channel
这三种数据类型声明时,可设置长度或容量,所以通过内置函数len()
和cap()
得到对应长度与容量。- len 函数原型:
func len(v Type) int
- cap 函数原型:
func cap(v Type) int
内置函数 | 参数s的类型 | 结果说明 |
---|---|---|
len(s) |
string |
string 类型s 的长度(按照字节计算) |
len(s) |
[n]T ,*[n]T |
数组类型s 的长度([n]T 、[n]*T 、*[n]T )数组指针*[n]T 支持语法糖 |
len(s) |
[]T |
切片类型s 的长度 |
len(s) |
map[K]T |
字典类型s 的长度 |
len(s) |
chan T |
通道类型s 的缓冲区排队的元素数量 |
cap(s) |
[]T |
切片类型s 的容量 |
cap(s) |
chan T |
通道类型s 的缓冲区容量 |
|
|
- 对于
len(s)
和cap(s)
,如果s
为nil
值,则两个函数的取值都是0
,此外:
|
|
Go
语言中,常量在某些计算条件下也可以通过表达式计算得到。- 假如
s
是字符串常量,则表达式len(s)
是常量。 - 如
s
的类型是数组或指向数组的指针而表达式不包含通道接收或(非常量)函数调用,则表达式len(s)
和cap(s)
是常量,否则len
和cap
的调用不是常量。
|
|
递归与回调
- 递归函数:函数直接或间接调用函数本身。使用递归函数时经常会遇到栈溢出。
|
|
Go
语言中也可以使用相互调用的递归函数,多个函数之间相互调用形成闭环。- 回调:
Go
语言函数可以作为其他函数的参数进行传递,然后在其他函数内调用执行。
|
|
匿名函数
- 匿名函数:函数值字面量是一种表达式。不给函数起名字的时候,可以使用匿名函数。
- 这样的函数不能独立存在,但是可以被赋值于某个变量,即保存函数的地址到变量中。
|
|
- 当然,也可以直接对匿名函数进行调用。注意匿名函数的最后加上括号并填入参数值,如果没有参数,也需要加上括号,代表直接调用。
|
|
- 计算0 ~ 100万整数的总和的匿名函数。
|
|
变参函数
- 可变参数就是不定长参数,支持可变参数列表的函数可以支持任意个传入参数。如:
fmt.Println
函数就是一个支持可变长参数列表的函数。
|
|