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)
  • double
  • float
  • unsigned long long
  • long long
  • unsigned long
  • long
  • unsigned int
  • int (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-


分享到:


相關文章: