标准转换
C++ 语言定义其基础类型之间的转换。 它还定义指针、引用和指向成员的指针派生类型的转换。 这些转换称为“标准转换”。
本节讨论下列标准转换:
整型提升
整型转换
浮点转换
浮点转换和整型转换
算术转换
指针转换
引用转换
指向成员的指针转换
以下代码将导致转换(本例中为整型提升):
long long_num1, long_num2;
int int_num;
// int_num promoted to type long prior to assignment.
long_num1 = int_num;
// int_num promoted to type long prior to multiplication.
long_num2 = int_num * long_num2;
仅当转换生成引用类型时,其结果才为左值。 例如,声明为 operator int&()
的用户定义转换返回一个引用且为 l-value。 但是,声明为 operator int()
的转换将返回一个对象,而不是 l-value。
整型提升
整型类型的对象可以转换为另一个更宽的整型类型,即,可表示更大的一组值的类型。 这种扩展类型的转换称为“整型提升”。 利用整型提升,你可以在可使用其他整型类型的任何位置将以下类型用于表达式:
char
和short int
类型的对象、文本和常量枚举类型
int
位域枚举器
C++ 提升是“值保留”,即提升后的值一定与提升前的值相同。 在值保留提升中,如果 int
可以表示原始类型的完整范围,较短的整型类型的对象(如 char
类型的位域或对象)将提升到 int
类型。 如果 int
无法表示完整范围的值,该对象将提升到 unsigned int
类型。 尽管此策略与标准 C 中使用的策略相同,但值保留转换不保留对象的“符号”。
值保留提升和保留符号的提升通常会生成相同的结果。 但是,如果提升的对象显示如下,它们可能生成不同的结果:
/
、%
、/=
、%=
、<
、<=
、>
或>=
的操作数这些运算符依赖于用于确定结果的符号。 当值保留和符号保留提升应用于这些操作数时,它们将生成不同的结果。
>>
或>>=
的左操作数这些运算符在移位运算中会区别对待有符号的数量和无符号的数量。 对于有符号的数量,右移位运算会将符号位传播到空位位置,而空位位置则以无符号数量填充零。
重载函数的参数,或重载运算符的操作数(取决于该操作数类型用于参数匹配的符号)。 有关定义重载运算符的详细信息,请参阅重载运算符。
整型转换
整型转换是整型类型之间的转换。 整型类型为 char
、short
(或 short int
)、int
、long
和 long long
。 这些类型可使用 signed
或 unsigned
进行限定,unsigned
可以用作 unsigned int
的简写。
有符号转换为无符号
有符号整数类型的对象可以转换为对应的无符号类型。 当发生这些转换时,实际位模式不会改变。 但是,对数据的解释会更改。 考虑此代码:
#include <iostream>
using namespace std;
int main()
{
short i = -3;
unsigned short u;
cout << (u = i) << "\n";
}
// Output: 65533
在前面的示例中,定义了 signed short
类型的 i
,并将其初始化为负数。 表达式 (u = i)
导致 i
在为 u
赋值前转换为 unsigned short
。
无符号转换为有符号
无符号整数类型的对象可以转换为对应的有符号类型。 但是,如果无符号值超出有符号类型的可表示范围,则结果将没有正确的值,如以下示例所示:
#include <iostream>
using namespace std;
int main()
{
short i;
unsigned short u = 65533;
cout << (i = u) << "\n";
}
//Output: -3
在前面的示例中,u
是一个 unsigned short
整型对象,必须将其转换为有符号的数量来计算表达式 (i = u)
。 由于其值无法在 signed short
中正确表示,因此数据被错误解释。
浮点转换
浮动类型的对象可以安全地转换为更精确的浮点类型,也就是说,转换不会导致基数丢失。 例如,从 float
到 double
或从 double
到 long double
的转换是安全的,并且值保持不变。
如果浮点类型的对象位于低精度类型可表示的范围中,则还可转换为该类型。 (请参阅浮点限制,了解浮点类型的范围。)如果原始值无法精确表示,则可以将其转换为下一个更高或更低的可表示值。 如果此类值不存在,则结果不确定。 请考虑以下示例:
cout << (float)1E300 << endl;
类型 float
可表示的最大值为 3.402823466E38,这比 1E300 小很多。 因此,该数字将转换为无穷大,结果为“inf”。
整型和浮点型之间的转换
某些表达式可能导致浮点型的对象转换为整型,反之亦然。 当整型类型的对象转换为浮点类型且无法精确表示原始值时,结果要么是下一个较大的可表示值,要么是下一个较小的可表示值。
当浮点类型的对象转换为整型类型时,小数部分将被截断,或四舍五入至零。 比如数字 1.3 将转换为 1,-1.3 将转换为 -1。 如果截断的值高于最高可表示值或低于最低可表示值,则结果不确定。
算术转换
很多二元运算符(在带二元运算符的表达式中有讨论)会导致操作数转换,并以相同的方式产生结果。 这些运算符导致的转换称为“常用算术转换”。 具有不同本机类型的操作数的算术转换按下表所示的方式完成。 Typedef 类型的行为方式基于其基础本机类型。
类型转换的条件
满足的条件 | 转换 |
---|---|
其中一个操作数是 long double 类型。 |
另一个操作数将转换为 long double 类型。 |
未满足上述条件,并且其中一个操作数是 double 类型。 |
另一个操作数将转换为 double 类型。 |
未满足上述条件,并且其中一个操作数是 float 类型。 |
另一个操作数将转换为 float 类型。 |
未满足上述条件(没有任何一个操作数属于浮动类型)。 | 操作数获得整型提升,如下所示: - 如果其中一个操作数的类型为 unsigned long ,则另一个操作数被转换为 unsigned long 类型。- 如果未满足上述条件,并且其中一个操作数是 long 类型,另一个操作数是 unsigned int 类型,则这两个操作数都将转换为 unsigned long 类型。- 如果未满足上述两个条件,并且其中一个操作数是 long 类型,则另一个操作数将转换为 long 类型。- 如果未满足上述三个条件,并且其中一个操作数是 unsigned int 类型,则另一个操作数将转换为 unsigned int 类型。- 如果上述条件均未满足,则两个操作数都将转换为 int 类型。 |
以下代码演示了上表中所述的转换规则:
double dVal;
float fVal;
int iVal;
unsigned long ulVal;
int main() {
// iVal converted to unsigned long
// result of multiplication converted to double
dVal = iVal * ulVal;
// ulVal converted to float
// result of addition converted to double
dVal = ulVal + fVal;
}
上面的示例中的第一个语句显示了两个整数类型 iVal
和 ulVal
的相乘。 满足的条件是两个操作数都不是浮点类型,并且一个操作数是 unsigned int
类型。 因此,另一个操作数 iVal
将转换为 unsigned int
类型。 然后,结果将分配给 dVal
。 这里满足的条件是,一个操作数是 double
类型;因此,乘法的 unsigned int
结果将转换为 double
类型。
前面示例中的第二个语句显示了 float
类型和整型类型的加法:fVal
和 ulVal
。 ulVal
变量将转换为 float
类型(表中的第三个条件)。 加法的结果将转换为 double
类型(表中的第二个条件)并分配给 dVal
。
指针转换
在赋值、初始化、比较和其他表达式中,可以转换指针。
指向类的指针
在两种情况下,指向类的指针可转换为指向基类的指针。
第一种情况是指定的基类可访问且转换是明确的。 有关不明确的基类引用的详细信息,请参阅多个基类。
基类是否可访问取决于派生中使用的继承的类型。 考虑下图中阐释的继承:
此图显示了基类 A。类 B 通过专用保护的公共继承自 A。 类 C 通过公共 B 继承自 B。
说明基类辅助功能的继承图
下表显示针对该图阐释的情况的基类可访问性。
函数的类型 | 衍生 | 从B* 至 A* 合法? |
---|---|---|
外部(非类范围)函数 | 专用 | 否 |
受保护 | 否 | |
公用 | 是 | |
B 成员函数(在 B 范围内) | 专用 | 是 |
Protected | 是 | |
公用 | 是 | |
C 成员函数(在 C 范围内) | Private | 否 |
受保护 | 是 | |
公用 | 是 |
第二种情况是,在您使用显式类型转换时,指向类的指针可转换为指向基类的指针。 有关显性类型转换的详细信息,请参阅显式类型转换运算符。
此类转换的结果是指向完全由基类描述的对象部分(即“子对象”)的指针。
以下代码定义了两个类(即 A
和 B
),其中 B
派生自 A
。 (有关继承的详细信息,请参阅派生类。)它随后定义 bObject
(一个 B
类型的对象),以及指向该对象的两个指针(pA
和 pB
)。
// C2039 expected
class A
{
public:
int AComponent;
int AMemberFunc();
};
class B : public A
{
public:
int BComponent;
int BMemberFunc();
};
int main()
{
B bObject;
A *pA = &bObject;
B *pB = &bObject;
pA->AMemberFunc(); // OK in class A
pB->AMemberFunc(); // OK: inherited from class A
pA->BMemberFunc(); // Error: not in class A
}
指针 pA
的类型为 A *
,可解释为“指向 A
类型的对象的指针”。bObject
的成员(如 BComponent
和 BMemberFunc
)对于类型 B
而言是唯一的,因此无法通过 pA
访问。 pA
指针只允许访问类 A
中定义的对象的那些特性(成员函数和数据)。
指向函数的指针
如果类型 void *
足以保留指向函数的指针,则该指针可以转换为 void *
类型。
指向 void 的指针
指向 void
类型的指针可以转换为指向其他任何类型的指针,但仅适合于显式类型转换(与在 C 中的情况不同)。 指向任何类型的指针可以隐式转换为指向 void
类型的指针。 指向类型的不完整对象的指针可以转换为指向 void
(隐式)和 back(显式)的指针。 此类转换的结果与原始指针的值相等。 对象被视为是不完整的(如果已声明对象),但未提供足够多的可用信息,无法确定其大小或基类。
指向不是 const
或 volatile
的任何对象的指针可以隐式转换为 void *
类型的指针。
固定和可变指针
C++ 不会应用从 const
或 volatile
类型到不是 const
或 volatile
类型的标准转换。 但是,任何类型的转换都可以用显式类型强制转换指定(包括不安全的转换)。
注意
指向成员的 C++ 指针(指向静态成员的指针除外)与常规指针不同,二者具有不同的标准转换。 指向静态成员的指针是普通指针,且与普通指针具有相同的转换。
null 指针转换
计算结果为零的整型常量表达式,或到某个指针类型的此类表达式强制转换,将转换为称为“空指针”的指针。 此指针与指向任何有效对象或函数的指针相比总是不相等的。 一个例外是指向基于对象的指针,它们可以具有相同的偏移量,但仍然指向不同的对象。
在 C++11 中,nullptr 类型应优先于 C 样式空指针。
指针表达式转换
带数组类型的所有表达式都可以转换为同一类型的指针。 转换的结果是指向第一个数组元素的指针。 下面的示例演示了这样的转换:
char szPath[_MAX_PATH]; // Array of type char.
char *pszPath = szPath; // Equals &szPath[0].
生成返回特定类型的函数的表达式将转换为指向返回该类型的函数的指针,以下情况除外:
表达式用作 address-of 运算符 (&) 的操作数。
表达式用作到 function-call 运算符的操作数。
引用转换
对类的引用可在以下情况下转换为对基类的引用:
指定的基类是可访问的。
转换是明确的。 (有关不明确的基类引用的详细信息,请参阅多个基类。)
转换的结果为指向表示基类的子对象的指针。
指向成员的指针
指向可在赋值、初始化、比较和其他语句中转换的类成员的指针。 本节描述以下指向成员的指针转换:
指向基类成员的指针
当满足以下条件时,指向基类的成员的指针可以转换为指向派生自基类的类的成员的指针:
从指向派生类的指针到基类指针的反向转换可以访问。
派生类并非以虚拟方式从基类继承。
当左操作数是指向成员的指针时,右操作数必须是 pointer-to-member 类型或计算结果为 0 的常量表达式。 此赋值仅在以下情况下有效:
右操作数是指向与左操作数相同的类的成员的指针。
左操作数是指向以公共但不明确的方式派生自右操作数的类的成员的指针。
指向成员的空指针转换
计算结果为零的整型常量表达式将转换为空指针。 此指针与指向任何有效对象或函数的指针相比总是不相等的。 一个例外是指向基于对象的指针,它们可以具有相同的偏移量,但仍然指向不同的对象。
以下代码演示了指向类 i
中的成员 A
的指针的定义。 指针 pai
将初始化为 0,因此是 null 指针。
class A
{
public:
int i;
};
int A::*pai = 0;
int main()
{
}