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 关键字来实现的.
C++ 语言学习(十九)--C++ 类型识别
来源: http://www.bubuko.com/infodetail-2743502.html