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

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

一、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++标准只是告诉编译器需要实现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 = 0

    Base& rb = d;//引用赋值,不产生切割
    rb.print();//Derived print data = 100

    Base* 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 = 101
    Derived* 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 = 102
    Derived* 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);//exception
    Derived b2(10);
    Derived& rd21 = static_cast<Derived&>(b2);
    rd21.print();//Derived print data = 10
    Derived& rd22 = dynamic_cast<Derived&>(b2);
    rd22.print();//Derived print data = 10

    return 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[])
{
    //BaseA
    cout << "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();
    }

    //BaseB
    cout << "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();//exception
    Derived* 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 = 101
    pd5->printB();//Derived printB data = 101
    Derived* pd6 = dynamic_cast<Derived*>(bpd);
    cout << pd6 << endl;
    if(pd6 != NULL)
    {
        pd6->printA();//Derived printA data = 101
        pd6->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关键字来实现的。

原文地址:http://blog.51cto.com/9291927/2164588

时间: 2024-10-10 06:17:20

C++语言学习(十九)——C++类型识别的相关文章

C++语言学习(九)——多态

C++语言学习(九)--多态 C++中所谓的多态(polymorphism)是指由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应.    多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性.可以减轻系统升级,维护,调试的工作量和复杂度. 多态是一种不同层次分类下的重要联系,是一种跨层操作. 一.多态实现的前提 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代.赋值兼容是一种默认行为,不需要任何的显式的转化步骤,只能发生在public继承方式中,是多态

Go语言学习(四)经常使用类型介绍

1.布尔类型 var v1 bool v1 = true; v2 := (1==2) // v2也会被推导为bool类型 2.整型 类 型 长度(字节) 值 范 围 int8 1 ? 128 ~ 127 uint8(即byte)1 0 ~ 255 int16 2 ? 32 768 ~ 32 767 uint16 2 0 ~ 65 535 int32 4 ? 2 147 483 648 ~ 2 147 483 647 uint32 4 0 ~ 4 294 967 295 int64 8 ? 9 2

C语言学习总结(三) 复杂类型

第五章.复杂数据类型 (数组.字符串.指针.结构体.枚举.共同体) 1.什么是数组? 概念:把具有相同类型的若干变量按有序的形式组织起来,这些按序排列的同类数据元素的集合称为数组: 按数组元素的类型不同,数组又可分为: 数值数组:用来存储数值得 字符数组:用来存储字符 ‘a’ 指针数组:用来存放指针(地址)的 结构数组:用来存放一个结构体类型的数据 按维度分类: 一维数组 二维数组 多维数组 1.一维数组 概念:所有的元素都不是数组 使用流程:定义数组---->给数组初始化---->使用数组

Go语言学习笔记(4)复合类型

  Go语言的复合类型,包括数组.切片和映射等. 值.指针和引用类型 通常情况下Go语言中的变量持有相应的值.也就是说,我们可以将一个变量想象成它所持有的值来使用.其中有些例外,通道.函数.方法.映射.切片是 引用变量,它们持有的都是引用,也即保存指针的变量.值在传递给函数或者方法的时候会被复制一次,对于布尔类型和数值类型来说这非常廉价,但是对于大型变 量代价却非常大.而且复制传参的方式,修改值只是修改了副本,这能保证原始变量不被修改,但也一定程度上增加了修改原始值的麻烦.幸好在Go语言中有指

Go语言学习(十二)面向对象编程-结构体

1.结构体的初始化方式 例如自定义一个结构体 package main import( "fmt" ) type Rect struct{ //type和struct为关键字 x,y float64 //结构体成员 widh,height float64 } func (r *Rect) Area() float64{ return r.width * r.height } func main(){ //初始结构体的几种方式: rect1 := new(Rect) rect2 := &

Dart语言学习( 五) Dart Bool类型

Dart Bool类型和其他语言类似,比较简单 其特点有: 1.使用 bool 表示布尔类型 2.布尔值只有 true 和 false 3.布尔类型bool默认值是null bool isTrue = true; bool isFalse = false; bool defaultBool ; print(isTrue); print(isFalse); print(defaultBool); print("Hello".isEmpty); 输出如下: true false null

初步swift语言学习笔记2(可选类型?和隐式可选类型!)

作者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/28904115 转载请注明出处 假设认为文章对你有所帮助.请通过留言或关注微信公众帐号fengsh998来支持我,谢谢. 可选类型.隐式可选类型 在swift中.可选类型其根源是一个枚举型.里面有None和Some两种类型.事实上所谓的nil就是Optional.None, 非nil就是Optional.Some, 然后会通过Some(T)包装(wrap)原始值,这

CSS基础学习十九:CSS布局之图文混排,图像签名,多图拼接和图片特效

学习了CSS布局的定位和浮动,我们可以简单地做出很多排版和内容拼接.今天就来做几个简单的实例展示现在 流行的DIV+CSS布局的方便好用之处.顺便也说一下CSS3新增的样式属性box-shadow和属性transform. 一图文混排 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd

Go语言学习(十)bytes包处理字节切片

bytes包提供了对字节切片进行读写操作的一系列函数 字节切片处理的函数比較多,分为基本处理函数,比較函数,后缀检查函数,索引函数,切割函数, 大写和小写处理函数和子切片处理函数等. 1.字节切片基本处理函数api 1.1Contains()函数 //Contains()函数的功能是检查字节切片b是否包括子切片subslice,假设包括返回true,否则返回false. func Contains(b,subslice []bytes) bool 1.2Count()函数 //Count()函数

android学习十九(WebView的用法)

android提供了一个WebView控件,借助它我们就可以在自己的应用程序中嵌入一个浏览器,从而轻松的展示各种各样的网页.下面来学习下简单的用法.新建一个WebViewTest项目,然后修改activity_main.xml中的代码,如下所示: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/