【C++】异常与文件IO、智能指针

版权声明:本文为博主原创文章,未经博主允许不得转载。Copyright (c) 2018, code farmer from sust. All rights reserved. https://blog.csdn.net/sustzc/article/details/82734943

传统错误处理方式
    1.终止程序 (除数为0)
    2.返回一个错误码  监视窗口输入err,hr查看错误信息描述  
        DWORD GetLastError(VOID); //微软提供的API,获取最近一次发生错误的错误码  DWORD双字占用四个字节,也可以用size_t接收返回值。
        在工具那一栏找到错误查找,输入相应的错误码即可找到错误信息描述。(ps:在该窗口查找的错误码是有上限的)
    3.返回一个合法值,让程序处于某种非法的状态(坑爹的atoi())    如果字符串中包含abc之类的字符,并不会转换。(abc有可能是16进制字符)
    4.调用一个预先准备好在出现”错误”的情况下用的函数(回调函数)
    5.暴力解决方式:abort()  //程序崩溃掉   或者 exit() // 程序终止
    6.使用goto语句      http://www.baidu.com    相当于http:是标签,//后面是注释内容,这样是可以编译的。
        goto语句只能在当前函数体内跳转
    7.setjmp()和longjmp()组合
        将发生错误和处理错误的地方分离
            jmp_buf buf; // buf是个整型数组,保存了16个寄存器信息
            longjmp(buf, 1);  // longjmp函数中第一个参数是个全局数组,第二个参数传入整型的值即可(一般当做错误状态处理)。
        
        #include<setjmp.h>
            int iState = setjmp(buf);   // 先要设置好保存上下文的buf   这里iState正常情况返回0,如果非0就去异常处理。
        缺陷:
            setjump必须先调用,在异常位置通过调用longjmp以恢复先前被保存的程序执行点(上下文数据),
        否则将导致不可预测的结果,甚至程序崩溃在调用setjmp的函数返回之前调动longjmp,否则结果不可预料。
    
    异常,当一个函数发现自己无法处理的错误时抛出异常,让函数的调用者直接或间接的处理这个问题。
        try
        {}
        catch(int err)   // 抛出异常的类型需要给出,err表示异常类型的编号,用switch...case接收
        {}
    
        1. 异常的抛出和捕获
            a.异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个处理代码   (不会发生隐式类型转换)
                比如抛出字符类型异常,但是只有整型类型的异常处理,这时候就会代码崩溃。
            b.被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
            c.抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对
        象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
        2. 栈展开
            抛出异常的时候,将暂停当前函数的执行,开始查找对应的匹配catch子句。首先检查throw本身是否在try块内部,
        如果是再查找匹配的catch语句。如果有匹配的,则处理。没有则退出当前函数栈,继续在调用函数的
        栈中进行查找,不断重复上述过程,若到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找
        匹配的catch子句的过程称为栈展开。找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
        3. 异常捕获的匹配规则
            异常对象的类型与catch说明符的类型必须完全匹配。只有以下几种情况例外:
                a.允许从非const对象到const的转换
                b.允许从派生类型到基类类型的转换
                c.将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针
        4. 异常重新抛出
            有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,
        catch则可以通过重新抛出将异常传递给更上层的函数进行处理。
            
            try
            {
            
            }
            catch(...)  // 不关心是什么类型的异常   万能异常捕获
            {
                // 相应的处理
                throw;  // 这里不处理异常,重新抛出
            }
    异常规范
        在函数声明之后,列出该函数可能抛出异常类型,并保证该函数不会抛出其他类型的异常。
            void TestFunc()throw(int, double)  // 异常参数列表中列出来可能抛出的异常类型
            {
                throw '1';  // 有的编译器可能不支持这种异常规范
            }
        
            注意:
                1.成员函数在类内声明和类外定义两处必须有相同的异常规范
                2.函数抛出一个没有被列在它异常规范中的异常时(且函数中抛出异常没有在函数内部进行处理),
            系统调用C++标准库中定义的函数unexpected()。
                3.如果异常规范为throw(),则表示不会抛出任何异常,该函数不用放在try块中。
                    void TestFunc()throw()
                    {
                        throw 1;
                    }
                4.派生类的虚函数的异常规范必须与基类虚函数的异常规范一样或更严格(是基类虚函数的异常的子集)。
            因为:派生类的虚函数被指向基类类型的指针调用时,保证不会违背基类成员函数的异常规范。
        
        注意:最好不要在构造函数和析构函数中抛异常。
            a.构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
            b.析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
    
        exception类是C++定义的一个标准异常的基类,如果需要我们可以通过继承exception类定义合适的异常类。
    
            try
            {
                int *p = new int[0x7fffffff/4];
            }
            
            catch(const exception& e)
            {
                cout<<typeid(e).name()<<endl;  // 查看库里面异常类型名
                e.what();   // what是一个抽象类中的方法(调用what()方法以获得其特性的显示说明)    这里抛出bad_alloc异常
            }
        
        C++中存在四种类型转换,dynamic_cast转换失败就会抛出bad_cast异常。
            throw
            try 与 catch 必须一起使用。
    
    不推荐使用异常的原因:异常处理是在运行阶段处理,这可能会影响到性能,而C++追求的就是性能的极致。
        抛出的异常不具体。
    
    总有一个地方可能我们没有考虑到而存在内存泄露。那能否让程序自己去控制在不需要资源时自动将其归还给系统呢?
        
