当前位置: 首页 > news >正文

【Go语言圣经】第五节:函数

第五章:函数

5.1 函数声明

和其它语言类似,Golang 的函数声明包括函数名、形参列表、返回值列表(可省略)以及函数体:

func name(parameter-list) (result-list) {/* ... Body ... */
}

需要注意的是,函数的返回值列表可省略,如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。

一个函数声明的例子如下:

func hypot(x, y float64) float64 {return math.Sqrt(x * x + y * y)
}

如果一组形参或返回值具有相同的类型,我们就不必为每个形参都写出参数类型:

func f(i, j, k int, s, t string)	{ /*... ... ...*/ }
// 等价于
func f(i int, j int, k int, s string, t string	{ /*... ... ...*/ }

函数的类型被称为函数的签名。如果两个函数的形参列表和返回值列表中的变量类型一一对应,那么这两个函数被认为具有相同的类型或签名。形参和返回值的变量名不影响函数签名,也不影响它们是否可以以省略参数类型的形式表示。

每次函数调用必须按照声明顺序为所有参数提供实参。在函数调用时,Golang 没有默认参数值,也没有办法可以通过参数名指定形参,因此形参和返回值的变量名对函数调用无意义。

实参通过值的方式传递,因此函数的形参是实参的拷贝。对形参的修改不会影响实参。但如果实参包括引用类型,比如指针、slice、map、func、channel 等,实参可能由于函数的间接引用被修改。

5.2 递归

大部分语言使用固定大小的函数调用栈,固定大小的栈会限制递归的深度,当用递归处理大量数据时,需要避免栈溢出;此外,还会导致安全性问题。与此相反,Golang 使用可变栈,栈的大小按需增加(初始时很小),这使得递归不必考虑溢出和安全性问题。

5.3 多返回值

在 Golang 当中,一个函数可以返回多个值。常见的有许多标准库的函数返回两个值,一个值是期望得到的值,另一个是函数出错的错误信息。

调用多返回值函数时,返回给调用者的是一组值,调用者必须显式地将这些值分配给变量:

links, err := findLinks(url)
// OR
links, _ := findLinks(url)

如果一个函数所有的返回值都有显式的变量名,那么该函数的 return 语句可以省略操作数,称为 bare return:

// CountWordsAndImages does an HTTP GET request for the HTML
// document url and returns the number of words and images in it.
func CountWordsAndImages(url string) (words, images int, err error) {resp, err := http.Get(url)if err != nil {return}doc, err := html.Parse(resp.Body)resp.Body.Close()if err != nil {err = fmt.Errorf("parsing HTML: %s", err)return}words, images = countWordsAndImages(doc)return	// 返回 words, images, err
}
func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }

bare return 使得代码难以理解,因此不宜过度使用。

5.4 错误

在 Golang 当中,函数调用发生错误时,错误的信息通常通过 error 类型以函数返回值的形式反馈给函数调用者。

内置的 error 是接口类型,但由于我们还没复习到接口,目前只需要知道 error 类型可能是 nil 或者 non-nil。nil 意味着函数运行成功,non-nil 表示失败。

可以调用 error 的 Error 函数或输出函数获得字符串类型的错误信息。

通常在函数返回 non-nil 的 error 时,其它的返回值可能是未定义的(undefined),这些未定义的返回值应该忽略。

Go 使用控制流机制(如 if 和 return)处理错误,这使得编码人员能更多地关注错误处理。

5.4.1 错误处理策略

最常用的方式是传播错误:

resp, err := http.Get(url)
if err != nil {return nil, err
}

可以使用 Errorf 来格式化错误信息:

doc, err := html.Parse(resp.Body)
resp.Body.Close()
if err != nil {return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
}

第二种错误处理的策略是,如果错误是偶然发生的,或由不可预知的问题导致的,一个明智的选择是重新尝试失败的操作:

