C++类型转换

翻译:http://www.cplusplus.com/doc/tutorial/typecasting/

类型转换

隐式转换

当一个值赋给另一个兼容的类型时,隐式转换自动执行,例如:

short a=2000;
int b;
b=a;

此处a的值从short转化int而不需要任何显示的操作符,此为标准转换。 标准转化影响基本数据类型,并且允许数字类型间的转化 (shortintintfloatdoubleint...), 到或者从bool, 及一些指针转化。

将较小的整数类型转化为int, 或将float转化为double即所谓的升级, 能够保证转化为目标类型后保存精确的数值, 其他类型间的转化可能无法保持准确的数值:

  • 把负整数数值转化为无符号整数,其结果对应负整数二进制补码的的解释 (即, -1将变成最大的整数,-2第二大,...).
  • 从或者到bool的转化将false视为0 (对于数字类型) 或null pointer (对于指针类型); true等效于其他所有值并转化为1.
  • 从浮点类型转化为整数类型,数值会被截断。若结果落在可表示的范围之外,则该转化导致无定义行为。
  • 否则如果转化是在同种数字类型 间进行(如整数到整数,浮点到浮点),则转化结果是有效的,但结果时执行时确定的。

一些这样的转化可能会丢失精度,编译器会给出警告,可通过显示转化避免这些警告。
对于非基本类型如数组和函数隐式转化为指针,允许以下转换:

  • Null pointers可以转化为任何类型的指针
  • 指向任何类型的指针可以转化为 void pointers.
  • 指针向上转化:继承类指针可以转化为其可以访问且无二义性的基类的指针,而不需要修改它的const或volatile规格。

类的隐式转化

对于类,其隐式转化通过以下三个成员函数实现:

  • 单参数构造函数:允许使用特定的类型初始化一个对象。
  • 赋值操作符:允许在赋值时将特定的类型做隐式转化。
  • 类型转化操作符:允许隐式转化为特定的类型。

例如:

// 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前面指定。

explicit关键字

在函数调用中,C++每个参数的隐式转化。对于类有时是个问题,因为并不总是故意这样做的。例如在最后一个例子中添加函数:

void fn (B arg) {}

该函数接受类型为B的参数,但同时也可以接受类型为A的参数

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++的类型转换type-casting),通用的类型转换有两种语法:函数和类C:

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_castreinterpret_caststatic_cast const_cast。 它们的格式是在尖括号 (<>)中紧跟着新的类型,然后接着的括号中是待转换的表达式:

dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression)
const_cast <new_type> (expression)

等效于这些表达式的传统类型转换是:

(new_type) expression
new_type (expression)

然而每一种都有各自的特征。

dynamic_cast

dynamic_cast只能应用到类的指针或引用 (或void*)。其目的是保证类型转换的结果指向一个有效的完整的目标类型的类。
这自然的包含了指针向上转换 (pointer upcast,从pointer-to-derived到pointer-to-base转换),像隐式转换那样的方式。
dynamic_cast也可以向下转换(downcast ,从pointer-to-base到pointer-to-derived转换) 为多态类 (具有虚函数),当且仅当所指向的类是一个指向目标类型的有效的完整的类。例如:

// 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;
}
Compatibility note: This type of dynamic_cast requires Run-Time Type Information (RTTI) to keep track of dynamic types. Some compilers support this feature as an option which is disabled by default. This needs to be enabled for runtime type checking using dynamic_cast to work properly with these types.

以上的代码尝试执行两个dynamic casts,从Base* (pba and pbb)到Derived*,但只有第一个成功,注意它们各自的初始化:

Base * pba = new Derived;
Base * pbb = new Base;

尽管两者类型都是Base*,但 pba事实上指向类型为Derived的对象,而pbb指向类型为Base的对象。当各自执行ynamic_cast时,pba 指向一个完整的Derived对象,pbb指向类型为Base的对象,是一个不完整的Derived对象。
dynamic_cast由于指针所指的不是一个完整的目标类型的对象,从而无法转换 -如前面例子的第二个转换- 它会返回一个空指针表示转换失败。如果dynamic_cast用来转换一个引用类型并且转换失败,一个类型为bad_cast的异常会被抛出。
dynamic_cast也可以执行其他指针间的隐式转换:在指针类型间(即便在不相关的类间)转换null pointers,以及将指向任何类型的任何指针转换为 void*指针。

static_cast

static_cast执行指向相关类的指针间的转换,不仅向上转换(从pointer-to-derived到pointer-to-base),还包括向下转换(从pointer-to-base到pointer-to-derived)。在运行时不进行检查以保证正被转换的对象是目标类型的一个完整对象。因此由程序员负责保证转换的安全性。另一方面,它不会引起dynamic_cast类型安全检查的结果

class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);

以上是有效的代码,因此static_cast执行类指针间的转换,既包括隐式允许的,也包括相反的转换。它能

  • 转换void*为任何指针类型。在这种情况,它保证:如果void*的值是通过将同样的指针类型转换得到的,那么结果的指针值将是一样的。
  • 转换整数,浮点或枚举类型为枚举类型。

此外,static_cast能够执行:

  • 显示调用单参数构造函数或类型转换操作符
  • 转换为右值引用
  • 将枚举类的值转化为整数或浮点数
  • 将任何类型转换为void,评估或丢弃该值

reinterpret_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_cast

这种转换控制指针所指对象的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;
}

以上的代码能够正确运行,因为函数print没有写到所指的对象。然而注意,移除所指对象的const属性用以写操作将导致无定义行为。behavior.

typeid

typeid允许检查一个表达式的类型:

typeid (expression)

该操作符返回一个类型为type_info 的常对象的引用,type_info定义在标准头文件<typeinfo>中。由typeid返回的值可以用来和另一个typeid返回的值通过操作符==!=进行比较或者可以通过它的name()成员获得表示数据类型或类类型的null结尾的字符串。

// 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;
}

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将指针的类型视为指针本身的类型 (ab的类型为Base *)。然而,当typeid应用到对象(如*a*btypeid得到的是动态类型 (即最完整的继承类类型).
如果typeid评估的类型是一个指针前带解引用符号*,并且该指针具有null值,则typeid 抛出bad_typeid异常.

posted on 2015-06-22 18:57  adanus  阅读(317)  评论(0编辑  收藏  举报

导航