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

一、运行时类型信息

1、typeid运算符

头文件:#include

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

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

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

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

 

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

 

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"<*/
//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(pa);//ok

B* pb=dynamic_cast(pa);//ok//C* pc=static_cast(pa);//因为pa已经指向了B*的类型,虽然可以通过,但是不安全
C* pc=dynamic_cast(pa);//程序执行阶段进行转换,而静态转换是程序编译阶段进行转换,虽然不报错,但是pc的地址将为空。执行时动态类型转换会做类型检查,如果是无关的类,无法转换。
D* pc=dynamic_cast(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
class A{public:A(void){cout<<"A::A()"<    cout<<"file open error!"<  }
  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_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限定”错误

 

转载于:https://www.cnblogs.com/ptfe/p/11300823.html


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部