一. 隐式转换
隐式转换是C中比较常见的转换 ,对于内置类型,我们通常使用的也是隐式转换
int a=10;
float c=a;
double d=120.43;
int d2=d;
可以看出从int 转向float 没有什么问题,但从double 转向int 会引起编译器警告,如果范围较大的数据类型中的数据符合范围较小的数据类型这也不会有太大的问题。但如果超出范围或者小数转向整数可能就会有一些我们意料不到的事情,比如正数变成负数等。
二. 显示转换
有时候我们知道数据符合范围较小数据类型的要求,或者需要把实数取整,但又不想引起编译器警告。这就需要显示转换。
double d=120.43;
int d2=(int)d;
(datatype) 这是一个运算符,而且是优先级比较高的运算符。它把参与运算的数据转换成相应的类型。使用显示转换是一个比较好的编程习惯。如果程序出现了一些奇怪的错误,它们应该是首先考虑的地方,而且找起来也比较方便。
三. C++中的转换
static_cast 用于"良性"和"适度良性"转换,包括不用强制转换。这就是上面所说的隐式和显示转换不过
是C++的方式。
const_cast 对"const"和"volatile"进行转换。这个在常量综述里已经提到过了。
reinterpret_cast 转换为完全不同的意思。为了安全使用它,关键必须转换回原来的类型。转换成的类型
一般只能用于位操作,否则就是为了其它隐秘的目的。这是所有转换中最危险的。
dynamic_cast 用于类型安全的向下转换
这里只说一下重解释转换(reinterpret_cast):
这是最不安全的一种转换机制,最有可能出问题。reinterpret_cat 把对象假象为模式,仿佛它是一个完全不同类型的对象。这是低级的位操作,在使用reinterpret_cast 做任何事情之前,实际上总是需要reinterpret_cast 回到原来的类型。
using namespace std;
const int sz = 100 ;
struct X { int a[sz]; };
void print(X * x)
{
for ( int i = 0 ;i < sz;i ++ )
cout << x -> a[i] << ' ' ;
cout << endl << " ----------------------------- " << endl;
}
int main()
{
X x;
int * xp = reinterpret_cast < int *> ( & x);
for ( int * i = xp;i < xp + sz;i ++ )
* i = 0 ;
// Can't use xp as an X* at this point
// unless you cast it back:
print(reinterpret_cast < X *> (xp));
// In this example you can just use
// the original identifier:
print( & x);
}
这段代码中struct X 中只包含一个整形数组,因此我们也可以用一个整形指针来完成它的工作。虽然没有什么实际的意义。reinterpret_cast 本身只是用于低级的位操作,一些对内存的操作。
四. 自动类型转换
构造函数转换:
如果定义一个构造函数,这个构造函数能把另一个类型对象(或引用)作为它的单个参数,那么这个构造函数允许编译器执行自动类型转换。
class One
{
public :
One() {}
};
class Two
{
public :
Two( const One & ) {}
};
void f(Two) {}
int main()
{
One one;
f(one); // Wants a Two, has a One
}
当编译器看到f() 以类One 的对象为参数调用时,编译器检查f() 的声明并注意到它需要一个类Two 的对象作为参数。然后,编译器检查是否有从对象One 到Two 的方法。它发现了构造函数Two::Two(One)。
如果在构造函数前加上关键字explicit ,那么只能使用显示的构造函数转换 f(Two(One)) 来实现函数的调用,这时f 创建了一个从类型One到Two 的临时对象。
运算符转换:
我们也可以创建一个成员函数,这个函数通过在关键字operator 后跟随想要转换到的类型的方法,将当前类型转换为希望的类型。这种形式的运算符重载是独特的,因为没有指定一个返回类型——返回类型就是正在重载的运算符的名字。
{
int i;
public :
Three( int ii = 0 , int = 0 ):i(ii) {}
};
class Four
{
int x;
public :
Four( int xx):x(xx) {}
operator Three() const { return Three(x); }
};
void g(Three) {}
int main()
{
Four four( 1 );
g(four);
g( 1 ); // Calls Three(1,0)
}
上面两种类型转换比较难区分。记住用构造函数技术,调用目的类里的函数进行转换。然而使用运算符技术,调用源类里的函数执行转换。
五. 向上类型转换
这个是属于继承与组合的内容。它描述的是将派生类的引用或指针转变成基类的引用或指针的活动。
enum note { middleC, Csharp, Cflat }; // etc.
class Instrument
{
public :
void play(note) const {}
};
// Wind objects are Instruments
// because they hae the same interface:
class Wind : public Instrument {};
void tune(Instrument & i)
{
i.play(middleC);
}
int main()
{
Wind flute;
tune(flute); // Upcasting
}
需要注意的是,向上类型转换发生在函数调用期间——在函数外的Wind对象被引用并且变成一个在这个函数内的Instrument 的引用。但是,向上类型转换会损失对象的类型信息。解决这个问题的方法就是多态。
六. 向下类型转换
有了向上类型转换,当然也有向下类型转换。就是在类层次中向下移动。不过在进行向上类型转换时,总是知道自己的基类是什么(多重继承除外)。而当向下类型转换时,通常会有多种选择让我们进行类型转换。这时前面所说的dynamic_cast 就派上用场了。
using namespace std;
class Pet { public : virtual ~ Pet() {} };
class Dog : public Pet {};
class Cat : public Pet {};
int main()
{
Pet * b = new Cat;
// Try to cast it to Dog*:
Dog * d1 = dynamic_cast < Dog *> (b);
// Try to cast it to Cat*:
Cat * d2 = dynamic_cast < Cat *> (b);
cout << " d1 = " << ( long )d1 << endl;
cout << " d2 = " << ( long )d2 << endl;
}
当使用dynamic_cast 时,必须对一个真正多态的层次进行操作——它含有虚函数——这因为dynamic_cast 使用了存储在VTABLE中的信息来判断实际的类型。dynamic_cast 运行时需要一点额外的开销。所以如果执行大量的dynamic_cast 就会影响性能,如果我们知道正在处理的是何种类型,可以用static_cast来代替。
小结:
C++对类型的检查很严格,因此不同类型的转换很复杂,上面讲的只是类型转换中初级和中级的主题。C++还有像动态语言(如python)的能力,实现运行时类型识别(RTTI)。