当将值复制到兼容类型时,将自动执行隐式转换。例如:
short a=2000;int b;b=a;在这里,a的值被从short转换到int而没有任何明确的操作。这就是所谓的标准转换。标准转换的影响基本数据类型,并允许数值类型之间的转换(short到int,int到float,double到int…),或从bool,和一些指针的转换。
从一些较小的整数类型转换为int,或从float到double称为扩展(PRomotion),保证在目的类型产生完全相同的值。其他算术类型之间的转换并不总是能够准确地表示相同的值:
(1)如果一个负整数的值转换为无符号类型,所产生的值相当于其补数表示。(即,-1成为表示该类型的最大值,-2是第二大,…)。 (2)bool类型转换例如flase,就可以相当于零(对于数值类型)或者空指针(指针类型);true相当于所有其他的值转换为等效的1。 (3)如果转换是从浮点类型到整型,则值被截断(小数部分被删除)。如果结果超出表示值类型范围,则导致未定义的行为。 (4)否则,如果转换介于同类型(int到int或float到float)的数值类型之间,则转换是有效的,但值是具体实现的(并且可能不可移植)。
这些转换可能意味着精度的损失,编译器可能发出警告信号。这个警告可以避免转换。
对于非基本类型,数组和函数隐式地转换为指针,而指针一般允许下列转换: (1)空指针可以转换为任何类型的指针 (2)指向任何类型的指针可以转换为空指针。 (3)指针向上:一个派生类指针可以转换为一个accessible 和unambiguous 基类的指针,而无需修改其const或volatile的属性。
在类的世界里,隐式转换可以通过三个成员函数来控制: (1)单参数构造函数:允许从特定类型的隐式转换初始化对象。 (2)赋值运算符:允许在赋值时从特定类型允许隐式转换。 (3)类型转换运算符:允许隐式转换为特定类型。
例如:
// 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;}类型转换运算符使用特定语法:它使用运算符关键字后跟目标类型和空括号集。注意,返回类型是目标类型,因此在运算符关键字之前没有指定。
在一个函数调用中,C++允许隐式转换发生在每个参数中。这对于某些类可能是有些问题的,因为它并不总是它想做的。例如,如果我们在最后一个例子中添加下面的函数:
void fn (B arg) {}这个函数需要类型B的参数,但它也可以用类型A的对象作为参数调用:
fn (foo);这可能是或可能不是想要实现的。但是,在任何情况下,可以通过显式关键字标记受影响的构造函数来防止:
// 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;}此外,显式标记的构造函数不能用赋值语法来调用;在上面的示例中,不能用:
B bar = foo;类型的成员函数(上一节中描述的)也可以被指定为显式。这可以防止与目标类型的显式指定构造函数一样的隐式转换。
C++是强类型语言。许多的转换,特别是那些隐含的值不同的解释,需要显式转换,在C++中称为type-casting。有两种通用type-casting主要语法:functional 和c-like:
double x = 10.3;int y;y = int (x); // functional notationy = (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;}程序声明了一个指向加法的指针,但是它使用显式类型赋值将它分配给另一个无关类型的对象的引用:
padd = (Addition*) &d;不受限制的显式类型转换允许将任何指针转换为其他指针类型,而独立于它们指向的类型。对成员结果的后续调用将产生运行时错误或其他一些意想不到的结果。
为了控制这些类型之间的相互转换的类,我们有四个具体的casting 操作: dynamic_cast,reinterpret_cast,static_cast和const_cast。它们的格式是按照新类型的括号括在角度括号之间,然后在括号之间转换表达式。
dynamic_cast <new_type> (expression)reinterpret_cast <new_type> (expression)static_cast <new_type> (expression)const_cast <new_type> (expression)传统类型type-casting 等价于这些表达式将:
(new_type) expressionnew_type (expression)但每一个有其特殊的特点:
dynamic_cast只能使用指针和引用的类(或void *)。其目的是确保类型转换的结果指向目标指针类型的有效完整对象。
这自然包括 pointer upcast(从指针的指针转换指针的基源),同样允许隐式转换。
但dynamic_cast也可以downcast,(将指针指向派生基地)多态类(那些虚拟成员)当且仅当指向的对象是一个有效的完整的目标对象类型。例如:
// 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;}兼容性注意:这种类型的dynamic_cast需要运行时类型信息(RTTI)跟踪动态类型。一些编译器支持此功能作为默认禁用的选项。这需要运行时进行类型检查使得dynamic_cast与这些类型能正常工作。
上面的代码试图执行从类型 base*指针的对象动态模型(PBA和PBB)到另一种指针类型Derived*派生的对象的两种dynamic_cast,但只有第一个是成功的。注意各自的初始化:
Base * pba = new Derived;Base * pbb = new Base;虽然都是类型base*指针,PBA其实指向类型派生的对象,而pbb 指向的是Base类型。因此,当各自的类型转换都进行dynamic_cast,pba 是指向一个完整的对象派生类Derived,而pbb 是指向基类Base的对象,这是一个不完整的对象派生类。
当dynamic_cast不能使用指针因为它不是一个完整的对象所需的类,而在前面的例子中第二转换,它返回一个空指针来表明是失败的。dynamic_cast用来转换为引用类型的转换是不可能的,反而是bad_cast抛出的异常类型。
dynamic_cast也可以允许在指针中执行其他隐式强制转换:casting 之间的指针类型的空指针(即使在不相关的类中),和casting 任何指针任何类型void*指针。
static_cast能在指针之间的相互转换相关的类,不仅upcasts(从pointer-to-derived到pointer-to-base),而且downcasts 强制类型转换(从pointer-to-base到pointer-to-derived)。在运行时不执行检查,以确保正在转换的对象实际上是目标类型的完整对象。因此,它是由程序 员来确保转换是安全的。另一方面,它不承担对dynamic_cast类型安全检查的开销。
class Base {};class Derived: public Base {};Base * a = new Base;Derived * b = static_cast<Derived*>(a);这是有效的代码,虽然b指向的类的一个对象的引用,如果不完全,还可能导致运行时错误。 因此,static_cast能够执行类的指针不仅可以隐式转换,而且其相反的转换。
static_cast也能够执行所有的转换允许隐式(不只是那些类的指针),也能完成这些相反的。它可以: (1)从void*转换为任何指针类型。在这种情况下,它保证如果从相同的指针类型转换为void*值,得到的指针值是相同的。 (2)转换为整数,浮点值和枚举类型的枚举类型。
此外,static_cast还可以执行以下: (1)显式调用单个参数构造函数或转换运算符。 (2)转换为右值引用。 (3)将枚举类的值转换为整数或浮点值。 (4)将任何类型转换为void,评估和丢弃这个值。
reinterpret_cast是转换任何指针类型的任何其他类型的指针,甚至不相关的类。操作结果是从一个指针到另一个指针的简单二进制复制。允许所有指针转换:既不指向指针的内容,也不检查指针类型本身。 它也可以将指针转换为整数类型。该整数值表示指针的格式是环境所特定的。唯一可以肯定的是,指针转换到一个大到足以完全包含它的整数类型(如intptr_t),保证可以追溯到一个有效的指针。 可以由reinterpret_cast但不能由static_cast转换的类型的二进制表示是低级操作的转换,这在大多数情况下,结果是系统特定的代码,因而非便携式。例如:
class A { /* ... */ };class B { /* ... */ };A * a = new A;B * b = reinterpret_cast<B*>(a);编译这个代码,虽然它没有多大意义,因为现在b指向一个完全不相关和可能不相容类的对象。引用b是不安全的。
这类casting 操纵对象所指的常量指针,或者被设置或者被删除。例如,为了通过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;}上面的例子保证工作,因为函数print 不向指针对象写入。需要注意的是,这消除指针对象从而写入它导致未定义的行为。
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;}注意:type_info成员的name字符串返回的名称取决于你的编译器和库的具体实现。它不一定是一个具有典型类型名称的简单字符串,就像在编译器中用来生成这个输出一样。
注意typeid认为指针的类型是指针类型本身(A和B的类型都是class Base )。然而,当typeid用于对象(如*a和 b)typeid得到的就是的动态类型(即他们的大部分来自完整的对象类型)。
如果typeid测试的类型是在解引用操作符(*)前的指针,这个指针有一个空值,那么typeid抛出一个异常bad_typeid。
新闻热点
疑难解答
图片精选