威海做网站推广的企业/网站优化查询
第五章:函数
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语言圣经】第五节:函数
第五章:函数 5.1 函数声明 和其它语言类似,Golang 的函数声明包括函数名、形参列表、返回值列表(可省略)以及函数体: func name(parameter-list) (result-list) {/* ... Body ... */ }需要注意的是,函数…...

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

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

Flink (十三) :Table API 与 DataStream API 的转换 (一)
Table API 和 DataStream API 在定义数据处理管道时同样重要。DataStream API 提供了流处理的基本操作(即时间、状态和数据流管理),并且是一个相对低级的命令式编程 API。而 Table API 抽象了许多内部实现,提供了一个结构化和声明…...

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

[EAI-023] FAST,机器人动作专用的Tokenizer,提高VLA模型的能力和训练效率
Paper Card 论文标题:FAST: Efficient Action Tokenization for Vision-Language-Action Models 论文作者:Karl Pertsch, Kyle Stachowicz, Brian Ichter, Danny Driess, Suraj Nair, Quan Vuong, Oier Mees, Chelsea Finn, Sergey Levine 论文链接&…...

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

SLAM技术栈 ——《视觉SLAM十四讲》学习笔记(一)
《视觉SLAM十四讲》学习笔记(一) 第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 实战--孢子记账--从单体到微服务--转向微服务】--前言
在我们的专栏《单体开发》中,我们实现了一个简单的记账软件的服务端,并且成功上线。随着用户数量的不断增长,问题逐渐开始显现。访问量逐渐增加,服务端的压力也随之加大。随着访问量的攀升,服务端的响应时间变得越来越…...

量子力学初步:微观领域的科学之旅
飞书📚链接:量子力学篇 长尾 - 什么是量子力学 (未完成… 等有时间再看,前面的内容可以参考下,比如了解自旋、以及斯特恩-盖拉赫实验) 【量子力学篇-01期】经典物理学的终结,量子力学的开端 量…...

趣味Python100例初学者练习01
1. 1 抓交通肇事犯 一辆卡车违反交通规则,撞人后逃跑。现场有三人目击该事件,但都没有记住车号,只记下了车号的一些特征。甲说:牌照的前两位数字是相同的;乙说:牌照的后两位数字是相同的,但与前…...

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

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

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

(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同
(1)经过之前的学习。俺认为结论是这样的,因为三次握手到四次挥手,到 RST 报文,都是 tcp 连接上收到了报文,这都属于读事件。所以: EPOLLIN : 包含了读事件, FIN 报文的正常四次挥手、…...

webpack传输性能优化
手动分包 基本原理 手动分包的总体思路是:先打包公共模块,然后再打包业务代码。 打包公共模块 公共模块会被打包成为动态链接库(dll Dynamic Link Library),并生成资源清单。 打包业务代码 打包时,如果…...

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

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

面试经典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、基本…...

openmv的端口被拆分为两个 导致电脑无法访问openmv文件系统解决办法 openmv USB功能改动 openmv驱动被更改如何修复
我之前误打误撞遇到一次,直接把openmv的全部端口删除卸载然后重新插上就会自动重新装上一个openmv端口修复成功,大家可以先试试不行再用下面的方法 全部卸载再重新插拔openmv 要解决OpenMV IDE中出现的两个端口问题,可以尝试以下步骤&#x…...

自制虚拟机(C/C++)(三、做成标准GUI Windows软件,扩展指令集,直接支持img软盘)
开源地址:VMwork 要使终端不弹出, #pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") 还要实现jmp near 0x01类似的 本次的main.cpp #include <graphics.h> #include <conio.h> #include <windows.h> #includ…...

算法题(56):旋转链表
审题: 我们需要根据k的大小把链表向右移动对应次数,并返回移动后的链表的头结点指针 思路: 根据提示中的数据大小我们发现:k的值可以远大于节点数。 也就是说我们对链表的操作存在周期,如果k%len0,说明我们…...

解决PyG安装中torch-sparse安装失败问题:详细指南
1 问题描述 最近在学习GNN,需要使用PyTorch Geometric(PyG)库。在安装PyG的过程中,遇到了torch-sparse安装失败的问题,错误提示为: ERROR: Failed building wheel for torch-sparse本文将详细记录问题的解…...

如何创建折叠式Title
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容,本章回中将介绍SliverAppBar组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似,它们的…...

go-zero学习笔记(三)
利用goctl生成rpc服务 编写proto文件 // 声明 proto 使用的语法版本 syntax "proto3";// proto 包名 package demoRpc;// golang 包名(可选) option go_package "./demo";// 如需为 .proto 文件添加注释,请使用 C/C 样式的 // 和 /* ... */…...

Wildcard工具详解:从入门到精通
1. Wildcard基础知识 什么是Wildcard? Wildcard(通配符)是一种用于匹配文件名或字符串的特殊字符。它允许用户使用简单的符号来表示复杂的匹配规则,从而快速定位目标文件或数据。 常见的Wildcard符号 *:匹配任意数量…...

冰蝎v3.0 beta7来啦
我用了一台kali,一台centos,一台windows,做了一个文件上传和一个反弹shell实验,载荷是AES加密的,终于感受到了对加密流量的无可奈何~ kali(php8.1)centos(php7.1)window…...

React中使用箭头函数定义事件处理程序
React中使用箭头函数定义事件处理程序 为什么使用箭头函数?1. 传递动态参数2. 避免闭包问题3. 确保每个方块的事件处理程序是独立的4. 代码可读性和维护性 示例代码总结 在React开发中,处理事件是一个常见的任务。特别是当我们需要传递动态参数时&#x…...

记忆化搜索和动态规划 --最长回文子串为例
记忆化搜索 记忆化搜索是一种优化递归算法的方法,通过将已经计算过的子问题的结果存储起来(通常使用哈希表或数组),避免重复计算相同的子问题。 本质上是通过缓存中间结果来减少计算的重复性。 动态规划 动态规划是通过将问题分…...