一、C++类型识别简介

1、C++类型识别简介

C++是静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改。

C++语言中,静态类型是对象自身的类型,动态类型是指针(引用)所指向对象的实际类型。

RTTI(Run-Time Type Information)即运行时类型识别,C++通过RTTI实现对多态的支持。
为了支持RTTI,C++提供了一个type_info类和typeid与dynamic_cast两个关键字。

2、type_info结构体

type_info :
  存储特点类型的相关信息,常用来比较对象类型,type_info类的具体内容由编译器实现来决定。其声明如下:

class type_info {
public:virtual ~type_info();bool operator== (const type_info& rhs) const;bool operator!= (const type_info& rhs) const;bool before (const type_info& rhs) const;const char* name() const;
private:type_info (const type_info& rhs);type_info& operator= (const type_info& rhs);
};

type_info的构造函数和赋值操作符为私有,因此,程序中创建type_info对象的唯一方法是使用typeid操作符。C++标准只是要求C++编译器需要实现type_info::name函数,但不同的编译器实现各不相同,因此typeid(int).name()不同编译器编译运行后输出不一样。

3、typeid关键字

typeid:
typeid语法规则如下:

typeid(expr);

typeid表达式返回type_info类型,expr可以是各种类型名,对象和内置基本数据类型的实例、指针或者引用。当作用于指针和引用时,将返回实际指向对象的类型信息。
  如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时确定;否则,typeid操作符返回表达式的静态类型,在编译时就可以确定。
  当把typeid作用于指针的解引用*p时,若指针p为0,则:如果p指向的类型是带虚函数的类类型,则typeid(*p)在运行时抛出一个bad_typeid异常;否则,typeid(*p)的结果与p的值是不相关的,在编译时就可以确定。

4、dynamic_cast关键字

dynamic_cast:
  动态类型转换,运行时类型安全检查。dynamic_cast会检查待转换的源对象是否真的可以转换成目标类型,这种检查不是语法上的,而是真实情况的。许多编译器都是通过vtable找到对象的RTTI信息的,如果基类没有虚方法,也就无法判断一个基类指针变量所指对象的真实类型。
dynamic_cast将一个指向基类的指针转换为一个指向派生类的指针,如果不能正确转换,则返回空指针。

C++语言提供了typeid关键字用于获取类型信息,typeid关键字返回对应参数的类型信息。typeid返回一个type_info类对象,当typeid的参数为NULL时将抛出异常。typeid的参数既可以时类型也可以是变量,当参数为类型,返回静态类型信息;当参数为变量,如果不存在虚函数表,返回静态类型信息,如果存在虚函数表,返回动态类型信息。

typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。

typeid在不同C++编译器实现是不同的。

RTTI(Run-Time Type Identification,运行时类型识别)

二、C++类型转换

C++类型转换分为向上类型转换和向下类型转换。

1、向上类型转换

C++语言中,向上类型转换描述的是子类向基类的强制类型转换,是一种隐式类型转换。在向上类型转换过程中,覆盖方法和子类对象数据丢失的现象称为切割。

#include <iostream>
using namespace std;class Base
{
public:Base(int value = 0){data = value;}virtual void print(){cout << "Base::print data = " << data << endl;}
protected:int data;
};class Derived : public Base
{
public:Derived(int value = 0){data = value;}virtual void print(){cout << "Derived print data = " << data << endl;}
protected:int data;
};int main(int argc, char *argv[])
{Derived d(100);//将子类向上转型为基类Base b = d;//直接赋值,产生切割b.print();//Base::print data = 0Base& rb = d;//引用赋值,不产生切割rb.print();//Derived print data = 100Base* pb = &d;//指针赋值,不产生切割pb->print();//Derived print data = 100//Derived* dp = pb;//error,不允许隐式向下转型return 0;
}

在向上强制转换过程中,使用指针和引用不会造成切割,而使用直接赋值会造成切割。

2、向下类型转换

