无符号和有符号比较

6

这里是非常简单的代码:

#include <iostream>
using namespace std;
int main() {
    unsigned int u=10;
    int i;
    int count=0;
    for (i=-1;i<=u;i++){
        count++;
    }
    cout<<count<<"\n";
    return 0;
}

计数器的值为0。为什么?

1
顺便提一下,你应该使用 std::endl 而不是将 \n 插入到流中,因为 std::endl 既具有可移植性,又确保输出立即显示。 - Ben Voigt
1
@Ben:\n 同样具有可移植性;只有在你特别想立即刷新流时才应该使用 endl - Mike Seymour
\n 是可移植的(它会被转换成平台的换行序列),并且不需要立即刷新流的开销。你应该使用具有你想要语义的那个:如果你想要刷新流,请使用 endl,如果你只想要一个换行,请使用 \n - jalf
1
std::endl 相当于 "\n" << std::flush,正如所说,flush 会造成额外开销。 - sqqqrly
5个回答

9
<=的两个操作数必须提升为相同的类型。显然它们被提升为unsigned int(我没有标准规则在手边,我会在一秒钟内查找)。由于(unsigned int)(-1) <= u是假的,因此循环永远不会执行。
规则可以在标准的第5节(表达式)第10段中找到,其中指出(我已经突出了适用于此处的规则):
许多希望算术或枚举类型的操作数的二元运算符以类似的方式进行转换并产生结果类型。目的是产生一个常见类型,这也是结果的类型。这种模式称为通常的算术转换,其定义如下:
  • 如果任一操作数是作用域枚举类型(7.2),则不执行转换;如果另一个操作数的类型不同,则表达式无效。
  • 如果任一操作数是long double类型,则另一个操作数应转换为long double。
  • 否则,如果任一操作数是double,则另一个操作数应转换为double。
  • 否则,如果任一操作数是float,则另一个操作数应转换为float。
  • 否则,应对两个操作数执行整数提升(4.5)。然后将以下规则应用于提升的操作数:
  • 如果两个操作数具有相同的类型,则不需要进一步转换。
  • 否则,如果两个操作数都具有带符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数应转换为具有更大等级的操作数的类型。
  • 否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则带符号整数类型的操作数应转换为具有无符号整数类型的操作数的类型。
  • 否则,如果具有带符号整数类型的操作数的类型可以表示无符号整数类型的所有值,则具有无符号整数类型的操作数应转换为具有带符号整数类型的操作数的类型。
  • 否则,两个操作数都应转换为与具有带符号整数类型的操作数的类型相对应的无符号整数类型。

4
在比较 (i <= u) 时,i 被升级为无符号整数,并在此过程中将 -1 转换为 UINT_MAX。
将负数转换为无符号整数会将 (UINT_MAX + 1) 添加到该数中,因此 -1 变成了 UINT_MAX,-2 变成了 UINT_MAX - 1,以此类推。
如果您仔细思考一下,为了使比较有效,必须将一个值转换为另一个值,通常编译器会将有符号值转换为无符号值。当然,在这种情况下,将无符号值转换为有符号值会更有意义,但编译器不能根据您的意图来决定遵循不同的规范。在这里,您应该显式地将无符号整数转换为有符号整数(或一开始就使用有符号整数)。

你的规则不起作用。实际上添加的是2 ** 32,也就是 UINT_MAX + 1,而不是 UINT_MAX - 1 - Ben Voigt
1
关于您的新评论,从有符号数到无符号数的转换对于所有输入都是明确定义的,但是从无符号数到有符号数的转换可能会调用实现定义的行为(在这里不会,因为u的值为10,适合于int)。 - Ben Voigt
是的,当我建议对 u 使用或转换为有符号整数时,这是假设 u 仍然在有符号整数允许的范围内。如果您可能会超过这个范围,并且确实需要无符号整数,那么您将不得不重新考虑代码 - 例如,可能不要从负一开始 i - thomasrutter

1
这是因为在比较之前,i 被提升为无符号值。这将把它设置为 UINT_MAX 的值,在 32 位机器上等于 4294967295。因此,你的循环本质上与以下代码相同:
// will never run
for (i = 4294967295; i <= u; i++) {
    count++;
}

1

这是因为-1被强制转换为无符号整数,所以for循环代码永远不会被执行。

尝试使用-Wall -Wextra编译,这样您就可以获得相应的警告(如果到目前为止没有收到它们,并且使用g++编译)

http://en.wikipedia.org/wiki/Two's_complement


谢谢,我想问你一件事,我会给出我的问题链接,请告诉我他们为什么降低了我的分数或者给了负评,好吗?我想要一个好的声誉分数,并且努力发布好的问题,但是他们却降低了它。 - user466534
1
@davit:你的另一个问题被踩是因为编译器告诉你出了什么问题(你漏了一个分号),但你却在这里发了问题,甚至没试着自己解决。 - Ben Voigt

0
在一个整数存储在4个字节的系统中,我相信-1的值等于2147483649的值(1000 0000 0000 0000 0000 0000 0000 0001)-它是1,最高有效位设置为1以表示它是负数。

1
不,以二进制补码表示时,“-1”被存储为全是1。但实际的表示方式并不重要,因为C++标准定义了从有符号整数类型转换为无符号整数类型的转换在模N算术下是同余的。 - Ben Voigt
1
虽然在补码系统中这是正确的,但现在这种情况已经非常罕见了。大多数当前的系统使用二进制补码表示整数,在这种情况下,-1将被表示为所有位都设置为1(在这种情况下,从有符号转换为无符号只需要改变数据的解释方式,而不需要改变数据本身)。 - Jerry Coffin
1
@Jerry:符号-大小并不罕见。IEEE 754要求使用它。几乎每台计算机都使用二进制补码整数和符号-大小浮点表示法。 - Ben Voigt
@Ben:是的,如果你仔细阅读第二句话,就会明显看出我在谈论整数而不是浮点数。 - Jerry Coffin
@Jerry: 我理解这一点。但我不希望有些计算机工程学生看到这个帖子,然后说“我不需要学习符号-幅度算法是如何工作的,因为没有人使用它”。 - Ben Voigt

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