一、函数基础

  • 函数由函数声明关键字 func、函数名、参数列表、返回列表、函数体组成
  • 函数是一种类型。函数类型变量可以像其他类型变量一样使用,可以作为其他函数的参数或返回值,也可以直接调用执行
  • 函数名首字母大小写决定了其包可见性
  • 参数和返回值需用()包裹,如果返回值是一个非命名的参数,则可省略。函数体使用{}包裹,且{必须位于同行行尾

1. 基本使用

// 1. 可以没有输入参数,也可以没有返回值(默认返回 0)
func A() {
...
}
// 2. 多个相邻的相同类型参数可以使用简写模式
func B(a, b int) int {
return a + b
}
// 3. 支持有名的返回值
func C(a, b int) (sum int) {
// sum 相当于函数体内的局部变量,初始化为零值
sum = a + b
return // 可以不带 sum
}
// 4. 不支持默认值参数
// 5. 不支持函数重载
// 6. 不支持函数嵌套定义,但支持嵌套匿名函数
func D(a, b int) (sum int) {
E := func(x, y int) int {
return x + y
}
return E(a, b)
}
// 7. 支持多值返回(一般将错误类型作为最后一个返回值)
func F(a, b int) (int, int) {
return b, a
}
// 8. 函数实参到形参的传递永远是**值拷贝**
func G(a *int) { // a 是实参指针变量的副本,和实参指向同一个地址
*a += 1
}

2. 不定参数

// 1. 不定参数类型相同
// 2. 不定参数必须是函数的最后一个参数
// 3. 不定参数在函数体内相当于切片
func sum(arr ...int) (sum int) {
for _, v := range arr { // arr 相当于切片,可使用 range访问
sum += v
}
return
}
// 4. 可以将切片传递给不定参数
array := [...]int{1, 2, 3, 4} // 不能将数组传递给不定参数
slice := []int{1, 2, 3, 4}
sum(slice...) // 切片名后要加 ...
// 5. 形参为不定参数的函数和形参为切片的函数类型不同
func suma(arr ...int) (sum int) {
for v := range arr {
sum += v
}
return
}
func sumb(arr []int) (sum int) {
for v := range arr {
sum += v
}
return
}
fmt.Printf("%T\n", suma) // func(...int) int
fmt.Printf("%T", sumb) // func([]int) int

3. 函数类型

函数类型又叫函数签名:函数定义行去掉函数名、参数名和 {

func add(a, b int) int { return a + b }
func sub(x int, y int) (c int) { c = x - y; return }
fmt.Printf("%T", add) // func(int, int) int
fmt.Printf("%T", sub) // func(int, int) int

可以使用 type 定义函数类型。函数类型变量和函数名都可以看做指针变量,该指针指向函数代码的开始位置

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 {
t := f
return t(a, b)
}
fmt.Println(do(add, 1, 2)) // 3
fmt.Println(do(sub, 1, 2)) // -1

4. 匿名函数

// 1. 直接赋值给函数变量
var sum = func(a, b int) int {
return a + b
}
func do(f func(int, int) int, a, b int) int {
return f(a, b)
}
// 2. 作为返回值
func getAdd() func(int, int) int {
return func(a, b int) int {
return a + b
}
}
func main() {
// 3. 直接被调用
defer func() {
if err:= recover(); err != nil {
fmt.Println(err)
}
}()
sum(1, 2)
getAdd()(1 , 2)
// 4. 作为实参
do(func(x, y int) int { return x + y }, 1, 2)
}

二、函数高级

1. defer

可注册多个延迟调用函数,以先进后出的顺序执行。常用于保证资源最终得到回收释放

func main() {
// defer 后跟函数或方法调用,不能是语句
defer func() {
println("first")
}()
defer func() {
println("second")
}()
println("main")
}
// main
// second
// first

defer 函数的实参在注册时传递,后续变更无影响

func f() int {
a := 1
defer func(i int) {
println("defer i =", i)
}(a)
a++
return a
}
print(f())
// defer i = 1
// 2

defer 若位于 return 后,则不会执行

func main() {
println("main")
return
defer func() {
println("first")
}()
}
// main

若主动调用os.Exit(int)退出进程,则不会执行 defer

func main() {
defer func() {
println("first")
}()
println("main")
os.Exit(1)
}
// main

关闭资源例子

func CopyFile(dst, src string) (w int64, err error) {
srcFile, err := os.Open(src)
if err != nil {
return
}
// defer 一般放在错误检查语句后面。若位置不当可能造成 panic
defer srcFile.Close() dstFile, err := os.Create(dst)
if err != nil {
return
}
defer dstFile.Close() w, err = io.Copy(dstFile, srcFile)
return
}

defer 使用注意事项:

  • defer 会延迟资源的释放
  • 尽量不要放在循环语句中
  • defer 相对于普通函数调用需要间接的数据结构支持,有一定性能损耗
  • defer 中最好不要对有名返回值进行操作

2. 闭包

  • 闭包是由函数及其相关引用环境组合成的实体。一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成
  • 闭包对闭包外的环境引入是直接引用:编译器检测到闭包,会将闭包引用的外部变量分配到堆上
  • 闭包是为了减少全局变量,在函数调用的过程中隐式地传递共享变量。但不够清晰,一般不建议用
  • 对象是附有行为的数据,而闭包是附有数据的行为。类在定义时已经显式地集中定义了行为,但闭包中的数据没有显式地集中声明的地方
// fa 返回的是一个闭包:形参a + 匿名函数
func fa(a int) func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa(1) // f 使用的 a 是 0xc0000200f0
g := fa(1) // g 使用的 a 是 0xc0000200f8
// f、g 引用的闭包环境中的 a 是函数调用产生的副本:每次调用都会为局部变量分配内存
println(f(1))
println(f(1)) // 闭包共享外部引用,因此修改的是同一个副本
println(g(1))
println(g(1))
}
// 0xc0000200f0 1
// 2
// 0xc0000200f0 2
// 3
// 0xc0000200f8 1
// 2
// 0xc0000200f8 2
// 3

闭包引用全局变量(不推荐)

var a = 0
// fa 返回的是一个闭包:全局变量a + 匿名函数
func fa() func(i int) int {
return func(i int) int {
println(&a, a)
a = a + i
return a
}
}
func main() {
f := fa()
g := fa()
// f、g 引用的闭包环境中的 a 是同一个
println(f(1))
println(g(1))
println(f(1))
println(g(1))
}
// 0x511020 0
// 1
// 0x511020 1
// 2
// 0x511020 2
// 3
// 0x511020 3
// 4

同一个函数返回的多个闭包共享该函数的局部变量

func fa(a int) (func(int) int, func(int) int) {
println(&a, a)
add := func(i int) int {
a += i
println(&a, a)
return a
}
sub := func(i int) int {
a -= i
println(&a, a)
return a
}
return add, sub
}
func main() {
f, g := fa(0) // f、g 使用的 a 都是 0xc0000200f0
s, k := fa(0) // s、k 使用的 a 都是 0xc0000200f8
println(f(1), g(2))
println(s(1), k(2))
}
// 0xc0000200f0 0
// 0xc0000200f8 0
// 0xc0000200f0 1
// 0xc0000200f0 -1
// 1 -1
// 0xc0000200f8 1
// 0xc0000200f8 -1
// 1 -1

三、错误处理

1. 错误和异常

  • 广义的错误:发生非期望的行为
  • 狭义的错误:发生非期望的己知行为
    • 这里的己知是指错误类型是预料并定义好的
  • 异常:发生非期待的未知行为,又被称为未捕获的错误
    • 这里的未知是指错误的类型不在预先定义的范围内
    • 程序在执行时发生未预先定义的错误,程序编译器和运行时都没有及时将其捕获处理,而是由操作系统进行异常处理。如 C 语言的 Segmentation Fault

Go 不会出现 untrapped error,只需处理 runtime errors 和程序逻辑错误

Go 提供两种错误处理机制

  • 通过 panic 打印程序调用栈,终止程序来处理错误
  • 通过函数返回错误类型的值来处理错误

Go 是静态强类型语言,程序的大部分错误是可以在编译器检测到的,但有些错误行为需要在运行期才能检测出来,此种错误行为将导致程序异常退出。建议:

  • 若程序发生的错误导致程序不能继续执行,此时程序应该主动调用 panic
  • 若程序发生的错误能够容错继续执行,此时应该使用 error 返回值的方式处理,或在非关键分支上使用 recover 捕获 panic

2. panic 和 recover

panic(i interface{})  // 主动抛出错误
recover() interface{} // 捕获抛出的错误
  • 引发panic情况:①主动调用panic;②程序运行时检测抛出运行时错误
  • panic 后,程序会从当前位置返回,逐层向上执行 defer 语句,逐层打印函数调用栈,直到被 recover 捕获或运行到最外层函数退出
  • 参数为空接口类型,可以传递任意类型变量
  • defer 中也可以 panic,能被后续 defer 捕获
  • recover 只有在 defer 函数体内被调用才能捕获 panic,否则返回 nil
// 以下场景捕获失败
defer recover()
defer fmt.Println(recover())
defer func() {
func() { // 两层嵌套
println("defer inner")
recover()
}()
}()
// 以下场景捕获成功
defer func() {
println("defer inner")
recover()
}()
func except() {
recover()
}
func test() {
defer except()
painc("test panic")
}

可以同时有多个 panic(只会出现在 defer 里),但只有最后一次 panic 能被捕获

func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
fmt.Println(recover())
}()
defer func() {
panic("first defer panic")
}()
defer func() {
panic("second defer panic")
}()
panic("main panic")
}
// first defer panic
// <nil>