智能指针--替用户管理资源(用户申请)
    解决了用户忘记释放资源,引起资源泄漏的问题。
        通过构造函数来分配空间,析构函数来回收空间。
    
    auto_ptr  资源的转移

    T* operator->()
    {
        return _ptr;
    }
    
    scoped_ptr   boost
        管理单个对象
        防止被拷贝  (将拷贝构造函数和赋值运算符重载的访问属性改为私有)   单个对象独占资源,不需要共享
            拷贝构造和赋值重载只给出声明不给出定义。
        声明为友元函数,就可以访问了。  (只给出声明,就不可以访问了。)
        
        C++ 11在拷贝构造和赋值重载后面加上=delete后,就不会被拷贝了。
            拷贝都是浅拷贝。
        C++11
            unique_ptr
            shared_ptr
            weak_ptr
    scoped_array
        管理一段连续空间
        T& operator[](size_t index)
        {
            return _ptr[index];
        }
    STL已经有了功能更加强大的vector,因此不需要scoped_array。
    
    RAII(Resource Acquisition Is Initialization)   资源请求即初始化  +  资源管理权转移  释放的权限bool _owner   (拷贝构造函数)
                                                                                        该对象是否具有释放资源的权利
                                                                                            可能会造成野指针
    为什么没有删除auto_ptr?
        为了兼容以前的工程代码。
    建议:什么情况下都不要使用AutoPtr
    资源分配即初始化:定义一个类来封装资源的分配和释放,在构造函数完成资源的分配和初始化,在析构函数完成资源的清理,
