为啥 C 和 C++ for 循环使用 int 而不是 unsigned int?

Posted

技术标签:

【中文标题】为啥 C 和 C++ for 循环使用 int 而不是 unsigned int?【英文标题】:Why is int rather than unsigned int used for C and C++ for loops?为什么 C 和 C++ for 循环使用 int 而不是 unsigned int? 【发布时间】:2011-11-21 06:37:57 【问题描述】:

这是一个相当愚蠢的问题,但为什么在 C 或 C++ 中为数组定义 for 循环时,通常使用 int 而不是 unsigned int

for(int i;i<arraySize;i++)
for(unsigned int i;i<arraySize;i++)

我认识到在执行数组索引以外的操作时使用 int 的好处以及在使用 C++ 容器时使用迭代器的好处。仅仅是因为在遍历数组时无关紧要吗?或者我应该避免所有这些并使用不同的类型,例如size_t

【问题讨论】:

写的少了。 这就像你为什么不一直用名字和姓氏称呼别人,而只用他们的名字称呼他们? 其实对于索引我更喜欢使用size_t,保证足够大,而且比unsigned int少打字。 一篇很好的文章解释了我们为什么需要size_tptrdiff_t:viva64.com/en/a/0050 @Blagovest:那篇文章的动机部分很好,但其余部分充满了错误信息(类型等价,在size_t 中存储指针的能力等)并且完全掩盖了ptrdiff_t 的签名溢出和范围问题。我会毫不犹豫地称它为“非常好”.. 【参考方案1】:

我使用int,因为它需要更少的物理输入并且没关系 - 它们占用相同数量的空间,除非你的数组有几十亿个元素,否则如果你不使用它就不会溢出一个 16 位编译器,我通常不是。

【讨论】:

不使用int 也提供了有关变量的更多上下文,可以视为自记录代码。也可以在这里阅读:viva64.com/en/a/0050【参考方案2】:

差别不大。 int 的一个好处是它被签名了。因此int i &lt; 0 有意义,而unsigned i &lt; 0 没有多大意义。

如果计算了索引,这可能是有益的(例如,如果某些结果是否定的,您可能会遇到永远不会进入循环的情况。

是的,写的更少:-)

【讨论】:

typedef unsigned us; 还有很多要写的。 @WTP - 你是那些即使旁边有“:-)”也不会理解讽刺的人之一?好吧,我想那里没有治愈方法...... 负大小或负索引没有意义 @MilesRout:尝试对负数项目进行操作通常与尝试对大量正数项目进行操作具有不同的含义。如果一个应该操作集合的最后一个项目以外的所有项目的函数被传递给一个没有项目的集合,那么将要处理的项目数识别为 -1 似乎比将其设置为 SIZE_MAX 更干净。【参考方案3】:

使用int 来索引数组是传统的,但仍被广泛采用。 int 只是一个通用的数字类型,不对应平台的寻址能力。如果它恰好比这更短或更长,在尝试索引超出范围的非常大的数组时可能会遇到奇怪的结果。

在现代平台上,off_tptrdiff_tsize_t 保证了更多的可移植性。

这些类型的另一个优点是它们为阅读代码的人提供了上下文。当你看到上面的类型你就知道代码会做数组下标或指针运算,而不仅仅是任何计算。

因此,如果您想编写防弹、可移植和上下文相关的代码,您可以通过几次按键来实现。

GCC 甚至支持typeof 扩展名,让您不必在各处输入相同的类型名:

typeof(arraySize) i;

for (i = 0; i < arraySize; i++) 
  ...

那么,如果你改变arraySize的类型,i的类型就会自动改变。

【讨论】:

公平地说,除了最晦涩的 32 位和 64 位平台外,您至少需要 40 亿个元素才能显示此类问题。而具有较小ints 的平台通常也有更少的内存,这使得int 通常足够。 @delnan:没那么简单。这种推理在过去导致了非常严重的漏洞,即使是那些认为自己是像 DJB 这样的安全之神的人......【参考方案4】:

这真的取决于编码器。一些程序员更喜欢类型完美主义,所以他们会使用他们比较的任何类型。例如,如果他们正在遍历 C 字符串,您可能会看到:

size_t sz = strlen("hello");
for (size_t i = 0; i < sz; i++) 
    ...

虽然他们只是做了 10 次某事,但您可能仍会看到 int

for (int i = 0; i < 10; i++) 
    ...

【讨论】:

【参考方案5】:

因为除非您有一个大小大于 2 GB 类型的 char 或 4 GB 类型的 short 或 8 GB 类型的 int 等的数组,否则变量是否已签名并不重要与否。

那么,既然可以少打,为什么还要多打呢?

