这是一个相当愚蠢的问题,但为什么在C或C ++中定义数组的for循环时通常使用int
而不是unsigned int
?
for(int i;i<arraySize;i++){}
for(unsigned int i;i<arraySize;i++){}
我认识到在执行除了数组索引以外的操作时使用 int
和使用 C++ 容器时使用迭代器的好处。这是否只是因为在循环遍历数组时没有关系?或者我应该避免全部使用它,而改用其他类型,如 size_t
?
从逻辑角度来看,使用 int
进行数组索引更加正确。
C 和 C++ 中的 unsigned
语义并非真正意义上的“非负”,而更像是“位掩码”或“模数整数”。
为了理解为什么 unsigned
不适合作为“非负”数字的类型,请考虑以下荒谬的说法:
显然以上语句都没有任何意义...但这确实是 C 和 C++ 中 unsigned
的语义。
对于容器的大小,实际上使用 unsigned
类型是 C++ 的设计失误,不幸的是,为了向后兼容,我们不得不永远使用这种错误的选择。你可能喜欢“unsigned”的名称,因为它类似于“非负数”,但名称是无关紧要的,关键是语义...而unsigned
与“非负数”相去甚远。
因此,在大多数向量循环中,我个人更喜欢以下的形式:
for (int i=0,n=v.size(); i<n; i++) {
...
}
(当然,假设向量大小在迭代期间不会更改,并且实际上需要该索引作为主体,否则 for(auto& x : v)...
更好)。
尽快摆脱无符号类型并使用普通整数的好处是避免了由于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 < b-1
与a+1 < b
即使对于常用值来说也不相同,就像在雷区跳舞一样。
从历史上看,将size_t
定义为无符号整数的理由是为了能够使用额外的位来表示值,例如在16位平台上能够有65535个元素而不仅仅是32767个。在我看来,即使在当时,这种错误的语义选择所带来的额外成本也不值得这种收益(如果现在32767个元素不够用,那么65535个元素也不会够用很长时间)。
无符号整数非常棒且非常有用,但不适合表示容器大小或索引;对于大小和索引,普通的有符号整数工作得更好,因为它们的语义是您所期望的。
无符号整数是理想的类型,当您需要模算术属性或者想要在比特级别上操作时。
size_t
被正确定义,那将是正确的方式。不幸的是,设计错误使size_t
成为了一个无符号数,因此这些值最终具有位掩码语义。除非您认为容器的大小是位掩码,否则使用size_t
是错误的选择。标准C++库不幸地做出了这个选择,但没有人强迫我在我的代码中重复同样的错误。我的建议是尽快放弃size_t
,而是使用常规整数,而不是将逻辑弯曲成这样,以便它也可以与size_t
一起工作。 - 6502size_t
,你可以在3G / 1G内存拆分的IA-32 Linux上使用大小为2.1G的vector<char>
。如果size_t
是有符号的,如果你将向量从<2G增加到更大的值会怎样呢?突然间,大小会变成负数。这根本没有任何意义。编程语言不应强制实施这种人为的限制。 - Ruslanunsigned size_t
设计是逻辑和显然的选择(并且不是一个错误)。我添加了自己的答案来进一步解释。当然,你可以自由地不同意我的答案并给它点踩。 - Myst这是一个更普遍的现象,很多人都没有使用正确的整数类型。现代C语言有语义化的typedef,其比原始的整型类型更容易使用。例如,所有的“大小”应该被定义为 size_t
类型。如果你在应用程序变量中系统地使用这些类型,循环变量也会更容易使用。
我曾经看到过几个难以检测的bug,其中一些来自于使用了 int
等类型。当处理大矩阵等数据时,代码突然崩溃。只要使用正确的类型编写代码就可以避免这种情况发生。
size_t
,不幸的是,size_t
本身使用了错误的类型(无符号),这是许多bug的根源。我更喜欢在代码中使用语义上正确的类型(例如int
)而不是使用形式上正确但语义上错误的类型。使用int
可能会在非常大(极其大)的值时遇到bug...使用无符号值的行为比较接近每天使用的疯狂行为(0)。 - 6502b
有一个元素而a
没有时a.size() - b.size()
应该约为40亿。某些人认为unsigned
对于非负数是一个绝妙的想法,你是正确的,但我的印象是他们过于注重名称而忽略了实际含义。在认为无符号类型对于计数器和索引是个坏主意的人中,Bjarne Stroustrup是其中之一...请参见https://dev59.com/iWkw5IYBdhLWcg3wDWTL - 6502size_t
是正确的类型。int
将是一个完全合理的索引类型。同样,如果您循环固定次数,比如 0 到 99,则 int
也可以使用。但还有另一个不使用 int
的原因:如果您在循环体中使用 i%2
来区分偶数/奇数索引,那么当 i
为有符号时,i%2
比无符号时要昂贵得多...for (size_t i=10; i-->0; )
- R.. GitHub STOP HELPING ICE区别不大。使用int
的好处是它是有符号的。因此,int i < 0
是有意义的,而unsigned i < 0
就没有多少意义。
如果计算索引,那可能会有好处(例如,如果某个结果为负数,你可能永远不会进入一个循环)。
当然,这样写起来更简单 :-)
int
来索引数组是一种传统的做法,但仍然被广泛采用。 int
只是一个通用的数字类型,不对应平台的寻址能力。如果它比实际长度短或长,尝试索引超出非常大的数组时可能会遇到奇怪的结果。off_t
、ptrdiff_t
和size_t
保证了更高的可移植性。typeof
扩展,它可以让你从重复输入同样的类型名中解脱出来:typeof(arraySize) i;
for (i = 0; i < arraySize; i++) {
...
}
然后,如果你改变arraySize
的类型,i
的类型也会自动更改。
这真的取决于程序员。有些程序员喜欢类型完美主义,所以他们会使用与其比较的任何类型。例如,如果他们正在迭代一个C字符串,你可能会看到:
size_t sz = strlen("hello");
for (size_t i = 0; i < sz; i++) {
...
}
如果他们只是做了10次某事,你可能仍然会看到int
:
for (int i = 0; i < 10; i++) {
...
}
我使用 int
类型是因为它需要更少的物理输入,而且这并不重要 - 它们占用相同的空间,除非你的数组有数十亿个元素,否则如果你没有使用 16 位编译器(我通常不会),你不会溢出。
int
иҝҳеҸҜд»ҘжҸҗдҫӣжӣҙеӨҡжңүе…іеҸҳйҮҸзҡ„дёҠдёӢж–ҮдҝЎжҒҜпјҢеҸҜиў«и§ҶдёәиҮӘжҲ‘и®°еҪ•д»Јз ҒгҖӮжӯӨеӨ–пјҢиҜ·еңЁжӯӨеӨ„йҳ…иҜ»пјҡhttp://www.viva64.com/en/a/0050/ - Blagovest Buyukliev因为除非你有一个大小大于2GB的char
数组,或者4GB的short
类型或8GB的int
类型等,否则变量是否带符号并不重要。
所以,为什么要打更多的字,当你可以打更少的字呢?
arraySize
是可变的,并且您想编写防弹代码,则off_t
,ptrdiff_t
和size_t
仍然具有一定的重要性。 - Blagovest Buyuklievint
。例如,如果您正在使用 O(n^2) 对 int
数组进行排序,并且元素超过 2M,即使您拥有 8GB 的内存,您也基本上需要永远等待数组排序完成。因此,通常即使您正确编制索引,大多数程序在输入如此之大的情况下都是无用的。那么为什么要让它们防弹呢? - Shahbazint
。 - Shahbaz除了输入更短的问题外,原因是它允许负数。
由于我们无法预先确定一个值是否可能为负数,大多数使用整数参数的函数都采用有符号类型。由于大多数函数使用有符号整数,因此在循环等方面使用有符号整数通常比使用无符号整数更容易。否则,您可能需要添加大量的类型转换。
随着我们向64位平台迈进,有符号整数的无符号范围应该足以满足大多数目的。在这些情况下,没有太多理由不使用有符号整数。
int
仍然倾向于是32位,而long
从32位移动到64位。 - Tony Delroy在大多数情况下,使用有符号的 int
是一个错误,很容易导致潜在的错误和未定义的行为。
使用 size_t
可以匹配系统的字长(64位系统上为64位,32位系统上为32位),始终允许正确的循环范围,并最小化整数溢出的风险。
int
的建议是为了解决一个问题,即经验不足的程序员经常错误地编写反向 for
循环(当然,int
可能不在正确的循环范围内):
/* a correct reverse for loop */
for (size_t i = count; i > 0;) {
--i; /* note that this is not part of the `for` statement */
/* code for loop where i is for zero based `index` */
}
/* an incorrect reverse for loop (bug on count == 0) */
for (size_t i = count - 1; i > 0; --i) {
/* i might have overflowed and undefined behavior occurs */
}
int
是不可避免的。然而,for
循环的正确类型通常是 size_t
。比如说,INT_MIN * -1
等于多少?(预处理器会保护你,但如果没有它,你就会陷入困境)。
P.S.
至于 @6502 在他们的答案中提供的示例,整个问题又是试图走捷径和一个简单的缺失的 if
语句。
当一个循环假设数组中至少有两个元素时,这个假设应该事先进行测试。例如:
// draw lines connecting the dots - forward loop
if(pts.size() > 1) { // first make sure there's enough dots
for (size_t i=0; i < pts.size()-1; i++) { // then loop
drawLine(pts[i], pts[i+1]);
}
}
// or test against i + 1 : which tests the desired pts[i+1]
for (size_t i = 0; i + 1 < pts.size(); i++) { // then loop
drawLine(pts[i], pts[i+1]);
}
// or start i as 1 : but note that `-` is slower than `+`
for (size_t i = 1; i < pts.size(); i++) { // then loop
drawLine(pts[i - 1], pts[i]);
}
size_t
作为索引,它保证足够大且输入量比unsigned int
更少。 - Matteo Italiasize_t
和ptrdiff_t
:http://www.viva64.com/en/a/0050/ - Blagovest Buyuklievsize_t
中存储指针的能力等),并完全忽略了ptrdiff_t
的有符号溢出和范围问题。我不敢称之为“非常好”。 - R.. GitHub STOP HELPING ICE