4-12 类型转换与static_cast介绍
隐式类型转换
考虑以下程序:
#include <iostream>
void print(double x) // print takes a double parameter
{
std::cout << x << '\n';
}
int main()
{
print(5); // what happens when we pass an int value?
return 0;
}
在上例中,print()函数的参数类型为double,但调用方传入的值5属于int类型。这种情况下会发生什么?
在多数情况下,C++允许我们将一种基本数据类型的值转换为另一种基本数据类型。将数据从一种类型转换为另一种类型的过程称为类型转换type conversion。因此,int类型的参数5将被转换为double类型的值5.0,然后复制到参数x中。print()函数将打印该值,最终输出如下:

提醒
默认情况下,小数部分为 0 的浮点数值在打印时不会显示小数位(例如 5.0 会打印为 5)。
当编译器在我们未明确要求的情况下自动执行类型转换时,我们称之为隐式类型转换implicit type conversion。上例就体现了这种情况——我们从未明确指示编译器将整数值5转换为双精度值5.0。相反,函数期望接收双精度值,而我们传入了整数参数。编译器会检测到类型不匹配,并隐式将整数转换为双精度类型。
以下是一个类似示例,其中参数是 int 变量而非 int 常量:
#include <iostream>
void print(double x) // print takes a double parameter
{
std::cout << x << '\n';
}
int main()
{
int y { 5 };
print(y); // y is of type int
return 0;
}

这与上述情况完全相同。整型变量 y(5)持有的值将被转换为双精度值 5.0,然后复制到参数 x 中。
值的类型转换会产生一个新值
类型转换过程不会修改提供待转换数据的值(或对象)。相反,转换过程将该数据作为输入,并产生转换后的结果。
关键洞察
将值转换为另一种类型时,其行为类似于调用一个返回类型与转换目标类型匹配的函数。待转换的数据作为参数传递,转换后的结果(封装在临时对象中)被返回供调用方使用。
在上例中,转换操作并未将变量 y 的类型从 int 改为 double,也未将 y 的值从 5 改为 5.0。相反,该转换以 y 的值(5)作为输入,返回一个值为 5.0 的 double 类型临时对象。随后,该临时对象被传递给函数 print。
对于高级读者
某些高级类型转换(例如涉及 const_cast 或 reinterpret_cast 的转换)不会返回临时对象,而是重新解释现有值或对象的类型。
隐式类型转换警告
尽管隐式类型转换足以满足大多数需要类型转换的情况,但仍有少数例外。请看以下程序,它与上文示例类似:
#include <iostream>
void print(int x) // print now takes an int parameter
{
std::cout << x << '\n';
}
int main()
{
print(5.5); // warning: we're passing in a double value
return 0;
}

在此程序中,我们已将print()函数修改为接受int参数,且函数调用现在传入了双精度值5.5。与前例类似,编译器将通过隐式类型转换将双精度值5.5转换为int类型值,以便传递给print()函数。
与最初的示例不同,当编译此程序时,编译器会生成关于可能丢失数据的警告。由于你已启用“将警告视为错误”选项(你确实启用了吧?),编译器将终止编译过程。
提示
若要编译此示例,您需要暂时禁用“将警告视为错误”选项。有关此设置的更多信息,请参阅第0.11课——配置编译器:警告与错误级别。
编译并运行后,该程序将输出以下内容:

