Go语言学习笔记10--异常捕获与文件操作

本文详细介绍了Go语言中的异常捕获机制,包括errors接口、panic、defer和recover的使用。同时,讲解了文件操作的相关内容,如创建、打开、读写文件的方法及其注意事项,强调了文件管理和异常处理的重要性。

1.异常捕获

(0)导言

        程序在运行过程中是有出现错误的可能的,而通常情况下程序执行过程一旦出现错误就会中断。
        go语言中为了提高程序的可执行性,提出了异常捕获的机制。
        异常捕获允许用户通过特殊的写法,当程序运行出现异常时,不去执行错误的代码,
        而是转而执行一句没有问题的替代代码,从而避免程序中断。
        在go语言中提供了四种异常捕获的手段:errors接口panic中断defer延迟调用recover拦截

(1)errors接口捕获错误信息

        在go语言中错误可以分成三大类,分别为【编辑时异常】【编译时异常】【运行时异常】。
            1)编辑时异常:代码编写时的硬语法错误,例如少写逗号或者关键词丢失

                     num int = 100 
                    //丢失var关键字,程序根本不能执行
                    //错误通常为syntax error前缀,后接错误信息

            2)编译时异常:IDE将代码转换为可执行文件时由于逻辑问题导致的错误,例如除数为0

                     var num int = 10/0;
                    //除数为0,但是程序从语法上来讲没错误
                    //错误通常只有单一的错误信息,division by zero

            3)运行时异常:程序在运行后出现的错误,例如数组下标溢出

                    var arr [3]int = [3]int{1,2,3};
                    for i:=0; i<4; i++{
                        fmt.Println(arr[i]);
                    }
                    //虽然程序从语法结构上来讲没有任何错误
                    //而且也不存在例如除数为0,下标是负数这种逻辑上的硬错误
                    //但是程序在执行到i=3的时候就会引发数组下标越界的问题。
                    //其错误通常使用runtime error前缀,后接错误信息

        而go语言提供了一个名为errors的系统包,它专门用来处理当错误发生时的一些问题
        可以通过将errors提供的方法放在可能发生异常问题的地方,来避免程序由于出现异常而中断

            err := errors.New("错误信息");
            eg:
                var arr [3]int = [3]int{1,2,3};
                for i:=0; i<4; i++{
                    if(i>2){
                        err := errors.New("数组下标访问越界了!");
                        fmt.Println(err);
                        break;
                    }
                    fmt.Println(arr[i]);
                }

        ps:其实errors包提供的方法也只是一个提示信息,就和正常的输出语句没什么区别。
               仍旧需要自己编写逻辑支持,而不能说单独一个方法就能规避异常产生了。

