标准转换

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&() 返回引用,并且是左值。 但是,声明为的转换将 operator int() 返回对象,而不是左值。

整型提升

整数类型的对象可以转换为另一个更宽的整型类型,即可以表示较大值集的类型。 这种扩大类型的转换被称为 整型提升。 使用整型提升时,可以在表达式中使用以下类型,只要可以使用其他整型类型:

  • 类型为和的对象、文本和常量 charshort int

  • 枚举类型

  • int 位域

  • 枚举器

C + + 升级是 "保留值",因为保证升级后的值与提升前的值相同。 在值保留提升的情况下, charint 如果 int 可以表示原始类型的完整范围,则较短整型类型的对象 (如位域或) 类型的对象。 如果 int 无法表示值的完整范围,则将对象提升为类型 unsigned int 。 尽管此策略与标准 C 所使用的策略相同,但值保留转换不保留对象的 "符号"。

值保留提升和保留符号的提升通常会生成相同的结果。 但是,如果升级的对象显示为,它们可能会产生不同的结果:

  • 、、、 /%/=%=< 、、 <=> 或的操作数 >=

    这些运算符依赖于用于确定结果的符号。 当应用于这些操作数时,值保留和签署保存会产生不同的结果。

  • 或的左操作数 >>>>=

    在移位操作中,这些运算符会以不同的方式处理签名和未签名的数量。 对于有符号数量,右移操作会将符号位传播到空出的位位置,而空出的位位置则以无符号的数量填充。

  • 重载函数的参数或重载运算符的操作数,它依赖于参数匹配的操作数类型的符号。 有关定义重载运算符的详细信息,请参阅 重载运算符

整型转换

整型转换 是整型之间的转换。 整数类型为 charshort (或 short int) 、、 intlonglong 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 shorti 定义,并将其初始化为负数。 表达式 (u = i) 导致在 i 赋值之前将转换为 unsigned shortu

无符号转换为有符号

无符号整数类型的对象可以转换为对应的有符号类型。 但是,如果无符号值不在已签名类型的可表示范围内,则结果将不具有正确的值,如以下示例中所示:

#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 ,因此会将数据解释为显示的错误。

浮点转换

可以将浮动类型的对象安全地转换为更精确的浮动类型 — ,也就是说,转换不会造成重要性损失。 例如,从 floatdouble 或从到的 double 转换 long double 是安全的,并且值不变。

如果浮动类型的对象位于由该类型表示的范围内,则它也可以转换为不太精确的类型。 (参阅浮动类型范围的 浮动限制 。 ) 如果原始值不能准确地表示,则可以将其转换为下一个较高的值或下一个较小的可表示值。 如果不存在这样的值,则结果是不确定的。 请考虑以下示例:

cout << (float)1E300 << endl;

类型表示的最大值 float 为 3.402823466 e 38, — 其数字比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;
}

上面的示例中的第一个语句显示了两个整数类型 iValulVal 的相乘。 满足的条件是,两个操作数都不是浮点型,一个操作数的类型为 unsigned int 。 因此,另一个操作数 iVal 将转换为类型 unsigned int 。 然后,将结果分配给 dVal 。 这里满足的条件是:一个操作数的类型为 double ,因此 unsigned int 相乘的结果转换为类型 double

