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