(2)panic异常处理

        当程序出现会发生崩溃中断的异常时,系统会自动调用panic函数来对异常信息进行报告。
        与errors不同的是errors一般用来报告一些一般性质的错误,这些错误不一定会引发程序崩溃,
        而panic则是用来报告当程序遇到不可恢复的错误状态时的错误信息,这些错误一定会引发程序崩溃。

        panic(v {}interface);
        eg:
            fmt.Println("hello1")
            panic("hello2")//程序会在此中断,并打印panic:hello2    
            fmt.Println("hello3")

        ps:通常来讲我们不应该使用panic函数来报告一般性质的普通错误
               因为panic函数的调用会直接出发panic异常报告,从而强制导致程序崩溃中断
        ps:panic接收任何类型的值作为参数。

 (3)defer延迟调用

        defer是一个go语言中的关键字,它能够使得defer后的函数调用语句延迟执行,
        具体时机是当任务队列中的语句全部执行完毕后。
        eg:
            defer 函数调用

            defer fmt.Println("111");
            fmt.Println("222");
            fmt.Println("333");
            //输出结果顺序是:222 333 111

        它的作用类似于JavaScript中的window.setTimeout()延迟调用函数的作用,
        但两者并不完全相同:
            1)在JavaScript中延迟调用函数是通过系统loop(堆区)完成的。
              (堆区特征:先进先出)
              在JavaScript中的延迟调用函数在多个函数具有相同延迟时间时,
              哪个延迟函数语句先被绑定,哪个延迟函数语句就先执行。

                    window.setTimeout(function (){console.log(111);},1000};
                    window.setTimeout(function (){console.log(222);},1000};
                    //两个函数都等待一秒后执行
                    //输出结果一定是:111 222

            2)而在go语言中的defer延迟调用时通过栈区来完成的。
              (栈区特征:后进先出)
              在go语言中如果存在多个被defer就是的函数语句,那么哪个函数最后被defer修饰
              哪个函数就最先被执行

                    defer fmt.Println("111");
                    defer fmt.Println("222");
                    //两个函数都延迟调用
                    //输出结果一定是:222 111

            当函数中存在返回值时,要慎用defer。因为defer的存在可能会导致返回值赋值落后。

                var value int
                func Sum (num1,num2 int){
                    value = num1+num2
                }
                func main(){
                    defer Sum(10,20);
                    fmt.Println(value);//0
                }

    (4)recover接口拦截错误

        运行时panic异常一旦被引发就会导致程序崩溃,这当然不是我们愿意看到的。
        因为谁也不能保证程序不会发生任何运行时错误。
        Go语言为我们提供了专门用于“拦截”运行时panic的系统函数recover。
        它可以使当前的程序从运行时panic的状态中恢复并且重新获得流程控制权。
        相关说明:
            1)recover函数的作用是阻止panic函数运行,防止程序崩溃。
            2)recover函数只有在defer调用的函数中有效。
            3)recover函数具有返回值,返回值是捕获到的异常信息,默认值是nil
            4)recover函数必须在异常发生之前被声明,尽管由于defer的原因会最后执行
            5)recover函数只能检测到第一个发生的panic异常,并将其后的所有内容忽略
            6)recover函数的作用范围是函数范围,也就是说超过recover函数范围的部分不受忽略影响
        相关语法:
            func recover() interface{}
        具体案例:

            func proRecover (){
                //recover函数的常规使用方式
                //尽管defer的存在使得本AIIFE会最后执行
                //但是这个“声明”必须写在可能发生的异常之前
                defer func() {
                    result := recover();
                    if result != nil{
                        fmt.Println(result);
                    }
                }()
                //第一个异常:空指针
                var p *int;
                *p = 100;
                //第二个异常:数组下标越界
                arr := [3]int{1,2,3};
                idx := 100;
                arr[idx] = 100;
            }
            func main() {
                //程序执行,检测到空指针异常
                //并且proRecover函数内第一个异常后的所有代码都没能继续执行。
                proRecover();
                //但是超过了recover所在的函数范围的内容则不会受到影响
                //换句话说下面这句打印111会正常输出
                fmt.Println(111);
            }

 

2.文件操作

(0)导言

        go语言的文件操作沿袭了传统c语言对文件操作的规定,都是需要通过文件指针来读写操作文件。
        但不同之处在于go语言不需要创建File类型指针,而是通过OS系统包中提供的函数来完成对文件的操作。

(1)创建文件

        文件指针,err标识 := os.Create("文件路径");

fp,err := os.Create("./test.txt");

        ps:文件路径通常情况下都通过相对路径来创建,绝对路径可能会出现一些不必要的问题。
        ps:文件创建时如果不存在会创建新文件,如果文件存在会将原文件覆盖。   

(2)打开文件

        1)os.Open("文件路径")

            本方法默认以只读的权限打开文件,打开后不能修改文件内容。
            本方法不能创建新文件,即如果文件不存在,是会打开失败的。
            下面的os.OpenFile方法同理。

                fp,err := os.Open("./test.txt");
                defer fp.Close();
                if err!=nil{
                    fmt.Println("文件打开失败");
                    return;
                }
                fmt.Println("文件打开成功")
                length,err := fp.WriteString("hello\n大家好");
                if err!=nil {
                    fmt.Println("文件写入失败");
                    return;
                }
                fmt.Println(length);
                //输出结果显然是打开成功,写入失败

        2)os.OpenFile("文件路径",打开模式,操作权限)

            本方法打开文件的模式是根据参数来决定的,其中
            ·打开模式:    os._RDONLY(只读模式)
                                   os._WRONLY(只写模式)
                                   os._RDWR(读写模式)
                                   os._APPEND(追加模式)

            ·操作权限:    0    没有任何权限
                                   1    执行权限(如果文件是可执行文件,那么有权执行文件)
                                   2    写权限
                                   3    写权限和执行权限
                                   4    读权限
                                   5    读权限和执行权限    
                                   6    读权限和写权限
                                   7    读写权限和执行权限

                fp,err := os.OpenFile("./test.txt",os.O_RDWR,7)
                defer fp.Close();
                if err!=nil{
                    fmt.Println("文件打开失败");
                    return;
                }
                fmt.Println("文件打开成功")
                length,err := fp.WriteString("hello\n大家好");
                if err!=nil {
                    fmt.Println("文件写入失败");
                    return;
                }
                fmt.Println(length);
                //此时输出结果为:文件打开成功 15