可以保证资源的正确初始化和释放。
    shared_ptr
        引用计数(int / static int)   使用int* _pCount; 指向引用计数
    不能修改别的用户空间的引用计数    资源泄漏     有几个用户空间,就有几个引用计数。
        注意:最后别忘记清理引用计数的空间。
        
        没有管理空间
        管理空间
            共享
            独占
    
    一般方式   malloc    文件
        typedef typename DF<T>::PDF PDF;   必须要用类型关键字typename  静态变量类型也是这样定义的,因此需要类型关键字标识。
    
    重载()   函数调用运算符
    
    void operator()(T*& pf)
    
    定制删除器
        仿函数  重载()   把函数当做对象来使用     Del()(_ptr);   相当于调用Del构造函数创建无名对象  Del d; d(_ptr);
        对应资源去匹配对应的释放操作-->由用户来指定
        
        1.函数指针;
            给出删除操作的模板类型(new malloc file)
        2.仿函数对象。
    
        缺陷
            不支持多线程(自己写的)   加锁 或者 原子操作
            存在循环引用的问题。
    
    循环引用
        双向链表
        #include <memory>
        
        处理多线程 _MT_DECR(_Mtx, _Uses)
            销毁sp1时,断开指向,由于别的pNext指向该空间,因此不能释放该结点,导致两个结点都不能释放而内存泄漏。
            
    要解决shared_ptr存在的循环引用问题,必须借助weak_ptr。
    
    weak_ptr  引用计数
        不能单独使用,需要配合shared_ptr使用。    修改pNext 和 pPre的类型为weak_ptr类型。
            use计数   shared_ptr
            weak计数 销毁结点的时候需要销毁资源,必须明确资源管理的关系。
                管理就是指向关系。   最后销毁引用计数
                
            class Ptr_Base
            {
            private:
                T* _ptr; // 指向用户空间
                Ref_Count_Base* _ref; // 引用计数
            };
            
            class Ref_Count_Base
            {
            private:
                virtual void _Destory() = 0; // 释放用户空间
                virtual void _Delete_this() = 0; // 释放引用计数
                
                Ref_Count_Base()
                :Uses(1)
                ,Weaks(1)
                {}
                
                long Uses;
                long Weaks;
            };
            
            默认构造函数中Uses和Weaks的初始化列表中的初始值为1。
            
            class Ref_Count
            {
                T* _ptr;
            };
            
            class Ref_Count_del
            {
                T* _ptr; // 管理用户空间
                Dx _dt; // 释放方式
            };
        
            class Ref_Count_del_alloc
            {
                T* _ptr;
                Dx _dt;
                Alloc _al; // 给出引用计数空间的方式  (空间配置器)
            };
            
IO流
    C语言IO复习
        int a = 1;
        scanf("%d", a);  // 这里没有对a取地址,但是这里要求是一个地址,那么就会取到1的地址,但是不一定是允许访问的,有问题。
    
    C++流是信息从外部设备(如键盘)向内存输入和从内存向外部输出设备(如显示器)输出的过程,这种输入输出的过程被比喻为"流"。
        C++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类。
        cin是istream类的一个对象,cout是ostream类的一个对象。
        istream和ostream继承自iostream。
        操作文件对象 ifstream 和 ofstream   <fstream>
        操作字符串对象 istringstream 和 ostringstream  <sstream>
        cerr和clog标准错误输出流,输出设备是显示器在流类库中,最重要的两部分功能为标准输入/输出(standard input/output)
    和文件处理。在C++的流类库中定义了四个全局流对象:cin,cout,cerr和clog:cin标准输入流对象,键盘为其对应的标准设备;
    cout标准输出流对象,显示器为标准设备。在新库中要使用这四个功能,必须包含文件并引入std标准命名空间。
        cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错
    了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据。不可能用刷新来清
    除缓冲区,所以不能输错,也不能多输。
        输入的数据类型必须与要提取的数据类型一致,否则出错。出错只是在流的状态字state中对应位置位(置1),程序继续。
            hex 16进制  dec 10进制  oct 8进制  left 左对齐  right 右对齐
        空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入。但如果是字符型和字符串,则空格(ASCII
    码为32)无法用cin输入,字符串中也不能有空格。回车符也无法读入。
    
        cerr错误的优先级高。  现在的编译器下这几个没有区别了。
        
        C++根据文件内容的数据格式分为二进制文件和文本文件。
        
        文件操作步骤
            1. 定义一个文件流对象
                ifstream ifile(只输入用)
                ofstream ofile(只输出用)
                fstream iofile(既输入又输出用)
            2. 使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
            3. 使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
            4. 关闭文件

猜你喜欢

转载自blog.csdn.net/sustzc/article/details/82734943