Go语言学习(六)| 函数
函数的定义
关键字 func
用来声明一个函数
func functionName(parameter type) returnType {
// 函数体
}
函数中的参数列表和返回值并非是必须的
func functionName(parameter type) {
// 函数体
}
如果有连续若干个参数,它们的类型一致,那么我们无须一一罗列,只需在最后一个参数后添加该类型。例:
func calc(num, price int) int {
return price * num
}
多返回值
Go 语言支持一个函数可以有多个返回值括号,同时可以命名返回值。一旦命名了返回值,可以认为这些值在函数第一行就被声明为变量了。如果有连续若干个返回值,它们的类型一致,那么我们无须一一罗列,只需在最后一个返回值后添加该类型。
例:
package main
func main() {
s := Factorial(4)
println(s)
}
func Factorial(x int) int {
var result int
if x == 0 {
result = 1
} else {
result = x * Factorial(x-1)
}
return result
}
也可以上述例子改为:例:
package main
func main() {
s := Factorial(4)
println(s)
}
func Factorial(x int) (result int) {
if x == 0 {
result = 1
} else {
result = x * Factorial(x-1)
}
return
}
官方建议:最好命名返回值,因为不命名返回值,虽然代码更加简洁了,但是会造成生成的文档可读性差。
保留函数
-
init
函数(能够应用于所有的package) -
main
函数(只能应用于package main)。
这两个函数在定义时不能有任何的参数和返回值。Go程序会自动调用 init()
和 main()
,所以你不需要在任何地方调用这两个函数。每个 package 中的 init
函数都是可选的,但 package main
就必须包含一个main函数。
变参
接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:
func funcName(arg ...int) {
}
arg ... int
告诉 Go 这个函数接受不定数量的参数。
注意 这些参数的类型全部是 int。在函数体中,变量 arg 是一个 int 类型的 slice
例:
package main
import "fmt"
func main() {
test("a", "b")
test("Hello", "world", "你好")
}
func test(arg ...string) {
for _, val := range arg {
fmt.Printf("%sn", val)
}
}
函数作为值
上例也可以这样:
package main
import "fmt"
func main() {
demo := func(arg ...string) { //定义一个匿名函数,并且赋值给demo
for _, val := range arg {
fmt.Printf("%sn", val)
}
}
demo("a", "b")
}
函数作为值也可以用在其他地方,如 map
。例:
package main
import "fmt"
func main() {
map1 := map[int]func(i int) int{
1: func(x int) int { return x + 1 },
2: func(y int) int { return y + 2 },
3: func(z int) int { return z + 3 },
}
fmt.Printf("%dn", map1[1](1))
fmt.Printf("%dn", map1[2](1))
fmt.Printf("%dn", map1[3](1))
}
回调函数
例:
package main
import "fmt"
func main() {
callback(2, printit)
}
func printit(x int) {
fmt.Printf("%dn", x)
}
func callback(y int, f func(int)) {
f(y)
}
输出结果:2
例2:
package main
import "fmt"
func main() {
callback(2, printit)
}
func printit(x int) (demo int) {
demo = x + 1
return
}
func callback(y int, f func(int) int) {
result := f(y)
res := fmt.Sprintf("%d", result) + "demo" // Sprintf()将int转换成string
fmt.Printf("%sn", res)
}
输出结果:3demo
传值和传指针
例3:
package main
import "fmt"
//简单的一个函数,实现了参数+1的操作
func add1(a *int) int { // 请注意,
*a = *a + 1 // 修改了a的值
return *a // 返回新值
}
func main() {
x := 3
fmt.Println("x = ", x) // 应该输出 "x = 3"
x1 := add1(&x) // 调用 add1(&x) 传x的地址
fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4"
fmt.Println("x = ", x) // 应该输出 "x = 4"
}
变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有 add1 函数知道 x 变量所在的地址,才能修改 x 变量的值。所以我们需要将x所在地址 &x
传入函数,并将函数的参数的类型由int改为 *int
,即改为指针类型,才能在函数中修改 x 变量的值。此时参数仍然是按 copy 传递的,只是 copy 的是一个指针。
函数传递指针的好处:
- 传指针使得多个函数能操作同一个对象。
- 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次 copy 上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
Go语言中 string
, slice
, map
这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变 slice
的长度,则仍需要取地址传递指针)
延迟代码 defer
package main
func ReadWrite() bool {
file.Open("file")
// 做一些工作
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
在这里有许多重复的代码。为了解决这些,Go 有了 defer 语句。在 defer
后指定的函数会在函数退出前调用。可改为如下代码:
package main
func ReadWrite() bool {
file.Open("file")
defer file.Close() //file.Close() 被添加到了 defer 列表
// 做一些工作
if failureX {
return false // Close() 现在自动调用
}
if failureY {
return false // 这里Close() 也将自动调用
}
return true
}
defer
有点类似于 PHP 类中的 __destruct()
析构方法。 defer
也可以这样写:
defer func(x int) {
//
}(5)
如果有很多调用 defer ,那么 defer 是采用后进先出模式
package main
func main() {
for j := 0; j < 5; j++ {
defer println(j)
}
}
输出:43210
例:
package main
func main() {
s := test(2)
println(s)
}
func test(i int) (t int) {
defer func() {
t++
}()
t = i + 1
return
}
输出结果:4
例2:
package main
func main() {
s := test(2)
println(s)
}
func test(i int) (t int) {
defer func(x int) {
t += x
}(5)
t = i + 1
return
}
输出结果:8
在 Go 中函数也是一种变量,可以通过 type
来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型
例:
package main
import "fmt"
type testInt func(int) bool //声明了一个函数类型
func main() {
slice := []int{1, 2, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) //函数当做值来传递了
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven) //函数当做值来传递了
fmt.Println("Even elements of slice are: ", even)
}
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
//声明的函数在这个地方当做了一个参数
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
返回结果:slice = [1 2 3 4 5 7]Odd elements of slice are: [1 3 5 7]Even elements of slice are: [2 4]
Panic和Recover
panic
panic
是用来表示非常严重的不可恢复的错误的。在 Go 语言中这是一个内置函数,接收一个 interface{}
类型的值(也就是任何值了)作为参数。panic
的作用就像我们平常接触的异常。不过Go可没有 try…catch ,所以, panic
一般会导致程序挂掉(除非 recover
)。所以,Go语言中的异常,那真的是异常了。但是,关键的一点是,即使函数执行的时候panic了,函数不往下走了,运行时并不是立刻向上传递 panic
,而是到defer那,等defer的东西都跑完了, panic
再向上传递。所以这时候 defer 有点类似 try-catch-finally 中的 finally。
recover
Go语言提供了 recover
内置函数,前面提到,一旦 panic
,逻辑就会走到 defer
那,那我们就在 defer
那等着,调用 recover
函数将会捕获到当前的 panic
(如果有的话),被捕获到的 panic
就不会向上传递了,于是,世界恢复了和平。你可以干你想干的事情了。
不过要注意的是, recover
之后,逻辑并不会恢复到 panic
那个点去,函数还是会在 defer
之后返回。
- IoC+AOP的简单实现
- 使用了继承、多态还有工厂模式和反射,但是还是没有OO的感觉。[已经增加了实现的代码]
- OO——从不知到知道一点,从迷茫到豁然开朗 (迟来的我的2002到2007)
- 只在UnitTest和WebHost中的出现的关于LogicalCallContext的严重问题
- TEST LAB V8在线渗透实验室教程(三)
- CMQ请求域名
- 在Entity Framework中使用存储过程(一):实现存储过程的自动映射
- 在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义?
- 表单控件的副产品——查询控件
- 表单控件续(1)——应用接口来简化和分散代码
- 通过自定义配置实现插件式设计
- 让IoC动态解析自定义配置(提供基于Unity的实现)
- 如何让ASP.NET默认的资源编程方式支持非.ResX资源存储
- 在VS中通过建立依赖关系使文件结构更清晰
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 单细胞转录组高级分析三:细胞通讯分析
- 单细胞转录组高级分析四:scRNA数据推断CNV
- 0808-7.1.1-如何在CDP7.1.1指定Hive SQL的资源池队列
- iOS多线程之GCD、OperationQueue 对比和实践记录
- singleR的7个数据库文件下载失败的解决方案
- Spring Boot 如何快速集成 Redis 哨兵?
- 一行命令实现成“吨”测试数据的转码
- Jenkins参数化构建与触发
- 数据无法模拟,自动化受阻怎么办?
- Quickprop介绍:一个加速梯度下降的学习方法
- PandaSQL:一个让你能够通过SQL语句进行pandas的操作的python包
- 每个数据科学家都应该知道的20个NumPy操作
- 机器学习特性缩放的介绍,什么时候为什么使用
- 聊聊claudb的set command
- 聊聊claudb的zset command