Golang核心编程(4)-函数以及错误处理

版权声明: https://blog.csdn.net/pbrlovejava/article/details/83934376


更多关于Golang核心编程知识的文章请看:Golang核心编程(0)-目录页


可能很多习惯用C或Java的朋友发现,Add方法是以大写开头的,这并不符合驼峰式方法命名的规范,但在Go语言中,**以名字以大写开头的函数表示可被包之外的代码去调用,而以小写开头的函数则表明只能被本包调用,相当于Java中的private关键字的作用。**错误处理是学习任何编程语言都需要考虑的一个重要话题。在早期的语言中,错误处理不是语言规范的一部分,通常只作为一种编程范式存在,比如C语言中的 errno 。但自C++语言以来,语言层面上会增加错误处理的支持,比如异常(exception)的概念和 try-catch 关键字的引入。`Go语言在此功能上考虑得更为深远。漂亮的错误处理规范是Go语言最大的亮点之一。

一、函数

1.1、函数定义

前面我们已经大概介绍过函数,这里我们用一个最简单的加法函数来进行详细说明:

package mymath
import "errors"
func Add(a int, b int) (ret int, err error) {
	if a < 0 || b < 0 { // 假设这个函数只支持两个非负数字的加法
	err= errors.New("Should be non-negative numbers!")
	return
}
	return a + b, nil  // 支持多重返回值
}

如果参数列表中若干个相邻的参数类型的相同,比如上面例子中的 a 和 b ,则可以在参数列表中省略前面变量的类型声明,如下所示:

func Add(a, b int)(ret int, err error) {
	// ...
}

如果返回值列表中多个返回值的类型相同,也可以用同样的方式合并。
如果函数只有一个返回值,也可以这么写:

func Add(a, b int) int {
	// ...
}

1.2、函数调用

函数调用非常方便,只要事先导入了该函数所在的包,就可以直接按照如下所示的方式调用函数:

import "mymath"// 假设Add被放在一个叫mymath的包中
	// ...
c := mymath.Add(1, 2)

可能很多习惯用C或Java的朋友发现,Add方法是以大写开头的,这并不符合驼峰式方法命名的规范,但在Go语言中,以名字以大写开头的函数表示可被包之外的代码去调用,而以小写开头的函数则表明只能被本包调用,相当于Java中的private关键字的作用。

1.3、不定参数

接触过Java的朋友应该知道Java中有的方法可以使用不定参数,而在Go语言中,也同样提供了这项机制。

public static void fun1(int ...numbers){
        for (int number : numbers) {
            System.out.println(number);
        }
    }

 public static void main(String[] args) {
        fun1(1,2,3,4,5,6,7);
    }
1.3.1、不定参数类型

不定参数是指函数传入的参数个数为不定数量,为了做到这点,首先需要将函数定义为接受不定参数类型:

func myfunc(args ...int) {
	for _, arg := range args {
	fmt.Println(arg)
}
}

这段代码的意思是,函数 myfunc() 接受不定数量的参数,这些参数的类型全部是 int ,所以它可以用如下方式调用:

myfunc(2, 3, 4)
myfunc(1, 3, 7, 13)

形如 …type 格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。它是一个语法糖(syntactic sugar),即这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序出错的机会。
从内部实现机理上来说,类型...type本质上是一个数组切片,也就是 []type,这也是为什么上面的参数args可以用for循环来获得每个传入的参数。

假如没有 ...type这样的语法糖,开发者将不得不这么写:

func myfunc2(args []int) {
	for _, arg := range args {
	fmt.Println(arg)
}
}
1.3.2、任意类型的不定参数

之前的例子中将不定参数类型约束为 int ,如果你希望传任意类型,可以指定类型为
interface{}

func function(args ...interface{}){
	// ...
}

在Java中,这相当于:

public static void function(int ...Object){
       //...
    }

1.4、多返回值

与C、C++和Java等开发语言的一个极大不同在于,Go语言的函数或者成员的方法可以有多个返回值,这个特性能够使我们写出比其他语言更优雅、更简洁的代码,比如 File.Read() 函数就可以同时返回读取的字节数和错误信息。如果读取文件成功,则返回值中的 n 为读取的字节数, err 为 nil ,否则 err 为具体的出错信息
func (file *File) Read(b []byte) (n int, err Error)

同样,从上面的方法原型可以看到,我们还可以给返回值命名,就像函数的输入参数一样。返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。在函数中执行不带任何参数的 return 语句时,会返回对应的返回值变量的值。

如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线“ _ ”来跳过这个返回值,比如下面的代码表示调用者在读文件的时候不想关心Read() 函数返回的错误码:
n, _ := f.Read(buf)

二、错误处理

错误处理是学习任何编程语言都需要考虑的一个重要话题。在早期的语言中,错误处理不是语言规范的一部分,通常只作为一种编程范式存在,比如C语言中的 errno 。但自C++语言以来,语言层面上会增加错误处理的支持,比如异常(exception)的概念和 try-catch 关键字的引入。`Go语言在此功能上考虑得更为深远。漂亮的错误处理规范是Go语言最大的亮点之一。