func WaitForServer(url string) error {const timeout = 1 * time.Minutedeadline := time.Now().Add(timeout)for tries := 0; time.Now().Before(deadline); tries++ {_, err := http.Head(url)if err == nil {return nil	// success}log.Printf("server not responding (%s); retrying...", err)time.Sleep(time.Second << unit(tries))	// exponential back-off}return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}

如果错误发生,程序无法继续运行,我们可以采取第三种策略:输出错误信息并结束程序。需要注意的是,这种策略只能在 main 中执行:

if err := WaitForServer(url); err != nil {fmt.Fprintf(os.Stderr, "Site is down: %v\n", err)os.Exit(1)
}

第四种策略:有时我们只需要输出错误信息就足够了,而不需要中断程序运行。

if err := Ping(); err != nil {log.Printf("Ping Failed: %v; networking disabled", nil)
}

最后一种策略:直接忽略错误。

5.4.2 文件结尾错误(EOF)

Golang 的 io 包保证任何由文件结束引起的读取失败都会返回同一个错误——io.EOF

package io
import "errors"
var EOF = errors.New("EOF")in := bufio.NewReader(os.Stdin)
for {r, _, err := in.ReadRune()if err == io.EOF {break}if err != nil {return fmt.Errorf("read failed: %v", err)}
}

5.5 函数值

在 Golang 当中,函数被视为一等公民(first-class values):函数像其他值一样,拥有类型,可以被赋值给其它变量,传递给函数,从函数返回。对函数值的调用类似函数调用:

func square(n int) int { return n * n }
func negative(n int) int { return -n }
func product(m, n int) int { return m * n }f := square			// 指定了函数的类型为 func(int)
fmt.Println(f(3))f - negative
fmt.Println(f(3))f = product			// 错误❌: 不能将 func(int, int) 赋值给 func(int) 类型

函数类型的零值为 nil,调用值为 nil 的函数值会引起 panic 错误。函数值可以和 nil 进行比较。

函数值使得我们不仅可以通过数据来参数化函数,亦可以通过行为。下例展示了使用 strings.Map 调用 add1 函数,并将每个 add1 函数的返回值组成一个新的字符串返回给调用者:

func add1(r rune) rune { return r + 1 }fmt.Println(strings.Map(add1, "HAL-9000")) // "IBM.:111"
fmt.Println(strings.Map(add1, "VMS"))      // "WNT"
fmt.Println(strings.Map(add1, "Admix"))    // "Benjy"

5.6 匿名函数

拥有函数名的函数只能在包级语法块中被使用,通过函数字面量(function literal),我们可以绕过这一限制,在任何表达式中表示一个函数值。

函数字面量的语法和函数声明相似,区别在于 func 关键字后面没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。

下例改写了之前例子中使用 strings.Map 调用 Add1 的例子:

strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")

通过上述方式定义的函数可以访问完整的词法环境(lexical environment),这意味着在函数中定义的内部函数可以引用该函数的变量:

// 也被称为函数闭包
func squares() func() int {	// squares 的返回值是匿名函数, 该匿名函数的返回值是 intvar x int				// 在闭包中定义 xreturn func() int {x ++return x * x}
}// 调用函数闭包的结果:
func main() {f := squares()fmt.Println(f()) // "1"fmt.Println(f()) // "4"fmt.Println(f()) // "9"fmt.Println(f()) // "16"
}

squares 的例子证明,Golang 当中的函数不仅仅是一串代码,它们还记录了函数内部的状态。通过这个例子,我们也看到变量的生命周期不由它的作用域决定,返回 squares 后,变量 x 仍然隐式存在于 f 中。

5.6.1 警告:捕获迭代变量

本节将介绍 Golang 词法作用域的一个陷阱。

考虑下述问题:你被要求首先创建一些目录,之后删除。正确的示例如下:

var rmdirs []func()
for _, d := range tempDirs() {dir := d	// 注意: 这一步是必须的os.MkdirAll(dir, 0755)rmdirs = append(rmdirs, func() {os.RemoveAll(dir)})
}for _, rmdir := rmdirs {rmdir()
}

我们可能感到困惑,为什么要在循环体内用循环变量 d 赋值给一个新的局部变量 dir,而不是直接使用循环变量 d?问题的原因在于循环变量的作用域。在上面的程序中,for 循环引入了新的词法块,循环变量 dir 在这个词法中被声明。在该循环中的所有函数值都共享相同的循环变量。以 d 为例,后续的迭代会不断更新 d 的值,当删除操作执行时,for 循环已经完成,d 当中存储的值等于最后一次迭代的值,这意味着,每次对 os.RemoveAll 调用的结果都是删除相同的目录。

通常为了解决上述问题,都会引入一个与循环变量同名或相似的局部变量,作为循环变量的副本。

上述问题不仅存在于基于 range 的循环当中,使用循环变量 i 时也存在相同问题。

5.7 可变参数

参数数量可变的参数称为可变参数函数,典型的例子是fmt.Printf及类似函数。

在声明可变参数函数时,需要在参数列表的最后一个参数类型之前添加省略号"..."

func sum(vals ...int) int {total := 0for _, val := range vals {total += val]return total
}

上述 sum 函数返回任意个 int 型参数的和。在函数体中,vals 被看作类型为[]int的切片。

如果原始参数已经是切片类型,如何传递给 sum ?只需要在最后一个参数后加上省略号:

values := []int{1, 2, 3, 4}
fmt.Println(sum(values...))

实际上,可变参数函数和以切片作为参数的函数是不同的:

func f(...int) {}
func g([]int)  {}
fmt.Printf("%T\n", f)	// func(...int)
fmt.Printf("%T\n", g)	// func([]int)

可变参数函数常被用于格式化字符串。下面的 errorf 函数构造了一个以行号开头的,经过格式化的错误信息。函数名的后缀 f 是一种通用的命名规范,代表该可变参数函数可以接收 Printf 风格的格式化字符串:

func errorf(linenum int, format string, args ...interface{}) {fmt.Fprintf(os.Stderr, "Line %d: ", linenum)fmt.Fprintf(os.Stderr, format, args...)fmt.Fprintln(os.Stderr)
}
linenum, name := 12, "count"
errorf(linenum, "undefined: %s", name) // "Line 12: undefined: count"

其中interface{}表示函数的最后一个参数可以接收任意类型。

5.8 Deferred 函数

只需要在普通调用函数或方法前加上 defer 关键字,就完成了 defer 所需要的语法。当执行到该语句时,函数和参数表达式得到计算,但直到包含该 defer 语句的函数执行完毕时,defer 后的语句才会执行。

可以在一个函数中执行多条 defer,它们的执行顺序与声明顺序相反

5.9 Panic 异常

一般而言,panic 发生时,程序会中断,并立即执行在该 goroutine 中被延迟(defer)的函数。

不是所有 panic 都来自运行时,直接调用内置的 panic 函数也会引发 panic 异常。

虽然 Go 的 panic 机制类似于其它语言的异常,但使用场景略微不同。由于 panic 会引起程序崩溃,因此 panic 一般只用于严重错误。对于大部分漏洞,我们应该使用 Go 提供的错误机制,而不是 panic,尽量避免程序崩溃。

5.10 Recover 捕获异常

通常来说,不应该对 panic 异常做任何处理,但有时我们希望程序可以从异常中恢复。

如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。

以 Parse 为例,说明 recover 的使用场景:

func Parse(input string) (s *Syntax, err error) {defer func() {if p := recover(); p != nil {err = fmt.Errorf("internal error: %v", p)}}// ... parser ...
}

recover 帮助 Parse 从 panic 恢复。在 deferred 函数内部,panic value 被附加到错误信息中。

我们不应该试图去恢复其他包引起的 panic,也不应该恢复由他人开发的函数引起的 panic。

相关文章:

【Go语言圣经】第五节:函数

第五章&#xff1a;函数 5.1 函数声明 和其它语言类似&#xff0c;Golang 的函数声明包括函数名、形参列表、返回值列表&#xff08;可省略&#xff09;以及函数体&#xff1a; func name(parameter-list) (result-list) {/* ... Body ... */ }需要注意的是&#xff0c;函数…...

win32汇编环境,窗口程序中使用进度条控件

;运行效果 ;win32汇编环境,窗口程序中使用进度条控件 ;进度条控件主要涉及的是长度单位&#xff0c;每步步长&#xff0c;推进的时间。 ;比如你的长度是1000&#xff0c;步长是100&#xff0c;每秒走1次&#xff0c;则10秒走完全程 ;比如你的长度是1000&#xff0c;步长是10&am…...

Vscode的AI插件 —— Cline

简介 vscode的一款AI辅助吃插件&#xff0c;主要用来辅助创建和编辑文件&#xff0c;探索大型项目&#xff0c;使用浏览器并执行终端命令&#xff08;需要多个tokens&#xff09;&#xff0c;可以使用模型上下文协议&#xff08;MCP&#xff09;来创建新工具并扩展自己(比较慢…...

Flink (十三) :Table API 与 DataStream API 的转换 (一)

Table API 和 DataStream API 在定义数据处理管道时同样重要。DataStream API 提供了流处理的基本操作&#xff08;即时间、状态和数据流管理&#xff09;&#xff0c;并且是一个相对低级的命令式编程 API。而 Table API 抽象了许多内部实现&#xff0c;提供了一个结构化和声明…...

Android --- handler详解

handler 理解 handler 是一套Android 消息传递机制&#xff0c;主要用于线程间通信。 tips&#xff1a; binder/socket 用于进程间通信。 参考&#xff1a; Android 进程间通信-CSDN博客 handler 就是主线程在起了一个子线程&#xff0c;子线程运行并生成message &#xff0c;l…...

[EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率

Paper Card 论文标题&#xff1a;FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者&#xff1a;Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...

关于贪心学习的文笔记录

贪心&#xff0c;顾名思义就是越贪越好&#xff0c;越多越有易&#xff0c;他给我的感觉是&#xff0c;通常是求最大或最小问题&#xff0c;相比于动态规划贪心让人更加琢磨不透&#xff0c;不易看出方法&#xff0c;为此在这记录我所见过的题型和思维方法&#xff0c;以便回头…...

SLAM技术栈 ——《视觉SLAM十四讲》学习笔记(一)

《视觉SLAM十四讲》学习笔记&#xff08;一&#xff09; 第2讲 初识SLAM习题部分 第3讲 三维空间刚体运动3.1 左手系与右手系3.2 齐次坐标3.3 旋转矩阵与变换矩阵3.4 正交群与欧式群3.5 旋转向量与欧拉角3.6 实践Eigen线性代数库3.6.1 QR分解(QR decomposition) 3.7 四元数到其…...

【ChatGPT:开启人工智能新纪元】

一、ChatGPT 是什么 最近,ChatGPT 可是火得一塌糊涂,不管是在科技圈、媒体界,还是咱们普通人的日常聊天里,都能听到它的大名。好多人都在讨论,这 ChatGPT 到底是个啥 “神器”,能让大家这么着迷?今天咱就好好唠唠。 ChatGPT,全称是 Chat Generative Pre-trained Trans…...

1. 【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--前言

在我们的专栏《单体开发》中&#xff0c;我们实现了一个简单的记账软件的服务端&#xff0c;并且成功上线。随着用户数量的不断增长&#xff0c;问题逐渐开始显现。访问量逐渐增加&#xff0c;服务端的压力也随之加大。随着访问量的攀升&#xff0c;服务端的响应时间变得越来越…...

量子力学初步:微观领域的科学之旅

飞书&#x1f4da;链接&#xff1a;量子力学篇 长尾 - 什么是量子力学 &#xff08;未完成… 等有时间再看&#xff0c;前面的内容可以参考下&#xff0c;比如了解自旋、以及斯特恩-盖拉赫实验&#xff09; 【量子力学篇-01期】经典物理学的终结&#xff0c;量子力学的开端 量…...

趣味Python100例初学者练习01

1. 1 抓交通肇事犯 一辆卡车违反交通规则&#xff0c;撞人后逃跑。现场有三人目击该事件&#xff0c;但都没有记住车号&#xff0c;只记下了车号的一些特征。甲说&#xff1a;牌照的前两位数字是相同的&#xff1b;乙说&#xff1a;牌照的后两位数字是相同的&#xff0c;但与前…...

postgresql的用户、数据库和表

在 PostgreSQL 中&#xff0c;用户、数据库和表是关系型数据库系统的基本组成部分。理解这些概念对数据库管理和操作至关重要。下面是对这些概念的详细解释&#xff1a; 1. 用户&#xff08;User&#xff09; 在 PostgreSQL 中&#xff0c;用户&#xff08;也称为 角色&#…...

对游戏宣发的粗浅思考

1.两极分化 认真观摩了mgs系列制作人的x账号&#xff0c; 其更新频率吓死人&#xff0c;一天能发几十条之多&#xff0c;吓死人。大部分都是转发相关账号的ds2或mgs相关内容&#xff0c; 每日刻意的供给这些内容来满足几十万粉丝需求&#xff0c;维护热情。 幕后是专业的公…...

【Java基础-42.3】Java 基本数据类型与字符串之间的转换:深入理解数据类型的转换方法

在 Java 开发中&#xff0c;基本数据类型与字符串之间的转换是非常常见的操作。无论是从用户输入中读取数据&#xff0c;还是将数据输出到日志或界面&#xff0c;都需要进行数据类型与字符串之间的转换。本文将深入探讨 Java 中基本数据类型与字符串之间的转换方法&#xff0c;…...

(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同

&#xff08;1&#xff09;经过之前的学习。俺认为结论是这样的&#xff0c;因为三次握手到四次挥手&#xff0c;到 RST 报文&#xff0c;都是 tcp 连接上收到了报文&#xff0c;这都属于读事件。所以&#xff1a; EPOLLIN : 包含了读事件&#xff0c; FIN 报文的正常四次挥手、…...

webpack传输性能优化

手动分包 基本原理 手动分包的总体思路是&#xff1a;先打包公共模块&#xff0c;然后再打包业务代码。 打包公共模块 公共模块会被打包成为动态链接库&#xff08;dll Dynamic Link Library&#xff09;&#xff0c;并生成资源清单。 打包业务代码 打包时&#xff0c;如果…...

智能小区物业管理系统打造高效智能社区服务新生态

内容概要 随着城市化进程的不断加快&#xff0c;智能小区物业管理系统的出现&#xff0c;正逐步改变传统物业管理的模式&#xff0c;为社区带来了崭新的管理理念和服务方式。该系统不仅提升了物业管理效率&#xff0c;还加强了业主与物业之间的互动&#xff0c;为每位居民提供…...

(done) MIT6.S081 2023 学习笔记 (Day7: LAB6 Multithreading)

网页&#xff1a;https://pdos.csail.mit.edu/6.S081/2023/labs/thread.html (任务1教会了你如何用 C 语言调用汇编&#xff0c;编译后链接即可) 任务1&#xff1a;Uthread: switching between threads (完成) 在这个练习中&#xff0c;你将设计一个用户级线程系统中的上下文切…...

面试经典150题——栈

文章目录 1、有效的括号1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、最小栈3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、逆波兰表达式求值4.1 题目链接4.2 题目描述4.3 解题代码4.4 解题思路 5、基本…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

免费PDF转图片工具

免费PDF转图片工具 一款简单易用的PDF转图片工具&#xff0c;可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件&#xff0c;也不需要在线上传文件&#xff0c;保护您的隐私。 工具截图 主要特点 &#x1f680; 快速转换&#xff1a;本地转换&#xff0c;无需等待上…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...