• C++-运行时类型信息,异常(day11)


    一、运行时类型信息

    1、typeid运算符

    头文件:#include<typeinfo>

    C++的标准头文件,都对应相应的类

    //sizeof(类型/变量/表达式),返回内存大小

    typeid(类型/变量/表达式),返回typeinfo类型的对象,其中包含name()成员函数,返回字符串,描述类型信息

    虽然与函数调用形式相同,但是typeid是操作符。

    int x;
    
    cout<<typeid(int).name()<<endl;//不同编译器的字符串描述可能不同
    
    cout<<typeid(x).name()<<endl;

    cout<<typeid(int* [5]).name()<<endl;

    cout<<typeid(int(*) [5]).name()<<endl;//数组指针
    class X{
    
    protected:
    
      virtual void foo(void){} 
    
    };
    
    
    
    class Y{
    
    protected:
    
      void foo(void){}  
    
    }
    
    
    
    class Z{
    
      void foo(void){}
    
    };
    
    void func(X& x){
    
    /*
    
      if(!strcmp(typeid(x).name(),"1Y")){
    
        cout<<"Y"<<endl;
    
      }
    
      
    
      else if(!strcmp(typeid(x).name(),"1Z")){
    
        cout<<"Z"<<endl;
    
      }
    
      else(!strcmp(typeid(x).name(),"1X")){
    
        cout<<"X"<<endl;
    
      }
    
    */
    //typeinfo类中已经重载的operator==函数
      if(!strcmp(typeid(x).==typeid(Y))){
    
        cout<<"Y"<<endl;
    
      }
    
      
    
      else if(!strcmp(typeid(x)==typeid(Z))){
    
        cout<<"Z"<<endl;
    
      }
    
      else(!strcmp(typeid(x)==typeid(X)){
    
        cout<<"X"<<endl;
    
      }
    
    }

    注意:

      typeid的使用是基于基类中存在虚函数,并且子类继承并覆盖了虚函数。否则typeid无法获取类型信息。

    2、动态类型转换运算符(day3)

    目标类型变量=dynamic_cast<目标类型>目标源类型变量

    使用场景:适用于具有多态继承关系的父子类指针或者引用之间的显式转换。

    class A{virtual void foo(void){}};
    
    class B:publicA{void foo(void)};
    
    class C:publicA{void foo(void)};
    
    class D{};
    
    
    
    B b;
    
    A* pa=&b;
    
    //B* pb=pa;//向下转换,编译报错
    
    //B* pb=static_cast<B*>(pa);//ok
    
    B* pb=dynamic_cast<B*>(pa);//ok
    
    
    
    //C* pc=static_cast<C*>(pa);//因为pa已经指向了B*的类型,虽然可以通过,但是不安全
    
     C* pc=dynamic_cast<C*>(pa);//程序执行阶段进行转换,而静态转换是程序编译阶段进行转换,虽然不报错,但是pc的地址将为空。执行时动态类型转换会做类型检查,如果是无关的类,无法转换。
    D* pc=dynamic_cast<D*>(pa);//也为空
    
    //可以打印出pa,pb,pc的地址,可见pc为空地址
    
    
    
    //使用引用的不合理类型动态转换时,执行时会进程会被终止

    dynamic_cast在转换的过程中,会根据多态的特性,检查父子类的指针或者引用目标类型是否一致,如果一致则转换成功,否则转换失败,如果是指针转换,则返回NULL,如果是引用转换,则抛出异常“bad_cast”


    二、异常(Exception)

    1、常见错误

      1)语法错误

      2)逻辑错误

      3)功能错误

      4)设计缺陷

      5)需求不符

      6)环境异常

      7)操作不当

    2、C的错误处理机制

    1)通过返回值表示错误

    class A{
    
    public:
    
      A(void){cout<<"A::A()"<<endl;}
    
      ~A(void){cout<<"~A::A()"<<endl;}
    
    };
    
     
    
    //通过返回值表示错误
    
    int func3(void){
    
      A a;
    
      FILE* fp=fopen("none.text","r");
    
      if(fp==NULL){
    
        cout<<"file open error!"<<endl;
    
        return -1;
    
      }
    
      fclose(fp)
    
      return 0;
    
    }
    
     
    
    int func2(void){
    
      A a;
    
      if(func3()==-1){
    
        return -1;
    
      }
    
      return 0;
    
    }
    
    int func1(void){
    
      A a;
    
      if(func2()==-1){
    
        return -1;
    
      }
    
      //...
    
      retuern 0;
    
    }
    
     
    
    int main(void){
    
      if(func1()==-1){
    
        return -1;
    
      }
    
      //...
    
      return 0;
    
    }
    
     
    
    
    
    
    //通过返回值表示错误
    
    栈区对象在出现异常之后,能够正常释放

    2)通过远程跳转来处理错误

    
    
    
     
    jmp_buf g_env;//包含头文件#include<setjmp.h>
    class A{
    
    public:
    
      A(void){cout<<"A::A()"<<endl;}
    
      ~A(void){cout<<"~A::A()"<<endl;}
    
    };
    
     
    
    //通过返回值表示错误
    
    int func3(void){
    
      A a;
    
      FILE* fp=fopen("none.text","r");
    
      if(fp==NULL){
    
        longjmp(g_env,-1);
      }
      //...
    
      fclose(fp)
    
      return 0;
    
    }
    
     
    
    int func2(void){
    
      A a;
    
      func3();
    
      return 0;
    
    }
    
    int func1(void){
    
      A a;
    
      func2();
    
    
      //...
    
      retuern 0;
    
    }
    
     
    
    int main(void){
    
      if(setjmp(g_nev)==-1){//先设置g_env,如果有错误,会再次直接跳转到此处
        cout<<"file open error!"<<endl;
      }
      func1();   //...   return 0; }
     

    使用远程跳转栈区对象无法得到释放

     

     通过返回值表示错误

      优点:函数调用路径中所有的局部对象都能够得到正常的析构,不会内存泄漏。

      缺点:错误处理流程比较复杂,逐层判断,代码臃肿

    通过盐城跳转机制处理错误

      优点:不需要逐层判断,实现一步到位的错误处理,代码精简

      缺点:函数调用的路径中布局对象失去被析构的机会,形成内存析构

     3、C++的异常处理机制

       结合C中两种错误处理的优点,同时避免他们的缺点,在形式上实现一步到位的错误处理,无需逐层判断返回值,所有的局部对象得到正常的析构。

    class A{
    
    public:
    
      A(void){cout<<"A::A()"<<endl;}
    
      ~A(void){cout<<"~A::A()"<<endl;}
    
    };
    
     
    
    //通过返回值表示错误
    
    int func3(void){
    
      A a;
    
      FILE* fp=fopen("none.text","r");
    
      if(fp==NULL){
    
        throw -1//抛出异常
      }
      //...
    
      fclose(fp)
    
      return 0;
    
    }
    
     
    
    int func2(void){
    
      A a;
    
      func3();
    
      return 0;
    
    }
    
    int func1(void){
    
      A a;
    
      func2();
    
    
      //...
    
      retuern 0;
    
    }
    
     
    
    int main(void){
    
     try{
        func1();
        //...出现异常,此处将不会得到执行,直接跳转到catch
      }
      catch(int ex/*抛出的异常数据类型*/){
        cout<<"file open error!"<<endl;
        return -1;
      }
    
    
      //...
    
      return 0;
    
    }
    
    
    如果执行到throw语句,会逐层返回执行},并且内存会得到释放

    4、C++异常语法

    (1)异常抛出

      throw 异常对象;//抛出的异常会被放到安全区,无法手动访问

    如:

      throw -1;

      throw "file error";

      throw 对象;

      

    (2)异常捕获

      try{

        //可能引发异常的语句

      }

      catch(异常类型1){

        //异常类型1的处理

      }

      catch(异常类型2){

        //异常类型2的处理

      }

      ...

      catch(.../*可以匹配任意类型*/){

        //针对其他类型的处理

      }

      

    class FileError{
    public:
      FileError(){}
      FileError(const string& file,int line):m_file(file),m_line(line){
        cout<<"抛出位置"<<m_file<<","<<m_line;
      }
    private:
      string m_file;
      int m_line;
    };
    class A{
    
    public:
    
      A(void){cout<<"A::A()"<<endl;}
    
      ~A(void){cout<<"~A::A()"<<endl;}
    
    };
    
     
    
    //通过返回值表示错误
    
    int func3(void){
    
      A a;
    
      FILE* fp=fopen("none.text","r");
    
      if(fp==NULL){
    
        throw FileError(__FILE__,__LINE__);
        /*
        注意,是双下划线,而不是单下划线
    
        __FILE__ 包含当前程序文件名的字符串
        __LINE__ 表示当前行号的整数
        __DATE__ 包含当前日期的字符串
        __STDC__ 如果编译器遵循ANSI C标准,它就是个非零值
        __TIME__ 包含当前时间的字符串
         */
        
        //FileError ex;
    
        //throw ex;
        
        throw "file error";
    
        throw -1//抛出异常
      }
      //...
    
      fclose(fp)
    
      return 0;
    
    }
    
     
    
    int func2(void){
    
      A a;
    
      func3();
    
      return 0;
    
    }
    
    int func1(void){
    
      A a;
    
      func2();
    
    
      //...
    
      retuern 0;
    
    }
    
     
    
    int main(void){
    
     try{
        func1();
        //...出现异常,此处将不会得到执行,直接跳转到catch
      }
      catch(int ex/*抛出的异常数据类型*/){
        cout<<"file open error!"<<endl;
        return -1;
      }
      catch(const char* ex){
        cout<<"file error!"<<endl;
        return -1;
      }
      catch(FileError& ex){//如果抛出的是对象,最好是用引用
         cout<<"FileError"<<endl;
         return -1;
      }
    
    
    
      //...
    
      return 0;
    
    }

    注意:

      (1)如果没有类型可以匹配,那么抛出的异常将被系统锁捕获,进程将被回收。内存被释放

      (2)如果有两个连续的throw语句,只会被执行一个,因为在throw时候,直接跳转到}执行。

      (3)如果抛出的是类的对象,最好使用引用。

      (4)在面向对象的编程中,一般都是抛出对象,而不是整数或者字符串等基本类型,因为类比基本类型可以存储更多信息。比如日志等 

    5、异常-扩展

    class A{};
    
    class B:public A{};
    
     
    
    void func(void){
    
      //...
    
      throw(B);
    
      //throw(A);
    
      
    
    }
    
     
    
    int main(void){
    
      try{
    
        func();
    
      }
    
      catch(A& ex){//向上造型可以匹配B类异常
    
        cout<<"捕获到A类异常"<<endl;
    
        return -1;
    
      }
    
      catch(B& ex){
    
        cout<<"捕获到B类的异常"<<endl;
    
        return -1;
    
      }
    
      return 0;
    
    }
    
    //上述代码中B异常将无法被捕获,无论是抛A,还是抛B,正确的处理方式应该把子类的异常捕获放在基类之前,防止发生向上造型。

    注意:

      catch的匹配是自上而下进行匹配,而不是选择最优匹配,所以应该把子类的异常捕获放在基类之前,防止发生向上造型。

    6、异常说明
    1)可以在函数原型中增加异常说明,说明该函数可能抛出的异常类型。提前通知编译器,函数会抛出的异常类型
     
     返回类型 函数名(形参表)[cosnt]throw(异常类型表){...}
      

      不加异常说明列表,异常也能够被正常捕获,和不加的区别在于,如果函数抛出了与说明列表不符的类型,这个异常将不会被捕获。自然也会被系统所捕获。

    2)函数的异常说明只是一种承诺,表示该函数不会抛出说明列表意外的类型。意外的异常将会被系统所捕获。

    3)如果不写异常说明,表示可以抛出任何异常

    4)空异常说明,throw(),表示不会抛出任何异常。

    5)如果函数的声明和定义分开,在声明和定义部分都要加上异常说明。并且说明列表必须相同,但是顺序可以改变。


    7、异常说明与多态
    class FileError{};
    class MemError{};
    
    class Base{
    public:
      virtual void func(void)throw(FileError,MemError){}  
    };
    
    class Derived:public Base{
    public:
      void func(void){}//虚函数覆盖会失败,因为子类的虚函数覆盖函数没有异常说明,这里异常说明范围可以缩小,但是不能扩大
    };

    如果基类中的虚函数带有异常说明,它的子类中,该函数的覆盖版本不能比基类版本抛出更多异常,否则编译器报出“放松throw限定”错误

  • 相关阅读:
    HDU_1709 The Balence (生成函数)
    Ural_1003 Parity(并查集)
    HDU_1171 Big Event in HDU(生成函数)
    Ural_1306. Sequence Median(堆)
    POJ_2823 Sliding Window(单调队列)
    HDU_2065 "红色病毒"问题(指数型生成函数)
    HDU_2082 找单词 (生成函数)
    最长上升子序列问题(LCS)
    HDU_1284 钱币兑换问题(生成函数)
    HDU_2152 Fruit(生成函数)
  • 原文地址:https://www.cnblogs.com/ptfe/p/11300823.html
Copyright © 2020-2023  润新知