函数定义
func funcName(形参列表) (返回值列表) {
// 函数体
return
}
函数名称首字母大写时,该函数对其它包可见;小写时,只有包内可见。
- 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
- 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用
, 分隔。
- 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用
() 包裹,并用, 分隔。
- 函数体:实现指定功能的代码块
函数特点
-
函数可以没有输入参数,也可以没有返回值(默认返回0)
func A() {
// do something
}
func A() int{
// do something
return 1
}
-
多个相邻的同类型参数可以简写
func add(a, b int) int {
return a + b
}
-
支持命名返回值变量,参数名就是函数体内最外层的局部变量。
-
不支持默认值参数
-
不支持函数重载
-
不支持命名函数嵌套定义,支持匿名函数嵌套
func add(a, b int) (sum int){
anonymous := func(x,y int) int{
return x + y
}
return anonymous(a, b)
}
函数分类
函数类型
一个函数的类型就是函数定义首行去掉函数名,参数名和{ 。可以使用fmt.Printf("%T")打印的类型
package main
import "fmt"
func add(a,b int) int{
return a+b
}
func main() {
fmt.Printf("%T\n", add) // func(int,int) int
}
判断2函数类型相同的条件
-
相同的形参列表和返回值列表。即列表元素的次序,个数和数量都相同,形参名可以不同
func add(a,b int) int {
return a + b
}
func sub(x int, y int) (c int) {
c = x - y
return c
}
// 上面2个函数的函数类型相同
使用 type 关键字定义函数类型
函数类型变量可以作为函数的参数或返回值
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
type Op func(int,int) int // 定义一个函数类型,输入的是两个 int 类型,返回值是int
func do(f Op, a, b int) int { // 定义一个函数,第一个参数是函数类型 Op
return f(a,b) // 函数类型变量可以直接用来进行函数调用返回
}
func main() {
a := do(add, 1, 2)
fmt.Printf("a=%v", a)
s := do(sub, 1, 2)
fmt.Printf("b=%v", b)
}
- 函数类型变量是一种引用类型, 未初始化的函数类型的变量默认值为
nil
- 命名函数的函数名可以使用函数名直接调用,也可以赋值给函数类型变量
匿名函数
使用场景
匿名函数需要保存到某个变量或者作为立即执行函数。
使用示例
-
匿名函数被直接赋值给函数变量
- 匿名函数没有函数名,无法通过函数名调用,因此,匿名函数需要保存到某个变量或者作为立即执行函数
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 wrap(op string) func(int, int) int {
switch op {
case "add":
return func(a, b int) int {
return a + b
}
case "sub":
return func(a, b int) int {
return a - b
}
default:
return nil
}
}
-
匿名函数作为实参
package main
import "fmt"
// 匿名函数被直接赋值函数变量
var sum = func(a, b int) int {
return a + b
}
// 匿名函数作为形参
func doinput(f func(int, int) int, a, b int) int {
return f(a, b)
}
// 匿名函数作为返回值
func wrap(op string) func(int, int) int {
switch op {
case "add":
return func(a, b int) int {
return a + b
}
case "sub":
return func(a, b int) int {
return a - b
}
default:
return nil
}
}
func main() {
// 直接调用匿名函数
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
sum(1,2)
// 匿名函数作为实参
doinput(func(x,y int) int {
return x + y
}, 1, 2) // return 1 + 2
opFunc := wrap("add")
re := opFunc(2,3)
fmt.Printf("%d\n", re) // 5
}
函数参数
传递方式
-
值类型参数默认就是值传递
-
引用类型参数默认就是引用传递
不管是值传递还是引用传递,传递给函数的都是变量的副本,值传递的是值的拷贝,引用传递的时地址的拷贝。通常地址拷贝方式效率高(数据量小),而值拷贝由变量的数据量大小决定其效率。
实参到形参的传递
函数实参到形参的传递永远是值拷贝
package main
import "fmt"
func chvalue(a int) int{
a = a + 1
return a
}
func chpointer(a *int) {
*a = *a + 1
return
}
func main() {
a := 10
chvalue(a) // 实参传递给形参是值拷贝
fmt.Println(a) // 10
chpointer(&a) // 实参传递给形参仍然是值拷贝,只不过复制的是 a 的地址值
fmt.Println(a) // 11
}
函数作为参数
package main
import "fmt"
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}
函数作为返回值
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("无法识别的操作符")
return nil, err
}
}
error作为返回值类型时,必须作为最后一个
不定参数
函数支持不定数目的形式参数,不定参数声明使用 param ...type 的语法格式
package main
import "fmt"
func add(args ...int) (sum int) {
for _, v := range args {
sum += v
}
return sum
}
func main() {
fmt.Printf("sum=%v", add(10, 1, 2))
}
-
不定参数类型必须相同
-
不定参数必须是函数最后一个参数
-
不定参数名在函数体内是相当于切片
-
切片作为参数传递给不定参数,切片名后要加上...
package main
import "fmt"
func add(args ...int) (sum int) {
for _, v := range args {
sum += v
}
return sum
}
func main() {
s1 := []int{1,2,3,4,5}
add(s1...)
}
变量作用域
全局变量
定义在函数外部的变量,它在整个运行周期内都有效。
package main
import "fmt"
// 全局变量
var num int = 1
func f1() {
fmt.Printf("num=%d\n", num) // 函数中可以访问全局变量num
}
func main() {
f1() //num=1
}
局部变量
函数内定义的变量无法在该函数外使用
如果局部变量和全局变量重名,优先访问局部变量。
package main
import "fmt"
//定义全局变量num
var num int64 = 10
func testNum() {
num := 100
fmt.Printf("num=%d\n", num) // 函数中优先使用局部变量
}
func main() {
testNum() // num=100
}
语句块内定义的变量
if条件判断、for循环、switch语句上定义的变量
for i := 0; i < 10; i++ {
fmt.Println(i) //变量i只在当前for语句块中生效
}
闭包
闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境 。
示例
package main
import "fmt"
func addUpper() func(int) int{
var n int = 10
return func(x int) int{
n = n + x
return n
}
}
func main() {
f := addUpper()
fmt.Println(f(1)) // 11 ,n 初始化为10
fmt.Println(f(2)) // 13 ,此时n为11
fmt.Println(f(3)) // 16 ,此时n为13
}
实践
编程题目要求
- 编写一个函数 makeSuffix(suffix string) ,可以接收一个文件后缀名(如 .jpg),并返回一个闭包
- 调用闭包,可以传入一个文件名,若该文件名没有指定的后缀(如 .jpg),则返回 "文件名.jpg",如果已经有后缀.jpg,则返回原文件名
- 使用闭包方式完成
- 提示:
strings.HasSuffix 函数用于判断某个字符串是否有指定后缀
代码
package main
import (
"fmt"
"strings"
)
func makeSuffix(suffix string) func(string) string {
return func(name string) string {
// 判断后缀
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main() {
f := makeSuffix(".jpg")
fmt.Println("文件名称: ", f("biao")) //
fmt.Println("文件名称: ", f("write.jpg")) //
}
-
返回的匿名函数 和 makeSuffix(suffix string) 的变量suffix 构成一个闭包。返回的函数引用外部变量 suffix
-
该案例中,闭包引用的变量可以反复使用
-
使用传统函数方法实现,每次都需要传入后缀名
package main
import (
"fmt"
"strings"
)
func makeSuffix2(name, suffix string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
func main() {
fmt.Println("文件名称: ", makeSuffix2("biao", ".jpg")) //
fmt.Println("文件名称: ", makeSuffix2("write.jpg", ".jpg")) //
}
defer (延迟调用)
defer 关键字用于注册延迟调用,它们以FILO(先进后出)的顺序在函数返回前被执行
defer 常用于确保资源(如:文件句柄、数据库连接、锁 ...)最终能被释放和回收。
-
defer 后面必须是函数或方法的调用,不能是语句
-
defer 函数的实参在注册时通过值拷贝传递进去
package main
import "fmt"
func f() int {
a := 0
// go 执行到 defer 时,不会立即执行defer后的语句,而是将defer后面的语句压入到一个独立的栈,然后执行函数的下一个语句
defer func(i int) {
fmt.Println("defer i= ", i) // defer 将语句入栈的同时,也会将相关的值拷贝入栈
}(a)
a++
// 当函数执行完毕时,再从栈顶中依次语句执行
return a
}
func main() {
f() // defer i= 0
}
-
若defer 位于 return 之后,则不会被执行
-
主动调用 os.Exit(int) 退出进程时,defer 无论如何都不会被执行
-
defer 注册要延迟执行的函数时,该函数的所有参数都需要确定其值
package main
import "fmt"
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
}
/* 函数调用执行顺序
1. calc("A", x, y) // A 1 2 3
2. calc("B", x, y) // B 10 2 12
3. calc("BB", x, 12) // BB 10 12 22
4. calc("AA", x, 3) // AA 1 3 4
*/
执行结果:
defer执行时机
函数中return 语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer 语句执行的时机就在返回值赋值操作后,RET指令执行前。
注意事项
- defer 语句的位置不当,有可能导致 panic ,一般 defer 语句放在错误检查语句之后。
- defer 也有明显的副作用:defer 会推迟资源的释放,defer 尽量不要放到循环语句里面,将大函数内部的 defer 语句单独拆分成一个小函数是一种很好的实践方式。
- defer 相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗 。
- defer 中最好不要对有名返回值参数进行操作 ,否则会引发匪夷所思的结果
|
请发表评论