章节目录

类型转换和static_cast简介

本节阅读量:

隐式类型转换

考虑以下程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

void print(double x) // 参数是 double 类型
{
	std::cout << x << '\n';
}

int main()
{
	print(5); // 但我们传递了一个int类型的数字

	return 0;
}

在上面的示例中,print() 函数有一个类型为double的参数,但调用方传入的是int类型的值5。在这种情况下会发生什么?

在大多数情况下,C++允许我们将一种基础类型的值转换为另一种基础类型。将值从一种类型转换为另一种类型的过程称为类型转换。因此,int参数5将被转换为双精度值5.0,然后复制到参数x中。print() 函数将打印该值,产生以下输出:

1
5

编译器在未经我们明确要求的情况下进行的类型转换,称为隐式类型转换。正如上面的例子所示——我们没有明确地告诉编译器将整数值5转换为双精度值5.0。相反,函数需要一个双精度值,而我们传入了一个整数。编译器会注意到类型不匹配,并隐式地将整数转换为双精度浮点数。

下面是一个类似的示例,其中我们的参数是int变量,而不是int值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>

void print(double x) //  参数是 double 类型
{
	std::cout << x << '\n';
}

int main()
{
	int y { 5 };
	print(y); // y 是 int 类型的变量

	return 0;
}

这与上面的工作原理相同。int变量y保存的值(5)将被转换为双精度值5.0,然后复制到参数x中。


类型转换会生成新值

即使被称为转换,类型转换实际上并不会更改原始值或其类型。相反,要转换的值被用作输入,转换操作会产生一个目标类型的新值。

在上面的示例中,转换不会将变量y的类型从int更改为double。相反,转换以y的值(5)作为输入,创建一个新的双精度值(5.0),然后将该双精度值传递给函数print。


隐式类型转换警告

虽然隐式类型转换在大多数情况下没有问题,但在少数情况下可能存在隐患。考虑下面的程序,它类似于上面的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

void print(int x) // 参数是 int 类型
{
	std::cout << x << '\n';
}

int main()
{
	print(5.5); // warning: 传入了一个 double 值

	return 0;
}

在这个程序中,我们将print() 更改为接受int参数,函数调用现在传入双精度值5.5。与上面类似,编译器将使用隐式类型转换将双精度值5.5转换为int类型的值,以便将其传递给函数print()。

与前面的示例不同,编译该程序时,编译器会生成关于可能丢失数据的警告。并且,如果您启用了”将警告视为错误”的选项,编译器将中止编译过程。

编译和运行时,此程序将打印以下内容:

1
5

请注意,尽管我们传入了值5.5,但程序打印了5。由于整数值不能保存小数部分,因此当双精度值5.5隐式转换为int时,小数部分会被丢弃,仅保留整数值。

由于将浮点值转换为整数值会导致小数部分被丢弃,因此编译器在执行从浮点到整数值的隐式类型转换时会发出警告。即使我们传入的浮点值不含小数部分,编译器仍然可能会警告该转换不安全。


通过static_cast操作符进行显式类型转换

回到我们最近的print()示例,如果我们故意想将双精度值传递给接受整数的函数,仅仅关闭”将警告视为错误”来让程序通过编译并不是好的做法,因为这样每次编译时都会有警告(我们很快就会习惯于忽略它),最终可能会忽视关于更严重问题的警告。

C++支持第二种类型转换方法,称为显式类型转换。显式类型转换允许我们(程序员)明确地告诉编译器将值从一种类型转换为另一种类型,并且我们对转换结果承担全部责任(这意味着,如果转换导致值的丢失,那是我们自己的责任)。

要执行显式类型转换,在大多数情况下我们会使用static_cast操作符。其语法看起来有点特别:

1
static_cast<新类型>(表达式)

static_cast将表达式的值作为输入,并返回转换为newtype所指定类型的值(例如int、bool、char、double)。

让我们使用static_cast更新之前的程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

