Home C&C++函数库 c++ 语法 程序源码 Linux C库

类型转换


☞  隐式类型转换



当将值复制到兼容类型时,将自动执行隐式转换。例如:

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

这里,a的值从short转换为int,不需要任何显式操作符。这就是所谓的标准转换。 标准转换影响基本数据类型,并允许在数值类型(short to int, int to float, double to int…), bool和一些指针之间进行转换。

从较小的整数类型转换为int,或从float类型转换为double类型称为提升, 并保证在目标类型中产生完全相同的值。算术类型之间的其他转换可能并不总是能够精确地表示相同的值:

  • 如果一个负整数值被转换为无符号类型,结果值对应于其2的补位表示(即,-1成为该类型可表示的最大值,-2是第二大值,…)。
  • bool类型转换 ,false转换为零(用于数值类型)和null pointer(用于指针类型);True等价于其他所有值,并被转换为1。
  • 如果是从浮点类型到整数类型的转换,值被截断(小数部分被舍去)。如果结果位于类型可表示值的范围之外,则转换将导致未定义的行为。
  • 此外,如果转换是在相同类型的数值类型之间(整型到整型或浮点型到浮点型),则转换是有效的,但值在于特定平台的实现(并且可能不可移植)。

其中一些转换可能意味着精度的损失,编译器可以用警告信号表示这一点。这个警告可以通过显式转换来避免。

对于非基本类型,数组和函数隐式转换为指针,指针通常允许以下转换:

  • 空指针可以转换为任何类型的指针.
  • 指向任何类型的指针都可以转换为空指针。
  • 指针向上转换:指向派生类的指针可以转换为可访问和明确基类的指针,而无需修改其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++中称为类型强制转换。类型强转通常有两种主要的语法: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)
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*一起使用)。 其目的是确保类型转换的结果指向目标指针类型的有效完整对象。

这自然包括指针向上转换(派生指针转换为基类指针),与隐式转换相同。

当且仅当所指向的对象是目标类型的有效完整对象时,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)



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还能够执行隐式允许的所有转换(不仅仅是那些指向类的指针),也能够执行相反的转换。它可以:

  • 从void*转换为任何指针类型。在这种情况下,它保证了如果void*值是通过从相同的指针类型转换而获得的, 那么结果指针值也是相同的。
  • 将整数、浮点值和enum类型转换为enum类型。

另外,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;
}
sample text

上面的例子可以保证工作,因为print函数不写入指向的对象。但是请注意, 删除对象指针的const性以实际写入会导致未定义的行为。

☞  类型检查(typeid)



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异常。


联系我们 免责声明 关于CandCplus 网站地图
>