C++|数据类型转换的场合及隐式类型转换

Previously, you learned that a value of a variable is stored as a sequence of bits, and the data type of the variable tells the compiler how to interpret those bits into meaningful values. Different data types may represent the “same” number differently -- for example, the integer value 3 and the float value 3.0 are stored as completely different binary patterns.

以前,您了解到变量的值存储为一个位序列,并且变量的数据类型告诉编译器如何将这些位解释为有意义的值。不同的数据类型可能以不同的方式表示“相同”的数字——例如,整数值3和浮点值3.0以完全不同的二进制模式存储。

So what happens when we do something like this?


float f{ 3 }; // initialize floating point variable with integer 3

In such a case, the compiler can’t just copy the bits representing the value 3 into float f. Instead, it needs to convert the integer 3 to a floating point number, which can then be assigned to variable f.

在这种情况下,编译器不能只将代表值3的位复制到浮点数f中,而是需要将整数3转换为浮点数,然后将浮点数赋给变量f。

1 Different cases of type conversion(类型转换的不同情形)

The process of converting a value from one data type to another is called a type conversion. Type conversions can happen in many different cases:

将值从一种数据类型转换为另一种数据类型的过程称为类型转换。类型转换可以在许多不同的情况下发生:

1.1 Assigning to or initializing a variable with a value of a different data type:


double d{ 3 }; // initialize double variable with integer value 3
d = 6; // assign double variable with integer value 6

1.2 Passing a value to a function where the function parameter is of a different data type:


void doSomething(long l)
{
}
doSomething(3); // pass integer value 3 to a function expecting a long parameter


1.3 Returning a value from a function where the function return type is of a different data type:


float doSomething()
{
return 3.0; // Return double value 3.0 back to caller through float return type
}

如果参数和返回值类型是自定义类,会自动调用拷贝构造函数,也存在同样的转换规则。

1.4 Using a binary operator with operands of different types:


double division{ 4.0 / 3 }; // division with a double and an integer

In all of these cases (and quite a few others), C++ will use type conversion to convert data from one type to another.

There are two basic types of type conversion: implicit type conversion, where the compiler automatically transforms one fundamental data type into another, and explicit type conversions, where the developer uses a casting operator to direct the conversion.

类型转换有两种基本类型:隐式类型转换(编译器自动将一种基本数据类型转换为另一种基本数据类型)和显式类型转换(开发人员使用转换运算符来指导转换)。

2 Implicit type conversion(隐式类型转换)

Implicit type conversion (also called automatic type conversion or coercion) is performed whenever one fundamental data type is expected, but a different fundamental data type is supplied, and the user does not explicitly tell the compiler how to perform this conversion (via a cast).

每当需要一个基本数据类型,但提供了不同的基本数据类型时,就会执行隐式类型转换(也称为自动类型转换或强制转换),并且用户不会显式地告诉编译器如何执行此转换(通过强制转换)。

All of the above examples are cases where implicit type conversion will be used.

There are two basic types of implicit type conversion: promotions and conversions.

隐式类型转换有两种基本类型:提升和转换。

3 Numeric promotion(数值提升)

Whenever a value from one type is converted into a value of a larger similar data type, this is called a numeric promotion (or widening, though this term is usually reserved for integers). For example, an int can be widened into a long, or a float promoted into a double:

每当一种类型的值转换为更大的相似数据类型的值时,这称为数值提升(或加宽,尽管此术语通常为整数保留)。例如,可以将int加宽为long,或将float提升为double:

long l{ 64 }; // widen the integer 64 into a long
double d{ 0.12f }; // promote the float 0.12 into a double

While the term “numeric promotion” covers any type of promotion, there are two other terms with specific meanings in C++:

3.1 Integral promotion involves the conversion of integer types narrower than int (which includes bool, char, unsigned char, signed char, unsigned short, signed short) to an int (if possible) or an unsigned int (otherwise).

3.2 Floating point promotion involves the conversion of a float to a double.

Integral promotion and floating point promotion are used in specific cases to convert smaller data types to int/unsigned int or double, because int and double are generally the most performant types to perform operations on.

整数提升和浮点提升在特定情况下用于将较小的数据类型转换为int/unsigned int或double,因为int和double通常是执行操作最有效的类型。

The important thing to remember about promotions is that they are always safe, and no data loss will result. Under the hood, promotions generally involve extending the binary representation of a number (e.g. for integers, adding leading 0s).

重要的是要记住提升总是安全的,不会导致数据丢失。在这种情况下,提升通常涉及扩展数字的二进制表示(例如,对于整数,添加前导0)。

4 Numeric conversions(数值转换)

When we convert a value from a larger type to a similar smaller type, or between different types, this is called a numeric conversion. For example:

当我们将一个值从一个较大的类型转换为一个类似的较小类型,或者在不同类型之间转换时,这称为数值转换,例如:

double d{ 3 }; // convert integer 3 to a double (between different types)
short s{ 2 }; // convert integer 2 to a short (from larger to smaller type)

Unlike promotions, which are always safe, conversions may or may not result in a loss of data. Because of this, any code that does an implicit conversion will often cause the compiler to issue a warning (on the other hand, if you do an explicit conversion using a cast, the compiler will assume you know what you’re doing and not issue a warning). Under the hood, conversions typically require converting the underlying binary representation to a different format.