void print(int x)
{
	std::cout << x << '\n';
}

int main()
{
	print( static_cast<int>(5.5) ); // 显示的将 double 值 5.5 转换为 int

	return 0;
}

因为我们现在显式地将双精度值5.5转换为int值,所以编译器不会生成关于可能丢失数据的警告(这意味着我们可以继续启用”将警告视为错误”)。


使用static_cast将char转换为int

在前面关于字符的课程中,我们看到使用std::cout打印char值时会将其输出为字符:

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    char ch{ 97 }; // 97 是 ASCII 码 'a'
    std::cout << ch << '\n';

    return 0;
}

这将打印:

1
a

如果想打印对应的整数值而不是字符,可以使用static_cast将值从char转换为int:

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    char ch{ 97 }; // 97 是 ASCII 码 'a'
    std::cout << ch << " has value " << static_cast<int>(ch) << '\n'; // 将 ch 转换为 int

    return 0;
}

这将打印:

1
a has value 97

值得注意的是,static_cast的参数是作为表达式求值的。当我们传入一个变量时,该变量会被求值以产生其值,然后该值被转换为新类型。变量本身不会受到类型转换的影响。在上面的例子中,变量ch仍然是char类型,即使在我们将其值转换为int之后,它仍然保持不变。


将无符号数字转换为有符号数字

要将无符号数字转换为有符号数字,也可以使用static_cast运算符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

int main()
{
    unsigned int u { 5 };
    int s { static_cast<int>(u) }; // 将变量 u 的值转换为 int

    std::cout << s << '\n';
    return 0;
}

static_cast操作符不执行任何范围检查,因此如果将值强制转换为超出其表示范围的类型,将导致未定义的行为。因此,如果无符号int的值大于有符号int能容纳的最大值,上述从无符号int到int的转换将产生不可预测的结果。


std::int8_t和std::uint8_t的行为可能类似于字符,而不是整数

如 固定宽度整数和size_t 中所述,大多数编译器将std::int8_t和std::uint8_t(以及相应的快速和最小固定宽度类型)分别定义和处理为有符号char和无符号char类型。现在我们已经了解了什么是字符,可以看看这在哪些地方会产生问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <cstdint>
#include <iostream>

int main()
{
    std::int8_t myInt{65};      // 初始化 myInt 为 65
    std::cout << myInt << '\n'; // 我们可能期望打印 65

    return 0;
}

因为std::int8_t的名称中包含"int",您可能会误以为上面的程序会打印整数值65。然而,在大多数系统上,该程序实际上会打印A(将myInt当作字符处理)。不过,这个行为并不确定(在某些系统上,它确实可能打印65)。

如果希望确保std::int8_t或std::uint8_t对象被当作整数处理,可以使用static_cast将其值转换为整数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <cstdint>
#include <iostream>

int main()
{
    std::int8_t myInt{65};
    std::cout << static_cast<int>(myInt) << '\n'; // 将会一直打印 65

    return 0;
}

当std::int8_t被视为字符时,从控制台读取输入也可能出现问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <cstdint>
#include <iostream>

int main()
{
    std::cout << "Enter a number between 0 and 127: ";
    std::int8_t myInt{};
    std::cin >> myInt;

    std::cout << "You entered: " << static_cast<int>(myInt) << '\n';

    return 0;
}

该程序的示例运行:

1
2
Enter a number between 0 and 127: 35
You entered: 51

下面解释一下这里发生了什么。当std::int8_t被视为字符时,我们的输入会被当作字符而非整数来解释。所以当我们输入35时,实际上输入了两个字符'3’和'5’。因为char对象只能包含一个字符,所以只提取了'3’(‘5’留在输入流中,供后续可能的提取)。因为字符'3’的ASCII码是51,所以值51被存储在myInt中,然后我们将其作为int打印出来。

相比之下,其他固定宽度类型始终会按整数值进行打印和输入。


4.10 字符

上一节

4.12 第4章总结

下一节