C/C++使用int或unsigned int的方法

41

在许多代码示例、源代码、库等中,我看到使用 int 的情况很多, 但是就我所见,使用 unsigned int 更加合理。

我在很多 for 循环中看到这种情况。请看下面的示例:

for(int i = 0; i < length; i++)
{
    // Do Stuff
}

你为什么要使用 int,而不是 unsigned int?这是因为懒惰吗 - 人们不能把 unsigned 打出来?


10
如果 length 可能为负数,使用 int 实际上更安全。如果使用 unsigned int,则 length 也会被转换为 unsigned int。-1将变成类似于4294967295的值,会发生一些不好的事情。 - Brian Bi
3
我同意,但是什么时候长度会变成负数呢?我更多地是在谈论不涉及负数的情况 - 就像上面的例子。 - brettwhiteman
1
如果 length 远小于 INT_MAX,那么使用 intunsigned int 是等效的。我会选择打字更少的那个 :) - M.M
在机器级别上,有符号整数和无符号整数具有完全相同的逻辑。除非您的i值达到最大值(如果i是数组索引且i为32位或更大,则这种情况可能性非常小),否则使用有符号或无符号并不重要。unsigned存在的唯一理由是,如果您实际上需要最高有效位将正数范围扩展另一个因子2但不是更多。 - Mark Lakata
一些函数返回正数表示成功的结果,返回负数表示错误代码。使用无符号类型无法实现此功能。 - cup
显示剩余2条评论
6个回答

37

使用unsigned可能会引入难以发现的编程错误,通常最好使用有符号的int以避免这些错误。一个例子是当你决定向后迭代而不是向前迭代并编写如下代码时:

for (unsigned i = 5; i >= 0; i--) {
    printf("%d\n", i);
}

另一种方法是在循环内进行一些数学计算:

for (unsigned i = 0; i < 10; i++) {
    for (unsigned j = 0; j < 10; j++) {
        if (i - j >= 4) printf("%d %d\n", i, j);
    }
}

使用unsigned会引入这些种类的错误,但实际上没有任何好处。


3
如果使用无符号整数作为i的类型,则编译器会将i >= 0报告为警告,这已经持续了十年之久。即使像您的示例一样,“5”作为数组的大小通常是size_t类型(无符号),为了避免警告(或强制转换),可能需要使用无符号整数和前向循环。 - Stefano Buora
3
在我看来,这些只是一些例子,说明人们不知道如何正确地使用他们选择的工具(或者说是由于错误选择本身)... 这两个例子都不是无符号整数的错,而是程序员希望使用一个不能产生负数值的工具来生成负数。试着用螺丝刀拧紧螺母,你很可能会失败。尽管如此,螺丝刀仍然是一个有用的工具 - 你难道会否认吗? - Aconcagua
1
@StefanoBuora 实际上,即使在反向循环中也有正确处理无符号整数的方法:for(unsigned int i = start + 1; i-- > 0; )for(unsigned int i = start; i <= start; --i) - 后者明确地从下溢中获益。 - Aconcagua

19

通常是因为懒惰或缺乏理解。

当数值不应为负数时,我总是使用 unsigned int。这也有助于文档说明正确的取值范围。

我认为声称使用 "int" 要比使用 "unsigned int" 更安全是错误的,这是一种不好的编程实践。

如果您曾经使用过 Ada 或 Pascal,您会习惯使用更安全的做法,即为值指定特定的范围(例如,只能是 1、2、3、4、5 的整数)。


8
我发现这里的一些回复令人担忧。因为它会掩盖程序错误而使用int类型是令人害怕的。 - user3344003
4
如果使用有符号整数,其他答案中的“编程错误”只是编程错误,如果使用无符号整数,则是完全合理的代码。 - nemetroid
1
@nemetroid 编程错误之所以是错误,是因为程序员选择了不适合他打算完成的任务的工具,或者没有正确使用它。这不是无符号整数的错。 - Aconcagua
4
“@Aconcagua: 一个普遍真理是,程序中的错误通常是由于代码书写不正确造成的。你可以对goto、空指针或任何其他容易出错的编程构造/工具提出同样的论点。” - nemetroid

