慕课网-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.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Porschev[钟慰]的专栏

一个例子理解C#位移

很多人提问,不知道C#位移,可能有些人在面试中也遇到过 其实很简单。。。 C#位移运算符: 左移:<< 右移:>> 位移理解可能简单一些:其实就是数据转换成二进...

2117
来自专栏微信公众号:Java团长

Java中的十个&quot;单行代码编程&quot;(One Liner)

本文列举了十个使用一行代码即可独立完成(不依赖其他代码)的业务逻辑,主要依赖的是Java8中的Lambda和Stream等新特性以及try-with-resou...

982
来自专栏章鱼的慢慢技术路

Go语言相关练习_选择题(1)

 解析:Go语言的内存回收机制规定,只要有一个指针指向引用一个变量,那么这个变量就不会被释放(内存逃逸),因此在Go语言中返回函数参数或临时变量是安全的。

621
来自专栏跟着阿笨一起玩NET

VB.NET自我总结语法

1061
来自专栏小樱的经验随笔

Codeforces 842A Kirill And The Game【暴力,水】

A. Kirill And The Game time limit per test:2 seconds memory limit per test:256 m...

2877
来自专栏一个会写诗的程序员的博客

《Kotlin极简教程》第五章 Kotlin面向对象编程(OOP)一个OOP版本的HelloWorld构造函数传参Data Class定义接口&实现之写pojo bean定一个Rectangle对象封

We frequently create a class to do nothing but hold data. In such a class some s...

2084
来自专栏机器学习入门

挑战程序竞赛系列(86):3.6极限情况(3)

挑战程序竞赛系列(86):3.6极限情况(3) 传送门:AOJ 2201: Immortal Jewels 翻译参考至hankcs: http://www....

21910
来自专栏数据结构与算法

3027 线段覆盖 2

3027 线段覆盖 2  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果 题目描述 Descript...

2896
来自专栏進无尽的文章

编码篇 - 正则表达式及其相关

有时我们需要在一大段长文本中过滤出我们需要的字段,或者检验该文本是否符合要求(该文本是否是邮箱,链接,电话号码或身份证),这时候就需要用到正则表达式了,当然我们...

1122
来自专栏分布式系统和大数据处理

.Net中的反射(动态创建类型实例) - Part.4

在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它。可以说,前面三节,我们学习的都是反射是什么,在接下来的...

1353

扫码关注云+社区

领取腾讯云代金券