380 likes | 480 Views
第 22 章 多重继承和类型变换. 多继承 (multiple inheritance) 指在一个继承树层次的剖 面上派生类可以拥有不止一个直接基类。多重继承是 C++ 语 言后期引进的代码重用的模式,单一继承对应派生类上逆时 恒只有一个直接基类。多重继承打破了单一继承这种单枝独 上的简单性。 C 风格的类型转换对于系统级的操作是必不可少的。 C++ 鉴于 C 风格的类型转换未提供足够明显的安全保障 机制,特引进新形式的类型变换规则。. 五、新的类型变换. 五、新的类型变换 英文 cast 可翻译成变换、映射或投影。 ~_cast 类型变
E N D
第22章 多重继承和类型变换 • 多继承(multiple inheritance)指在一个继承树层次的剖 • 面上派生类可以拥有不止一个直接基类。多重继承是C++语 • 言后期引进的代码重用的模式,单一继承对应派生类上逆时 • 恒只有一个直接基类。多重继承打破了单一继承这种单枝独 • 上的简单性。 • C风格的类型转换对于系统级的操作是必不可少的。 • C++鉴于C风格的类型转换未提供足够明显的安全保障 • 机制,特引进新形式的类型变换规则。
五、新的类型变换 • 英文cast可翻译成变换、映射或投影。~_cast 类型变 • 换有四种形式,格式如下: • dynamic_cast<ClassC*>(pObj) • static_cast< type >(expre ) • reinterpret_cast< type* >( pExpre ) • const _cast< type* >(pExpre ) • 其中尖括号中的类型指出表达式结果的类型,圆括号中 • 的操作数为待变换的数据。
1. dynamic_cast动态类型变换 • 动态类型变换dynamic_cast用于操作一个多态类的对 • 象指针以便进行上下映射。仅当基类存在至少一个虚函数 • 时,才可以启动动态类型变换机制。 • 设继承层次的类从上到下的关系为ClassB, ClassC, • ClassD, 对象指针变换的语法格式常用的为: • dynamic_cast<ClassC*>(pObj) • //不支持 dynamic _cast <float*>(pObj) • pObj是指向对象的指针,这个指针具有动态类型和静态 • 类型。如果pObj的动态类型和静态类型是派生类的类型 • ClassD*,表达式的结果是ClassC*类型。
如果pObj的静态类型是基类的类型ClassB*,表达式的如果pObj的静态类型是基类的类型ClassB*,表达式的 • 结果根据pObj的动态类型确定: • 如果pObj的动态类型是ClassC*型或ClassD*型的对象 • 指针,则表达式的结果是ClassC*类型; • 如果pObj的动态类型是ClassB*类型则表达式返回 • NULL。此种类型转换不支持将对象指针类型转换为算术指 • 针类型。 • 例如,不支持 dynamic _cast <int*>(pObj)。
[例]动态类型变换 • #include <stdio.h> • class ClassB • { public: long m_n; • public: ClassB() {m_n=1;} • virtual f(){} • void Show() {printf ("ClassB::m_n=%d\t",m_n); } • class ClassC: public ClassB • { public: ClassC() {m_n=2;} • void Show() • { printf ("ClassC::m_n=%d\t",m_n); } • };
class ClassD: public ClassC • { public:ClassD () {m_n=3; } • void Show() • { printf ("ClassD::m_n=%d\t",m_n); } • }; • void fcast (ClassB* pObj) • { • ClassC* pObjc=dynamic_cast<ClassC*>(pObj) ; • if (pObjc!=NULL) • pObjc->Show(); • }
void main() • { ClassB objb; fcast (&objb); • ClassB *pObjb=new ClassD(); • //pObjb具有动态类型为ClassD* • fcast (pObjb); • ((ClassD*)pObjb)->Show(); • //将pObjb真正变换到原来的对象地址 • ClassC objc; • fcast (&objc); • } //输出: • ClassC::m_n=3 ClassD::m_n=3 ClassC::m_n=2
动态类型变换dynamic_cast也可操作一个多态类的对动态类型变换dynamic_cast也可操作一个多态类的对 • 象引用,以便将派生类对象或对象引用的实际值向上变换到 • 基类的引用。对象引用变换的语法格式为: • dynamic_cast<ClassC&>(rObj) • //不支持 static_cast <int&>(rObj) • rObj是对象引用。如果rObj的动态类型和静态类型都 • 是派生类的类型ClassD&,表达式的结果是ClassC&型的对 • 象引用。 • 如果rObj的静态类型是基类的类型ClassB&,而动态类 • 型是ClassC&或ClassD&类型,则表达式的结果是 • ClassC&类型; • 如果rObj的动态类型是ClassB&类型,表达式抛出异 • 常bad_cast。该形式需要typeinfo.h 头文件的支持。
[例]引用形式的动态类型变换 • #include <stdio.h> • #include<typeinfo.h> • class ClassB • { public: long m_n; • public: ClassB() {m_n=1;} • virtual void Show() • { printf ("ClassB::m_n=%d\n",m_n); } • }; • class ClassC:public ClassB • { public: ClassC() { m_n=2; } • void Show() • { printf ("ClassC::m_n=%d\n",m_n); } • };
class ClassD:public ClassC • { public:ClassD() { m_n=3; } • void Show () {printf ("ClassD::m_n=%d\n",m_n);} • ~ClassD() { printf ("~ClassD;\n"); } • }; • void fcast (ClassB& rObj) • { try{ ClassC& rObjc=dynamic_cast<ClassC&>(rObj) ; • rObjc.Show(); • } • catch (bad_cast) { printf ("bad_cast produced\n"); } • }
void main() • { ClassB objb; • fcast (objb); • ClassB* pObjb=new ClassD(); • fcast (*pObjb); //动态绑定发生作用 • ((ClassD&)*pObjb). Show (); • ClassC objc; • fcast (objc); • delete (ClassD*)pObjb; • } 程序输出结果: bad_cast produced ClassD::m_n=3 ClassD::m_n=3 ClassC::m_n=2 ~ClassD;
动态类型变换可以在多继承的层次上进行。 • 最晚派生类对象地址可以向上沿任意分支投影。 • 当向上投影到到其中一个分支的基对象指针时,这个指 • 针可以借助动态类型变换间接地向上转换到另外的分支。 • 如果对象指针的当前动态值指向继承树下面的派生类对 • 象,则可以将其安全地变换到另一个基类的对象地址。 • 动态类型变换支持的是向上投影对象指针的实际值。
2. static_cast静态类型变换 • 静态类型变换可以在相关对象引用或对象指针之间进 • 行,也可以在内置数据类型之间进行。 • dynamic_cast类型转换要求基类存在至少一个虚函 • 数,静态类型变换不要求这一点。 • 对象指针指针变换的语法格式为: • static_cast <ClassC*>(pObj) • //不支持 static_cast <double*>(pObj) • pObj是指向类继承层次的对象的指针,不管这个指针具 • 有动态类型和静态类型,表达式的结果都强制性的转换为 • ClassC*型的对象指针。
对象引用变换的语法格式为: • static_cast <ClassC&>(rObj) • //不支持 static_cast <double&>(rObj) • rObj是类的继承层次的对象引用,不管这个引用动态类 • 型和静态类型如何,表达式的结果都强制性的转换为 • ClassC&型的对象引用。 • 对于对象别名的类型转换,编译器保留虚函数的动态绑 • 定机制。
[例]继承层次的静态类型变换 • #include <stdio.h> • class ClassB • { public: long m_n; • public: ClassB() { m_n=1; } • //virtual // 去掉双斜杠Show()为虚函数 • void Show() { printf ("ClassB::m_n=%d\n",m_n);} • }; • class ClassC:public ClassB • { public: ClassC() { m_n=2; } • void Show() • { printf ("ClassC::m_n=%d\n",m_n); } • };
class ClassD:public ClassC • { public:ClassD() {m_n=3;} • void Show() {printf ("ClassD::m_n=%d\n",m_n);} • ~ClassD() { printf ("~ClassD;\n"); } • }; • void fcastb (ClassB& rObj,int n) • { if (n==1) • { ClassC& rObjc=static_cast<ClassC&>(rObj) ; • rObjc.Show(); } • else { ClassC& rObjc= (ClassC&)(rObj) ; • rObjc.Show(); } • } //上面if分支两种变换格式效果是等价的
void fcasta (ClassB* pObj,int n) • { ClassC* pObjc; • if (n==1) • pObjc=static_cast<ClassC*>(pObj) ; • else pObjc=(ClassC*)(pObj) ; • pObjc->Show(); • } • void fa () //显示指针类型静态变换 • { ClassB objb; fcasta(&objb,1); • ClassB* pObjb=new ClassD(); fcasta(pObjb,1); • ClassC objc; fcasta(&objc,1); • delete (ClassD*)pObjb; • }
void fb () //显示引用形式静态类型变换 • { ClassB objb; fcastb(objb,1); • ClassB* pObjb=new ClassD(); • fcastb (*pObjb,2); • ClassC objc; fcastb(objc,1); • } • void main() { fa(); fb(); } • 静态类型变换也适应于内置数据类型的转换,即可以将 • double型的表达式转换为int型的表达式等,反之亦然。其 • 形式为: • static_cast< type >( expre ) • //类似于C形式的 (type) expre • 这个静态类型变换将expre类型转换为目标type类型。
Show不是虚函数时 Show是虚函数时输出 • 输出如下结果: 结果如下: • ClassC::m_n=1 ClassB::m_n=1 • ClassC::m_n=3 ClassD::m_n=3 • ClassC::m_n=2 ClassC::m_n=2 • ~ClassD; ~ClassD; • ClassC::m_n=1 ClassB::m_n=1 • ClassC::m_n=3 ClassD::m_n=3 • ClassC::m_n=2 ClassC::m_n=2
3. reinterpret_cast 重新翻译变换 • 重新翻译变换的语法格式如下: • reinterpret_cast< type* >( pExpre ) • //相当于 (type*) pExpre • reinterpret_cast< type& >( LExpre ) • //相当于 (type&)LExpre • reinterpret_cast重新翻译变换是C风格类型转换 • (type*) pExpre或 (type&)LExpre的替代物。这个类型变换 • 允许任意指针类型pExpre转换为其它type*的指针类型,甚 • 至允许整型数与指针类型的相互转换。但编程时从不将整型 • 数据变换为地址表达式,因为这违反指针仅用于操作系统固 • 有的内存地址编号的规则。
reinterpret_cast也允许左值表达式LExpre转换为其它 • 的引用类型。 • 前面的动态类型变换和静态类型变换要求类的继承层次 • 之间的上下转换,不能将int*的指针变换成long*型的指针, • 而这里的reinterpret_cast则支持这种形式的变换。 • reinterpret_cast不支持(type)expre风格的数据类型转 • 换,例如不能将int的表达式变换成double型的数据,而 • static_cast则支持这种C-style cast 类型的转换。
[例]简单变量的指针和引用变换 • #include <stdio.h> • void f(short& v) { printf ("%d\t",v);} • void f(double* p) { printf ("%f\t",*p);} • void main() • { long l=10; float tf =20.0f; • f (reinterpret_cast<short&>(l)); • f (reinterpret_cast<double*>(&tf)); • f ((short&) l ); • printf ("*((double*)&tf)=%f\t",*((double*)&tf)); • f ((double*)(&tf)); • } //////////////程序运行输出结果:///////////////////// • 10 0.000000 10 *((double*)&tf)=0.000000 0.000000
4. const_cast 松动const属性变换 • 形如void f(type *p)的函数接口是一个普通指针形参, • 普通指针不可以指向由定义语句[const type v;]定义的不变 • 量v,const_cast在变换时可以松动不变量的const属性。 • 通常普通指针形参本可以声明为只读指针形参 • void f (const type *p),但由于程序员的疏忽而遗漏了 • const关键字,这种情况下const_cast变换便能有效的发挥 • 作用。
const_cast的语法格式为: • const _cast< type* >( pExpre ) • //相当于 (type*) pExpre • const _cast< type& >( LExpre ) • //相当于 (type&)vExpre • 其中pExpre是const type*的指针表达式,vExpre是 • const type的表达式。也就是源操作数具有const的只读性 • 质,而目标操作数是同类型的没有const的表达式。 • const _cast 变换将只读的表达式变换成可变的形式。 • 这种变换只应该是形式上的,即原来的只读数据源不应因为 • 变换的结果而遭到非预期的攻击。
[例]松动实参const属性的变换 • #include <stdio.h> • void fc (int* p) { printf ("%d\t",*p); } • void fc (int& v) { printf ("%d\n",v); } • void main() • { const int d=20; • fc ( const_cast<int*>(&d)); • fc ( const_cast<int&>(d)); • } //输出结果:20 20 • void fc(int* p) { printf (“%d\t”,*p); }本可以实质地改为 • void f(const int* p) { printf (“%dt”,*p);},函数fc(int*)不能 • 匹配只读指针实参,而函数f(const int*)可安全地匹配int*型 • 的实参。
5. typeid关键字 • 运行时类型识别(RTTI)信息是一种编程的重要标志,它 • 允许变量的类型在运行期间确定。 • 对于typeid运算符而言,表达式的类型分两种:一种是 • type型的变量,另一种是type*型的指针。type&型的引用参 • 照type型的变量,type*&型的引用参照type*型的指针。 • typeid关键字可以用来求出表达式的类型信息type或 • type*。 • 对于多态类则可以进一步求出表达式的动态类型信息, • 此时应打开/GR开关。
a. typeid关键字的用法 • typeid关键字的用法有点类似于sizeof 运算符。Typeid • 函数用于确定表达式或某一数据的类型,其语法格式为: • const class type_info typeid (expression) • const class type_info typeid (type) • type 是定义变量的类型名, expression 是表达式。 • class type_info是在头文件<typeinfo.h>中声明的类, • typeid函数就返回这个类的const对象。下面表达式的结果 • 为真。 • typeid(1.0f) ==typeid(float) typeid(1.0) ==typeid(double) • typeid(1) ==typeid(int) typeid('1')==typeid(char) • double d; typeid(&d) ==typeid(double*)
[例] typeid显示表达式的类型信息 • #include <stdio.h> • #include<typeinfo.h> • class ClassB //多态类之根基类 • { public: virtual void SetB() {m_b=1;} • int m_b; • }; • class ClassC : public ClassB //多态类继承体系之派生类 • { public: void SetB() { m_c=2;} int m_c; }; • void main() • { double d; • if (typeid (int) !=typeid (double*))
if (typeid(&d) ==typeid(double*)) • printf("%s\t", typeid(double*).name()); • If (typeid('1')==typeid(char)) • printf ("%s\t", typeid('1').name()); • long n; • float& rn=(float&)n; • printf ("%s,\t%s\t", typeid(n).name(),typeid(rn).name()); • ClassB b; ClassC c; • ClassB& rb=c; • printf ("%s,\t%s\t", typeid(b).name(),typeid(rb).name()); • } //输出: • double* char long, float class ClassB, class ClassC
b. type_info类 • VC6.0中type_info类的声明如下: • class type_info • { public: • virtual ~type_info(); • int operator==(const type_info& rhs) const; • int operator!=(const type_info& rhs) const; • int before(const type_info& rhs) const; • const char* name() const; • const char* raw_name() const;
private: • void *_m_data; • char _m_d_name[1]; • type_info(const type_info& rhs); • type_info& operator=(const type_info& rhs); • };
[例] before和raw_name()成员函数 • #include <stdio.h> • #include<typeinfo.h> • #include<string.h> • class ClassB • { public: • virtual void SetB() { m_b=1; } • int m_b; • }; • class ClassC : • public ClassB • { public: int m_c; void SetB(){ m_c=2; } • };
void f1(int,float){} • void f2(ClassC*,long){} • void main() • { double d=1; • const type_info& t1=typeid(f1); • const type_info& t2=typeid(f2); • printf ("%s,\t%s\n", t1.name(),t1.raw_name()); • printf ("%s,\t%s\n", t2.name(),t2.raw_name()); • ClassC c; ClassB& rb=c; • const type_info& tc=typeid(ClassC*); • // typeid(e)是const对象,别名也是const属性
const type_info& tb=typeid(rb); • if (t1.before(t2)) • printf("1--t1.before(t2)\t"); • If (strcmp (t1.raw_name(),t2.raw_name())<0) • printf ("2--t1.before(t2)\n"); • If (tb.before(tc)) • printf("3--%s,\t%s\t", tb.name(),tc.name()); • if (strcmp (tb.raw_name(),tc.raw_name())<0) • printf ("4--%s,\t%s\n", tb.name(),tc.name()); • void* vp=&d; • printf ("%s,\t%s\n", typeid(vp).name(),typeid • (*(double*)vp).name()); • }
///////////////输出结果:///////////////////// • void (__cdecl*)(int,float), .P6AXHM@Z • void (__cdecl*)(class ClassC *,long), • .P6AXPAVClassC@@J@Z • 1--t1.before(t2) 2--t1.before(t2) • 3--class ClassC, class ClassC * • 4--class ClassC, class ClassC * • void *, double
说明: • 成员函数before的排序算法在编译器内部通过strcmp • 函数相仿的机制进行,在微软VC中根据编码后的字符串 • raw_name()进行操作。这样表达式tb.before(tc)在效果上 • 与strcmp(tb.raw_name(),tc.raw_name())<0是相当的。 • 其它的编译器可能将字符串name()作为排序的标准。 • vp的类名是void*,但vp的间接变量*vp是无效的访问指针形 • 式, typeid(*vp)导致错误提示。 • 将这个指针予以复原,然后再间接访问复原的指针 • *(double*)vp则得到double型的表达式,输出的结果表明这 • 一点。