慕课网-C++远征之多态篇(下)-学习笔记

RTTI(运行时类型识别)

Run-Time Type Identification

  • typeid < - > dynamic_cast

例子:

class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落
};

class Bird:public Flyable
{
public:
    void foraging(){}
    virtual void takeoff(){}
    virtual void land(){}
private:
    //...
};

class Plane:public Flyable
{
public:
    void carry(){}
    virtual void takeoff(){}
    virtual void land(){}
};

使用时:

void doSomething(Flyable *obj)
{
    obj ->takeoff();
    //如果是bird,则觅食
    //如果是plane,则运输
    obj -> land();
}

如果对指针能进行判断。然后根据传入指针不同调用不同方法。

void doSomething(Flyable *obj)
{
    obj ->takeoff();
    cout << typeid(*obj).name() <<endl;
    if(typeid(*obj) == typeid(Bird))
    {
        Bird *bird = dynamic_cast<Bird *>(obj);
        //尖括号内是我们想要转化成的类型。
        bird -> foraging();
    }
    obj -> land();
}

总结:

dynamic_cast注意事项:

  • 只能应用于指针和引用的转换
  • 要转换的类型中必须包含虚函数
  • 转换成功返回子类的地址,失败返回NULL

typeid注意事项:

  • type_id返回一个type_info对象的引用
  • 如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数
  • 只能获取对象的实际类型.(不能判断当前指针)
type_info

name() & 运算符重载

RTTI代码示例
要求
#ifndef FLYABLE_H
#define FLYABLE_H
class Flyable
{
public:
    virtual void takeoff() = 0;//起飞
    virtual void land() = 0; //降落
};
#endif

#ifndef PLANE_H
#define PLANE_H

#include <string>
#include "Flyable.h"
using namespace std;

class Plane :public Flyable
{
public:
    void carry();
    virtual void takeoff();
    virtual void land();
};


#endif


#include <iostream>
#include "Plane.h"
using namespace std;

void Plane::carry()
{
    cout << "Plane::carry()" << endl;
}
void Plane::takeoff()
{
    cout << "Plane::takeoff()" << endl;
}
void Plane::land()
{
    cout << "Plane::land()" << endl;
}


#ifndef BIRD_H
#define BIRD_H
#include "Flyable.h"
#include <string>
using namespace std;
class Bird :public Flyable
{
public:
    void foraging();
    virtual void takeoff();
    virtual void land();
};

#endif // !BIRD_H

#include <iostream>
#include "Bird.h"
using namespace std;

void Bird::foraging()
{
    cout << "Bird::foraging()" << endl;
}
void Bird::takeoff()
{
    cout << " Bird::takeoff()" << endl;
}
void Bird::land()
{
    cout << "  Bird::land()" << endl;
}

main.cpp1:

#include <iostream>
#include "Bird.h"
#include "Plane.h"
using namespace std;
#include <stdlib.h>

void doSomething(Flyable *obj)
{
    cout << typeid(*obj).name() << endl;
    obj->takeoff();
    if (typeid(*obj) == typeid(Bird))
    {
        Bird *bird = dynamic_cast<Bird *>(obj);
            bird->foraging();
    }
    if (typeid(*obj) == typeid(Plane))
    {
        Plane *plane = dynamic_cast<Plane *>(obj);
        plane->carry();
    }

    obj->land();
}

int main()
{
    Bird b;
    doSomething(&b);

    system("pause");
    return 0;
}

运行结果:

运行结果
  • 传入的是bird。所以执行了bird分支下的觅食。
  • 当传入是plane时。执行carry。
plane-carry
代码说明typeid .dynamic_cast注意事项
int main()
{
    int i =0;
    cout <<typeid(i).name() << endl; 
}

输出为int。打印出数据类型。

int main()
{
    Flyable *p = new Bird();
    cout << typeid(p).name() << endl;
    cout << typeid(*p).name() << endl;
    system("pause");
    return 0;
}
p类型为Flyable *,*p类型为bird对象

将Flyable.h的两个纯虚函数改为普通的。

