Go函数

由若干语句组成的语句块、函数名称、参数列表、返回值构成,它是组织代码的最小单元

完成一定的功能

函数的作用

结构化编程对代码的最基本的封装,一般按照功能组织一段代码

封装的目的为了复用,减少冗余代码

代码更加简洁美观、可读易懂

函数的分类

内建函数,如make、new、panic等

库函数,如math.Ceil()等

自定义函数,使用func关键字定义

函数定义

func 函数名(参数列表) [(返回值列表)]{
    函数体(代码块)
   [return 返回值]
}
// 这里[]表示其中的内容可有可无

函数名就是标识符,命名要求一样

定义中的参数列表称为形式参数,只是一种符号表达(标识符),简称形参

返回值列表可有可无,需要return语句配合,表示一个功能函数执行完返回的结果

函数名(参数列表) [(返回值列表)] 这部分称为函数签名

Go语言中形参也被称为入参,返回值也被称为出参

函数调用

函数定义,只是声明了一个函数,它不能被执行,需要调用执行

调用的方式,就是函数名后加上小括号,如有必要在括号内填写上参数

调用时写的参数是实际参数,是实实在在传入的值,简称实参,这个过程称为传实参,简称传参

如果定义了返回值列表,就需要配合使用return来返回这些值

// 函数定义
// x、y是形式参数,result是返回值
func add(a, b int) int {
 result := a + b // 函数体
 return result   // 返回值
}
func mod(a,b int) int {
 result := a / b
 return result
}
func main() {
 a := 4
 b := 5 
 out := add(a, b)  // 函数调用,可能有返回值,使用变量接收这个返回值
 fmt.Println(out)  // 对于Println函数来说,这也是调用,传入了实参out
 out = add(a, b) 
 fmt.Println(out)  
}

上面代码中,定义一个函数add,函数名是add,能接受2个整型参数,该函数计算的结果,通过return语句返回"返回值"实现;调用时,通过函数名add后加2个参数,返回值可使用变量接收; 函数名也是标识符,返回值也是值, 一般习惯上函数定义需要在调用之前,也就是说调用时,函数已经被定义过了。

函数调用原理

函数调用相当于运行一次函数定义好的代码,函数本来就是为了复用,试想你可以用加法函数,我也可 以用加法函数,你加你的,我加我的,应该互不干扰的使用函数。为了实现这个目标,函数调用的一般 实现,都是把函数压栈(LIFO),每一个函数调用都会在栈中分配专用的栈帧,局部变量、参数值、返回地值等数据都保存在这里。

如上面的代码,首先调用main函数,main压栈,接着调用add(4, 5)时,add函数压栈,压在main的栈帧 之上,add调用return,将add返回值保存在main栈帧的本地变量out上,add栈帧消亡,回到main栈帧。

 

 函数类型

package main
import "fmt"
func fn1()             {}
func fn2(i int) int     { return 100 }
func fn3(j int) (r int) { return 200 }
func main() {
 fmt.Printf("%T\n", fn1)
 fmt.Printf("%T\n", fn2)
 fmt.Printf("%T\n", fn3)
}
输出如下
func()
func(int) int
func(int) int

// 同一种签名的函数是同一种类型

返回值

返回值变量是局部变量

1、无返回值函数 在Go语言中仅仅一个return并不一定表示无返回值,只能说在一个无返回值的函数中,return表示无返 回值函数返回。

// 无返回值函数,可以不使用return,或在必要时使用return
func fn1() {
 fmt.Println("无返回值函数")
 return // return可有可无,如有需要,在必要的时候使用return来返回
}
t := fn1()         // 错误,无返回值函数无返回值可用
fmt.Println(fn1()) // 错误,无返回值函数无返回值可打印

2、返回一个值

Go语言中返回值不允许赋值给一个常量。

// 返回一个值,没有变量名只有类型。匿名返回值
func fn2() int {
 a := 100
 return a + 1 // return后面只要类型匹配就行
}
fmt.Println(fn2()) // 返回101
t := fn2()         // 返回101