C++语言中,向下类型转换描述的是基类向子类的强制类型转换,使用dynamic_cast进行向下强制类型转换。dynamic_cast会在运行时进行类型检查。如果向下转型是安全的(如果基类指针或者引用实际指向一个派生类的对象),dynamic_cast会返回类型转换后的指针。如果向下转型不安全(即基类指针或者引用没有指向一个派生类的对象),dynamic_cast返回空指针。 使用dynamic_cast时,类中必须定义虚函数。

#include <iostream>
using namespace std;class Base
{
public:Base(int value = 0){data = value;}virtual void print(){cout << "Base::print data = " << data << endl;}
protected:int data;
};class Derived : public Base
{
public:Derived(int value = 0){data = value;}virtual void print(){cout << "Derived print data = " << data << endl;}
protected:int data;
};int main(int argc, char *argv[])
{//指针Base* bp1 = new Base(101);Derived* dp11 = static_cast<Derived*>(bp1);cout << "Base" << endl;cout << bp1 << endl;cout << dp11<< endl;dp11->print();//Base::print data = 101Derived* dp12 = dynamic_cast<Derived*>(bp1);cout << dp12 << endl;//0,向下转型失败if(dp12 != NULL){dp12->print();}Base* bp2 = new Derived(102);Derived* dp21 = static_cast<Derived*>(bp2);cout << "Derived" << endl;cout << bp2 << endl;cout << dp21<< endl;dp21->print();//Derived print data = 102Derived* dp22 = dynamic_cast<Derived*>(bp2);cout << dp22 << endl;//向下转型成功if(dp22 != NULL){dp22->print();//Derived print data = 102}//引用Base b1(10);Derived& rd11 = static_cast<Derived&>(b1);rd11.print();//Base::print data = 10//Derived& rd12 = dynamic_cast<Derived&>(b1);//exceptionDerived b2(10);Derived& rd21 = static_cast<Derived&>(b2);rd21.print();//Derived print data = 10Derived& rd22 = dynamic_cast<Derived&>(b2);rd22.print();//Derived print data = 10return 0;
}

上述代码中,如果指针、引用实际指向的对象为派生类对象,使用static_cast、dynamic_cast转换都是安全的;如果指针、引用实际指向的对象为基类对象,使用dynamic_cast会返回NULL指针或抛出异常,使用static_cast关键字返回执行基类对象的指针或引用,不能访问派生类的覆盖方法与成员。

3、多继承时的向下转型

#include <iostream>
using namespace std;class BaseA
{
public:BaseA(int value = 0){data = value;}virtual void printA(){cout << "BaseA::print data = " << data << endl;}
protected:int data;
};class BaseB
{
public:BaseB(int value = 0){data = value;}virtual void printB(){cout << "BaseB::print data = " << data << endl;}
protected:int data;
};class Derived : public BaseA, public BaseB
{
public:Derived(int value = 0){data = value;}virtual void printA(){cout << "Derived printA data = " << data << endl;}virtual void printB(){cout << "Derived printB data = " << data << endl;}
protected:int data;
};int main(int argc, char *argv[])
{//BaseAcout << "BaseA" << endl;BaseA* bpa = new BaseA(10);cout << bpa << endl;Derived* pd1 = static_cast<Derived*>(bpa);cout << pd1 << endl;pd1->printA();//BaseB::print data 10//pd1->printB();//exception,实际指向BaseA对象,没有printB方法Derived* pd2 = dynamic_cast<Derived*>(bpa);cout << pd2 << endl;//0,向下转型失败if(pd2 != NULL){pd2->printA();pd2->printB();}//BaseBcout << "BaseB" << endl;BaseB* bpb = new BaseB(10);cout << bpb << endl;//pd3指向bpb前8字节的地址Derived* pd3 = static_cast<Derived*>(bpb);cout << pd3 << endl;//pd3->printA();//exception//pd3->printB();//exceptionDerived* pd4 = dynamic_cast<Derived*>(bpb);cout << pd4 << endl;//0,向下转型失败if(pd4 != NULL){pd4->printA();pd4->printB();}cout << "Derived" << endl;BaseA* bpd = new Derived(101);cout << bpd << endl;Derived* pd5 = static_cast<Derived*>(bpd);cout << pd5 << endl;pd5->printA();//Derived printA data = 101pd5->printB();//Derived printB data = 101Derived* pd6 = dynamic_cast<Derived*>(bpd);cout << pd6 << endl;if(pd6 != NULL){pd6->printA();//Derived printA data = 101pd6->printB();//Derived printB data = 101}BaseA* pa = static_cast<BaseA*>(bpd);pa->printA();//BaseB* pb = static_cast<BaseB*>(bpd);//error,BaseB* pb = dynamic_cast<BaseB*>(bpd);//正确,pb->printB();cout << "Derived+" << endl;Derived* dpd = new Derived(102);cout << dpd << endl;BaseA* dpa = static_cast<BaseA*>(dpd);cout << dpa << endl;dpa->printA();BaseB* dpb1 = static_cast<BaseB*>(dpd);//cout << dpb1 << endl;dpb1->printB();BaseB* dpb2 = dynamic_cast<BaseB*>(dpd);//cout << dpb2 << endl;dpb2->printB();return 0;
}