2.1、error 接口

Go语言引入了一个关于错误处理的标准模式,即error 接口,该接口的定义如下:

type error interface {
	Error() string
}

对于大多数函数,如果要返回错误,大致上都可以定义为如下模式,将 error 作为多种返回值中的最后一个,但这并非是强制要求:

func Foo(param int)(n int, err error) {
	// ...
}

//调用时的代码建议按如下方式处理错误情况:
n, err := Foo(0)
if err != nil {
// 错误处理
} else {
// 使用返回值n
}

2.2、自定义的错误类型

首先,定义一个用于承载错误信息的类型。因为Go语言中接口的灵活性,你根本不需要从error 接口继承或者像Java一样需要使用 implements来明确指定类型和接口之间的关系,具体代码如下:

type PathError struct {
	Op string
	Path string
	Err error
}

如果这样的话,编译器又怎能知道 PathError 可以当一个 error 来传递呢?关键在于下面的代码实现了 Error() 方法:

func (e *PathError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}

syscall.Stat()失败返回 err 时,将该 err 包装到一个 PathError 对象中返回:

    func Stat(name string) (fi FileInfo, err error) {
        var stat syscall.Stat_t
                err = syscall.Stat(name, &stat)
        if err != nil {
            return nil, &PathError {"stat", name, err}
        }
        return fileInfoFromStat(&stat, name), nil
    }

2.3、defer

关键字 defer是Go语言引入的一个非常有意思的特性,defer关键字声明的代码或者方法无论是否有错误出现都会继续执行下去,比如这些关闭资源的方法,可以用defer声明。

    func CopyFile(dst, src string) (w int64, err error) {
        srcFile, err := os.Open(src)
        if err != nil {
            return
        }
        defer srcFile.Close()
        dstFile, err := os.Create(dstName)
        if err != nil {
            return
        }
        defer dstFile.Close()
        return io.Copy(dstFile, srcFile)
    }

即使其中的 Copy() 函数抛出异常,Go仍然会保证 dstFile 和 srcFile 会被正常关闭。
如果觉得一句话干不完清理的工作,也可以使用在 defer 后加一个匿名函数的做法:

defer func() {
	// 做你复杂的清理工作
} ()

另外,一个函数中可以存在多个 defer 语句,因此需要注意的是, defer 语句的调用是类似于堆栈(Stack)遵照先进后出的原则,即最后一个 defer 语句将最先被执行。

2.4、panic()和recover()

Go语言引入了两个内置函数 panic()recover()以报告和处理运行时错误和程序中的错误场景:

func panic(interface{})
func recover() interface{}

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行 panic流程,直至所属的goroutine中所有正在执行的函数被终止。错误信息将被报告,包括在调用panic() 函数时传入的参数,这个过程称为错误处理流程。

panic()的参数类型interface{}我们可以得知,该函数接收任意类型的数据,比如整
型、字符串、对象等
。调用方法很简单,下面为几个例子:

panic(404)
panic("network broken")
panic(Error("file not exists"))

recover()函数用于终止错误处理流程。一般情况下, recover()应该在一个使用 defer
关键字的函数中执行以有效截取错误处理流程。如果没有在发生异常的goroutine中明确调用恢复过程(使用 recover 关键字),会导致该goroutine所属的进程打印异常信息后直接退出。

猜你喜欢

转载自blog.csdn.net/pbrlovejava/article/details/83934376