#ifndef FLYABLE_H
#define FLYABLE_H
class Flyable
{
public:
    void takeoff(){ };//起飞
    void land() {}; //降落
};
#endif

bird.h虚函数去掉。

#ifndef BIRD_H
#define BIRD_H
#include "Flyable.h"
#include <string>
using namespace std;
class Bird :public Flyable
{
public:
    void foraging();
    void takeoff();
    void land();
};

#endif // !BIRD_H

bird 和flyable变成普通的继承。

int main()
{
   // Flyable *p = new Bird();
   // Bird *b = dynamic_cast<Bird *>p;
   // //“dynamic_cast”:“Flyable”不是多态类型
   // 
   
   system("pause");
   return 0;
}

对于dynamic_cast.转换类型还是被转类型都要求有虚函数。

int main()
{
    Flyable p;
    Bird b = dynamic_cast<Bird>p;
    //“dynamic_cast”:“Flyable”不是多态类型

    system("pause");
    return 0;

  • 只能应用于指针和引用的转换
练习题
  • 继承关系不是RTTI的充分条件,只是必要条件,所以存在继承关系的类不一定可以用RTTI技术
  • RTTI的含义是运行时类型识别
  • RTTI技术可以通过父类指针识别其所指向对象的真实数据类型
  • 运行时类型别必须建立在虚函数的基础上,否则无需RTTI技术
巩固练习

定义一个能够移动(Movable)类,要求含有纯虚函数move
定义一个公交车(Bus)类,继承Movable类,并实现函数move,定义函数carry
定义一个坦克(Tank)类,继承Movable类,并实现函数move,定义函数shot。
定义函数doSomething(Movable *obj),根据s指向对象的类型调用相应的函数。

实例化公交车类和坦克类,将对象传入到doSomething函数中,调用相应函数

#include <iostream>
#include <stdlib.h>
#include <string>
#include <typeinfo>
using namespace std;

/**
 * 定义移动类:Movable
 * 纯虚函数:move
 */
class Movable
{
public:
    virtual void move() = 0;
};

/**
 * 定义公交车类:Bus
 * 公有继承移动类
 * 特有方法carry
 */
class Bus : public Movable
{
public:
    virtual void move()
    {
        cout << "Bus -- move" << endl;
    }
    
    void carry()
    {
        cout << "Bus -- carry" << endl;
    }
};

/**
 * 定义坦克类:Tank
 * 公有继承移动类
 * 特有方法fire
 */
class Tank :public Movable
{
public:
    virtual void move()
    {
        cout << "Tank -- move" << endl;
    }

    void fire()
    {
        cout << "Tank -- fire" << endl;
    }
};

/**
 * 定义函数doSomething含参数
 * 使用dynamic_cast转换类型
 */
void doSomething(Movable *obj)
{
    obj->move();

    if(typeid(*obj) == typeid(Bus))
    {
        Bus *bus = dynamic_cast<Bus *>(obj);
        bus->carry();
    }

    if(typeid(*obj) == typeid(Tank))
    {
        Tank *tank = dynamic_cast<Tank *>(obj);
        tank->fire();
    }
}

int main(void)
{
    Bus b;
    Tank t;
    doSomething(&b);
    doSomething(&t);
    return 0;
}

运行结果:

运行结果
异常处理
  • 异常:程序运行期出现的错误。
  • 异常处理:对有可能发生异常的地方做预见性的安排

网线- 内存不足。

关键字:

  • try...catch... :尝试 捕获
  • throw 抛出异常

思想:

主逻辑(try)与异常处理逻辑(catch)分开

异常处理

三个函数f1,f2,f3.用f2调用f1,f3调用f2.
当f1出现异常会往上抛。如果f2可以处理就可以处理完成。
如果不能处理,会继续进行异常的传播。f3捕获并处理。

void fun1()
{
    throw 1;
}

int main(){
    try {
        fun1();
    }catch(int)//throw的是1,所以用int捕获
    {
        //.....
    }
    return 0;
}

try{
    fun1();
}
catch(int)
{}
catch(double)
{}
catch)(...)//捕获所有的异常
{}

