共计 7372 个字符,预计需要花费 19 分钟才能阅读完成。
golang 函数
本文视频教程:https://www.bilibili.com/vide…
关注公众号,支付课程材料及源码
golang 函数简介
函数的 go 语言中的 一级公民,咱们把所有的性能单元都定义在函数中,能够重复使用。函数蕴含函数的名称、参数列表和返回值类型,这些形成了函数的签名(signature)。
go 语言中函数个性
- go 语言中有 3 种函数:一般函数、匿名函数(没有名称的函数)、办法(定义在 struct 上的函数)。receiver
- go 语言中不容许函数重载(overload),也就是说不容许函数同名。
- go 语言中的函数不能嵌套函数,但能够嵌套匿名函数。
- 函数是一个值,能够将函数赋值给变量,使得这个变量也成为函数。
- 函数能够作为参数传递给另一个函数。
- 函数的返回值能够是一个函数。
- 函数调用的时候,如果有参数传递给函数,则先拷贝参数的正本,再将正本传递给函数。
- 函数参数能够没有名称。
go 语言中函数的定义和调用
函数在应用之前必须先定义,能够调用函数来实现某个工作。函数能够反复调用,从而达到代码重用。
go 语言函数定义语法
func function_name([parameter list] ) [return_types]
{函数体}
语法解析:
func
:函数由func
开始申明function_name
:函数名称,函数名和参数列表一起形成了函数签名。[parameter list]
:参数列表,参数就像一个占位符,当函数被调用时,你能够将值传递给参数,这个值被称为理论参数。参数列表指定的是参数类型、程序、及参数个数。参数是可选的,也就是说函数也能够不蕴含参数。return_types
:返回类型,函数返回一列值。return_types
是该列值的数据类型。有些性能不须要返回值,这种状况下return_types
不是必须的。- 函数体:函数定义的代码汇合。
go 语言函数定义实例
定义一个求和函数
func sum(a int, b int) (ret int) {
ret = a + b
return ret
}
定义一个比拟两个数大小的函数
func compare(a int, b int) (max int) {
if a > b {max = a} else {max = b}
return max
}
go 语言函数调用
当咱们要实现某个工作时,能够调用函数来实现。调用函数要传递参数,如何有返回值能够取得返回值。
func main() {s := sum(1, 2)
fmt.Printf("s: %v\n", s)
max := compare(1, 2)
fmt.Printf("max: %v\n", max)
}
运行后果
s: 3
max: 2
golang 函数的返回值
函数能够有 0 或多个返回值,返回值须要指定数据类型,返回值通过 return
关键字来指定。
return
能够有参数,也能够没有参数,这些返回值能够有名称,也能够没有名称。go 中的函数能够有多个返回值。
return
关键字中指定了参数时,返回值能够不必名称。如果return
省略参数,则返回值局部必须带名称- 当返回值有名称时,必须应用括号突围,逗号分隔,即便只有一个返回值
- 但即便返回值命名了,
return
中也能够强制指定其它返回值的名称,也就是说return
的优先级更高 - 命名的返回值是事后申明好的,在函数外部能够间接应用,无需再次申明。命名返回值的名称不能和函数参数名称雷同,否则报错提醒变量反复定义
return
中能够有表达式,但不能呈现赋值表达式,这和其它语言可能有所不同。例如return a+b
是正确的,但return c=a+b
是谬误的。
go 语言函数返回值实例
没有返回值
func f1() {fmt.Printf("我没有返回值,只是进行一些计算")
}
有一个返回值
func sum(a int, b int) (ret int) {
ret = a + b
return ret
}
多个返回值,且在 return 中指定返回的内容
func f2() (name string, age int) {
name = "老郭"
age = 30
return name, age
}
多个返回值,返回值名称没有被应用
func f3() (name string, age int) {
name = "老郭"
age = 30
return // 等价于 return name, age
}
return 笼罩命名返回值,返回值名称没有被应用
func f4() (name string, age int) {
n := "老郭"
a := 30
return n, a
}
Go 中常常会应用其中一个返回值作为函数是否执行胜利、是否有错误信息的判断条件。例如
return value,exists
、return value,ok
、return value,err
等。当函数的 返回值过多 时,例如有 4 个以上的返回值,应该将这些返回值收集到容器中,而后以返回容器的形式去返回。例如,同类型的返回值能够放进 slice 中,不同类型的返回值能够放进 map 中。
但函数有多个返回值时,如果其中某个或某几个返回值不想应用,能够通过下划线
_
来抛弃这些返回值。例如上面的f1
函数两个返回值,调用该函数时,抛弃了第二个返回值 b,只保留了第一个返回值 a 赋值给了变量a
。
package main
import "fmt"
func f1() (int, int) {return 1, 2}
func main() {_, x := f1()
fmt.Printf("x: %v\n", x)
}
运行后果
x: 2
golang 函数的参数
go 语言函数能够有 0 或多个参数,参数须要指定 数据类型。
申明函数时的参数列表叫做形参,调用时传递的参数叫做实参。
go 语言是通过 传值的形式传参 的,意味着传递给函数的是拷贝后的正本,所以函数外部拜访、批改的也是这个正本。
go 语言能够应用 变长参数 ,有时候并不能确定参数的个数,能够应用变长参数,能够在函数定义语句的参数局部应用ARGS...TYPE
的形式。这时会将 ...
代表的参数全副保留到一个名为 ARGS 的 slice 中,留神这些参数的数据类型都是 TYPE。
go 语言函数的参数实例
go 语言传参
// 形参列表
func f1(a int, b int) int {
if a > b {return a} else {return b}
}
func main() {
// 实参列表
r := f1(1, 2)
fmt.Printf("r: %v\n", r)
}
演示参数传递,按值传递
func f1(a int) {
a = 200
fmt.Printf("a1: %v\n", a)
}
func main() {
a := 100
f1(a)
fmt.Printf("a: %v\n", a)
}
运行后果
a1: 200
a: 100
从运行后果能够看到,调用函数 f1 后,a 的值并没有被扭转,阐明参数传递是拷贝了一个正本,也就是拷贝了一份新的内容进行运算。
map
、slice
、interface
、channel
这些数据类型自身就是 指针 类型的,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数依然指向底层数据结构,所以批改它们 可能 会影响内部数据结构的值。
package main
import "fmt"
func f1(a []int) {a[0] = 100
}
func main() {a := []int{1, 2}
f1(a)
fmt.Printf("a: %v\n", a)
}
运行后果
a: [1 2]
a: [100 2]
从运行后果发现,调用函数后,slice 内容被扭转了。
变长参数
package main
import "fmt"
func f1(args ...int) {
for _, v := range args {fmt.Printf("v: %v\n", v)
}
}
func f2(name string, age int, args ...int) {fmt.Printf("name: %v\n", name)
fmt.Printf("age: %v\n", age)
for _, v := range args {fmt.Printf("v: %v\n", v)
}
}
func main() {f1(1, 2, 3)
fmt.Println("------------")
f1(1, 2, 3, 4, 5, 6)
fmt.Println("------------")
f2("tom", 20, 1, 2, 3)
}
运行后果
v: 1
v: 2
v: 3
------------
v: 1
v: 2
v: 3
v: 4
v: 5
v: 6
------------
name: tom
age: 20
v: 1
v: 2
v: 3
golang 函数类型与函数变量
能够应用 type
关键字来定义一个函数类型,语法格局如下:
type fun func(int, int) int
下面语句定义了一个 fun
函数类型,它是一种函数类型,这种函数接管两个 int
类型的参数,并且返回一个 int
类型的返回值。
上面咱们定义两个这样构造的两个函数,一个求和,一个比拟大小:
func sum(a int, b int) int {return a + b}
func max(a int, b int) int {
if a > b {return a} else {return b}
}
上面定义一个 fun
函数类型,把 sum
和max
赋值给它
package main
import "fmt"
type fun func(int, int) int
func sum(a int, b int) int {return a + b}
func max(a int, b int) int {
if a > b {return a} else {return b}
}
func main() {
var f fun
f = sum
s := f(1, 2)
fmt.Printf("s: %v\n", s)
f = max
m := f(3, 4)
fmt.Printf("m: %v\n", m)
}
运行后果
s: 3
m: 4
golang 高阶函数
go 语言的函数,能够作为函数的参数,传递给另外一个函数,能够能够作为,另外一个函数的返回值返回。
go 语言函数作为参数
package main
import "fmt"
func sayHello(name string) {fmt.Printf("Hello,%s", name)
}
func f1(name string, f func(string)) {f(name)
}
func main() {f1("tom", sayHello)
}
运行后果
Hello,tom
go 语言函数作为返回值
package main
import "fmt"
func add(x, y int) int {return x + y}
func sub(x, y int) int {return x - y}
func cal(s string) func(int, int) int {
switch s {
case "+":
return add
case "-":
return sub
default:
return nil
}
}
func main() {add := cal("+")
r := add(1, 2)
fmt.Printf("r: %v\n", r)
fmt.Println("-----------")
sub := cal("-")
r = sub(100, 50)
fmt.Printf("r: %v\n", r)
}
运行后果
r: 3
-----------
r: 50
golang 匿名函数
go 语言函数不能嵌套,然而在函数外部能够定义匿名函数,实现一下简略性能调用。
所谓匿名函数就是,没有名称的函数。
语法格局如下:
func (参数列表)(返回值)
当然能够既没有参数,能够没有返回值
匿名函数实例
func main() {max := func (a int, b int) int {
if a > b {return a} else {return b}
}
i := max(1, 2)
fmt.Printf("i: %v\n", i)
}
运行后果
i: 2
本人执行
func main() {
// 本人执行
func(a int, b int) {
max := 0
if a > b {max = a} else {max = b}
fmt.Printf("max: %v\n", max)
}(1, 2)
}
运行后果
max: 2
golang 闭包
闭包能够了解成 定义在一个函数外部的函数。在实质上,闭包是将函数外部和函数内部连接起来的桥梁。或者说是函数和其援用环境的组合体。
闭包指的是一个函数和与其相干的援用环境组合而成的实体。简略来说,闭包 = 函数 + 援用环境
。首先咱们来看一个例子:
package main
import "fmt"
// 返回一个函数
func add() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {var f = add()
fmt.Println(f(10))
fmt.Println(f(20))
fmt.Println(f(30))
fmt.Println("-----------")
f1 := add()
fmt.Println(f1(40))
fmt.Println(f1(50))
}
运行后果
10
30
60
-----------
40
90
变量 f
是一个函数并且它援用了其内部作用域中的 x
变量,此时 f
就是一个闭包。在 f
的生命周期内,变量 x
也始终无效。 闭包进阶示例 1:
package main
import "fmt"
func add(x int) func(int) int {return func(y int) int {
x += y
return x
}
}
func main() {var f = add(10)
fmt.Println(f(10))
fmt.Println(f(20))
fmt.Println(f(30))
fmt.Println("----------")
f1 := add(20)
fmt.Println(f1(40))
fmt.Println(f1(50))
}
运行后果
20
40
70
----------
60
110
闭包进阶示例 2:
func makeSuffixFunc(suffix string) func(string) string {return func(name string) string {if !strings.HasSuffix(name, suffix) {return name + suffix}
return name
}
}
func main() {jpgFunc := makeSuffixFunc(".jpg")
txtFunc := makeSuffixFunc(".txt")
fmt.Println(jpgFunc("test"))
fmt.Println(txtFunc("test"))
}
运行后果
test.jpg
test.txt
闭包进阶示例 3:
func calc(base int) (func(int) int, func(int) int) {add := func(i int) int {
base += i
return base
}
sub := func(i int) int {
base -= i
return base
}
return add, sub
}
func main() {f1, f2 := calc(10)
fmt.Println(f1(1), f2(2))
fmt.Println(f1(3), f2(4))
fmt.Println(f1(5), f2(6))
}
运行后果
11 9
12 8
13 7
闭包其实并不简单,只有牢记 闭包 = 函数 + 援用环境
。
golang 递归
函数外部调用函数本身的函数称为递归函数。
应用递归函数最重要的三点:
- 递归就是本人调用本人。
- 必须先定义函数的退出条件,没有退出条件,递归将成为死循环。
- go 语言递归函数很可能会产生一大堆的 goroutine,也很可能会呈现栈空间内存溢出问题。
go 语言递归实例
阶乘
package main
import "fmt"
func a(n int) int {
// 返回条件
if n == 1 {return 1} else {
// 本人调用本人
return n * a(n-1)
}
}
func main() {
n := 5
r := a(n)
fmt.Printf("r: %v\n", r)
}
运行后果
r: 120
斐波那契数列
它的计算公式为 f(n)=f(n-1)+f(n-2)
且f(2)=f(1)=1
package main
func f(n int) int {
// 退出点判断
if n == 1 || n == 2 {return 1}
// 递归表达式
return f(n-1) + f(n-2)
}
func main() {r := f(5)
fmt.Printf("r: %v\n", r)
}
运行后果
r: 5
golang defer 语句
go 语言中的 defer
语句会将其前面追随的语句进行 提早 解决。在 defer
归属的函数行将返回时,将提早解决的语句按 defer
定义的 逆序 进行执行,也就是说,先被 defer
的语句最初被执行,最初被 defer
的语句,最先被执行。stack
defer 个性
- 关键字
defer
用于注册提早调用。 - 这些调用直到
return
前才被执。因而,能够用来做资源清理。 - 多个
defer
语句,按先进后出的形式执行。 defer
语句中的变量,在defer
申明时就决定了。
defer 用处
- 敞开文件句柄
- 锁资源开释
- 数据库连贯开释
go 语言 defer 语句实例
查看执行程序
func main() {fmt.Println("start")
defer fmt.Println("step1")
defer fmt.Println("step2")
defer fmt.Println("step3")
fmt.Println("end")
}
运行后果
start
end
step3
step2
step1
golang init 函数
golang 有一个非凡的函数 init
函数,先于 main
函数执行,实现包级别的一些 初始化 操作。
init 函数的次要特点
- init 函数先于 main 函数 主动执行,不能被其余函数调用;
- init 函数没有输出参数、返回值;
- 每个包能够有多个 init 函数;
- 包的每个源文件也能够有多个 init 函数,这点比拟非凡;
- 同一个包的 init 执行程序,golang 没有明确定义,编程时要留神程序不要依赖这个执行程序。
- 不同包的 init 函数依照包导入的依赖关系决定执行程序。
golang 初始化程序
初始化程序:变量初始化 ->init()->main()
实例
package main
import "fmt"
var a int = initVar()
func init() {fmt.Println("init2")
}
func init() {fmt.Println("init")
}
func initVar() int {fmt.Println("init var...")
return 100
}
func main() {fmt.Println("main...")
}
运行后果
init var...
init2
init
main...