类型转换

将给定类型的一个表达式转换为另一种类型称为类型转换。我们已经见过一些类型转换的方法

隐式转换

隐式转换不需要任何运算符。当一个值被复制到兼容类型时,它们会自动执行。例如

1
2
3
short a=2000;
int b;
b=a;

这里,a的值从short升级为intshort升级为, 升级为float, double升级为...)以及与bool之间的转换,还有一些指针转换。其中一些转换可能意味着精度的损失,编译器会发出警告。可以使用显式转换来避免此警告。

隐式转换还包括构造函数或运算符转换,它们影响包含特定构造函数或运算符函数以执行转换的类。例如

1
2
3
4
5
class A {};
class B { public: B (A a) {} };

A a;
B b=a;

这里,在class Aclass BB有一个构造函数接受A类的对象作为参数。因此,允许从AB进行隐式转换。

显式转换

C++ 是一种强类型语言。许多转换,特别是那些意味着对值进行不同解释的转换,都需要显式转换。我们已经见过两种显式类型转换的表示法:函数式转换和 C 风格转换。

1
2
3
4
short a=2000;
int b;
b = (int) a;    // c-like cast notation
b = int (a);    // functional notation 

这些显式转换运算符的功能足以满足基本数据类型的大多数需求。然而,这些运算符可以不加区分地应用于类和类指针,这可能导致代码虽然在语法上正确,但会导致运行时错误。例如,以下代码在语法上是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// class type-casting
#include <iostream>
using namespace std;

class CDummy {
    float i,j;
};

class CAddition {
	int x,y;
  public:
	CAddition (int a, int b) { x=a; y=b; }
	int result() { return x+y;}
};

int main () {
  CDummy d;
  CAddition * padd;
  padd = (CAddition*) &d;
  cout << padd->result();
  return 0;
}
 

该程序声明了一个指向CAddition的指针,但随后使用显式类型转换将其赋值给了一个指向另一个不兼容类型的对象的引用。

1
padd = (CAddition*) &d;

传统的显式类型转换允许将任何指针转换为任何其他指针类型,而不管它们指向的类型。随后对成员result的调用将导致运行时错误或意外的结果。

为了控制这类类之间的转换,我们有四种特定的转换运算符:dynamic_cast, reinterpret_cast, static_castconst_cast它们的格式是跟随新类型,用尖括号(<>)括起来,紧接着是括号内的要转换的表达式。

dynamic_cast <新类型> (表达式)
reinterpret_cast <新类型> (表达式)
static_cast <新类型> (表达式)
const_cast <新类型> (表达式)

这些表达式的传统类型转换等价物是

(新类型) 表达式
新类型 (表达式)

但它们各自具有特殊的特性。

dynamic_cast


dynamic_cast只能用于指向对象的指针和引用。其目的是确保类型转换的结果是所请求类的有效完整对象。

因此,dynamic_cast在将类转换为其基类之一时,总是成功的。

1
2
3
4
5
6
7
8
class CBase { };
class CDerived: public CBase { };

CBase b; CBase* pb;
CDerived d; CDerived* pd;

pb = dynamic_cast<CBase*>(&d);     // ok: derived-to-base
pd = dynamic_cast<CDerived*>(&b);  // wrong: base-to-derived 

此代码段中的第二个转换将产生编译错误,因为基类到派生类的转换不允许使用dynamic_cast,除非基类是多态的。

当一个类是多态的时,dynamic_cast在运行时执行特殊检查,以确保表达式产生所请求类的有效完整对象。

// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;

class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };

int main () {
  try {
    CBase * pba = new CDerived;
    CBase * pbb = new CBase;
    CDerived * pd;

    pd = dynamic_cast<CDerived*>(pba);
    if (pd==0) cout << "Null pointer on first type-cast" << endl;

    pd = dynamic_cast<CDerived*>(pbb);
    if (pd==0) cout << "Null pointer on second type-cast" << endl;

  } catch (exception& e) {cout << "Exception: " << e.what();}
  return 0;
}
Null pointer on second type-cast

兼容性注意事项 dynamic_cast需要运行时类型信息(RTTI)来跟踪动态类型。一些编译器支持此功能作为选项,该选项默认禁用。必须启用此功能才能使dynamic_cast正常工作以进行运行时类型检查。

