第十五章 - 友元、异常和其它

一,异常机制

1.1,异常机制的作用

C++异常是对程序在运行过程中发生异常情况的一种响应,异常提供了将程序的控制权从程序的一部分传递到另一部分的响应。异常的抛出和处理主要使用了以下三个关键字: try、 throw 、 catch 。

try:放在try块中的代码是可能发生异常的代码。
throw:这个关键字引发异常,紧随其后的值指出了异常的特征。throw语句实际上是跳转,即命令程序跳到另一语句。throw语句必须包含在try块中,也可以是被包含在外层函数的try块中。
catch:捕获异常,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型。try-catch语句形式如下:

ry{
        包含可能抛出异常的语句;
}
catch(类型名 [形参名]){  // 捕获特定类型的异常
        //异常处理程序
}
catch(...) {  // 三个点则表示捕获所有类型的异常
        //异常处理程序
}

1.2,使用异常机制处理异常

double fun(double x, double y){
    if(y == 0){
        throw y;  //除数为0,抛出异常
    }
    return x / y;
}

int main()
{
    double res;
    try{
        res = fun(2, 0);
    } catch(double val){
        cout<<"除数为:"<<val<<endl;
        exit(1);
    }
    return 0;
}

1.3,使用自定义的对象作为异常类型

使用自定义的对象作为异常类型的优点:

  • 可以使用不同的异常类型来区分引发不同的异常。
  • 对象可以携带信息,程序员可以根据这些信息来确定引发异常的信息。
class bad_func{
private:
    int v1;
public:
    bad_func(int v1){
        this->v1 = v1;
    }
    void mesg(){
        cout<<"除数不能为:"<<v1<<endl;
    }
};

double fun(double x, double y){
    if(y == 0){
        throw bad_func(0);  //除数为0,抛出异常
    }
    return x / y;
}

int main()
{
    double res;
    try{
        res = fun(2, 0);
    } catch(bad_func &exp){
        exp.mesg();
        exit(1);
    }
    return 0;
}

1.4,栈解退

假设在try块没有直接调用引发异常的函数,而是调用了另外一个会引发异常的函数,则程序的流程将从引发异常的函数直接跳到包含try块和处理程序的函数,这涉及到栈解退。栈解退的过程如下:如果一个函数由于异常而终止,程序将进行出栈的操作,但是不会在回退到栈的第一个返回地址后停止,而是继续回退栈,直到找到一个位于try块中的返回地址。然后控制权交给块尾的处理程序,而不是函数调用后的第一条语句,这个过程称为栈解退。

void funcA();
void funcB();
void funcC();

void funcA(){
    funcB();
}

void funcB(){
    funcC();
}

void funcC(){
    throw 0;  //抛出一个异常
}

int main()
{
    double res;
    try{
        funcA();
    } catch(int val){
        exit(1);
    }
    return 0;
}

funcC()函数引发了一个异常,此时会回退到调用funcC()的funcB(),在funcB()中发现funcC()并没有放到try块中,即在funcB()中没有对异常进行处理;此时会回退到调用funcB()的funcA(),同样在funcA()中发现funcB()并没有放到try块中,此时会回退到调用funcA()的main()函数,在main()函数中funcA()是放在try块中,此时程序的控制权就跳转到main()函数中。

1.5,throw-catch语句与函数参数与函数返回机制的区别

  • 函数func()中的返回语句将控制权返回到调用func()的函数,但是throw语句将控制权向上返回到第一个这样的函数,即包含能够捕获异常的try-catch块。
  • 引发异常时编译器总是创建异常对象的一个临时拷贝,即catch块中的参数是引用类型。
class bad_func{
private:
    int v1;
public:
    bad_func(int v1){
        this->v1 = v1;
        cout<<"bad_func构造函数"<<endl;
    }
    void mesg(){
        cout<<"除数不能为:"<<v1<<endl;
    }
    bad_func(const bad_func &p){
        this->v1 = p.v1;
        cout<<"bad_func复制构造函数"<<endl;
    }
    ~bad_func(){
        cout<<"bad_func析构函数: "<<this<<endl;
    }
};

double fun(double x, double y){
    if(y == 0){
        bad_func obj(0);
        cout<<"bad_func对象地址: "<<&obj<<endl;
        throw obj;  //除数为0,抛出异常
    }
    return x / y;
}

int main()
{
    double res;
    try{
        fun(2, 0);
    } catch(bad_func &p){
        cout<<"bad_func对象地址: "<<&p<<endl;
        exit(1);
    }
    return 0;
}

输出结果

bad_func构造函数
bad_func对象地址: 0x69fe6c
bad_func复制构造函数
bad_func析构函数: 0x69fe6c
bad_func对象地址: 0x6f0dc0
bad_func析构函数: 0x6f0dc0

Process returned 1 (0x1)   execution time : 0.008 s
Press any key to continue.

1.6,exception类

较新的C++编译器将异常合并到语音中。例如,为了支持该语言,exception头文件定义了exception类作为基类。其中有一个名为what()的虚函数,它返回一个字符串,该字符串的特征随实现而异。