请注意,尽管我们传入了值5.5,程序却输出5。由于整数值无法存储小数部分,当双精度值5.5被隐式转换为整数时,小数部分会被舍弃,仅保留整数部分。
由于将浮点数转换为整数会导致小数部分丢失,编译器在执行浮点数到整数的隐式类型转换时会发出警告。即使传入的是无小数部分的浮点数(如5.0),编译器仍会发出警告——尽管在此特例中转换为整数5时实际未丢失数值,但编译器仍可能提示该转换存在风险。
关键洞察
某些类型转换(如字符型转整型)始终保留被转换的值,而另一些(如双精度型转整型)则可能导致转换过程中值发生改变。不安全的隐式转换通常会触发编译器警告,或(在花括号初始化场景下)引发错误。
这正是大括号初始化成为首选初始化形式的主要原因之一。大括号初始化能确保我们不会尝试使用会因隐式类型转换而丢失值的初始化表达式来初始化变量:
int main() { double d { 5 }; // okay: int to double is safe int x { 5.5 }; // error: double to int not safe return 0; }
相关内容
隐式类型转换是一个内容丰富的主题。我们将在后续课程中深入探讨这个主题,从第10.1课——隐式类型转换开始。
通过static_cast运算符实现显式类型转换的介绍
回到我们最近的 print() 示例,如果我们有意将双精度值传递给接受整数的函数(明知转换后的值会舍弃小数部分),该怎么办?仅仅为了让程序编译通过而关闭“将警告视为错误”是个坏主意,因为这样每次编译都会出现警告(我们很快就会学会忽略它们),而且我们可能会忽略更严重问题的警告。
C++支持另一种类型转换方式——显式类型转换。显式类型转换Explicit type conversion允许程序员明确告知编译器将值从一种类型转换为另一种类型,并完全承担转换结果的责任。若此类转换导致数据丢失,编译器将不会发出警告。
执行显式类型转换时,我们通常使用static_cast运算符。静态转换的语法看起来有些特别:
static_cast<new_type>(expression)
static_cast 接受表达式中的值作为输入,并返回该值转换为 new_type 指定的类型(例如 int、bool、char、double)后的结果。
关键要点
每当你看到C++语法(不包括预处理器)中使用尖括号(<>)时,尖括号之间的内容很可能就是类型。这通常是C++处理需要参数化类型的代码的方式。
让我们使用static_cast更新之前的程序:
#include <iostream>
void print(int x)
{
std::cout << x << '\n';
}
int main()
{
print( static_cast<int>(5.5) ); // explicitly convert double value 5.5 to an int
return 0;
}

由于我们现在明确要求将双精度值 5.5 转换为整数值,编译器在编译时不会生成关于可能丢失数据的警告(这意味着我们可以保持“将警告视为错误”选项启用)。
相关内容
C++支持其他类型的强制转换。关于不同类型强制转换的详细说明,将在后续第10.6节——显式类型转换(强制转换)与static_cast中展开讨论。
使用static_cast将char转换为int
在字符章节4.11中,我们看到使用std::cout打印char值时,该值会以char类型显示:
#include <iostream>
int main()
{
char ch{ 97 }; // 97 is ASCII code for 'a'
std::cout << ch << '\n';
return 0;
}
这将输出:

值得注意的是,static_cast 的实参会被视为表达式进行求值。当我们传入一个变量时,该变量会被求值以产生其值,然后该值会被转换为新类型。变量本身不会因将其值转换为新类型而受到影响。在上例中,变量 ch 仍然是 char 类型,即使将其值转换为 int 后,它仍然保持相同的值。
#include <iostream>
int main()
{
unsigned int u1 { 5 };
// Convert value of u1 to a signed int
int s1 { static_cast<int>(u1) };
std::cout << s1 << '\n'; // prints 5
int s2 { 5 };
// Convert value of s2 to an unsigned int
unsigned int u2 { static_cast<unsigned int>(s2) };
std::cout << u2 << '\n'; // prints 5
return 0;
}
这将输出:

由于数值5同时处于有符号整型和无符号整型的取值范围内,因此该值可无障碍转换为任意类型。
若待转换值无法在目标类型中表示:
- 若目标类型为无符号类型,则值将进行模数折返。模数折返机制详见第4.5课——无符号整型及其禁忌。
- 若目标类型为有符号类型,在C++20之前其行为由实现定义,而C++20及之后版本将进行模数折返。
以下是两个无法在目标类型中表示的值的转换示例(假设为32位整数):
#include <iostream>
int main()
{
int s { -1 };
std::cout << static_cast<unsigned int>(s) << '\n'; // prints 4294967295
unsigned int u { 4294967295 }; // largest 32-bit unsigned int
std::cout << static_cast<int>(u) << '\n'; // implementation-defined prior to C++20, -1 as of C++20
return 0;
}
截至 C++20,这将产生以下结果:
(C++23 亦如此)

有符号整数值 -1 无法表示为无符号整数。结果模运算将折返为无符号整数值 4294967295。
无符号整数值 4294967295 无法表示为有符号整数。在 C++20 之前,结果由实现定义(但很可能为 -1)。自 C++20 起,结果将向下取整为 -1。
警告
在 C++20 之前,若待转换的无符号整数值无法表示为有符号类型,将其转换为有符号整数将导致实现定义的行为。
std::int8_t 和 std::uint8_t 可能表现得像 char 类型而非整数类型
正如第4.6节——固定宽度整数与size_t所述,大多数编译器将std::int8_t和std::uint8_t(以及对应的快速类型和最小固定宽度类型)分别定义并视为有符号char和无符号char类型。既然我们已阐明char的本质,现在可以展示这种处理方式可能引发的问题:
#include <cstdint>
#include <iostream>
int main()
{
std::int8_t myInt{65}; // initialize myInt with value 65
std::cout << myInt << '\n'; // you're probably expecting this to print 65
return 0;
}

由于 std::int8_t 将自身描述为 int 类型,你可能会误以为上述程序会输出整数值 65。然而在多数系统上,该程序实际会输出字母 A(将 myInt 视为带符号 char)。但这种行为并非绝对(某些系统可能确实输出 65)。
若需确保 std::int8_t 或 std::uint8_t 对象被视为整数,可使用 static_cast 将其强制转换为整数类型:
#include <cstdint>
#include <iostream>
int main()
{
std::int8_t myInt{65};
std::cout << static_cast<int>(myInt) << '\n'; // will always print 65
return 0;
}

当 std::int8_t 被视为 char 时,来自控制台的输入也可能引发问题:
#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;
}
该程序的示例运行:

