类型转换和类型安全(现代C++)
文档标识常见类型转换问题并描述如何避免它们在 C++ 代码。
当您编写 c. c++ 程序,务必确保这是类型安全的。 这意味着每个变量、函数参数和函数返回值存储到可接受的数据,并且,涉及不同类型的值“的操作有意义”和不导致数据丢失、位组合的解释不正确或内存损坏。 程序不显式或隐式强制转换从一个类型的值赋给另一个根据定义是类型安全的。 但是,有时,需要类型转换,即使不安全的转换。 例如,可以在类型 int的变量可能必须存储浮点运算的结果,或者您可能必须通过在无符号 int 的值到使用签名的 int的功能。 因为它们可能会导致数据丢失的值或重新定义,两个示例演示不安全的转换。
当编译器检测到不安全的转换时,将发出错误或警告。 错误停止生成;警告允许生成继续,但指示代码中的一个可能的错误。 但是,因此,即使您的程序进行编译,而无需警告,这会导致隐式类型转换导致错误的结果它仍可以包含代码。 类型错误能显式转换或转换还引入了,在代码。
隐式类型转换
在表达式包含不同的内置类型时操作的,因此,显式转换不存在,则编译器将使用内置 标准转换 将某个操作线程,以使类型匹配。 编译器尝试在显式定义的转换,直到一个成功。 如果选定的转换是提升,编译器不发出警告。 如果转换为 out,编译器会发出有关可能丢失数据的警告。 实际数据丢失是否发生取决于涉及的实际值,但是,建议您将此警告视为错误。 如果用户定义的类型是包含的,则编译器尝试使用您在类定义中指定的转换。 如果找不到一个接受的转换,编译器将发出错误,并且不生成程序。 有关管理标准转换规则的更多信息,请参见 标准转换。 有关用户定义的转换的更多信息,请参见 用户定义的转换 (C++/CLI)。
扩大转换 (提升)
在一个扩大转换,在一个较小的变量的值赋给一个更大的变量不会丢失数据。 由于扩大转换始终是安全的,编译器不提示的情况下执行它们并不发出警告。 下面的转换扩展转换。
发件人 |
若要 |
---|---|
任何带符号或无符号整型除 long long 或 __int64 |
double |
bool 或 char |
其他内置类型 |
short 或 wchar_t |
int, long, long long |
int, long |
long long |
float |
double |
收缩转换 (强制转换)
编译器隐式执行收缩转换,但是,它会警告有关可能的数据丢失。 非常仔细采用这些警告。 如果您确定数据丢失不会发生,因为在较大的变量的值始终以适应较小的变量,则添加显式强制转换,让编译器将不再发出警告。 如果您不确定转换是安全的,请添加到您的代码运行时检查处理可能丢失数据,使它不会使程序产生错误的结果。 有关为处理此情况下,如何的建议参见 如何:收缩转换 (C++) 的句柄。
因为放弃浮点值的部分且丢失,从浮点类型的所有转换为整型有收缩转换。
下面的代码示例演示一些隐式收缩转换和编译器为其发出的警告。
int i = INT_MAX + 1; //warning C4307:'+':integral constant overflow
wchar_t wch = 'A'; //OK
char c = wch; // warning C4244:'initializing':conversion from 'wchar_t'
// to 'char', possible loss of data
unsigned char c2 = 0xfffe; //warning C4305:'initializing':truncation from
// 'int' to 'unsigned char'
int j = 1.9f; // warning C4244:'initializing':conversion from 'float' to
// 'int', possible loss of data
int k = 7.7; // warning C4244:'initializing':conversion from 'double' to
// 'int', possible loss of data
签名-未签名的转换
一个带符号的整数类型及其无符号复制始终大小相同,但是,它们在位组合如何不同的值将被解释。 下面的代码示例演示会发生什么,而同一个位组合解释为一个有符号值和为无符号的值。 在两 num 和 num2 存储的该位组合。内容从不更改对以前的插图显示。
using namespace std;
unsigned short num = numeric_limits<unsigned short>::max(); // #include <limits>
short num2 = num;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: unsigned val = 65535 signed val = -1
// Go the other way.
num2 = -1;
num = num2;
cout << "unsigned val = " << num << " signed val = " << num2 << endl;
// Prints: unsigned val = 65535 signed val = -1
通知值在两个方向上重新定义。 如果程序生成值的符号会反转从中对多个结果您所期望的结果,请查找有符号和无符号整数类型之间的隐式转换。 在下面的示例中,表达式 (0 的结果– 1) 从 int 隐式转换为 unsigned int ,当在 num时存储状态。 这使得位模式重新定义。
unsigned int u3 = 0 - 1;
cout << u3 << endl; // prints 4294967295
编译器不警告有关有符号和无符号整数类型之间的隐式转换。 因此,建议您避免完全签名为无符号的转换。 如果您不能避免它们,然后添加到您的代码检测到运行时的检查转换的值是否大于或等于零和小于或等于已签名的类型的最大值。 此范围的值将调用从签名到非托管签名或从无符号为签名,而不会重新定义。
指针转换
在许多表达式,c. 样式数组隐式转换为对第一个元素的指针在数组,并且,常量转换可能不提示的情况下发生。 虽然此方法很方便,它也是可能容易出错。 例如,下面的非常设计的代码示例是无意义,它仍在 Visual C++ 将编译并导致“p "结果。 首先,指向数组的第一个元素的“帮助”字符串常量文本转换为 char* ;该指针由三个组件然后添加,以便它现在指向最后一个元素“p”。
char* s = "Help" + 3;
显式转换 (转换)
使用转换操作,您可以指示编译器将一种类型的值转换为另一种类型。 编译器在某些情况下会引发错误,如果两个类型完全无关,但是,它并不会引发错误,即使操作不是类型安全的。 因为从一种类型的任何转换到另一个是潜在的源的编程错误,使用慎重转换。 但是,有时需要转换,因此,不是所有的转换是具有相同的危险的。 将的一个有效使用是代码执行收缩转换,并且知道转换不会使程序产生错误的结果。 实际上,这指示编译器知道您执行和停止打扰有警告于此。 还可以将指向派生的选件类为指针到基础选件类。 还可以转换 const-将该变量的状态为要求非const 参数的函数。 大部分转换运算涉及一定的风险。
在 C 编程,同一 C 样式转换运算符为多种转换使用。
(int) x; // old-style cast, old-style syntax
int(x); // old-style cast, functional syntax
C 样式转换运算符与调用运算符相同 () 因此是不显眼在代码易于被忽略。 两个有错误,因为它们难以一目了然地标识或搜索,并且,它们足以不同的调用 static、const和 reinterpret_cast的任意组合。 确定一个旧式强制转换实际上可能很难且容易出错。 对所有这些原因,那么,当需要时转换,建议使用下面的 C++ 转换运算符之一,在某些情况下会更加类型安全的,因此,更加明确表示该编程的目的:
static_cast,只在编译时检查的转换。 static_cast 返回错误,如果编译器检测您尝试将是完全不兼容的类型。 您还可以使用它将指针到基础之间以及指向派生,但是,编译器无法始终指示此类转换是否是安全的运行时。
double d = 1.58947; int i = d; // warning C4244 possible loss of data int j = static_cast<int>(d); // No warning. string s = static_cast<string>(d); // Error C2440:cannot convert from // double to std:string // No error but not necessarily safe. Base* b = new Base(); Derived* d2 = static_cast<Derived*>(b);
有关更多信息,请参见 static_cast。
dynamic_cast,安全的,则运行时检查的指针传递基础转换为指向派生。 dynamic_cast 比向下转换的 static_cast 安全,但是,运行时检查导致一些系统开销。
Base* b = new Base(); // Run-time check to determine whether b is actually a Derived* Derived* d3 = dynamic_cast<Derived*>(b); // If b was originally a Derived*, then d3 is a valid pointer. if(d3) { // Safe to call Derived method. cout << d3->DoSomethingMore() << endl; } else { // Run-time check failed. cout << "d3 is null" << endl; } //Output: d3 is null;
有关更多信息,请参见 dynamic_cast。
const_cast,将 const-变量的状态、转换的非const 变量是 const。 转换 const-使用此运算符的状态为正容易出错。使用 c. 样式转换,除此之外,与 const-cast 要意外转置执行转换。 有时必须将 const-变量的状态,例如,通过 const 变量到具有非const 参数的函数。 下面的示例演示如何执行此操作。
void Func(double& d) { ... } void ConstCast() { const double pi = 3.14; Func(const_cast<double&>(pi)); //No error. }
有关更多信息,请参见 const_cast。
reinterpret_cast,在不相关的类型之间的转换如用于 int的 pointer。
备注
此转换运算符相同通常不使用与其他,因此,无法保证移植到其他编译器。
下面的示例演示 reinterpret_cast 与 static_cast不同。
const char* str = "hello"; int i = static_cast<int>(str);//error C2440: 'static_cast' : cannot // convert from 'const char *' to 'int' int j = (int)str; // C-style cast. Did the programmer really intend // to do this? int k = reinterpret_cast<int>(str);// Programming intent is clear. // However, it is not 64-bit safe.
有关更多信息,请参见reinterpret_cast运算符。