重学Go语言 | 函数详解
我正在参加「掘金·启航计划」
对于大多数编程语言来说,函数都是很重要的内容,尤其是用面向过程编程语言(比如C
语言)编写的程序,就是由一个个函数构成的,另外,很多编程语言的入口就是一个函数,即main
函数,Go
语言的入口也是main
函数:
package main
func main(){
//程序入口
}
掌握函数的使用非常重要,在这篇文章中,我们就来学习Go
函数。
什么是函数
函数是一个有指定名称的可以重复使用的逻辑代码块,我们可以将重复或复杂的逻辑代码封装在一个函数中,在之后的代码中重复使用,比如fmt.Println
函数就是Go
标准库为我们封装好的一个函数,我们可以调用该函数向控制台打印信息:
fmt.Println("hello")
函数的作用
- 将复杂的逻辑封装成一个函数,多次复用。
- 将一个复杂的任务拆解为多个小任务,由多个简单的函数完成。
- 复用其他开发者封装的函数,或者将自己的代码封装成函数给其他开发者使用。
函数的使用
要使用函数,则必须先声明函数。
函数声明
声明函数用关键字func
,后面跟着函数名称、小括号中的参数列表、返回值列表(可省略)以及花括号中的函数体(可以为空),如下所示:
func functionName([param1 type,param2 type,...paramN type])[(result1 type,result2 type,...resultN type)] {
//function body
}
函数的参数列表与返回值列表被称为函数签名。
函数声明示例:
//没有参数和返回值
func say(){
fmt.Println("hello")
}
//两个参数和一个返回值
func sum(a,b int) int {
return a+b
}
函数调用
函数声明后,就可以调用函数了,如果函数有参数,调用时必须传入参数,Go函数的参数没有默认值。
对于同一个包下的函数,可以直接调用:
package main
import "fmt"
func sum(a,b int)int{
return a + b
}
func main(){
fmt.Println(sum(1,2))
}
如果函数名首字母为大写,则在其他包中可以通过该函数所在包名调用该函数,如果函数小写,则该函数只能在包内调用:
package person
func Eat(){
fmt.Println("I'm eatting")
}
func say(){
fmt.Println("hello")
}
使用包名调用其他包的函数:
package main
import "person"
func main(){
person.Eat()
person.say() //错误
}
参数列表
Go函数可以有0
到N
个参数,N
个表示函数的参数是没有限制,可以是10个,也可以是100个,甚至是1000或10000,但一般推荐函数参数最好不要超过3个。
func isOdd(n int) bool {
return n % 2 != 0 ? true : false
}
func sum(a int,b int,c int) int {
return a + b + c
}
如果相临的参数据数据类型相同,则前面的参数可以省略数据类型:
func sum(a int,b int,c int) int {
return a + b + c
}
//改为
func sum(a,b,c int) int {
return a + b + c
}
函数参数作用域只在函数内,在函数外部无法访问函数的参数:
func sum(a,b int) int {
return a+b;
}
sum(1,2)
fmt.Println(a) //错误,无法调用函数的参数
有时候,我们希望函数的参数数量是可变,比如我们前面一直调用的打印函数fmt.Println()
,我们可以传入任意数量的参数:
fmt.Println(1,2,3)
我们也可以定义自己的可变参数函数,可变参数变量在函数内部就像一个切片:
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
可变参数变量必须是函数的最后一个参数:
//错误
func sum(vals ...int,x int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
//正确
func sum(x int,vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
返回值列表
在Go
语言中,函数支持0
到N
个返回值,返回值写在return
关键词后面,多个返回值由逗号隔开:
func sum(a,b int) (n int) {
return a+b
}
func isOdd(n int) (bool,error) {
if n == 0{
return false,errors.New("param n can not be zero")
}
if n % 2 != 0 {
return true,nil
}
return false,nil
}
当函数没有返回值时,或者只有一个返回值且返回值没有名称时,返回值列表的小括号可以省略:
func hello(str string){
fmt.Println(str)
}
func sum(a,b int) int {
return a+b
}
返回值也是函数内部参数,当函数直接操作返回值时,return
关键词后面也可不跟返回值,调用者仍能获得返值:
func sum(a,b int) (n int) {
n = a + b
return
}
fmt.Println(sum(1,2))//输出3
如果有多个返回值,无论返回值是否有名称,都必须写在小括号中:
func test()(a int,b string){
return 10,"test"
}
返回值也可以省略名称,比如上面的函数可以改写为:
func test()(int,string){
return 10,"test"
}
在Go语言中,一般推荐第二个返回值为error类型,用于提示调用者该函数在执行过程中,是否有错误,Go标准库的很多函数就是这么做的:
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
f, err := openFileNolog(name, flag, perm)
if err != nil {
return nil, err
}
f.appendMode = flag&O_APPEND != 0
return f, nil
}
匿名函数
函数作为Go
语言中的一等公民,只能在包一级声明,Go
不支持在函数中再声明其他函数:
package main
//正确
func sum1(){
}
func main(){
//在函数中声明其他函数,无法通过编译
func sum2(){
}
}
如果想在其他函数中声明一个函数,可以使用匿名函数,所谓匿名函数,就是省略了函数名的函数,也称为函数字面量:
package main
//正常声明函数
func getFile(){
}
func main(){
go getFile()
//匿名函数
go func(){
}()
}
将匿名函数作为函数的参数,也称为闭包:
package main
import (
"io"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello from a HandleFunc #1!\n")
})
http.HandleFunc("/endpoint",func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello from a HandleFunc #2!\n")
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
匿名函数也可以作为一个值赋给一个变量,比如我们上面的例子可以改写为下面的样子:
package main
import (
"io"
"log"
"net/http"
)
func main() {
h1 := func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello from a HandleFunc #1!\n")
}
h2 := func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello from a HandleFunc #2!\n")
}
http.HandleFunc("/", h1)
http.HandleFunc("/endpoint", h2)
log.Fatal(http.ListenAndServe(":8080", nil))
}
匿名函数可以作为函数的返回值或者直接用定义函数类型的变量。
defer机制
很多时候,我们的函数需要打开网络连接去读取网络数据,又或者打开文件读取数据,这些操作往往需要在函数的最后面执行对应的关闭操作,不过随着函数代码越写越长,我们可能会忘记关闭打开的资源。
在其他编程语言中(比如Java),会把关闭资源的操作放在try...catch
后面finally
模块中:
try{
//执行可能会抛出异常的代码
}catch(Exception e){
//捕获异常
}finally {
//执行资源关闭操作
}
只是Go
并不支持try...catch
,Go语言的defer
机制可以达到同样的效果,defer关键字后面跟着一个函数调用,当函数执行完成后,会调用defer后面的函数:
func getData() []byte{
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
//.....
}
函数异常处理
开发函数时,一个好的做法是通过函数第二个返回值为error类型来提供调用者是否出错,由调用者自行决定是否终止程序执行,不过Go也支持在函数中直接抛出异常,以终止程序运行。
Go
抛出异常使用内置panic()
函数,执行该函数后,程序会终止运行:
package main
func main() {
add(0, 0)
}
func add(x, y int) int {
if x == 0 && y == 0 {
panic("x与y不能同时为0")
}
return x + y
}
一般而言,如果调用函数过程出现了panic
异常,不应该进行错误恢复的,因为panic异常代表程序出现严重的错误了,比如数组越界等。
而有时候, 我们希望在产生panic
异常后做一些善后处理,比如将异常中的错误信息打印到日志等,这时候可以使用内置recover()
函数。
recover()
需要在defer
后面的函数中调用才会生效:
package main
import "fmt"
func main() {
caller()
}
func caller() {
defer func() {
if p := recover(); p != nil {
err := fmt.Errorf("执行出错: %v", p)
fmt.Println(err)
}
}()
r1 := add(1, 2)
fmt.Println(r1)
r2 := add(0, 0)
fmt.Println(r2)
}
func add(x, y int) int {
if x == 0 && y == 0 {
panic("x与y不能同时为0")
}
return x + y
}
小结
Go语言的函数是比较重要的内容了,在程序开发过程中,我们总是需要声明自己的函数或者调用其他开发者的函数。
总结一下,这篇文章中,我们讲了以下几点:
- 什么是函数,函数有什么作用
- 函数的声明与使用
- 匿名函数
- defer机制与函数的异常处理
转载自:https://juejin.cn/post/7247306840199807031