为什么C++将无符号字符值打印为负数?

3

我希望你能帮我翻译一下C++中的隐式转换规则,这与IT技术有关。当两种基本类型之间只有一个操作符时,"低类型"会提升为"高类型"。例如:

int a = 5;
float b = 0.5;

std::cout << a + b << "\n";

因为'a'被提升为浮点类型,所以应该打印5.5。我也明白无符号类型比有符号类型更高级。

int c = 5;
unsigned int d = 10;

std::cout << c - d << "\n";

由于'c'被提升为无符号整数,而无符号类型在小于零时会循环,因此打印出4294967291这个大数字。

然而,在下面的情况中,我不明白为什么我得到的是-105而不是一个正数。

#include <iostream>

int main(void) {
    unsigned char a = 150;
    std::cout << static_cast<int>(a - static_cast<unsigned char>(255)) << "\n";
    return 0;
}

我猜这段代码的作用是:

a - static_cast<unsigned char>(255)

应该得到一个正数,因此最终的转换(转换为int)不应该影响最终结果,对吗?


2
你忘记了整数提升(所有比int小的整数类型,包括char,在执行你在帖子开头描述的规则之前都会被提升为int)。 - M.M
1
这是C++。你应该删除C标签。 - Mark Benningfield
你猜 a - static_cast<unsigned char>(255) 是正数,但从未费心测试它。你应该这样做。 - user743382
@Mark Benningfield 已被移除。 - RandomGuy
3个回答

4

你错过了从unsigned charint的(隐式)转换,该转换会执行-(减法)操作。只要您尝试将任何整数操作应用于小于int的某些整数类型的值,就会发生此整数提升。


不包括 boolchar16_tchar32_t 或者 wchar_t,将其转换为某个比 int 更小的整型值,是吗? - Sourav Ghosh
1
@SouravGhosh:不,没有针对特定类型的例外。当然,在某些平台上,这些类型中的一些可能不会比int更小,因此在这些平台上,没有转换适用。 - Chris Dodd
谢谢!我不知道有这种隐式转换!顺便说一下,“...对于某些小于int的整数类型的值应用任何整数操作。”,这是否也适用于乘法(*)和除法(/)运算符,还是只适用于加法(+)和减法(-)?我尝试了乘法(*),似乎隐式转换没有发生。 - RandomGuy
@SouravGhosh,为了扩展Chris的评论,这些类型的促销活动是由同一部分中的其他规则指定的,你从中复制到你的答案中排除这些类型的规则。 - eerorika
@user2079303 对,这并不限制它们只能是 intunsigned int。无论如何,克里斯在评论中说的是真的。 - Sourav Ghosh
@SouravGhosh 保持严谨:对于除了 bool 以外的其他类型,它们会被提升为精确的 int - eerorika

2

这里有一些显示正在发生什么的答案。我不会重复。我将给你一个简单的工具来帮助你。

以下是一个技巧,可以快速找到表达式的类型:

template <class> struct Name; // purposely no definition given

Name<decltype(your_expression)> n;

当未定义模板“Name”时,这将生成编译器错误,但我们真正感兴趣的是出现在错误消息中的模板参数类型。

例如,如果您想查看在两个unsigned char之间进行算术运算时所获得的类型:

#include <utility>
template <class> struct Name;

auto test()
{
    Name<decltype(std::declval<unsigned char>() - std::declval<unsigned char>())> n;

   // or
   unsigned char a{};
   Name<decltype(a - a)> n2;
}

会让你获得

error: implicit instantiation of undefined template 'Name<int>'

这将向您显示表达式的类型为int

当然,这并不会告诉您涉及的规则,但它是一个快速的起点,可以查看表达式的类型或验证您对表达式类型的假设。


谢谢!这很有帮助!在Visual Studio 2013编译器和GCC(MinGW)中运行得很好。 - RandomGuy

2
引用自C++14,第5.7节:
加法运算符+和减法运算符-从左到右分组。对于算术或枚举类型的操作数执行通常的算术转换。
对于“通常的算术转换”(特定于此情况),如下所示:
否则,必须对两个操作数进行整数提升。
最后,在“整数提升”章节§4.5中:
整数类型的prvalue,除了bool,char16_t,char32_t或wchar_t之外,如果其整数转换等级(4.13)小于int的等级,则可以将其转换为int类型的prvalue,如果int可以表示源类型的所有值; 否则,源prvalue可以转换为unsigned int类型的prvalue。
因此,无符号字符操作数被提升为int,然后计算结果。

1
这个规则背后的思想,我觉得很有用,值得记忆,就是比int小的整数类型主要被视为“存储类型”,即在内存中节省空间的一种方式。当它们被读回到寄存器中进行操作时,它们自然会被提升到机器寄存器大小(通常与int相同),并相应地执行操作。 - Matteo Italia
@MatteoItalia 是的,确实。 - Sourav Ghosh
非常感谢您的回答。我尝试使用char16_t重新编写了我的代码,但结果仍然是一个负数,这种类型不应该成为隐式转换的例外吗? - RandomGuy
1
@RandomGuy 嗯,不是的。这些类型有更广泛的范围,但它们仍然会得到晋升。 - Sourav Ghosh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接