与总是安全的提升不同,转换可能会也可能不会导致数据丢失。因此,执行隐式转换的任何代码通常都会导致编译器发出警告(另一方面,如果使用强制转换执行显式转换,编译器将假定您知道您在做什么,而不会发出警告)。在幕后,转换通常需要将底层二进制表示转换为不同的格式。

The rules for conversions are complicated and numerous, so we’ll just cover the common cases here.

In all cases, converting a value into a type that doesn’t have a large enough range to support the value will lead to unexpected results. This should be avoided. For example:

在所有情况下,将值转换为没有足够大的范围来支持该值的类型将导致意外的结果,这应该避免。例如:

int main()
{
int i{ 30000 };
char c = i;
std::cout << static_cast(c);
return 0;
}

In this example, we’ve assigned a large integer to a char (that has range -128 to 127). This causes the char to overflow, and produces an unexpected result:

48

However, converting from a larger integral or floating point type to a smaller similar type will generally work so long as the value fits in the range of the smaller type. For example:

但是,从较大的整数或浮点类型转换为较小的类似类型通常可以工作,只要该值适合较小类型的范围。例如:

int i{ 2 };
short s = i; // convert from int to short
std::cout << s << '\\n';
double d{ 0.1234 };
float f = d;
std::cout << f << '\\n';

This produces the expected result:

2
0.1234

In the case of floating point values, some rounding may occur due to a loss of precision in the smaller type. For example:


float f = 0.123456789; // double value 0.123456789 has 9 significant digits, but float can only support about 7


std::cout << std::setprecision(9) << f << '\\n'; // std::setprecision defined in iomanip header

In this case, we see a loss of precision because the float can’t hold as much precision as a double:

0.123456791

Converting from an integer to a floating point number generally works as long as the value fits within the range of the floating type. For example:

int i{ 10 };
float f = i;
std::cout << f;

This produces the expected result:

10

Converting from a floating point to an integer works as long as the value fits within the range of the integer, but any fractional values are lost. For example:


int i = 3.5;
std::cout << i << '\\n';

In this example, the fractional value (.5) is lost, leaving the following result:

3

Conversions that could cause loss of information, eg. floating point to integer, are called narrowing conversions. Since information loss is generally bad, brace initialization doesn’t allow it.

可能导致信息丢失的转换(例如浮点到整数)称为缩小转换。由于信息丢失通常很糟糕,因此大括号初始化不允许这样做(等于号初始化允许)。

double d{ 10.0 };
int i{ d }; // Error. A double can store values that don't fit into an int.

5 Evaluating arithmetic expressions(计算算术表达式)

When evaluating expressions, the compiler breaks each expression down into individual subexpressions. The arithmetic operators require their operands to be of the same type. To ensure this, the compiler uses the following rules:

在计算表达式时,编译器将每个表达式分解为单独的子表达式。算术运算符要求其操作数为同一类型。为了确保这一点,编译器使用以下规则:

5.1 If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int.

如果一个操作数是一个比int窄的整数,它将被整数提升(如上所述)为int或unsigned int。

5.2 If the operands still do not match, then the compiler finds the highest priority operand and implicitly converts the other operand to match.

如果操作数仍然不匹配,则编译器会找到优先级最高的操作数,并隐式将另一个操作数转换为匹配。

The priority of operands is as follows:

long double (highest)doublefloatunsigned long longlong longunsigned longlongunsigned intint (lowest)

We can see the usual arithmetic conversion take place via use of the typeid() operator (included in the typeinfo header), which can be used to show the resulting type of an expression.

In the following example, we add two shorts:

#include <iostream>
#include <typeinfo> // for typeid()
int main()
{
short a{ 4 };
short b{ 5 };
std::cout << typeid(a + b).name() << " " << a + b << '\\n'; // show us the type of a + b
return 0;
}
/<typeinfo>/<iostream>

Because shorts are integers, they undergo integral promotion to ints before being added. The result of adding two ints is an int, as you would expect:

int 9

Note: Your compiler may display something slightly different as the format of typeid.name() is left up to the compiler.

Let’s take a look at another case:


#include <iostream>
#include <typeinfo> // for typeid()
int main()
{
double d{ 4.0 };
short s{ 2 };
std::cout << typeid(d + s).name() << ' ' << d + s << '\\n'; // show us the type of d + s


return 0;
}
/<typeinfo>/<iostream>

In this case, the short undergoes integral promotion to an int. However, the int and double still do not match. Since double is higher on the hierarchy of types, the integer 2 gets converted to the double 2.0, and the doubles are added to produce a double result.

double 6.0

This hierarchy can cause some interesting issues. For example, take a look at the following code:


std::cout << 5u - 10; // 5u means treat 5 as an unsigned integer

you might expect the expression 5u - 10 to evaluate to -5 since 5 - 10 = -5. But here’s what actually happens:

4294967291

In this case, the signed integer (10) is promoted to an unsigned integer (which has higher priority), and the expression is evaluated as an unsigned int. Since -5 can’t be stored in an unsigned int, the calculation wraps around, and we get an answer we don’t expect.

在本例中,有符号整数(10)被提升为无符号整数(具有更高的优先级),表达式被计算为无符号整数。由于-5不能存储在无符号整数中,因此计算将被环绕,我们得到了一个我们不期望的答案。

This is one of many good reasons to avoid unsigned integers in general.

reference: https://www.learncpp.com/cpp-tutorial/44-implicit-type-conversion-coercion/

-End-