// 返回一个值,有变量名和类型。命名返回值
func fn3() (r int) {
 r = 200
 return r - 1 // 类型匹配
}
fmt.Println(fn3())

//上面的函数还可以写成下面的形式
func fn3() (r int) {
 r = 200
 return // 如果返回的标识符就是返回值列表中的标识符,可以省略
}
fmt.Println(fn3())

3、返回多值

Go语言是运行函数返回多个值

// 返回多个值
func fn4() (int, bool) {
    a, b := 100, true
 return a, b
}
fmt.Println(fn4())
x, y := fn4() // 需要两个变量接收返回值

// 返回多个值
func fn4() (i int, b bool) {
 i, b = 100, true
 return // 如果和返回值列表定义的标识符名称和顺序一样,可省略
}
fmt.Println(fn4())
x, y := fn4() // 需要两个变量接收返回值

可以返回0个或多个值

可以在函数定义中写好返回值参数列表

可以没有标识符,只写类型。但是有时候不便于代码阅读,不知道返回参数的含义

可以和形参一样,写标识符和类型来命名返回值变量,相邻类型相同可以合并写

如果返回值参数列表中只有一个返回参数值类型,小括号可以省略

以上2种方式不能混用,也就是返回值参数要么都命名,要么都不要命名

return之后的语句不会执行,函数将结束执行;如果函数无返回值,函数体内根据实际情况使用return

return后如果写值,必须写和返回值参数类型和个数一致的数据

return后什么都不写那么就使用返回值参数列表中的返回参数的值

形式参数

 可以无形参,也可以多个形参

不支持形式参数的默认值

形参是局部变量

func fn1()                   {} // 无形参
func fn2(int)               {} // 有一个int形参,但是没法用它,不推荐
func fn3(x int)             {} // 单参函数
func fn4(x int, y int)       {} // 多参函数
func fn5(x, y int, z string) {} // 相邻形参类型相同,可以写到一起
fn1()
fn2(5)
fn3(10)
fn4(4, 5)
fn5(7, 8, "ok")

 可变参数

可变参数variadic。其他语言也有类似的被称为剩余参数,但Go语言有所不同。

可变参数收集实参到一个切片中

如果有可变参数,那它必须位于参数列表中最后。

func fn6(nums ...int) { // 可变形参
 fmt.Printf("%T %[1]v, %d, %d\n", nums, len(nums), cap(nums))
}
fn6(1)       // []int, [1]
fn6(3, 5)    // []int, [3 5]
fn6(7, 8, 9) // []int, [7 8 9]
func fn7(x, y int, nums ...int) {
 fmt.Printf("%d %d; %T %[3]v, %d, %d\n", x, y, nums, len(nums), cap(nums))
}
fn7(1, 2)       // 1 2; []int [], 0, 0
fn7(1, 2, 3)    // 1 2; []int [3], 1, 1
fn7(1, 2, 3, 4) // 1 2; []int [3 4], 2, 2

切片传递

可变参数限制较多,可以直接提供对应实参,封装成一个新的切片

可以使用使用切片传递的方式 切片... ,但是这种方式只能单独为可变形参提供实参,因为这是实参切片的header的复制

func fn6(nums ...int) { // 可变形参
 fmt.Printf("%T %[1]v, %d, %d\n", nums, len(nums), cap(nums))
}
var p = []int{1, 3, 5}
fmt.Printf("%p, %p, %v\n", &p, &p[0], p)
fn6(p...)

// 这种方式并不是把p这个切片分解了,然后传递给fn6函数,再封装成一个新的切片nums。而是相当于切片header的复制。
func fn7(x, y int, nums ...int) {
 fmt.Printf("%d %d; %T %[3]v, %d, %d\n", x, y, nums, len(nums), cap(nums))
}
p := []int{4, 5, 6}
fn7(p...) // 这在Go中不行,报奇怪的错,原因还是不能用在非可变参数上,就用4、5用在x、y上了
// 这个例子,本以为p被分解,4和5分别对应x和y,6被可变参数nums收集,但是这在Go语言中是错误的

 

posted on 2023-06-19 15:28  自然洒脱  阅读(117)  评论(0编辑  收藏  举报