代码尝试执行两个动态转换,从类型为CBase* (pbapbb的对象指针,转换为类型为CDerived*的对象指针,但只有第一个成功。注意它们各自的初始化。

1
2
CBase * pba = new CDerived;
CBase * pbb = new CBase;

尽管两者都是类型为CBase*, pba的指针,但CDerived指向的是一个类型为pbb的指针,但CBase指向的是一个类型为dynamic_cast, pba指向的是一个完整的类对象CDerived的迭代器,而pbb指向的是一个类对象CBase,而CDerived.

dynamic_cast无法转换指针,因为它不是所需类的完整对象(如上例中的第二次转换),它会返回一个空指针来表示失败。如果dynamic_cast用于转换为引用类型但转换不可行,则会抛出类型为bad_cast的异常。

dynamic_cast还可以转换空指针,甚至可以转换不相关类指针,还可以将任何类型的指针转换为 void 指针(void*).

static_cast

static_cast可以执行相关类指针之间的转换,不仅可以从派生类到其基类,还可以从基类到其派生类。这确保了至少类是兼容的,如果转换了正确的对象,但运行时没有执行任何安全检查来验证被转换的对象实际上是目标类型的完整对象。因此,程序员有责任确保转换是安全的。另一方面,避免了dynamic_cast的类型安全检查的开销。

1
2
3
4
class CBase {};
class CDerived: public CBase {};
CBase * a = new CBase;
CDerived * b = static_cast<CDerived*>(a);

这将是有效的,尽管和 b将指向一个不完整的类对象,并且如果解引用它可能会导致运行时错误。

static_cast还可以用于执行任何其他可以隐式执行的非指针转换,例如基本类型之间的标准转换。

1
2
double d=3.14159265;
int i = static_cast<int>(d);

或具有显式构造函数或运算符函数的类之间的任何转换,如上面“隐式转换”中所述。

reinterpret_cast

reinterpret_cast将任何指针类型转换为任何其他指针类型,即使是不相关类的指针。操作结果是将值从一个指针到另一个指针的简单二进制复制。允许所有指针转换:被指向的内容或指针类型本身都不会被检查。

它还可以将指针转换为整数类型或从整数类型转换。此整数值表示指针的格式是平台特定的。唯一保证的是,一个足够大的指针转换为整数类型,可以完全包含它,就能够被转换回一个有效的指针。

可以由reinterpret_cast执行但不能由static_cast执行的转换是低级操作,其解释会产生系统特定的代码,因此不具有可移植性。例如。

1
2
3
4
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);

这是有效的 C++ 代码,尽管它意义不大,因为现在我们有一个指向不兼容类对象的指针,因此解引用它是不安全的。

const_cast

这种类型的转换会操纵对象的 constness,无论是设置还是移除。例如,为了将 const 参数传递给需要非 const 参数的函数。

// const_cast
#include <iostream>
using namespace std;

void print (char * str)
{
  cout << str << endl;
}

int main () {
  const char * c = "sample text";
  print ( const_cast<char *> (c) );
  return 0;
}
sample text

typeid

typeid允许检查表达式的类型。

typeid (表达式)

此运算符返回对类型对象的常量引用type_info定义在标准头文件<typeinfo>中。返回的值可以使用运算符进行比较,==!=或通过使用其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 CBase { virtual void f(){} };
class CDerived : public CBase {};

int main () {
  try {
    CBase* a = new CBase;
    CBase* b = new CDerived;
    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() << endl; }
  return 0;
}
a is: class CBase *
b is: class CBase *
*a is: class CBase
*b is: class CDerived

注意:来自成员名称的字符串 type_info 取决于你编译器和库的特定实现。它不一定是具有典型类型名称的简单字符串,就像用于生成此输出的编译器一样。
注意,typeid考虑指针的类型是其指针类型本身(两者都是a和 bjclass CBase *)。然而,当typeid应用于对象(如*a*b) typeid)时,它会产生它们的动态类型(即其最派生完整对象的类型)。

如果类型typeid求值的是一个后跟解引用运算符(*)的指针,并且该指针的值为空,typeid会抛出一个bad_typeid异常。

上面示例中的编译器生成的名称 type_info::name 是用户可读的,但这并非必需:编译器可以只返回任何字符串。
Index
目录