C++类型转换

显示转换

C ++是一种强类型的语言。许多转换,特别是那些暗示对值的不同解释的转换,都需要显式转换,在C ++中称为类型转换。通用类型转换存在两种主要语法:函数形式和像c一样的形式:
double x = 10.3;
int y;
y = int (x); // 函数形式
y = (int) x; // 像c一样的形式

这些通用类型转换的功能,可以满足基本数据类型的大多数需求。但是,这些运算符也可以应用于类和指向类的指针,这可能会产生代码,在语法上正确;但同时会导致运行时错误。例如,下面的代码编译没有错误:

例子

#include <iostream>
using namespace std;

class product {
     string  name;
};

class employee {
  string name;
  int       age;
  public:
employee (string emp_name, int emp_age) { 
      name = emp_name; 
      age= emp_age; 
}
int get_age() { 
     return age;
}
};

int main () {
     product d;
     employee * padd;
     padd = (employee *) &d;
     cout << padd->get_age();
     return 0;
}

该程序声明了一个指向employee的指针,但是随后使用显式类型转换为它分配了对另一个不相关类型的对象的引用:
padd = (employee *) &d;
无限制的显式类型转换,允许将任何指针转换为任何其他指针类型,而与它们指向的类型无关。后续对成员结果的调用将产生运行时错误或某些其他意外结果。
为了控制类之间的这些转换类型,C++有四个特定的转换运算符:
• dynamic_cast
• reinterpret_cast
• static_cast
• const_cast

它们的格式遵循在尖括号(<>)之间以及括号之后要转换的表达式之后的新类型。

  1. dynamic_cast <new_type> (expression)
  2. reinterpret_cast <new_type> (expression)
  3. static_cast <new_type> (expression)
  4. const_cast <new_type> (expression)

dynamic_cast

RTTI(Run-time type information)

在 C++ 中,RTTI是一种在运行时公开对象数据类型信息的机制,仅适用于至少具有一个虚函数的类。它允许在程序执行期间确定对象的类型

例如,在下列程序中,

#include<iostream>

using namespace std;

class Base { };
class Derived: public Base {};
  
int main()
{
    Base *b = new Derived;
    Derived *d = dynamic_cast<Derived *>(b);
    if(d != nullptr)
        cout<<"works";
    else
        cout<<"cannot cast Base* to Derived*";

    return 0;
}

dynamic_cast 使用 RTTI,编译器会出现错误
error: cannot dynamic_cast ‘b’ (of type ‘class Base*’) to type ‘class Derived*’ (source type is not polymorphic)

在基类中, 加一个虚函数,
virtual void dummy(){};
dynamic_cast 工作正常。


#include<iostream>

using namespace std;

class Base {
    virtual void dummy(){};
};
class Derived: public Base {
public:
	void output() {
		cout << "Derived" << endl;
	}
};
  
int main()
{
    Base *b = new Derived;
    Derived *d = dynamic_cast<Derived *>(b);
    if(d != nullptr)
        cout << "works" << endl;
    else
        cout<<"cannot cast Base* to Derived*" << endl;

	d->output();
	
    return 0;
}

输出结果

works
Derived

dynamic_cast应用

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

因此,当我们将一个类强制转换为其基类之一时,dynamic_cast总是成功的:

class Base { };
class Derived: public Base { };

Base b;
Base* pb;
Derived d;
Derived* pd;

pb = dynamic_cast<Base*>(&d); // ok: derived-to-base

当一个类是多态的时,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" << endl;

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

     } catch (exception& e) {
           cout << "Exception: " << e.what();
     }
     return 0;
}

