likes
comments
collection
share

重学Go语言 | 函数详解

作者站长头像
站长
· 阅读数 17

我正在参加「掘金·启航计划」

对于大多数编程语言来说,函数都是很重要的内容,尤其是用面向过程编程语言(比如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函数可以有0N个参数,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语言中,函数支持0N个返回值,返回值写在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
评论
请登录