上述代码中,bpa指针指向BaseA对象,使用static_cast关键字对bpa进行向下转型为Derived指针对象时,返回bpa的值,由于实际指向BaseA对象,因此对BaseB方法时会导致异常;使用dynamic_cast关键字对bpa进行向下转型时,转型失败,返回NULL。

bpb指针实际指向BaseB对象,使用static_cast关键字对bpb进行向下转型为Derived指针对象时,返回bpb地址的-8字节的地址,该地值是一个不合法的Derived对象地址,因此对该地址调用BaseA、BaseB类的方法时会导致异常;使用dynamic_cast关键字对bpa进行向下转型时,转型失败,返回NULL。

bpd指针实际指向Derived对象,使用static_cast关键字对bpd进行向下转型为Derived指针对象时,返回bpd的值,可以合法调用BaseA、BaseB类的方法;使用dynamic_cast关键字对bpd进行向下转型时,返回bpd的值,可以合法调用BaseA、BaseB类的方法。如果使用static_cast关键字将BaseA*类型指针bpd转型为BaseB*指针时,C++编译器报错;必须使用dynamic_cast关键字,dynamic_cast会在运行时对指针进行调整。

Derived类型的dpd指针指向Derived对象,使用static_cast关键字和dynamic_cast关键字都可以进行向上转型。

三、C++内省机制

所谓内省是指面向对象语言的一种在运行期间查询对象信息的能力, 比如如果语言具有运行期间检查对象型别的能力,那么语言是型别内省(type intropection)的,型别内省可以用来实施多态。

C++的内省比较有限,仅支持型别内省, C++的型别内省是通过运行时类型识别(RTTI)(Run-Time Type Information)中的typeid 以及dynamic_case关键字来实现的。