【讨论】:

但是,如果arraySize是可变的并且你想编写防弹代码,off_tptrdiff_tsize_t仍然具有一定的意义。 是的,如果你可能有这么大的数组,那是绝对必要的,但由于人们通常没有,所以他们只使用简单易写的int。例如,如果您使用 O(n^2) 对 int 的数组进行排序,如果元素超过 2M,则基本上必须永远等待数组被排序,如果您甚至有 8GB记忆。所以你看,通常即使你做正确的索引,当输入这么大时,大多数程序都是无用的。那么为什么要让它们防弹呢? @Shahbaz:如果传递一个巨大的数组会使排序需要数周才能完成,我们大多数人会觉得很不幸,但是当传递一个巨大的数组会产生一个根 shell 时,我们会发现这是完全不可接受的。跨度> @R.. 不要误会我的意思,我并不是说这很好,我是在回答为什么人们一直使用 int 的问题。 我正在回复您最近的评论。【参考方案6】:

除了打字更短的问题之外,原因是它允许负数。

由于我们不能提前说一个值是否可以是负数,所以大多数采用整数参数的函数都采用有符号变量。由于大多数函数都使用有符号整数,因此将有符号整数用于循环之类的工作通常较少。否则,您可能不得不添加一堆类型转换。

随着我们转向 64 位平台,带符号整数的无符号范围对于大多数用途来说应该绰绰有余。在这些情况下,没有太多理由不使用有符号整数。

【讨论】:

负值是一个关键点,而你的答案是唯一一个不仅仅是象征性地提及这一点的答案。但是,遗憾的是,有符号参数类型和无符号参数类型之间存在隐式标准转换,这意味着混合它们只会填满,而不是您描述的“必须添加一堆类型转换”的不方便但安全的场景。并且“随着我们转向 64 位平台,有符号整数的无符号范围......”对于大多数编译器/操作系统来说实际上并没有增长 - ints 仍然倾向于 32 位,longs 正在移动从 32 到 64。【参考方案7】:

这是一个更普遍的现象,通常人们没有为他们的整数使用正确的类型。现代 C 具有比原始整数类型更可取的语义类型定义。例如,“大小”的所有内容都应输入为size_t。如果您系统地为应用程序变量使用语义类型,循环变量也更容易使用这些类型。

我已经看到了几个难以检测的错误,这些错误来自使用int 左右。突然在大型矩阵之类的东西上崩溃的代码。只需使用正确的类型正确编码就可以避免这种情况。

【讨论】:

正确的大小类型是size_t,不幸的是size_t 本身使用了错误的类型(无符号)定义,这是大量错误的根源。我更喜欢对代码使用语义正确的类型(例如int),而不是使用形式上正确但语义错误的类型。使用ints,您可能会遇到非常大(难以置信的大)值的错误......使用unsigned 值,疯狂行为更接近日常使用(0)。 @6502,对此的看法似乎有很大不同。你可以看看我的博客文章:gustedt.wordpress.com/2013/07/15/… @JensGustedt:语义错误并不是一种意见,除非你认为a.size() - b.size() 应该是大约四十亿,而b 有一个元素而a 没有元素是正确的。有人认为unsigned 对于非负数来说是一个绝妙的主意,你是对的,但我的印象是他们过于重视名称而不是真正的含义。 Bjarne Stroustrup 认为 unsigned 对计数器和索引来说是个坏主意……见 ***.com/q/10168079/320726 @6502,正如我所说,意见差异很大。 SO 不应该是一个讨论意见的地方,尤其是那些不参与讨论的人。 Stroustrup 肯定是许多事情的参考,但不是 C。 @6502 抱歉,您认为正确的语义不是。 size_t - size_t 应该是 off_t,而不是 size_t。【参考方案8】:

这纯粹是懒惰和无知。您应该始终为索引使用正确的类型,除非您有更多信息限制了可能的索引范围,否则size_t 是正确的类型。

当然,如果维度是从文件中的单字节字段读取的,那么您知道它在 0-255 范围内,int 将是一个完全合理的索引类型。同样,int 可以在循环固定次数(例如 0 到 99)时使用。但是还有另一个不使用 int 的原因:如果您在循环体中使用 i%2 来处理 even/奇数索引不同,i%2 签名时 ii 未签名时要贵得多...

【讨论】:

在我的回答中看到#3,它不是“纯粹的”懒惰/无知 这并不能改变代码错误的事实。这是修复它的一种方法:for (size_t i=10; i--&gt;0; )【参考方案9】:

从逻辑角度来看,使用int 对数组进行索引更正确。