(3)开闭原则

        文件打开(创建后默认打开)后必须手动进行关闭操作,通过文件指针打开的文件不会自动关闭。
        如果不主动进行关闭,会大量占用系统的内存memory资源与缓冲区buffer资源

            fp,err := os.Create("./test.txt");
            if err!=nil{
                fmt.Println("文件创建失败");
                return
            }
            defer fp.Close();
            fmt.Println("文件创建成功");

        ps:可以使用defer延迟调用,保证文件关闭操作总是在最后执行。避免影响文件的其他读写操作
        ps:一个程序能够打开的文件数量是有限的,如果超过65535个文件会产生不可预知的后果。 

(4)写入文件

        1)相关说明

            ·文件写入必须在文件创建成功或打开成功之后进行,文件打开之后必须被正确关闭!!
            ·在windows换行是\n\r, unix和linux是\n    
            ·go语言中中文占3个字符长度

        2)写入方法

            所有的写入方法如果不是从文件的末尾开始写入内容,都会从写入的位置开始覆盖源内容。
            ·fp.WriteString("string")

                length,err := fp.WriteString("写入内容");//length是写入字符串的长度
                eg:
                    length,err := fp.WriteString("hello\n大家好");
                    if err!=nil {
                        fmt.Println("文件写入失败");
                        return;
                    }
                    fmt.Println(length);//15

            ·fp.Write([]byte)

                length,err := fp.Write([]byte{});//length是写入字符串的长度
                eg:
                    tempStr := "你好我是一个雇佣兵,我就要上前线了";
                    tempSlice := []byte(tempStr);//字符串强转字符切片
                    length,err := fp.Write(tempSlice);
                    fmt.Println(length);

            ·fp.WriteAt()

                length,err := fp.WriteAt([]byte, int64);//idx是开始写入文件内容的光标位置
                eg:
                    tempStr := "你好我是一个雇佣兵,我就要上前线了";
                    tempSlice := []byte(tempStr);//字符串强转字符切片
                    length,err := fp.WriteAt(tempSlice, 0);
                    fmt.Println(length);

        3)光标偏移

            写入文件时如果需要主动调整写入的位置,可以通过seek方法来进行“光标”的调整

n,err := fp.seek(offset:偏移量,whence从哪个位置计算偏移量);

                whence是一个int类型的参数:
                    io_SeekCurrent    --之前是-->  os.SEEK_CUR     当前位置
                    io_SeekEnd        --之前是-->  os.SEEK_END        结束位置
                    io_SeekStart    --之前是-->  os.SEEK_SET        起始位置
                n为最后得到的光标所在位置
            ps:
                如果当前光标已经在末尾,而offset偏移量又继续向后偏移
                那么最终写入文件的时候,实际内容紧挨着前文,但是文件整体长度会发生变化。

                    已知文件内容是abcde (5字节大小)
                    然后执行下列代码
                        n,_ := fp.seek(10, io,SeekEnd)
                        length,_ := fp.WriteAt([]byte{'-'},n)
                        fmt.Println(n,length) 
                    那么最终控制台中输出结果是:15 1
                    而文件内容会变更为abcde- (16字节大小)

(5)读取文件

        1)相关说明

            ·文件读取必须在文件创建成功或打开成功之后进行,文件打开之后必须被正确关闭!!

        2)读取方法

            所有的读取方法都是默认读取到文件结束为止。如果存储数据的容器有大小限制,
            那么读取方法就会按照容器的大小限制读取,直到将容器存满为止。
            ·fp.Read(容器)

                    已知文件内容是abcde 
                    然后执行下列代码
                        data := make([]byte, 100);
                        fp.Read(data);
                    那么最终控制台中输出结果是:abcde

            ·fp.readAt(容器,光标位置)

                    已知文件内容是abcde 
                    然后执行下列代码
                        data := make([]byte, 100);
                        n,_ := fp.Seek(1,io.SeekStart);
                        fp.ReadAt(data,n);
                    那么最终控制台中输出结果是:bcde

            ·缓冲区读取
                除了直接通过文件进行读取之外,还能够使用系统提供的buffer缓冲区进行读取。
                因为buffer缓冲区提供了很多读取文件的方法。

                    已知文件内容是:abcde
                                  12345 
                    然后执行下列代码
                    buffer := bufio.NewReader(fp)
                    for{
                        buf,err := buffer.ReadBytes('\n');//遇到\n就停止当前次读取的内容,但是当前这个字符也能读取
                        if err!=nil{                      
                            if err == io.EOF{//文件末尾
                                break
                            }
                        }
                        fmt.Printf("%s",string(buf));    
                    }
                    那么最终控制台中输出结果是:abcde
                                             12345


                            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值