当将值复制到兼容类型时,将自动执行隐式转换。例如:
short a=2000; int b; b=a; |
这里,a的值从short转换为int,不需要任何显式操作符。这就是所谓的标准转换。 标准转换影响基本数据类型,并允许在数值类型(short to int, int to float, double to int…), bool和一些指针之间进行转换。
从较小的整数类型转换为int,或从float类型转换为double类型称为提升, 并保证在目标类型中产生完全相同的值。算术类型之间的其他转换可能并不总是能够精确地表示相同的值:
其中一些转换可能意味着精度的损失,编译器可以用警告信号表示这一点。这个警告可以通过显式转换来避免。
对于非基本类型,数组和函数隐式转换为指针,指针通常允许以下转换:
// implicit conversion of classes: #include <iostream> using namespace std; class A {}; class B { public: // conversion from A (constructor): B (const A& x) {} // conversion from A (assignment): B& operator= (const A& x) {return *this;} // conversion to A (type-cast operator) operator A() {return A();} }; int main () { A foo; B bar = foo; // calls constructor bar = foo; // calls assignment foo = bar; // calls type-cast operator return 0; } |
类型转换操作符使用一种特殊的语法:它使用operator关键字后跟目标类型和一组空括号。 注意,返回类型是目标类型,因此没有在operator关键字之前指定。
在函数调用时,c++允许对每个参数进行隐式转换。对于类来说,这可能有些问题,因为它并不总是预期的那样。 例如,如果我们将以下函数添加到最后一个示例:
void fn (B arg) {} |
fn (foo); |
这可能是,也可能不是我们想要的。但是,在任何情况下, 都可以通过用explicit 关键字标记受影响的构造函数来防止这种情况:
// explicit: #include <iostream> using namespace std; class A {}; class B { public: explicit B (const A& x) {} B& operator= (const A& x) {return *this;} operator A() {return A();} }; void fn (B x) {} int main () { A foo; B bar (foo); bar = foo; foo = bar; // fn (foo); // not allowed for explicit ctor. fn (bar); return 0; } |
此外,用explicit 标记的构造函数不能用类似赋值的语法调用;在上面的例子中,bar不可能被构造为:
B bar = foo; |
成员函数类型转换(上一节中描述的)也可以指定为explicit。这可以防止隐式转换, 就像explicit指定的构造函数那样。
c++是一种强类型语言。许多转换,特别是那些暗示值的不同解释的转换, 需要显式转换,在c++中称为类型强制转换。类型强转通常有两种主要的语法:function和c-like:
double x = 10.3; int y; y = int (x); // functional notation y = (int) x; // c-like cast notation |
这些通用类型转换形式足以满足大多数基本数据类型的需求。 但是,这些操作可以不加区分地应用于类和类的指针,这可能导致代码在语法正确的情况下运行时错误。 例如,以下代码编译时没有错误:
// class type-casting #include <iostream> using namespace std; class Dummy { double i,j; }; class Addition { int x,y; public: Addition (int a, int b) { x=a; y=b; } int result() { return x+y;} }; int main () { Dummy d; Addition * padd; padd = (Addition*) &d; cout << padd->result(); return 0; } |
程序声明了一个指向Addition的指针,然后通过显式的类型转换将一个 不相关类型的对象的引用赋给指针:
padd = (Addition*) &d; |
不受限制的显式类型转换允许将指针转换为其他指针类型,独立于它们所指向的类型。 对成员result 的后续调用将产生运行时错误或其他一些意外结果。
为了控制类之间的转换类型,我们有四个特定的转换操作符:dynamic_cast、reinterpret_cast、static_cast和const_cast。
它们遵循的格式是:尖括号(<>)之间的新类型,以及紧随其后的括号之间转换的表达式。
dynamic_cast <new_type> (expression)Dynamic_cast只能与指向类的指针和引用一起使用(或与void*一起使用)。 其目的是确保类型转换的结果指向目标指针类型的有效完整对象。
这自然包括指针向上转换(派生指针转换为基类指针),与隐式转换相同。
当且仅当所指向的对象是目标类型的有效完整对象时,dynamic_cast也可以向下转型 (基类指针到派生类指针)多态类(具有虚成员的类)。例如:
// dynamic_cast #include <iostream> #include <exception> using namespace std; class Base { virtual void dummy() {} }; class Derived: public Base { int a; }; int main () { try { Base * pba = new Derived; Base * pbb = new Base; Derived * pd; pd = dynamic_cast<Derived*>(pba); if (pd==0) cout << "Null pointer on first type-cast.\n"; pd = dynamic_cast<Derived*>(pbb); if (pd==0) cout << "Null pointer on second type-cast.\n"; } catch (exception& e) {cout << "Exception: " << e.what();} return 0; } |
Null pointer on second type-cast. |
兼容性注意:dynamic_cast类型需要( Run-Time Type Information)运行时类型信息(RTTI)来跟踪动态类型信息。 有些编译器默认情况下禁用此功能。 这需要使用dynamic_cast启用运行时类型检查,以正确处理这些类型。 |
上面的代码尝试执行两个动态强制转换,从Base*类型的指针对象(pba和pbb)到Derived*类型的指针对象, 但只有第一个成功。注意它们各自的初始化:
Base * pba = new Derived; Base * pbb = new Base; |
尽管两者都是Base*类型的指针,但pba实际上指向Derived类的对象,而pbb指向Base类型的对象。 因此,当使用dynamic_cast执行它们各自的类型转换时,pba指向Derived类的完整对象, 而pbb指向Base类的对象,这是Derived类的不完整对象。
当dynamic_cast不能强制转换指针,因为它不是所需类的完整对象时——就像前面例子中的第二个转换那样——它返回一个空指针来表示失败。 如果dynamic_cast用于转换为引用类型,而转换不可能,则会抛出一个bad_cast类型的异常。
Dynamic_cast还可以对指针执行其他隐式强制转换:在指针类型之间(甚至在不相关的类之间)强制转换空指针,以及将任何类型的指针强制转换为void*指针。
Static_cast可以在指向相关类的指针之间进行转换,不仅可以进行上转换(), 还可以进行下转换(从基类指针到派生类指针)。 在运行时期间不会执行任何检查以确保正在转换的对象实际上是目标类型的完整对象。 因此,它是由程序员来确保转换安全的。另一方面,它不会引起dynamic_cast类型安全检查的开销。
class Base {}; class Derived: public Base {}; Base * a = new Base; Derived * b = static_cast<Derived*>(a); |
这将是有效的代码,尽管b将指向类的一个不完整的对象,并且如果解引用可能导致运行时错误。
因此,static_cast不仅可以使用指向类的指针执行隐式允许的转换,还可以执行它们的相反转换。
Static_cast还能够执行隐式允许的所有转换(不仅仅是那些指向类的指针),也能够执行相反的转换。它可以:Reinterpret_cast将任何指针类型转换为任何其他指针类型,即使是不相关的类。 操作结果是该值从一个指针到另一个指针的简单二进制副本。 允许所有指针转换:既不检查指针所指向的内容,也不检查指针类型本身。
它还可以强制转换指向整数类型或从整数类型转换的指针。这个整数值表示指针的格式是平台特定的。 唯一的保证是,一个转换为足以完全包含整型的指针(例如intptr_t), 可以保证能够转换回一个有效的指针。
可以由reinterpret_cast执行而不能由static_cast执行的转换是基于重新解释类型的二进制表示的低级操作, 在大多数情况下,这会导致特定于系统的代码,因此是不可移植的。例如:
class A { /* ... */ }; class B { /* ... */ }; A * a = new A; B * b = reinterpret_cast<B*>(a); |
这段代码可以编译,尽管它没有多大意义,因为现在b指向一个完全不相关且可能不兼容的类的对象。解引用b是不安全的。
这种类型的强制转换修改指针所指向的对象的const属性,可以设置也可以移除。 例如,为了将const指针传递给需要非const实参的函数:
// const_cast #include <iostream> using namespace std; void print (char * str) { cout << str << '\n'; } int main () { const char * c = "sample text"; print ( const_cast<char *> (c) ); return 0; } |
sample text |
上面的例子可以保证工作,因为print函数不写入指向的对象。但是请注意, 删除对象指针的const性以实际写入会导致未定义的行为。
Typeid允许检查表达式的类型:
typeid (expression)该操作符返回对标准头文件<typeinfo>中定义的类型为type_info的常量对象的引用。 typeid返回的值可以通过操作符==和!=与typeid返回的另一个值进行比较, 或者可以通过使用其name()成员获得一个以空终止字符串,表示数据类型或类名。
// typeid #include <iostream> #include <typeinfo> using namespace std; int main () { int * a,b; a=0; b=0; if (typeid(a) != typeid(b)) { cout << "a and b are of different types:\n"; cout << "a is: " << typeid(a).name() << '\n'; cout << "b is: " << typeid(b).name() << '\n'; } return 0; } |
a and b are of different types: a is: int * b is: int |
当typeid应用于类时,typeid使用RTTI来跟踪动态对象的类型。 当typeid应用于其类型为多态类的表达式时,结果是最终派生的完整对象的类型:
// typeid, polymorphic class #include <iostream> #include <typeinfo> #include <exception> using namespace std; class Base { virtual void f(){} }; class Derived : public Base {}; int main () { try { Base* a = new Base; Base* b = new Derived; cout << "a is: " << typeid(a).name() << '\n'; cout << "b is: " << typeid(b).name() << '\n'; cout << "*a is: " << typeid(*a).name() << '\n'; cout << "*b is: " << typeid(*b).name() << '\n'; } catch (exception& e) { cout << "Exception: " << e.what() << '\n'; } return 0; } |
a is: class Base * b is: class Base * *a is: class Base *b is: class Derived |
注意:type_info成员名返回的字符串取决于编译器和库的具体实现。 它不一定是具有典型类型名称的简单字符串,就像用于产生此输出的编译器中的那样。
注意typeid为指针生成的类型是指针本身类型(a和b都是Base *类型)。 然而,当typeid应用于对象(如*a和*b)时,typeid将生成它们的动态类型(即它们最终派生的完整对象的类型)。
如果typeid计算的类型是前面有解引用操作符(*)的指针,且该指针的值为空,typeid将抛出bad_typeid异常。