C++ 隐式转换(有符号 + 无符号)

17

我理解,在隐式转换方面,如果我们有一个无符号类型的操作数和一个带符号类型的操作数,且无符号操作数的类型与(或大于)带符号操作数的类型相同,则带符号操作数将被转换为无符号。

因此:

unsigned int u = 10;  
signed int s = -8;

std::cout << s + u << std::endl;

//prints 2 because it will convert `s` to `unsigned int`, now `s` has the value
//4294967288, then it will add `u` to it, which is an out-of-range value, so,
//in my machine, `4294967298 % 4294967296 = 2`

我不理解的是 - 我读到如果有符号操作数的类型比无符号操作数的类型大:

  • 如果无符号类型中的所有值都适合更大的类型,则无符号操作数将转换为有符号类型

  • 如果无符号类型中的值不适合更大的类型,则有符号操作数将转换为无符号类型

因此,在以下代码中:

signed long long s = -8;
unsigned int u = 10;
std::cout << s + u << std::endl;

u将被转换为有符号长长整型,因为int类型的值可以适合有符号长长整型?

如果是这种情况,有哪些情况下较小的类型值不适合较大的类型?


1
你的第一个例子不太好,因为结果无论如何都应该是2。如果你想在计算后查看值是否带符号,请使用结果为负数的值。 - Mats Petersson
3个回答

28

标准中相关引用:

5 表达式 [expr]

10 许多期望算术或枚举类型的操作数的二进制运算符会导致转换并以类似的方式生成结果类型。目的是产生一个普通类型,该普通类型也是结果的类型。这种模式称为常规算术转换,其定义如下:

[省略了关于相等类型或相等符号的2个子句]

— 否则,如果具有无符号整数类型的操作数的级别大于或等于其他操作数类型的级别,则带有带符号整数类型的操作数应转换为具有无符号整数类型的操作数的类型。

— 否则,如果带符号整数类型的操作数的类型可以代表无符号整数类型的所有值,则应将具有无符号整数类型的操作数转换为带符号整数类型的操作数的类型。

— 否则,两个操作数都应转换为与带符号整数类型的操作数相应的无符号整数类型。

我们考虑以下3个样例情况,针对上述3个子句中的每一个,在sizeof(int) < sizeof(long) == sizeof(long long)的系统上进行(可以轻松适应其他情况)。

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;

signed long int s2 = -4;
unsigned int u2 = 2;

signed long long int s3 = -4;
unsigned long int u3 = 2;

int main()
{
    std::cout << (s1 + u1) << "\n"; // 4294967294
    std::cout << (s2 + u2) << "\n"; // -2 
    std::cout << (s3 + u3) << "\n"; // 18446744073709551614  
}

现场示例,包含输出。

第一条子句:类型等级相同,因此将 signed int 操作数转换为 unsigned int。这涉及到一个值变换,(使用二进制补码)得出打印的值。

第二个子句:有符号类型具有更高的等级,并且(在该平台上!)可以表示无符号类型的所有值,因此将无符号操作数转换为有符号类型,您会得到 -2。

第三个子句:有符号类型再次具有更高的等级,但是(在该平台上!)不能表示无符号类型的所有值,因此两个操作数都将转换为 unsigned long long,并在对有符号操作数进行值变换后,您会得到打印的值。

请注意,当无符号操作数足够大(例如,在这些示例中为 6)时,由于无符号整数溢出,最终结果将给出所有 3 个示例的值为 2。

(添加)请注意,当您对这些类型进行比较时,会得到更多意想不到的结果。让我们考虑上面的第一个示例,使用<

#include <iostream>

signed int s1 = -4;
unsigned int u1 = 2;
int main()
{
    std::cout << (s1 < u1 ? "s1 < u1" : "s1 !< u1") << "\n";  // "s1 !< u1"
    std::cout << (-4 < 2u ? "-4 < 2u" : "-4 !< 2u") << "\n";  // "-4 !< 2u"
}

由于u后缀将2u明确地定义为无符号类型,因此相同的规则适用。当在C++中写 -4 < 2u 进行比较时,结果可能与您期望的不同...


+1 很好的完整答案。顺便说一下:可能需要注意这是针对4字节整数和8字节长长整型的。其他可能会有所不同。 - chux - Reinstate Monica
@chux 我已将我的示例简化为标准中的3个子句,并指出了平台依赖性所在的位置。 - TemplateRex
@chux,我使用的平台(Coliru在线编译器)是64位的,其中sizeof(long) == sizeof(long long)。你可能使用32位,其中sizeof(long) == sizeof(int),因此第二个和第三个情况被交换了。标准用可以表示的范围来表达,这就是为什么我在我的答案中使用了“在这个平台上”的短语。 - TemplateRex
也许在你的精彩回答中使用更明确相对等级的类型,如“short”、“long”、“long long”,并避免使用大小为“int”的类型,以减少歧义。 - chux - Reinstate Monica
除了相对排名的<=之外,标准对于整数类型并没有提供太多绝对大小的保证。此外,在进行算术转换之前,对于charshort类型也存在整数提升int的情况。我认为这只会让答案变得更加复杂。标准的推理加上代码示例应该足够清楚地解决每个实际情况。 - TemplateRex
好的 - 关于 short 问题我同意。谢谢。 - chux - Reinstate Monica

2

signed int无法转换为unsigned long long。因此,您需要进行以下转换: signed int -> unsigned long long


1
请注意,C++11标准并没有讨论更大或更小的类型,而是讨论低级别或高级别的类型。
以long int和unsigned int为例,它们都是32位的。long int的级别比unsigned int高,但由于long int和unsigned int都是32位的,long int不能表示所有unsigned int的值。
因此,我们落入了最后一种情况(C++11:5.6p9):
否则,两个操作数都必须转换为与具有带符号整数类型的操作数相对应的无符号整数类型。
这意味着long int和unsigned int都将转换为unsigned long int。

但是同时,unsigned long 无法表示 long int 的所有值(例如-1)。那么,为什么 long int 转换为 unsigned int 而不是相反呢? - sasha.sochka
@sasha.sochka:实际上它们都被转换为“unsigned long int”(我最初犯了一个错误)。 - Vaughn Cato

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