unsigned 在 C 和 C++ 中的语义并不真正意味着“非负数”,而是更像是“位掩码”或“模整数”。

要了解为什么unsigned 不是“非负”数字的好类型,请考虑以下完全荒谬的陈述:

将一个可能为负的整数与一个非负整数相加得到一个非负整数 两个非负整数的差总是一个非负整数 将非负整数乘以负整数得到非负结果

显然,上述短语都没有任何意义......但 C 和 C++ unsigned 语义确实是这样工作的。

实际上使用unsigned 类型作为容器大小是C++ 的设计错误,不幸的是我们现在注定要永远使用这个错误的选择(为了向后兼容)。您可能喜欢“无符号”这个名称,因为它类似于“非负数”,但名称无关紧要,重要的是语义......而且unsigned 与“非负数”相差甚远。

因此,在对向量上的大多数循环进行编码时,我个人首选的形式是:

for (int i=0,n=v.size(); i<n; i++) 
    ...

(当然假设向量的大小在迭代期间没有改变,并且我实际上需要正文中的索引,否则for (auto&amp; x : v)... 更好)。

尽快逃离unsigned 并使用纯整数的优点是避免了unsigned size_t 设计错误导致的陷阱。例如考虑:

// draw lines connecting the dots
for (size_t i=0; i<pts.size()-1; i++) 
    drawLine(pts[i], pts[i+1]);

如果pts 向量为空,上面的代码就会出现问题,因为在这种情况下pts.size()-1 是一个巨大的无意义数字。处理a &lt; b-1a+1 &lt; b 不同的表达式,即使是常用值,就像在雷区跳舞。

从历史上看,size_t 无符号的理由是能够将额外的位用于值,例如能够在数组中有 65535 个元素,而不是在 16 位平台上只有 32767 个元素。在我看来,即使在当时,这种错误语义选择的额外成本也不值得获得(如果现在 32767 个元素还不够,那么 65535 无论如何也不会足够长)。

无符号值很棒而且非常有用,但不适用于表示容器大小或索引;对于大小和索引,常规有符号整数效果更好,因为语义是您所期望的。

当您需要模算术属性或想要在位级别工作时,无符号值是理想的类型。

【讨论】:

我认为你是对的,因为 java(“改进的”c++)不支持 unsigned int。另外我认为写该行的正确方法是: size_t arr_index; for (size_t i=1; i @carlos:不。如果size_t 被正确定义,那 是正确的方法。不幸的是,设计错误使size_t 成为unsigned,因此这些值最终具有位掩码语义。除非您认为容器的大小是位掩码是正确的,否则使用 size_t 是错误的选择。不幸的是,标准 C++ 库做出了一个选择,但没有人强迫我在代码中重复相同的错误。我的建议是远离size_t 并尽快使用常规整数而不是床上逻辑,以便它也适用于size_t 不仅仅是 16 位平台。使用当前的size_t,您可以使用例如vector&lt;char&gt; 的大小,例如IA-32 Linux 上的 2.1G,具有 3G/1G 内存拆分。如果 size_t 已签名,如果将向量从 @Ruslan:令人惊讶的是,即使对于相当优秀的程序员,这个非常弱的论点也能坚持下去:包含单个字节的单个数组占用大部分地址空间的想法是完全荒谬的,我敢肯定不是经常面对但显然被“未签名”***者认为非常重要的东西。拥有一种能够使用所有位并具有“非负”整数语义的数据类型会很好,但不幸的是,C++ 中不存在这样的类型,而使用无符号而不是无符号是无稽之谈。 @Ruslan:你的意思是代码被用来寻找大于 8 EB 的文件,所以他们发现了一个错误?【参考方案10】:

考虑以下简单示例:

int max = some_user_input; // or some_calculation_result
for(unsigned int i = 0; i < max; ++i)
    do_something;

如果max恰好是负值,比如-1,则-1将被视为UINT_MAX(当比较两个具有sam秩但符号不同的整数时,有符号的将是视为未签名的)。另一方面,下面的代码不会有这个问题:

int max = some_user_input;
for(int i = 0; i < max; ++i)
    do_something;

给一个否定的max 输入,循环将被安全地跳过。

【讨论】:

以上是关于为啥 C 和 C++ for 循环使用 int 而不是 unsigned int?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我应该在循环中使用 foreach 而不是 for (int i=0; i<length; i++) ?

计算使用 C++ std 函数而不是 for 循环的大 O

c语言int k=10; for(;!k;k--)printf("%d",k--)为啥无答案

golang for循环取值为啥不按顺序输出?

为啥在 C for 循环的条件中使用表达式而不是常量?

为啥我不能将整数向量推入 C++ 中的二维整数向量?