类型转换是使C ++具有更多类型安全性,更强大的功能,并且可以说服您在C上使用它。但是,当您是新手或从C背景移居时,这也是一个被低估的主题。 因此,我想出了一篇关于它的文章。 在这里,我们将不仅看到带有C开发人员示例的C ++类型转换,而且还将介绍为什么我们需要类型转换? & C ++类型的铸造作弊代码,C开发人员可以轻松记住和使用它。
尽管我不是专家,但是到目前为止,这是我从各种来源和5年以上的行业经验中学到的。
在C ++中,有5种不同类型的强制类型转换:C样式强制类型转换,static_cast,const_cast,dynamic_cast和reinterpret_cast。
我通常以“我们为什么需要它?”开头,但是这一次我们首先快速了解一些术语,并以一些有关类型转换的CPP核心指南结束本文。
您需要面对的行话
- 隐式转换:编译器在其中自动进行类型转换。 像float f = 3;一样,在这里编译器不会抱怨,而是直接将整数类型3转换为float并分配给f。
- 显式转换 :开发人员使用强制转换运算符指导转换。 所有类型的手动转换都属于显式类型转换类别。 像int * p =(int *)std :: malloc(10);,这里我们显式将void *强制转换为int *。
- l-value :代表内存位置的标识符。 例如,变量名,* ptr(ptr指向内存位置等)。
- r值 :不是l值的值,r值出现在赋值(=)运算符的右侧。 喜欢
int a = 5 ; // 5 = r-value,
q = p + 5 ; // p + 5 is r-value
注意:尽管在C ++中有一些例外和更多内容需要学习lvalue,rvalue及其引用 。
为什么我们需要类型转换?
- 数据表示内存中的位(0和1)。
- 数据类型是编译器指令,它告诉编译器如何存储和处理特定数据。uint32_t a = 5; 通过此语句,您可以假定将在内存中保留4个字节,并在执行时将在该内存位置存储0000 0000 0000 0000 0000 0000 0000 0000 0101数据位。 这很简单。
- 让我们再进一步一点,浮点数f = 3.0; 该语句还将在内存中保留4个字节并以1)的形式存储数据位。 符号位2)。 指数&3)。 尾数。 回忆一下浮点数是如何存储在内存中的 。
- 但是,当您编写类似float f = 3;的代码时,编译器会困惑于如何在float类型的内存中存储整数值。
- 因此,它会自动假定(此处为隐式转换 )要存储3.0而不是3,从人的角度来看,这在技术上是相同的,但是从计算机内存的角度来看,它们的存储方式不同,这是不同的。
- 在许多这样的方案中,您提供了要存储在内存中的数据,这些数据用来表示不同的数据类型。
- 例如,在下面的示例中,您尝试将类型B的对象分配给类型A的对象
class A { };
class B { };
int main ()
{
B b;
A a = b;
return 0 ;
}
- 在这种情况下,编译器无法假定任何内容,只会引发编译错误:
exit status 1
error: no viable conversion from 'B' to 'A'
A a = b;
^ ~
note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'B' to ' const A &' for 1st argument
class A {};
^
note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'B' to 'A &&' for 1st argument
class A {};
^
1 error generated.
- 但是,当您按以下方式定义转换运算符时:
class B {
public :
operator A () {
cout << "CONVERSION OPERATOR\n" ;
return A();
}
};
- 编译器将简单地调用此成员函数并且不会引发任何错误,因为程序员明确提到这就是他/她想要转换的方式。
5种C ++类型转换以及C开发人员示例
1️C型演员表
int main () {
float res = 10 / 4 ;
cout <<res<< endl ;
return 0 ;
}
- 当您尝试运行上面的代码时,您会得到2作为我们没有想到的输出。 为了正确初始化res变量,我们需要使用float进行类型转换,如下所示:
float res = ( float ) 10 / 4 ;
- 现在您的答案将是2.5。 这种类型的铸造看起来非常简单直接。
- 您也可以在C ++中使用以下形式编写:
float res = float ( 10 ) / 4 ;
- C样式强制转换可以更改数据类型,而无需更改基础内存表示,这可能导致垃圾结果。
2️ static_cast
- 如果您是像我这样的C开发人员,那么这将是您最好的goto C ++ cast,它适合大多数示例,例如:
int * p = std :: malloc ( 10 );
- 当您尝试使用C编译器编译以上代码时,它可以正常工作。 但是C ++编译器还不够好。 它将引发如下错误:
exit status 1
error: cannot initialize a variable of type 'int *' with an rvalue of type 'void *'
int * p = std :: malloc ( 10 );
^ ~~~~~~~~~~
1 error generated.
- 首先想到的是C样式的强制转换:
int * p = ( int *) std :: malloc ( 10 );
- 这将起作用,但是在C ++中不建议使用这种样式的转换。 static_cast会像这样处理隐式转换。 我们将主要使用它在隐式转换失败的地方进行转换,例如std :: malloc。
int * p = static_cast < int *>( std :: malloc ( 10 ));
- static_cast的主要优点是,它提供了编译时类型检查,这使得更容易出错。 让我们用C ++示例来理解这一点:
class B { };
class D : public B {};
class X { };
int main ()
{
D* d = new D;
B* b = static_cast <B*>(d); // this works
X* x = static_cast <X*>(d); // ERROR - Won't compile
return 0 ;
}
- 如您所见,在不了解所有涉及的所有类的情况下,没有简单的方法来区分这两种情况。
- C样式转换的另一个问题是很难定位。 在复杂的表达式中,很难看到C样式的强制类型转换,例如T(something)语法等效于(T)something。
3️ const_cast
- 现在,我们将直接跳至示例。 没有理论可以比例子更好地解释这一点。
1.忽略常数
int i = 0 ;
const int & ref = i;
const int * ptr = &i;
*ptr = 3 ; // Not OK
const_cast < int &>(ref) = 3 ; //OK
* const_cast < int *>(ptr) = 3 ; //OK
- 由于被分配给对象(此处为i)不是const,因此您可以修改i。 如果将const限定符添加到i,代码将进行编译,但是其行为将是不确定的(这意味着从“工作得很好”到“程序将崩溃>”。)
2.使用const this指针修改数据成员
- const_cast可以通过将指针声明为const的方法来更改非const类成员。 -当基于const重载成员函数时,这也很有用,例如:
class X
{
public :
int var;
void changeAndPrint ( int temp) const
{
this ->var = temp; // Throw compilation error
( const_cast <X *>( this ))->var = temp; // Works fine
}
void changeAndPrint ( int *temp)
{
// Do some stuff
}
};
int main ()
{
int a = 4 ;
X x;
x.changeAndPrint(&a);
x.changeAndPrint( 5 );
cout << x.var << endl ;
return 0 ;
}
3.将const参数传递给仅接受非const参数的函数
- const_cast也可以用于将const数据传递给不接收const参数的函数。 请参见以下代码:
int fun ( int * ptr)
{
return (*ptr + 10 );
}
int main ( void )
{
const int val = 10 ;
cout << fun( const_cast < int *>(&val));
return 0 ;
}
4.被抛弃的挥发性属性
- const_cast也可以用于丢弃volatile属性。 上面我们在const_cast中讨论的内容对于volatile关键字也有效。
4️ dynamic_cast
- dynamic_cast在运行时使用类型检查,而static_cast则在编译时使用类型检查。 当您不知道它代表的输入类型时,dynamic_cast更为有用。 假设:
Base* CreateRandom ()
{
if ( (rand()% 2 ) == 0 )
return new Derived1;
else
return new Derived2;
}
Base* base = CreateRandom();
- 如您所见,我们不知道CreateRandom()在运行时将返回哪个对象,但是如果它返回Derived1,则要执行Derived1的Method1()。 因此,在这种情况下,您可以按以下方式使用dynamic_cast
Derived1 *pD1 = dynamic_cast <Derived1 *>(base);
if (pD1){
pD1->Method1();
}
- 如果dynamic_cast的输入未指向有效数据,则它将为指针返回nullptr或为引用抛出std :: bad_cast异常。 为了使用dynamic_cast,您的类必须是多态类型,即必须至少包含一个虚拟方法。
- dynamic_cast利用了RTTI( 运行时类型识别 )机制。
5️ reinterpret_cast
- reinterpret_cast是一个编译器指令,它告诉编译器将当前类型视为新类型。
- 您可以使用reinterpret_cast将任何指针或整数类型转换为任何其他指针或整数类型。
- 这可能会导致危险的情况:什么都不会阻止您将int转换为std :: string *。
- 您将在嵌入式系统中使用reinterpret_cast。 适用于reinterpret_cast的常见情况是在uintptr_t与实际指针之间或在以下两者之间进行转换:
error: static_cast from 'int *' to 'uintptr_t'
(aka 'unsigned long' ) is not allowed
uintptr_t ptr = static_cast < uintptr_t >(p);
^~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
- 而是使用以下命令:
uintptr_t ptr = reinterpret_cast < uintptr_t >(p);
- 我尝试涵盖了大多数复杂性,以阐明不同类型转换背后的主要概念,但是,仍然有可能我会错过一些。 因此,C ++类型转换就是这样,并为C开发人员提供了一个示例。 让我们快速回顾一下:
供C开发人员在类型转换时使用C ++的备忘代码
阅读完所有这些内容后,您可能会混淆使用什么以及何时使用! 这就是为什么我创建了这个作弊代码
- 避免使用C样式强制转换。
- 投放时请确定要什么。
- 无论您使用的是C型转换,都应使用static_cast。
- 对多态类使用dynamic_cast。 请记住,仅对继承层次结构中具有至少一个虚拟成员的类使用dynamic_cast。
- 需要删除
const
或volatile
限定符时,请使用const_cast
。 - 如果没有选择,请使用reinterpret_cast。
注意:通常应避免使用const_cast
和reinterpret_cast
因为如果使用不正确,它们可能会有害。 除非您有充分的理由使用它们,否则不要使用它。
一些C ++核心准则
- P.4:理想情况下,程序应为静态(编译时)类型安全
- ES.48:避免强制转换
- ES.49:如果必须使用强制转换,请使用命名的 强制转换ES.50:不要强制转换 const
- C.146: 在无法避免类层次结构导航的地方 使用 dynamic_cast
- C.147: 当未能找到所需的类被认为是错误时, 请 对引用类型 使用 dynamic_cast
- C.148: 当无法找到所需的类被认为是有效的替代方法时, 使用 dynamic_cast 指向指针类型
From: https://hackernoon.com/c-type-casting-for-c-developers-0c823y9k