函数
1.函数定义
Go语言中定义函数使用 func 关键字,具体格式如下:
func (接收者)函数名(参数)(返回值){
函数体
}
其中:
- 接收者:只有在定义方法时,才需要设置接收者。(可选项)
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用
,
分隔。(可选项) - 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用
()
包裹,并用,
分隔。(可选项) - 函数体:实现指定功能的代码块。
有返回值的函数,必须有明确的终止语句,否则会引发编译错误。
当两个或多个连续的参数是同一类型,则除了最后一个类型之外,其他都可以省略。例如:func add(x,y int) int
2.参数
值传递
Go 语言中,函数的参数传递只有值传递,没有引用传递。
- 值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引用传递:指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
为什么没有引用传递?
- Go 语言中不存在引用变量,自然没有引用传递。
什么是引用变量?
- 在 C++ 中,引用变量相当于原变量的一个别名
#include <stdio.h>
int main() {
int a = 10;
int &b = a;
int &c = b;
printf("&a = %p\n &b = %p\n &c = %p\n", &a, &b, &c);
// &a = 0x7ffe114f0b14
// &b = 0x7ffe114f0b14
// &c = 0x7ffe114f0b14
return 0;
}
- 从上面的 c++ 程序可以看到,a、b、c 这三个变量的地址相同,共享一个内存地址(值自然也是相同的)。而在 Go 中,不同变量可以有相同的值,但地址是不会相同的,因此Go 语言中不存在引用变量。
没有引用传递,那我们想在函数内修改函数外的变量,该怎么做?
- 传递指针,通过指针修改实际参数。
引用和指针的区别?
- 指针变量是指向目的内存的变量,而引用变量是目的内存上的变量的一个别名
可变参数
可变参数是指函数的参数数量不固定。Go语言中的可变参数通过在参数名后加 ...type 来标识。(type是可变参数的类型)
注意:
- 可变参数通常要作为函数的最后一个参数。
- 可变参数底层是由切片实现的
举个例子:
func intAdd(args ...int) int {
sum := 0
for _, arg := range args {
sum = sum + arg
}
return sum
}
若想传递任意类型的可变参数,可以将可变参数的类型设置为 interface{}
3.函数返回值
Go语言中通过 return 关键字向外输出返回值。
- 支持多返回值,函数如果有多个返回值时必须用 ( ) 将所有返回值包裹起来。
- 支持返回值命名
返回值命名
注意:
- 当命名时,它们在函数开始时被初始化为其类型的零值
- 命名后,如果 return 没有指定变量,则会默认返回声明的返回变量
- 返回值命名,不能只命名一个,必须为该函数的所有返回值命名(可以用占位符 _ 命名,相当于没有命名)
举个例子:
func intAdd(args ...int) (sum int, str string, _ bool, _ error) {
sum = 0
str = "good"
for _, arg := range args {
sum = sum + arg
}
return
}
func main() {
sum, str, right, err := intAdd(100, 150, 200, 1, 20)
fmt.Println(sum, str, right, err) //471 good false <nil>
}
命名后,即便 return 其他变量(x),也会将 其他变量的值(x) 复制给 返回值命名的返回变量(y),再返回 返回值命名的返回变量(y)
package main
import "fmt"
func re_test() (y int) {
x := 5
defer func() { //使用 defer 在调试时可以看的更明显
y++ // y = y + 1 = 6
}()
return x // y = x = 5
}
func main(){
fmt.Println(re_test()) //6
}
具体原理 并不清楚,姑且先这么认为(也许有误),待之后有机会再详细分析。 在下一篇的函数进阶中会详细阐述。
4.函数变量
函数类型
在 Go 中,函数类型也是一种数据类型,我们可以使用 type 关键字来定义一个函数类型,具体格式如下:
type int_calculation func(int, int) int
上面语句定义了一个 int_calculation 类型,它是一种函数类型,这种函数接收两个 int 类型的参数并且返回一个 int 类型的返回值,简单来说,凡是满足这个条件的函数都是 int_calculation 类型的函数。
举个例子:
func add(x, y int) int {
return x + y
}
func sub(x, y int) int {
return x - y
}
上面两个函数都是 int_calculation 类型
函数类型变量
定义了一个函数类型后,我们可以声明该函数类型的变量,并赋值。
func main() {
var c int_calculation // 声明一个int_calculation类型的变量c
c = add // 把add赋值给c
fmt.Printf("type of c:%T\n", c) // type of c:main.int_calculation
fmt.Println(c(1, 2)) // 像调用add一样调用c
f := add // 将函数add赋值给变量f
fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
fmt.Println(f(10, 20)) // 像调用add一样调用f
var a struct {
fn int_calculation //结构体内封装函数类型的变量
}
a.fn = add //为函数变量赋值
fmt.Println(a.fn(1, 2))
}
5.匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式。其定义格式如下:
匿名函数因为没有函数名,所以需要赋值给某个变量或者作为立即执行函数。
func main() {
// 将匿名函数保存到变量
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 通过变量调用匿名函数
//自执行函数:匿名函数定义完加()直接执行
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
匿名函数能够直接作为函数类型,来声明变量
func add(x, y int) int {
return x + y
}
func main() {
var a func(int, int) int= add
fmt.Println(a(1, 2))
d := struct {
fn func(int, int) int
}{
fn: func(x int, y int) int { return x + y },
//fn: add 也行
}
fmt.Println(d.fn(1, 2))
}
6.高阶函数
函数作为参数
函数既然能作为变量,自然也能够作为参数
func calc1(x, y int, op func(int, int) int) int { //用匿名函数作为参数类型
return op(x, y)
}
func calc2(x, y int, op int_calculation) int { //用定义的函数类型作为参数类型
return op(x, y)
}
func main() {
ret2 := calc1(10, 20, add)
fmt.Println(ret2) //30
}
函数作为返回值
func operator(s string) (int_calculation, error) { //此处的 int_calculation 可以用 func(int, int) int 代替
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的运算符")
return nil, err
}
}
7.闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,当匿名函数引用了外部作用域的变量(自由变量)时,就成了闭包函数。有了闭包后函数就成为了一个变量的值,只要变量没被释放,函数就会一直处于存活并独享的状态。
举个例子:
func adder2(x int) func(int) int {
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder2(10)
fmt.Println(f(10)) //20
fmt.Println(f(20)) //40
fmt.Println(f(30)) //70
f1 := adder2(20)
fmt.Println(f1(40)) //60
fmt.Println(f1(50)) //110
}
这里只是简单了解闭包的概念,更具体的内容留到下一篇(函数进阶),再进一步了解。
8.内置函数
Go 语言提供了15个内置函数
内置函数 | 介绍 |
---|---|
close | 主要用来关闭channel |
len | 用来求长度,比如string、array、slice、map、channel |
cap | 返回值的容量,值的类型不同,值的容量含义也不同 |
new | 用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针 |
make | 用来分配内存,主要用来分配引用类型,比如chan、map、slice |
append | 用来追加元素到数组、slice中 |
panic和recover | 用来做错误处理 |
copy | 可以将源切片中的元素拷贝到目标切片 |
delete | 通过指定键 m[key] 删除 map 中的元素,如果 map 是 nil 或没有元素,delete 不做任何操作 |
print和printIn | 输入到标准错误流中并打印,官方推荐使用 fmt 包 |
complex | 将两个浮点型的值构造为一个复合类型的值,需要注意的是,实部和虚部必须是相同类型,即都是 float32 或 float64 |
real | 返回复合类型的值的实部,返回值是对应的浮点数类型 |
imag | 返回复合类型的值的虚部,返回值是对应的浮点数类型 |
10.defer
defer特性
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执行。因此可以用来做资源清理。
- 多个defer语句,按先进后出的方式执行。
- defer语句中的变量,在defer声明时就决定了,只是延后执行而已。
defer用途
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
defer执行时机
Go 语言的函数中 return 语句在底层并不是原子操作,它分为给 返回值赋值 和 RET指令 两步。而 defer 语句执行的时机就在返回值赋值操作后,RET指令执行前。具体如下图所示:
defer例子
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4
//前两个是因为:defer 函数在 运行到时 就确定了参数的值,于是就直接执行了参数里的函数
//后两个是因为:defer 延迟执行,在 return 后按 后进先出 的顺序执行
输出结果
11.函数的例子
本篇的最后以一个例子结尾,依此深化对函数的理解:
package main
import "fmt"
func f1() int {
x := 5
defer func() {
x++
}()
return x
}
func f2() (x int) {
defer func() {
x++
}()
return 5
}
func f3() (y int) {
x := 5
defer func() {
x++
}()
return x
}
func f4() (x int) {
defer func(x int) {
x++
}(x)
return 5
}
func main() {
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
输出结果
func f1() int { //用户没有为返回值命名,但内部还是有对返回值的命名,这里是 ~r0
x := 5 //① x =5
defer func() {
x++ //③ x = x + 1 = 6
}()
return x //② ~r0 = x = 5
}// 返回 ~r0 = 5
f1()的执行过程
func f2() (x int) { //返回值命名为 x,最后返回变量 x
defer func() {
x++ //② x = x + 1 = 6
}()
return 5 //① x = 5
}// 返回 x = 6
f2()的执行过程
func f3() (y int) { //返回变量 y
x := 5 //① x = 5
defer func() {
x++ //③ x = x + 1 = 6
}()
return x //② y = x = 5
}//返回 y = 5
f3()的执行过程
func f4() (x int) { //返回变量 x
defer func(x int) { //① 运行到 defer,发现有传参,先传参
x++ //④ x = x + 1 = 1,这里的 x 是局部变量,不会改变外面的返回变量 x
}(x) //② 传参,x = 0
return 5 //③ x = 5
}// 返回 x = 5
f4()的执行过程