❀RTTI
RTTI(Run-Time Type Identification)是面向对象程序设计中一种重要的技术。现行的C++标准对RTTI已经有了明确的支持。不过在某些情况下出于特殊的开发需要,我们需要自己编码来实现。和很多其他语言一样,C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致。有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了运行时类型识别的要求。
C++提供了两个关键字typeid和dynamic_cast,一个类type_info来支持RTTI。
dynamic_cast操作符:它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构安全地转换类型。dynamic_cast提供了两种转换方式,把基类指针转换成派生类指针,或者把指向基类的左值转换成派生类的引用。
void company::payroll(employee *pe) {
//对指针转换失败,dynamic_cast返回NULL
if(programmer *pm=dynamic_cast(pe)){
pm->bonus();
}
}
void company::payroll(employee &re) {
try{
//对引用转换失败的话,则会以抛出异常来报告错误
programmer &rm=dynamic_cast(re);
pm->bonus();
}
catch(std::bad_cast){
}
}
这里bonus是programmer的成员函数,基类employee不具备这个特性。所以我们必须使用安全的由基类到派生类类型转换,识别出programmer指针。
动态映射dynamic_cast<目标*><源指针>,先恢复源指针的RTTI信息,再取目标的RTTI信息,比较两者是否相同,或者是目标类型的基类;由于它需要检查一长串基类列表,故动态映射的开销比typeid要大。
second *p2=dynamic_cast<second *><p>;//p是基类指针,把基类指针p通过这种方式变成其派生类的指针。
或者second *p2=dynamic_cast<p>;
typeid操作符:它指出指针或引用指向的对象的实际派生类型。
employee* pe=new manager;
typeid(*pe)==typeid(manager) //true
typeid可以用于作用于各种类型名,对象和内置基本数据类型的实例、指针或者引用,当作用于指针和引用将返回它实际指向对象的类型信息。typeid的返回是type_info类型。
type_info类:这个类的确切定义是与编译器实现相关的,下面是《C++ Primer》中给出的定义(参考资料[2]中谈到编译器必须提供的最小信息量):
class type_info {
private:
type_info(const type_info&);
type_info& operator=( const type_info& );
public:
virtual ~type_info();
int operator==( const type_info& ) const;
int operator!=( const type_info& ) const;
const char* name() const;
};
运行类型识别RTTI使用需要注意的问题
(1)在分布式系统中,不适用RTTI的一个合理的解释 是RTTI行为不可预期及缺乏扩展性。
(2)用typeid()返回一个typeinfo对象,也可以用于内部类型,当用用于非多态类型时没有虚函数,用typeid返回的将是基类地址;
(3)不能对void指针进行映射;
(4)如果p是指针,typeid(*p)返回p所指向的派生类类型,typeid(p)返回基类类型;如果r是引用,typeid(r)返回派生类类型,typeid(&r)返回基类类型;
(5)C++里面的typeid运算符返回值是 type_info常量对象的引用。
❀运算符重载
所谓重载,就是重新赋予新的含义,函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。
运算符的重载主要存在两种形式,一种是作为类的成员函数进行使用,另一种则是作为类的友元函数进行使用。
运算符的重载的形式:
返回类型 operator 运算符符号(参数说明)
{
//函数体的内部实现
}
例如,能否用“+”号进行两个复数的相加,在C++中不能在程序中直接用运算符“+”对复数进行相加运算,用户必须自己设法实现复数相加。
运算符重载运算符的运算规则
(1)运算符重载函数也是函数,重载的运算符不会改变运算符的优先级、结合型和参数的个数。
(2)重载运算符不能违反语言的语法规则。
(3)赋值运算符除外,重载运算符可由派生类继承下去。
(4)重载运算符不能使用默认参数。
(5)运算符=、()、[]和->等操作符必须定义为类的成员函数,将这些操作符定义为友元函数将在编译时标记为错误。
(6)C++中不能重载的运算符只有5个:
. (成员访问运算符)
.* (成员指针访问运算符)
∷ (域运算符)
sizeof(长度运算符)
?: (条件运算符)
因为前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具重载的特征。
(7)友元运算符的参数规则与类成员运算符的参数规则不同,一元运算符必须显示地声明一个参数,二元运算符必须显示地声明两个参数。类成员运算符重载时,参数中隐含了一个this指针。(另外,C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流输入“>>”和流输出“<<”运算符、类型转换运算符))。
重载为类的成员函数时,参数个数=原操作数个数-1(后置++、--除外),它可以通过this指针自由地访问本类的数据成员,可以少写一个函数的参数,但必须要求运算表达式的第一个参数(即运算符左侧的操作数)是一个类对象。
重载为类的友元函数时,参数个数 = 原操作数个数,且至少应该有一个自定义类型的形参。
定义一个重载运算符函数参数表中参数的决定个数,取决于两个主要因素operator@
1)运算符是一元的(一个参数)还是二元的(两个参数)
2)运算符被定义为全局函数:对于一元运算符,一个参数;对于二元运算符是两个参数
3)运算符是成员函数:对于一元运算符没有参数,对于二元元素符是一个参数
如何重载增量运算符++和--
运算符++和—-有前置和后置两种形式,要使用operator++( )或operator--( )来重载前置运算符,使用operator++(int)或operator--(int)来重载后置运算符,调用时,参数int被传递给值0。
重载流输入运算符和流输出运算符
istream 类的对象cin;
ostream类的对象cout;
如果想直接用“<<”和“>>”输出和输入自己声明的类型的数据,必须对它们重载,对“<<”和“>>”重载的函数形式如下:
istream & operator >> (istream &,自定义类 &);
ostream & operator << (ostream &,自定义类 &);
重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。
重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。
只能将重载“>>”和“<<”的函数作为 友元函数或普通的函数,而不能将它们定义为成员函数。
friend ostream & operator <<(ostream & out, const classM & A);//在类classM中声明友元函数
ostream & operator <<(ostream & out, const classM & A)
{
out<<A.num<<endl;
return out;
}//在类外写函数体
调用:cout<<A;//重载<<运算符,输出对象A的num属性值。