具体情况如下:当将 std::int8_t 视为 char 类型时,输入程序会将输入数据解释为字符序列而非整数。因此输入 35 时,实际输入的是两个字符 ‘3’ 和 ‘5’。由于 char 对象只能存储单个字符,‘3’ 被提取出来(‘5’ 保留于输入流中以备后续提取)。由于字符'3'的ASCII码点为51,值51被存储在myInt中,随后我们将其作为整数输出。
相比之下,其他固定宽度类型始终以整数形式进行输入输出。
测验时间
问题 #1
编写一个简短程序,要求用户输入单个字符。使用 static_cast 打印该字符的值及其 ASCII 码。
程序输出应与以下示例一致:
Enter a single character: a
You entered 'a', which has ASCII code 97.

显示解答
#include <iostream>
int main()
{
std::cout << "Enter a single character: ";
char c{};
std::cin >> c;
std::cout << "You entered '" << c << "', which has ASCII code " << static_cast<int>(c) << ".\n";
return 0;
}
问题 #2
修改你为测验 #1 编写的程序,用隐式类型转换替代 static_cast。你能想到多少种实现方式?
注意:应优先使用显式转换而非隐式转换,因此请勿在实际程序中采用此做法——此题仅用于检验你对隐式转换发生场景的理解。
显示解答
实现这个目标有几种简单的方法。
首先,我们可以创建一个整型变量,并在初始化时赋予字符值。这样在初始化时就会进行隐式转换。
#include <iostream>
int main()
{
std::cout << "Enter a single character: ";
char c{};
std::cin >> c;
int ascii{ c };
std::cout << "You entered '" << c << "', which has ASCII code " << ascii << ".\n";
return 0;
}
或者,我们可以使用函数将字符值作为整数返回。这将在返回点进行隐式转换。
#include <iostream>
int charAsInt(char c)
{
return c;
}
int main()
{
std::cout << "Enter a single character: ";
char c{};
std::cin >> c;
std::cout << "You entered '" << c << "', which has ASCII code " << charAsInt(c) << ".\n";
return 0;
}
我们也可以使用函数,并在参数被复制到函数参数的位置发生隐式转换:
#include <iostream>
int getInt(int c)
{
return c;
}
int main()
{
std::cout << "Enter a single character: ";
char c{};
std::cin >> c;
std::cout << "You entered '" << c << "', which has ASCII code " << getInt(c) << ".\n";
return 0;
}


浙公网安备 33010602011771号