class bad_func : exception{
private:
    int v1;
public:
    bad_func(int v1){
        this->v1 = v1;
        cout<<"bad_func构造函数"<<endl;
    }
    bad_func(const bad_func &p){
        this->v1 = p.v1;
        cout<<"bad_func复制构造函数"<<endl;
    }
    //重写虚函数what()
    const char* what(){
        cout<<"除数不能为0.";
    }
};

double fun(double x, double y){
    if(y == 0){
        bad_func obj(0);
        cout<<"bad_func对象地址: "<<&obj<<endl;
        throw obj;  //除数为0,抛出异常
    }
    return x / y;
}

int main()
{
    double res;
    try{
        fun(2, 0);
    } catch(bad_func &p){
        cout<<"bad_func对象地址: "<<&p<<endl;
        p.what();
        exit(1);
    }
    return 0;
}

二,RTTI

2.1,RTTI的作用

假设有一个类层次结构,其中的类都是从同一个基类派生而来的,则可以让基类指针指向其中任何一个类的对象。这样便可以调用这样一个函数:在处理一些信息后,选择一个类,并创建这种类型的对象,然后返回它的地址,而该地址可以被赋为基类指针。如何知道指针指向的是哪种类型的对象呢?在回答这个问题之前,先考虑为何要知道类型。可能希望调用类方法的正确版本,在这种情况下,这要该函数是类层次结构中所有成员都拥有的虚函数,则并不真正需要知道对象的类型。但派生类可能包含不是派生而来的方法,在这种情况下,只有某些类型的对象可以使用该方法。对于这种情况,RTTI提供了解决方案。

注意:

只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种层次结构,才应该将派生类对象的地址赋给基类指针。

2.2,RTTI的工作原理

C++ 有三个支持RTTI的元素

2.2.1,dynamic_cast

dynamic_cast的语法与作用

dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,用来执行”安全向下类型转换”,也就是用来决定某对象是否属于某继承体系中的某个类。通常,想知道类型的原因在于:知道类型后,就可以知道调用特定的方法是否安全。例如,有下面的类继承体系:

class Grand{...};
class Superb : public Grand{...};
class Magnificent : public Superb{...};

该运算符的用法如下,其中pg指向某个对象:

Superb *pb = dynamic_cast<Superb *>(pg);

这提出了这样的问题:指针pg的类型是否可以安全地转换为Superb *?如果可以,运算符将返回对象的地址,否则返回一个空指针。通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型,则下面的表达式将指针(*pt)转换为Type类型的指针。

dynamic_cast<Type *>(pt);

使用dynamic_cast来检查是否可以进行安全类型转换

class Grand{
public:
    virtual void speak(){
        cout<<"Grand Speak"<<endl;
    }
};

class Superb : public Grand{
public:
    virtual void speak(){
        cout<<"Superb Speak"<<endl;
    }
    virtual void say(){
        cout<<"Superb say"<<endl;
    }
};

class Magnificent : public Superb{
public:
    void speak(){
        cout<<"Magnificent Speak"<<endl;
    }
    void say(){
        cout<<"Magnificent say"<<endl;
    }
};

上面定义了三个类,Grand、Superb、Magnificent。Grand类定义了虚函数speak(),而其它的类都继承了该虚函数。Superb类定义了一个虚函数say(),而Magnificent也重新定义了它。程序定义了getOne()函数,该函数随机创建这三种类中某个类的对象。然后将对象的地址作为Grand*指针返回,循环将返回的指针赋给Grand*变量pg,然后使用pg调用speak()函数。因为这个函数是虚函数,所以代码能够正确调用指向的对象的speak()版本。如下:

Grand *pg;
for(int i = 0; i < 5; i ++){
    pg = getOne();
    pg->speak();
}

但是不能用相同的方式来调用say()函数,因为Grand类没有定义say()函数。然而,可以使用dynamic_cast运算符来检测是否可以安全地将pg转换为Superb类型指针。如果pg指向的对象的实际类型为Superb或Magnificent,则可以安全类型转换。在这两种情况下,都可以安全调用say()函数。

Grand *pg;
Superb *ps;
for(int i = 0; i < 5; i ++){
    pg = getOne();
    if(ps = dynamic_cast<Superb *>(pg)){
        ps->say();
    }
}

2.2.2,typeid

typeid运算符的语法与作用

typeid运算符能够确定两个对象是否是同种类型。它接受两个参数,一是类名,二是结果为对象的表达式。typeid运算符返回一个type_info对象的引用,其中type_info是在头文件typeinfo中定义的一个类。type_info类重载了==和!=运算符,以便于使用这些运算符对类型进行比较。

typeid运算符的使用

typeid(Magnificent) == typeid(*pg);

如果pg指向的是一个Magnificent对象,则上面表达式的结果为true,否则为false。如果pg是一个空指针,程序将引发bad_typeid异常。

2.2.3,type_info

type_info的语法与作用

type_info的实现随厂商而异,但包含一个name()成员,该函数返回一个随实现而异的字符串,通常是类的名称。

type_info的使用

cout<<typeid(*pg).name()<<endl;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值