4

我在编程时选择尽可能明确。也就是说,如果我打算使用一个值始终为正的变量,则使用 unsigned。许多人提到“难以发现的错误”,但很少给出例子。请考虑以下支持使用 unsigned 的示例,与这里大多数帖子不同:

enum num_things {
    THINGA = 0,
    THINGB,
    THINGC,
    NUM_THINGS
};

int unsafe_function(int thing_ID){
    if(thing_ID >= NUM_THINGS)
        return -1;

    ...
}

int safe_function(unsigned int thing_ID){
    if(thing_ID >= NUM_THINGS)
        return -1;

    ...
}

int other_safe_function(int thing_ID){
    if((thing_ID >=0 ) && (thing_ID >= NUM_THINGS))
        return -1;

    ...
}

/* Error not caught */
unsafe_function(-1);

/* Error is caught */
safe_function((unsigned int)-1);

在上面的示例中,如果传入一个负值作为thing_ID会发生什么?在第一种情况下,您会发现负值不大于或等于NUM_THINGS,因此函数将继续执行。
在第二种情况下,您实际上将在运行时捕获到这个错误,因为thing_ID的符号强制条件执行了无符号比较。
当然,您可以像使用有符号整数那样使用other_safe_function这样的方法,但这似乎更像是使用带符号整数的曲解,而不是更明确地使用无符号整数。

4
如果length也是int,那么你应该使用相同的整数类型,否则当你在比较语句中混合有符号和无符号类型时会出现奇怪的问题。大多数编译器都会给出警告。
你可能会问,为什么length应该是有符号的?嗯,这可能是历史原因。
此外,如果你决定反转循环,即
for(int i=length-1;i>=0 ;i--)
{
   // do stuff
}

如果使用无符号整数,则逻辑会出现错误。


3
您可以使用无符号整数反向遍历循环:for (unsigned int i = length; i--; ) - M.M
@MattMcNabb - 你说得对!哇,我编程多年了,从未见过这种模式(for条件具有副作用)。每天都会学到新东西。 - Mark Lakata
2
当然,你可以使用无符号整数编写反向循环。重点是(至少我认为)如果使用无符号整数编写循环,更容易出错。 - Benjamin Lindley
不是“奇怪的事情”,而是“整数提升”。 - Ben Jackson
@MarkLakata 另一个你可能会喜欢的模式:for(unsigned int i = length - 1; i < length; --i)(我个人不偏好其中任何一个,只是为了完整性)。 - Aconcagua

1
我认为最重要的原因是,如果你选择使用unsigned int,你可能会遇到一些逻辑错误。实际上,你通常并不需要unsigned int的范围,使用int更安全。

2
我不同意。如果使用得当,就不应该出现“逻辑错误”。当我编程时,我喜欢明确表达,而不是使用安全选项,因为后者可能会导致意想不到的后果(例如溢出)。 - brettwhiteman
3
@Brett: 添加一个额外的位并不能有效地避免溢出的可能性。如果你正在处理的数字足够大,可能会发生溢出,那么你应该使用一个更大的整数类型,以确保溢出不是可能的,如果必要的话,可以使用无限制的整数类型。 - Benjamin Lindley
有时候溢出甚至是期望的,例如微控制器上的时间计数器。如果MCU运行时间足够长以超出计数器的范围,否则会发生什么?有符号整数在溢出时会产生负值 - 除此之外,在C++20之前,这实际上是未定义的行为! - Aconcagua

0

这段代码与用例相关,如果你调用了某些向量元素,那原型就是int类型,但在C++中有许多现代化的方法来做到这一点,例如:for(const auto &v : vec) {} 或者使用迭代器,在某些计算中,如果没有减法/达到负数,应该使用无符号数(更好地解释了预期的值范围),有时候像这里发布的许多示例一样,实际上需要int,但事实是这完全取决于用例和情况,没有任何严格的规则适用于所有用例,强制执行一个规则会很愚蠢...


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