前面的示例中的第二个语句显示添加了 float 和整型类型: fValulValulVal变量将转换为 float 表) (第三个条件中的类型。 添加的结果将转换为 double 表中的第二个条件 (类型) 并分配给 dVal

指针转换

在赋值、初始化、比较和其他表达式中,可以转换指针。

指向类的指针

在两种情况下,指向类的指针可转换为指向基类的指针。

第一种情况是指定的基类可访问且转换是明确的。 有关不明确的基类引用的详细信息,请参阅 多个基类

基类是否可访问取决于派生中使用的继承的类型。 考虑下图中阐释的继承。

Inheritance graph showing base class accessibility.
阐明基类可访问性的继承关系图

下表显示针对该图阐释的情况的基类可访问性。

函数的类型 派生

B* 到 A* 的转换是否合法?
外部(非类范围)函数 Private
Protected
公共
B 成员函数(在 B 范围内) Private
Protected
公共
C 成员函数(在 C 范围内) Private
Protected
公共

第二种情况是,在您使用显式类型转换时,指向类的指针可转换为指向基类的指针。 有关显式类型转换的详细信息,请参阅 显式类型转换运算符

此类转换的结果是指向子 对象的指针,该对象是由基类完全描述的对象部分。

以下代码定义了两个类(即 AB),其中 B 派生自 A。 (有关继承的详细信息,请参阅 派生类。 ) 然后定义 bObject 、类型的对象 B 和两个指向对象的指针 (pApB) 。

// 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 ,因此无法通过进行访问 pApA 指针只允许访问类 A 中定义的对象的那些特性(成员函数和数据)。

指向函数的指针

指向函数的指针可以转换为类型 void * ,如果类型 void * 足够大,无法保存该指针。

指向 void 的指针

指向类型的指针 void 可以转换为指向任何其他类型的指针,但对于显式类型转换 (与 C) 不同。 指向任何类型的指针可以隐式转换为指向类型的指针 void 。 指向某个类型的不完整对象的指针可以转换为指向 void 隐式) 并) (显式 (的指针。 此类转换的结果与原始指针的值相等。 如果对象已声明,但没有足够的信息可用于确定其大小或基类,则认为该对象是不完整的。

指向任何不是 constvolatile 可隐式转换为类型的指针的对象的指针 void *

固定和可变指针

C + + 不提供从 constvolatile 类型到不是或类型的标准转换 constvolatile 。 但是,任何类型的转换都可以用显式类型强制转换指定(包括不安全的转换)。

注意

指向成员的 c + + 指针(指向静态成员的指针除外)与普通指针不同,并且不具有相同的标准转换。 指向静态成员的指针是普通指针,且与普通指针具有相同的转换。

null 指针转换

计算结果为零的整数常量表达式,或者转换为指针类型的表达式,转换为名为 null 指针的指针。 此指针始终将与指向任意有效对象或函数的指针进行比较。 异常是指向基于对象的指针,它们可以具有相同的偏移量,并且仍指向不同的对象。

在 c + + 11 中, nullptr 类型应首选用于 C 样式 null 指针。

指针表达式转换

带数组类型的所有表达式都可以转换为同一类型的指针。 转换的结果是指向第一个数组元素的指针。 下面的示例演示了这样的转换:

char szPath[_MAX_PATH]; // Array of type char.
char *pszPath = szPath; // Equals &szPath[0].

生成返回特定类型的函数的表达式将转换为指向返回该类型的函数的指针,以下情况除外:

  • 表达式用作) (的地址运算符的操作数 &

  • 表达式用作到 function-call 运算符的操作数。

引用转换

在这些情况下,可以将对类的引用转换为对基类的引用:

  • 指定的基类是可访问的。

  • 转换是明确的。 (有关不明确的基类引用的详细信息,请参阅 多个基类。 )

转换的结果为指向表示基类的子对象的指针。

指向成员的指针

指向可在赋值、初始化、比较和其他语句中转换的类成员的指针。 本节描述以下指向成员的指针转换:

指向基类成员的指针

当满足以下条件时,指向基类的成员的指针可以转换为指向派生自基类的类的成员的指针:

  • 从指向派生类的指针到基类指针的反向转换可以访问。

  • 派生类并非以虚拟方式从基类继承。

当左操作数是指向成员的指针时,右操作数必须是 pointer-to-member 类型或计算结果为 0 的常量表达式。 此赋值仅在以下情况下有效:

  • 右操作数是指向与左操作数相同的类的成员的指针。

  • 左操作数是指向以公共但不明确的方式派生自右操作数的类的成员的指针。

指向成员转换的 null 指针

计算结果为零的整数常量表达式将转换为 null 指针。 此指针始终将与指向任意有效对象或函数的指针进行比较。 异常是指向基于对象的指针,它们可以具有相同的偏移量,并且仍指向不同的对象。

以下代码演示了指向类 i 中的成员 A 的指针的定义。 指针 pai 将初始化为 0,因此是 null 指针。

class A
{
public:
int i;
};

int A::*pai = 0;

int main()
{
}

另请参阅

C++ 语言参考