包中 init 函数引发的 panic 只能在 init 函数中捕获(init 先于 main 执行)

函数不能捕获内部新启动的 goroutine 抛出的 panic

func do() {
// 不能捕获 da 中的 panic
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
go da()
time.Sleep(3 * time.Second)
}
func da() {
panic("panic da")
}

3. error

Go 内置错误接口类型 error。任何类型只要实现Error() string方法,都可以传递 error 接口类型变量???

type error interface {
Error() string
}

使用 error:

  • 在多个返回值的函数中,error 作为函数最后一个返回值
  • 若函数返回 error 类型变量,先处理error != nil的异常场景,再处理其他流程
  • defer 放在 error 判断的后面

四、底层实现

TODO

Go 函数详解的更多相关文章

  1. malloc 与 free函数详解<转载>

    malloc和free函数详解   本文介绍malloc和free函数的内容. 在C中,对内存的管理是相当重要.下面开始介绍这两个函数: 一.malloc()和free()的基本概念以及基本用法: 1 ...

  2. NSSearchPathForDirectoriesInDomains函数详解

    NSSearchPathForDirectoriesInDomains函数详解     #import "NSString+FilePath.h" @implementation ...

  3. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  4. Linux C popen()函数详解

    表头文件 #include<stdio.h> 定义函数 FILE * popen( const char * command,const char * type); 函数说明 popen( ...

  5. kzalloc 函数详解(转载)

    用kzalloc申请内存的时候, 效果等同于先是用 kmalloc() 申请空间 , 然后用 memset() 来初始化 ,所有申请的元素都被初始化为 0. view plain /** * kzal ...

  6. Netsuite Formula > Oracle函数列表速查(PL/SQL单行函数和组函数详解).txt

    PL/SQL单行函数和组函数详解 函数是一种有零个或多个参数并且有一个返回值的程序.在SQL中Oracle内建了一系列函数,这些函数都可被称为SQL或PL/SQL语句,函数主要分为两大类: 单行函数 ...

  7. jQuery.attr() 函数详解

    一,jQuery.attr()  函数详解: http://www.365mini.com/page/jquery-attr.htm 二,jQuery函数attr()和prop()的区别: http: ...

  8. memset函数详解

    语言中memset函数详解(2011-11-16 21:11:02)转载▼标签: 杂谈 分类: 工具相关  功 能: 将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值, 块的大 ...

  9. CreateFile函数详解

    CreateFile函数详解 CreateFile The CreateFile function creates or opens the following objects and returns ...

  10. MYSQL常用内置函数详解说明

    函数中可以将字段名当作变量来用,变量的值就是该列对应的所有值:在整理98在线字典数据时(http://zidian.98zw.com/),有这要一个需求,想从多音字duoyinzi字段值提取第一个拼音 ...

随机推荐

  1. Springboot2.x集成lettuce连接redis集群报超时异常Command timed out after 6 second(s)

    文/朱季谦 背景:最近在对一新开发Springboot系统做压测,发现刚开始压测时,可以正常对redis集群进行数据存取,但是暂停几分钟后,接着继续用jmeter进行压测时,发现redis就开始突然疯 ...

  2. sqli-labs系列——第四关

    less4 第四关的sql语句是这样的: select * from user where id=("$id"); ?id=1")–+回显正常 order by 4报错, ...

  3. XML数据持久化学习笔记

    一.XML基础语法 1.XML结构:XML是一种树结构的文本 2.XML注释:格式:<!--在其中书写注释-->,在注释中可以单行注释也可以多行注释 3.固定内容:<?xml ver ...

  4. Oracle 19c Data Guard DML Redirection ADG备库上执行DML重定向(未来更好的进行读写分离)

    资料来自官方网站: https://docs.oracle.com/en/database/oracle/oracle-database/19/sbydb/managing-oracle-data-g ...

  5. 《环形队列》游戏高《TPS》模式下减少cpu线程切换

    序言 什么高TPS?QPS,其实很多人都知道,还有人说大数据,大流量这些关键词夜以继日的出现在我们眼前: 针对高TPS,QPS这些词汇还有一个次可能比较陌生那就是CCU,tps,qps接受满天飞,CC ...

  6. 热更新解决方案--tolua学习笔记

    一.tolua使用准备工作:从GitHub上下载tolua(说明:这篇笔记使用的Unity版本是2019.4.18f1c1,使用的tolua是2021年4月9日从GitHub上Clone的tolua工 ...

  7. 记一次metasploitable2内网渗透之8180端口tomcat

    扫描网段存活主机,确定内网metasploitable主机位置 nmap -T4 -sP 192.168.1.0/24 对目标主机进行扫描端口开放和系统信息 nmap -T4 -sV -Pn 192. ...

  8. Java基础 Java-IO流 深入浅出

    建议阅读 重要性由高到低 Java基础-3 吃透Java IO:字节流.字符流.缓冲流 廖雪峰Java IO Java-IO流 JAVA设计模式初探之装饰者模式 为什么我觉得 Java 的 IO 很复 ...

  9. Mybatis的多表操作

    1.Mybatis多表查询 1.1 一对一查询 1.1.1 一对一查询的模型MapperScannerConfigurer 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户 一对一 ...

  10. Updating a Dictionary UVA - 12504

    In this problem, a dictionary is collection of key-value pairs, where keys are lower-case letters, a ...