dynamic_cast需要运行时类型信息(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指针(void *)。

static_cast

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也可以用于执行任何其他也可以隐式执行的非指针转换,例如,基本类型之间的标准转换:
double d=3.14159265;
int i = static_cast(d);

或如上面“隐式转换”中所述的具有显式构造函数或运算符的类之间的任何转换。
基类与派生类之间的转换
static_cast可用于在指向相关类的指针之间进行转换。它还可以执行隐式转换。
class Vehicle {
};

class SUV: public Vehicle {
};

SUV *h = new SUV; // Pointer to object of derived type
Vehicle *m = static_cast< Vehicle *>(h); // cast it to pointer to base type.
// static_cast here is unnecessary
SUV *m2 = static_cast<SUV *>(m); // cast back to pointer to derived type

每个SUV都是Vehicle。因此,SUV*可以隐式转换为Vehicle *。如果仍然使用static_cast,实际上,它执行此隐式强制转换。
但不是每个Vehicle都是SUV。因此,不允许从Vehicle *到 SUV *的隐式转换。这就是需要使用static_cast的地方。如果我们省略了静态类型转换,编译器将给出下列错误

invalid conversion from ‘Vehicle *’ to ‘SUV *’
但是,使用static_cast,我们仍然可以进行转换。
static_cast不执行任何检查。因此,确保转换有效是你的责任。无效的转换,编译器不会失败,但是,稍后在指针指向不完整的类型,并被取消引用时,可能会导致问题。例如,
class Animal {
public:
virtual void food(){
};
};

class Tiger: public Animal {
public:
virtual void food() { // Note the virtual
std::cout << “Meat” << std::endl;
}
};

class Sheep: public Animal {
public:
virtual void food() { //
std::cout << “Grass” << std::endl;
}
};

Animal *m = new Sheep; //
Tiger *h = static_cast< Tiger *>(m); // OK so far
h -> food();

因此,除非您完全确定转换有效,否则请不要使用static_cast向下转换到推导类。
static_cast不允许您在两个不相关的类之间进行转换
class ClassA {
};
·
class ClassB {
};

ClassA *a = new ClassA;
ClassB *b = static_cast<ClassA *>(a);

编译器将给出下列错误
cannot convert ‘ClassA*’ to ‘ClassB*’ in initialization

reinterpret_cast

reinterpret_cast可以将任何指针类型转换为任何其他指针类型,即使是不相关的类也是如此。运算结果是从一个指针到另一个指针的值的简单二进制副本。
reinterpret_cast允许所有指针转换:既不检查指向的内容,也不检查指针类型本身。
它还可以将指针转换为整数,或从整数转换指针。该整数值表示指针的格式是特定于平台的。唯一的保证是,将指针强制转换为足够大,以完全包含整数的整数类型,可以将其强制转换为有效指针。
可以通过reinterpret_cast;但不能通过static_cast进行的转换是低级操作,其解释导致代码通常是特定于系统的,因此是不可移植的。例如:
class A {
};

class B {
};

A * a = new A;
B * b = reinterpret_cast<B*>(a);

这是有效的C ++代码,尽管意义不大。现在我们有了一个指向不兼容类的对象的指针,因此,删除/引用它是不安全的。
“不安全”转换
reinterpret_cast关键字会导致两种不同类型的“不安全”转换:
• “类型校正”转换,可用于访问一种类型的内存,就好像它是另一种类型一样。
• 整数类型和指针类型之间的任一方向上的转换。
类型校正

  1. reinterpret_cast将指向对象类型的指针(特别是引用)转换为指向任何其他对象类型的指针(特别是引用)。这不会调用任何构造函数或转换函数。
    int x = 42;
    char* p = static_cast<char*>(&x); // error: static_cast 不支持
    char* p = reinterpret_cast<char*>(&x); // OK
    *p = ‘z’; // maybe this modifies x

  2. 只要地址针与目标类型进行了适当地对齐,则reinterpret_cast的结果总是与操作数拥有相同的地址;否则,结果不确定。
    int x = 42;
    char& r = reinterpret_cast<char&>(x);
    const void* px = &x;
    const void* pr = &r;
    assert(px == pr); // 这个断言永远不会发生

  3. 只要目标类型的对齐要求不比源类型的对齐要求更严格,指针(特别是引用)可以在源类型与目标类型之间来回转换;否则reinterpret_cast的结果是不确定的。

int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456

在大多数实现中,reinterpret_cast不会更改地址,直到C ++ 11才对该要求进行标准化。
reinterpret_cast也可以用于将一种指针指向数据成员的类型转换为另一种,或者将一种指针指向成员函数的类型转换为另一种。
使用reinterpret_cast被认为是危险的,因为当源类型和目标类型不相关时,通过使用reinterpret_cast获得的指针或引用进行的读或写操作可能会触发未定义的行为。
可以使用reinterpret_cast将对象指针(包括void *)或函数指针转换为整数类型。仅当目标类型足够长时才编译。结果是实现定义的,通常会生成内存指针所指向的字节的数字地址。
如果类型std :: intptr_t和std :: uintptr_t存在,则保证它们足够长以容纳void *(并因此指向对象类型的任何指针)。但是,不能保证它们足够长以容纳函数指针。
同样,reinterpret_cast可用于将整数类型转换为指针类型。同样,结果是实现定义的,但是指针值通过整数类型的往返保证是不变的。该标准不保证将零值转换为空指针。

const_cast

这种类型的转换可以操纵对象的固定性,可以设置或删除对象。例如,为了将const参数传递给需要非恒定参数的函数:
例子

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

Typeid

typeid允许检查表达式的类型:
typeid(表达式)
此运算符返回对标准头文件中定义的type_info类型的常量对象的引用。可以使用==和!=运算符将该返回值与另一个值进行比较,或者可以通过使用其name()成员来获取表示数据类型或类名称的以空终止的字符序列。

例子

#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应用于类型为多态类的表达式时,结果是最派生的完整对象的类型:

例子

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

注意:由type_info的成员名称返回的字符串取决于编译器和库的特定实现。它不一定是具有典型类型名称的简单字符串,就像在用于生成此输出的编译器中一样。
注意typeid认为指针的类型是指针类型本身(a和b都属于类CBase *)。但是,当将typeid应用于对象(例如* a和* b)时,typeid会产生其动态类型(即,其派生程度最高的完整对象的类型)。

如果typeid评估的类型是在取消引用运算符(*)之前的指针,并且该指针具有null值,则typeid抛出bad_typeid异常。

上面示例中的编译器生成的type_info :: name名称易于用户读取,但这不是必须的:编译器可以只返回任何字符串。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值