Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,但它也具有Pi运算的特征,比如通道传输。在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。
接下来介绍Go语言中的错误处理,这是一个程序员最基本的功夫。
在介绍defer延迟语句之前,我们先来看一段非常简单的代码:
package main
import "fmt"
func trydefer(){
fmt.Println("1")
fmt.Println("2")
fmt.Println("3")
}
func main() {
trydefer()
}
//运行结果:
1
2
3
现在我们将输出“1”的语句修改为defer fmt.Println("1"),发现输出结果为2 3 1;接着在将输出“2”的语句修改为defer fmt.Println("2"),再来看看输出结果3 2 1;如果全部前面都添加defer呢?猜一下输出结果:3 2 1。
因此我们知道defer延迟语句确保调用在函数结束时发生,也就是说函数运行到defer语句处会跳过当前语句,等该函数执行完毕后,按照先进后出的顺序执行defer语句。
延迟语句被用于执行一个函数调用,在这个函数之前,延迟语句返回。参数在defer语句时计算,defer列表为先进后出或者是后进先出。
defer语句碰到panic,return,error等情况也能正常执行:
func trydefer(){
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
return
fmt.Println("4") //这个代码不会执行,因为已经return了
}
func main() {
trydefer()
}
//运行结果:
3 2 1
再来看一下panic:
func trydefer(){
defer fmt.Println("1")
defer fmt.Println("2")
fmt.Println("3")
panic("程序运行到这里肯定会出错")
fmt.Println("4")
}
func main() {
trydefer()
}
//运行结果:
3 2 1 panic: 程序运行到这里肯定会出错
但是一般都是在写文件的时候才会采用defer防止自己忘记关闭连接,关闭文件,释放资源等。新建一个fib文件,接着在里面新建一个fib.go文件,里面写入闭包实现输出斐波那契数列的函数:
package fib
//闭包
func Fptest()func()int{
a, b:=0,1
return func() int {
a,b = b,a+b
return a
}
}
然后回到main包下的main.go文件,里面写入以下代码:
package main
import (
"bufio"
"fib"
"fmt"
"os"
)
func Writefile(filename string) {
file ,err := os.Create(filename)
if err!=nil{
panic(err)
}
defer file.Close() //关闭文件
//这样写的话文件速度较慢,可以先写到内存中,然后一次性写入文件中,下面就是这个代码
writer:=bufio.NewWriter(file)
defer writer.Flush() //记得将数据从内存推入文件中,否则输出的数据仅仅在内存中
f:= fib.Fptest()
for i:=0;i<5;i++{
fmt.Fprintln(writer,f())
}
}
func main() {
Writefile("fib.txt")
}
运行该文件后,发现"fib.txt"文件中已经写入1 1 2 3 5了。
前面我们都是使用panic来输出错误error,但是并没有处理错误,下面我们就来尝试处理一下错误。在上面写文件的时候,我们使用了os.Create(filename)这个方法,查看一下它的源码:
// Create creates the named file with mode 0666 (before umask), truncating
// it if it already exists. If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
看到没它其实是会返回很多信息的,我们尝试修改一下之前的代码,将file ,err := os.Create(filename)这句修改为file ,err := os.OpenFile(filename,os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0666)。然后运行一下,会发现出了问题,报以下错误:
panic: open fib.txt: The file exists.
既然知道会报错,因此可以不再输出panic(err),而是使用下面这种形式的代码,这样程序可以正常执行,而不会因为错误而停止运行:
if err!=nil{
fmt.Println("The file exists.")
return
}
这样其实前提是已经知道发生了什么错误,如果不知道可以直接查看error本身的源码,发现它是一个接口:
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
既然是接口,那就可以直接拿来使用了,直接输入fmt.Println("err:",err.Error()),再次运行一下发现结果真的输出了错误:err: open fib.txt: The file exists.。
这个代码其实是一下子全部把错误信息输出来了,前面在看Create方法源码的时候,你是否注意到了一点:If there is an error, it will be of type *PathError.如果出现一个错误,那它将会是*PathError,也就是多种PathError类型。使用前面介绍的动态类型检查错误的具体类型,然后再一次修改错误处理的代码:
if err !=nil{
if pathError,ok :=err.(*os.PathError);!ok{
panic(err)
}else{
fmt.Printf("%s,%s,%s\\n",pathError.Op,pathError.Path,pathError.Err)
//op是操作,path是路径,err是错误
}
}
//运行结果:
open,fib.txt,The file exists.
当然你还可以自己定义error,使用如下方法:err=errors.New("这是自定义的error"),然后程序运行就会触发panic,进而导致程序崩溃,因为自己定义的error不是os.PathError类型。
简单总结一下错误类型的表示,它是一个接口,因此任何实现该接口的类型都可以作为一个错误,进而调用该方法实现对错误的描述。
type error interface {
Error() string
}
现在又这么一个场景,100台服务器同时出现了某个错误,那么我们应该怎样编写高可复用的代码呢?往下看你就知道了。
error和panic区别 : 意料之中用error,如文件打不开;意料之外用panic,如数组越界。
一般在下列情况使用defer:Open/Close;Lock/Unlock;PrintHeader/PrintFooter等情况。
与C++相比,Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收、接口(Interface)等特性的语言级支持。Go 2.0版本将支持泛型,对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。
¥299.00
¥399.00
¥699.00
¥399.00