C++ Tutorials: C++ Language: Other language features: Type conversions

本文详细介绍了C++中的类型转换,包括隐式转换和显式转换。隐式转换如基本类型之间的提升、从bool转换、指针转换等,而显式转换分为四种类型:dynamic_cast、reinterpret_cast、static_cast和const_cast,每种都有特定的使用场景和注意事项。文章强调了dynamic_cast的安全性检查和static_cast的效率与风险,同时提醒开发者在使用类型转换时要谨慎,避免潜在的运行时错误。
摘要由CSDN通过智能技术生成

C++官方参考链接:Type conversions - C++ Tutorials (cplusplus.com)

类型转换
隐式转换
将值复制到兼容类型时,将自动执行隐式转换。例如:
short a=2000;
int b;
b=a;
在这里,a的值从short提升为int,而不需要任何显式操作符。这被称为标准转换标准转换影响基本数据类型,并允许在数值类型(short到int、int到float、double到int…)、到/从bool和一些指针的转换。 
从某个较小的整数类型转换为int,或从float转换为double被称为提升,并保证在目标类型中产生完全相同的值。算术类型之间的其他转换可能并不总是能够精确地表示相同的值:
如果将一个负整数值转换为无符号类型,则结果值对应于其2的补位表示(即,-1成为该类型可表示的最大值、-2为第二大值,…)
从/到bool类型的转换认为false等效于0(对于数值类型)和空指针(对于指针类型);true等价于所有的其他值,并被转换为等价于1。 
如果从浮点类型转换为整数类型,则值将被截断(小数点部分被删除)。如果结果不在类型可表示值的范围内,则转换会导致未定义的行为。
否则,如果转换是在相同类型的数值类型(整数到整数或浮点数到浮点数)之间进行的,则转换是有效的,但值是特定于实现的(并且可能不可移植)。
其中一些转换可能意味着精度的损失,编译器可以用警告发出信号。通过显式转换可以避免此警告
对于非基本类型,数组和函数隐式转换为指针,而指针通常允许以下转换:
空指针(NULL)可以转换为任何类型的指针
任何类型的指针都可以转换为void指针。
指针向上转换:指向派生类的指针可以转换为可访问且明确的基类的指针,而无需修改其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++中称为类型强制转换。泛型类型强制转换有两种主要语法:函数型和类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_castconst_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;
}

兼容性注意:这种类型的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还可以执行指针上允许的其他隐式强制转换:在指针类型之间(甚至在不相关的类之间)强制转换空指针(NULL),将任何类型的指针强制转换为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*值是通过从相同的指针类型转换获得的,则得到的指针值是相同的。
*将整数、浮点值和枚举类型转换为枚举类型。
*此外,static_cast还可以执行以下操作: 
*显式调用单实参构造函数或类型强制转换操作符。
*转换为右值引用。
*将enum class值转换为整数或浮点值。
*将任何类型转换为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_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允许检查表达式的类型:
typeid(expression) 
该操作符返回type_info类型的常量对象的引用,该对象定义在标准头文件<typeinfo>。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的成员name返回的字符串取决于编译器和库的具体实现。它不一定是具有典型类型名的简单字符串,就像用于生成此输出的编译器中那样。 
注意typeid考虑的指针类型是指针类型本身(a和b的类型都是Base *类型)。然而,当typeid应用于对象(如*a和*b)时,typeid生成它们的动态类型(即它们派生最完整对象的类型)。
如果typeid求值的类型是前面有解引用操作符(*)的指针,且该指针具有空值(NULL或者nullptr),则typeid抛出bad_typeid异常。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_40186813

你的能量无可限量。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值