一个try可以有多个catch。不同异常做不同处理。

  • 抛出值,捕获数据类型。
  • 下面我们来做捕获值。
char getChar(const string& aStr,const int aIndex)
{
    if (aIndex > aStr.size())
    {
        throw string("ivalid index!");
    }
    return aStr[aIndex];
}

string str("hello world");
cahr ch;
try{
    ch = getChar(str,100);//这句抛异常,下句不会运行
    cout << ch << endl;
}catch(string& aval){
    cout << aval << endl;
}

常见的异常:

  • 数组下标越界
  • 除数为0
  • 内存不足

异常处理与多态的关系:

异常处理&多态

定义一个接口exception。多个子类来继承该类。那么我们可以通过父类对象捕获不同子类对象的异常。

void fun1()
{
    throw new SizeErr();
}

void fun2()
{
    throw new MemoryErr();
}

try{
    fun1();
}catch(Exception &e)
{
    e.xxx();
}

try{
    fun2();
}catch(Exception &e)
{
    e.xxx();
}

通过父类的引用,调用相应的子类处理函数。

异常处理代码示例
要求
#include <iostream>
#include <stdlib.h>
#include "IndexException.h"
using namespace std;
void test()
{
    throw 0.1;
}
int main(void)
{
    try
    {
        test();
    }
    catch (double)
    {
        cout << "exception" << endl;
    }
    system("pause");
    return 0;
}
  • 1 对应 int
  • 1.0 对应 double
catch (double &e)
    {
        cout << e << endl;
    }

可以打印出抛出来的异常值:如0.1

#ifndef EXCEPTION_H
#define EXCEPTION_H
class Exception
{
public:
    virtual void printException();
    virtual ~Exception() {};


};
#endif 


#include "Exception.h"
#include <iostream>
using namespace std;

void Exception::printException()
{
    cout << " Exception::printException()" << endl;
}


#ifndef INDEX_EXCEPTION_H
#define INDEX_EXCEPTION_H

#include "Exception.h"
class IndexException:public Exception
{
public:
    virtual void printException();

};
#endif


#include "IndexException.h"
#include <iostream>
using namespace std;

void IndexException::printException()
{
    cout << "提示:下标越界" << endl;
}


#include <iostream>
#include <stdlib.h>
#include "IndexException.h"
using namespace std;
void test()
{
    throw IndexException();
}
int main(void)
{
    try
    {
        test();
    }
    catch (IndexException &e)
    {
        e.printException();
    }
    system("pause");
    return 0;
}

运行结果:

运行结果
int main(void)
{
    try
    {
        test();
    }
    catch (Exception &e)
    {
        e.printException();
    }
    system("pause");
    return 0;
}

依然打印出数组的提示。父类的引用可以使用到子类的处理函数。

int main(void)
{
    try
    {
        test();
    }
    catch (...)
    {
        cout << "error" << endl;
    }
    system("pause");
    return 0;
}

通过...可以捕获到所有异常。

练习题

  • 在C++中异常处理通常使用try...catch...语法结构。
  • 一个try语句可以对应一个或多个catch语句,但不能没有catch语句
  • C++中使用throw抛出异常,通过catch捕获异常
巩固练习

函数division的两个参数为dividend(被除数)和divisor(除数)
要求用户输入除数和被除数,并作为参数传递给division函数
如果除数为0,则抛出异常,并被捕获,将异常的内容显示到屏幕上

#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;

/**
 * 定义函数division
 * 参数整型dividend、整型divisor
 */
int division(int dividend, int divisor)
{
    if(0 == divisor)
    {
        // 抛出异常,字符串“除数不能为0”
        throw string("除数不能为0");
    }
    else
    {
        return dividend / divisor;
    }
}

int main(void)
{
    int d1 = 0;
    int d2 = 0;
    int r = 0;
    cin >> d1;
    cin >> d2;
    // 使用try...catch...捕获异常
    try{
        r = division(d1,d2);
        cout << r << endl;
    }catch(string &str){
        cout << str <<endl;
    }

    return 0;
}

运行结果:

除数不能为0.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值