tags:
- Cpp
aliases:
C-Style Casts in C++
Do this at first: Integer Literal & Float Literal in C++
在 C 语言中,我们有许多的数据类型(int
, float
, char
等)。有时候,为了处理不同类型的数据,我们需要将一个数据从一种类型转换成另一种类型。比方说下面常见的运算类型:
#include <iostream>
int main(){
int si = 80000;
short s = si; // casting integer i to a short type variable
std::cout << s << std::endl;
int i = -1;
unsigned int ui = 10;
if(i > u){
std::cout << "What's going on??" << std::endl;
}
}
你会得到这样的输出:
14464
What's going on??
运行的结果告诉你 80000 = 14464,-1 比 10 要大。这是因为 int
类型变量有 4 字节,而 short
类型的变量只有 2 字节(最大存储范围是 -32768 - 32767)由于 80000 超出了 short
可存储的范围,所以发生了整数溢出。导致的结果就是 s
只存储了两字节的截断后的值。
00000000 00000001 00111010 00000000 int
00111010 00000000 short
而当我们比较一个带符号 int
类型的 -1 和一个无符号 int
类型的 10 进行比较时,编译器会自动将带符号的 i_
转换成无符号数来进行比较。-1 在机器中的二进制表示是下面这样的。如果被转换成一个无符号数,那么这个数字的大小就是 4294967295。远远比 10 要大。
11111111 11111111 11111111 11111111 (-1)
奇奇怪怪对吧?这是因为参与运算的双方类型不一致,编译器帮你把类型自动转换了,这叫隐式类型转换 (Implicit casting)。在指针操作中,我们常用 void*
指针类型来存储任意类型的数据,在使用时转换成相应数据类型的指针,这种由程序员手动明确指定的类型转换叫显示类型转换 (Explicit casting),也叫强制类型转换。
void* ptr = nullptr;
int i = 10;
ptr = &i;
std::cout << *(int*)ptr << std::endl;
float f = 3.14;
ptr = &f;
std::cout << *(float*)ptr << std::endl;
虽然类型转换非常有用,但是如果操作不当,就会发生一些像上面演示中那些匪夷所思的事情。为了避免这些问题,C++ 提供了许多方法。
我们接着看一个例子,编译一下,观察输出结果。你会发现,输出的 pi_int 的值是 3 的,而且 355/113 也是输出了一个整数 3 。这是为什么?
#include <iostream>
int main(){
int pi_int = 3.14159;
std::cout << pi_int << std::endl; // 3
std::cout << 355/113 << std::endl;// 3
return 0;
}
这两个问题都同样和 implicit narrowing conversion 有关。当编译器看到 int pi_int = 3.14159;
时,编译器会将右边的浮点数看成一个 double
类型的数。而我们想把这个浮点数存到一个 int
类型。由于类型不同,这里会发生 narrow conversion(窄转换)。将结果截断到整数个位,损失小数的精度。所以我们得到的结果为 3
。
在执行 std::cout << 355/113 << std::endl;
时,编译器会先检查除数和被除数的类型。这两种类型都是 int
类型,所以编译器断定商数应该是 int
类型的,在运行这行代码时也会出现截断。要输出正确的值,我们可以将类型显式地告诉编译器或者显式地使用浮点字面量类型。如:
#include <iostream>
int main(){
std::cout << (double)355/(double)113 << std::endl; // 3.14159
// or, explicitly use the float literals
std::cout << 355.0/113.0 << std::endl; // 3.14159
return 0;
}
为了类型安全,在 C++11 后还引入了列表初始化禁止这种 narrowing conversion 的发生。
int pi = 3.14; // okay
int pi{3.14}; // error
<utility>
对于有符号和无符号类型的比较,C++20 中的 <utility>
库中提供了如 std::cmp_greater()
这样的函数,用于安全地比较有符号和无符号整数,避免常见的类型转换问题。
#include <iostream>
#include <utility>
int main() {
int i = -1;
unsigned int u = 10;
if (std::cmp_greater(i, u)) {
std::cout << "Correct comparison!" << std::endl;
} else {
std::cout << "Expected result!" << std::endl;
}
}
不同于 C 语言,C++ 会对语言进行一些封装以提供更好的安全性并支持 C++ 的语言特性。在 C++ 中,我们主要有四种显式类型转换方式:
static_cast
:基本类型转换,对 C 语言中显示类型的封装。dynamic_cast
:用于类类型之间的转换,通常用于运行时多态。reinterpret_cast
:用于指针类型的转换,如 int*
-> char*
。const_cast
:去除变量的 const
修饰,使得 const
变量可修改。这几种转换类型在之后的 parts 中介绍。
当 C++ 编译器遇到 C 类型的类型转换时,它会将其解释为 C++ 的显式类型转换。