Hello C++(十九)——C++类型识别相关推荐

  1. Swift中文教程(十九) 类型嵌套

    枚举类型常被用于实现特定类或结构体的功能.也能够在有多种变量类型的环境中,方便地定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义类型嵌套,可以在枚举类型.类和结构体中定义支持嵌套的类型 ...

  2. C++语言学习(十九)——C++类型识别

    C++语言学习(十九)--C++类型识别 一.C++类型识别简介 1.C++类型识别简介 C++是静态类型语言,其数据类型是在编译期就确定的,不能在运行时更改. C++语言中,静态类型是对象自身的类型 ...

  3. 【SQL开发实战技巧】系列(十九):数据仓库中时间类型操作(进阶)如何一个SQL打印当月或一年的日历?如何确定某月内第一个和最后—个周内某天的日期?

    系列文章目录 [SQL开发实战技巧]系列(一):关于SQL不得不说的那些事 [SQL开发实战技巧]系列(二):简单单表查询 [SQL开发实战技巧]系列(三):SQL排序的那些事 [SQL开发实战技巧] ...

  4. Python编程基础:第四十九节 鸭子类型Duck Typing

    第四十九节 鸭子类型Duck Typing 前言 实践 前言 本节我们一起学习一个非常有趣的知识点:鸭子类型.有这么一句话:If it walks like a duck, and it quacks ...

  5. ROS探索总结(十六)(十七)(十八)(十九)——HRMRP机器人的设计 构建完整的机器人应用系统 重读tf 如何配置机器人的导航功能

    ROS探索总结(十六)--HRMRP机器人的设计 1. HRMRP简介         HRMRP(Hybrid Real-time Mobile Robot Platform,混合实时移动机器人平台 ...

  6. 鸟哥的Linux私房菜(基础篇)- 第十九章、认识与分析登录文件

    第十九章.认识与分析登录文件 最近升级日期:2009/09/14 当你的 Linux 系统出现不明原因的问题时,很多人都告诉你,你要查阅一下登录文件才能够知道系统出了什么问题了,所以说,了解登录文件是 ...

  7. 类 workbooks 的 open 方法无效_第十九章 Cach 命令大全 OPEN 命令

    第十九章 Caché 命令大全 OPEN 命令 获取输入/输出操作的设备或文件的所有权. 重点 打开设备要指定延迟秒,否则将无限等待. 大纲 OPEN:pc device:(parameters):t ...

  8. 信息系统项目管理师核心考点(十九)制定项目章程依据(输入)

    科科过为您带来软考信息系统项目管理师核心重点考点(十九)制定项目章程依据(输入),内含思维导图+真题 [信息系统项目管理师核心考点]制定项目章程依据(输入) 一.商业论证 二.协议(通常为合同):协议 ...

  9. [Python从零到壹] 五十九.图像增强及运算篇之图像锐化Scharr、Canny、LOG实现边缘检测

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  10. 【CS231n】斯坦福大学李飞飞视觉识别课程笔记(十九):卷积神经网络笔记(下)

    [CS231n]斯坦福大学李飞飞视觉识别课程笔记 由官方授权的CS231n课程笔记翻译知乎专栏--智能单元,比较详细地翻译了课程笔记,我这里就是参考和总结. [CS231n]斯坦福大学李飞飞视觉识别课 ...

最新文章

  1. redis学习之——Redis事务(transactions)
  2. mysql的crud语句_MySQL数据库CRUD语句快速入门
  3. 二、数据库的基本操作
  4. 使用word完成毕业论文的所有详细步骤
  5. rzsz for linux,linux下如何安装rzsz
  6. zen服务器芯片,服务器版Zen处理器简直大杀器:32核64线程,8通道DDR4内存
  7. js获取字符串出现次数最多_js如何获取字符串中出现次数最多的字符
  8. 对发表论文的深层次思考
  9. c# key event
  10. C# Winform编程ListBox之DrawItem事件
  11. 中文GRasshopper插件lunchbox(午餐盒),首发哦!
  12. Programiz 中文系列教程·翻译完成
  13. 喜提JDK的BUG一枚!多线程的情况下请谨慎使用这个类的stream遍历。
  14. iPhone12 系列机型详细配置对比,iPhone 12mini\12\12Pro\12ProMax
  15. win7磁盘清理_电脑磁盘已满怎么清理?磁盘清理的注意事项有哪些?
  16. 算法——弗洛伊德算法(Floyd-Warshall)(图论)(c++)
  17. Linux基操 笔记
  18. 多用途通讯录短信定位获取系统(安卓Android+苹果IOS+双端)带视频教程
  19. 区块链的命和血汗工厂的病
  20. LINUX驱动、系统底层

热门文章

  1. excel中输入身份证号后三位变成0怎么办
  2. 芭蕉树上第十二根芭蕉--opencv配置问题
  3. 程序员应该具备哪些素养
  4. Java尚能饭否?10月编程排行榜告诉你!
  5. 复合函数求导定义证明_复合函数求导公式如何证明?
  6. WIN10系统的ghost备份还原的详细步骤
  7. git从克隆项目到本地分支和远程分支关联
  8. mysql binlog 回滚_【MySQL】Mysql误操作后使用binlog2sql快速回滚
  9. ZYNQ的Linux Linaro系统镜像制作SD卡启动
  10. 信息学奥赛一本通T1183-病人排队-题解(C语言代码)