C/C++程序设计
1
多继承 (multiple inheritance)指在一个继承树层次的剖面上派生类可以拥有不止一个直接基类。多重继承是 C++语言后期引进的代码重用的模式,单一继承对应派生类上逆时恒只有一个直接基类。多重继承打破了单一继承这种单枝独上的简单性。
C风格的类型转换对于系统级的操作是必不可少的。
C++鉴于 C风格的类型转换未提供足够明显的安全保障机制,特引进新形式的类型变换规则。
C/C++程序设计
2
五、新的类型变换
C/C++程序设计
3
五、新的类型变换英文 cast可翻译成变换、映射或投影。 ~_cast 类型变换有四种形式,格式如下:
dynamic_cast<ClassC*>(pObj)
static_cast< type >(expre )
reinterpret_cast< type* >( pExpre )
const _cast< type* >(pExpre )
其中尖括号中的类型指出表达式结果的类型,圆括号中的操作数为待变换的数据。
C/C++程序设计
4
1,dynamic_cast动态类型变换动态类型变换 dynamic_cast用于操作一个多态类的对象指针以便进行上下映射。仅当基类存在至少一个虚函数时,才可以启动动态类型变换机制。
设继承层次的类从上到下的关系为 ClassB,ClassC,
ClassD,对象指针变换的语法格式常用的为:
dynamic_cast<ClassC*>(pObj)
//不支持 dynamic _cast <float*>(pObj)
pObj是指向对象的指针,这个指针具有动态类型和静态类型。如果 pObj的动态类型和静态类型是派生类的类型
ClassD*,表达式的结果是 ClassC*类型。
C/C++程序设计
5
如果 pObj的静态类型是基类的类型 ClassB*,表达式的结果根据 pObj的动态类型确定:
如果 pObj的动态类型是 ClassC*型或 ClassD*型的对象指针,则表达式的结果是 ClassC*类型;
如果 pObj的动态类型是 ClassB*类型则表达式返回
NULL。此种类型转换不支持将对象指针类型转换为算术指针类型。
例如,不支持 dynamic _cast <int*>(pObj)。
C/C++程序设计
6
[例 ] 动态类型变换
#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); }
};
C/C++程序设计
7
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();
}
C/C++程序设计
8
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
C/C++程序设计
9
动态类型变换 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 头文件的支持。
C/C++程序设计
10
[例 ] 引用形式的动态类型变换
#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); }
};
C/C++程序设计
11
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"); }
}
C/C++程序设计
12
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;
C/C++程序设计
13
动态类型变换可以在多继承的层次上进行。
最晚派生类对象地址可以向上沿任意分支投影。
当向上投影到到其中一个分支的基对象指针时,这个指针可以借助动态类型变换间接地向上转换到另外的分支。
如果对象指针的当前动态值指向继承树下面的派生类对象,则可以将其安全地变换到另一个基类的对象地址。
动态类型变换支持的是向上投影对象指针的实际值。
C/C++程序设计
14
2,static_cast静态类型变换静态类型变换可以在相关对象引用或对象指针之间进行,也可以在内置数据类型之间进行。
dynamic_cast类型转换要求基类存在至少一个虚函数,静态类型变换不要求这一点。
对象指针指针变换的语法格式为:
static_cast <ClassC*>(pObj)
//不支持 static_cast <double*>(pObj)
pObj是指向类继承层次的对象的指针,不管这个指针具有动态类型和静态类型,表达式的结果都强制性的转换为
ClassC*型的对象指针。
C/C++程序设计
15
对象引用变换的语法格式为:
static_cast <ClassC&>(rObj)
//不支持 static_cast <double&>(rObj)
rObj是类的继承层次的对象引用,不管这个引用动态类型和静态类型如何,表达式的结果都强制性的转换为
ClassC&型的对象引用。
对于对象别名的类型转换,编译器保留虚函数的动态绑定机制。
C/C++程序设计
16
[例 ] 继承层次的静态类型变换
#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); }
};
C/C++程序设计
17
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分支两种变换格式效果是等价的
C/C++程序设计
18
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;
}
C/C++程序设计
19
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类型。
C/C++程序设计
20
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
C/C++程序设计
21
3,reinterpret_cast 重新翻译变换重新翻译变换的语法格式如下,
reinterpret_cast< type* >( pExpre )
//相当于 (type*) pExpre
reinterpret_cast< type& >( LExpre )
//相当于 (type&)LExpre
reinterpret_cast重新翻译变换是 C风格类型转换
(type*) pExpre或 (type&)LExpre的替代物。这个类型变换允许任意指针类型 pExpre转换为其它 type*的指针类型,甚至允许整型数与指针类型的相互转换。但编程时从不将整型数据变换为地址表达式,因为这违反指针仅用于操作系统固有的内存地址编号的规则。
C/C++程序设计
22
reinterpret_cast也允许左值表达式 LExpre转换为其它的引用类型。
前面的动态类型变换和静态类型变换要求类的继承层次之间的上下转换,不能将 int*的指针变换成 long*型的指针,
而这里的 reinterpret_cast则支持这种形式的变换。
reinterpret_cast不支持 (type)expre风格的数据类型转换,例如不能将 int的表达式变换成 double型的数据,而
static_cast则支持这种 C-style cast 类型的转换。
C/C++程序设计
23
[例 ] 简单变量的指针和引用变换
#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
C/C++程序设计
24
4,const_cast 松动 const属性变换形如 void f(type *p)的函数接口是一个普通指针形参,
普通指针不可以指向由定义语句 [const type v;]定义的不变量 v,const_cast在变换时可以松动不变量的 const属性。
通常普通指针形参本可以声明为只读指针形参
void f (const type *p),但由于程序员的疏忽而遗漏了
const关键字,这种情况下 const_cast变换便能有效的发挥作用。
C/C++程序设计
25
const_cast的语法格式为:
const _cast< type* >( pExpre )
//相当于 (type*) pExpre
const _cast< type& >( LExpre )
//相当于 (type&)vExpre
其中 pExpre是 const type*的指针表达式,vExpre是
const type的表达式。也就是源操作数具有 const的只读性质,而目标操作数是同类型的没有 const的表达式。
const _cast 变换将只读的表达式变换成可变的形式。
这种变换只应该是形式上的,即原来的只读数据源不应因为变换的结果而遭到非预期的攻击。
C/C++程序设计
26
[例 ] 松动实参 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*型的实参。
C/C++程序设计
27
5,typeid关键字运行时类型识别 (RTTI)信息是一种编程的重要标志,它允许变量的类型在运行期间确定。
对于 typeid运算符而言,表达式的类型分两种:一种是
type型的变量,另一种是 type*型的指针。 type&型的引用参照 type型的变量,type*&型的引用参照 type*型的指针。
typeid关键字可以用来求出表达式的类型信息 type或
type*。
对于多态类则可以进一步求出表达式的动态类型信息,
此时应打开 /GR开关。
C/C++程序设计
28
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*)
C/C++程序设计
29
[例 ] 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*))
C/C++程序设计
30
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
C/C++程序设计
31
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;
C/C++程序设计
32
private,
void *_m_data;
char _m_d_name[1];
type_info(const type_info& rhs);
type_info& operator=(const type_info& rhs);
};
C/C++程序设计
33
[例 ] 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; }
};
C/C++程序设计
34
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属性
C/C++程序设计
35
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());
}
C/C++程序设计
36
///////////////输出结果,/////////////////////
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
C/C++程序设计
37
说明,
成员函数 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型的表达式,输出的结果表明这一点。
C/C++程序设计
38