在使用STL容器时,我应该使用int还是unsigned int?

14

参考这个指南:
https://google.github.io/styleguide/cppguide.html#Integer_Types

Google建议在大部分情况下使用int
我尝试遵循这个指南,唯一的问题是使用STL容器。


例子 1。

void setElement(int index, int value)
{
    if (index > someExternalVector.size()) return;
    ...
}

比较 index.size() 会生成警告。

示例 2。

for (int i = 0; i < someExternalVector.size(); ++i)
{
    ...
}

i.size() 之间出现相似的警告。


如果我将 index 或者 i 声明为 unsigned int,那么警告会消失,但是类型声明会传播,因此我必须将更多变量声明为 unsigned int,这与指南相矛盾且不一致。

我所能想到的最好方法是使用强制类型转换,例如:

if (index > static_cast<int>(someExternalVector.size())

或者

for (int i = 0; i < static_cast<int>(someExternalVector.size()); ++i)

但我真的不喜欢强制类型转换。

有什么建议吗?


以下是一些详细思考:

将所有整数都声明为有符号整数的优点在于:可以避免 signed/unsigned 的警告、强制类型转换,并确保每个值都可以是负数(保持一致),因此 -1 可以用来表示无效值。

有许多情况下,循环计数器的使用与某些其他常量或结构成员混合在一起。如果 signed/unsigned 不一致,那么这将是有问题的。这将充满警告和强制类型转换。


C++11 的 autodecltype 肯定会对此有所帮助。您还应该阅读 Herb Sutter 的这篇文章。 - Mark Garcia
你使用的编译器是什么?C++本身并不规定警告。另外,你尝试过使用64位或更大的整数类型吗?在int与其大小相同或更大的unsigned类型交互方面存在一些怪异之处... - Yakk - Adam Nevraumont
我使用Visual Studio 2008作为编译器。 - Marson Mao
1
你想遵循那个所谓的“指南”有什么原因吗? - Cubbi
@MarsonMao 如果你不想担心“signed/unsigned”,就不要使用它们,而是使用函数返回的值类型,我建议使用auto。这个特性从GCC 4.4/Clang 2.9(!)/MSVC 2010开始支持。 - rubenvb
显示剩余2条评论
2个回答

12

无符号类型有三个特点,其中一个是优点,另一个是缺点

  • 它们可以容纳与相同大小的有符号类型相比两倍数量的值(优点)
  • size_t版本(即,在32位机器上为32位,在64位机器上为64位等)用于表示内存(地址、大小等)(中性)
  • 它们在0以下循环,因此在循环中减去1或使用-1表示无效索引可能会导致错误(坏)。有符号类型也会出现这种情况

STL使用无符号类型是基于以上前两点:vectordeque等类似数组的数据结构不受元素数量限制(尽管你需要考虑是否真的需要一个4294967296元素的数据结构);因为负值永远不会是大多数数据结构中的有效索引;而size_t是任何与内存相关的东西的正确类型,例如结构体的大小和字符串长度(见下文)。虽然这不一定是将其用于索引或其他非内存目的(如循环变量)的好理由,但在C++中,最佳实践是这样做,这有点像反向构造,因为它与容器及其他方法一起使用,一旦使用后,余下的代码必须匹配以避免你遇到的问题。

当值可能变为负数时,请使用有符号类型。

当值不可能变为负数时,请使用无符号类型(可能与“不应该”不同)。

处理内存大小时,请使用size_t(例如,sizeof的结果,通常是字符串长度等)。 它通常被选择为默认的无符号类型,因为它与编译代码的平台相匹配。 例如,由于字符串只能具有0个或更多元素,并且没有原因将字符串的长度任意限制为低于平台所能表示的长度(例如,在32位平台上使用16位长度(0-65535)),因此字符串的长度是size_t。注意(感谢评论者Morwenstd::intptr_tstd::uintptr_t类似概念的用法-始终是您平台的正确大小-如果您需要某些不是指针的东西,则应将其用于内存地址。注意2(感谢评论者rubenvb),由于npos的值,字符串只能容纳size_t-1个元素。详情见下文。

这意味着如果您使用-1表示无效值,则应使用有符号整数。如果您使用循环向后迭代数据,则应考虑使用有符号整数,如果您不确定循环结构是否正确(正如其他答案中提到的那样,很容易出现错误)。在我看来,您不应该采用技巧来确保代码工作-因为那通常是危险信号。此外,对于遵循您并阅读代码的人而言,理解起来也更难。这两个原因都不建议按照上面@Jasmin Gray的答案。std::vector<foo> bar; for (std::vector<foo>::const_iterator it = bar.begin(); it != bar.end(); ++it) { // Access using *it or it->, e.g.: const foo & a = *it;

当您这样做时,您不需要担心类型转换、符号等问题。

迭代器可以向前(如上所示)或向后迭代。使用相同的语法it != bar.end(),因为end()表示迭代的结束,而不是基础概念数组、树或其他结构的结束。

换句话说,您在处理STL容器时,“应该使用int还是unsigned int”的答案是“都不用。改用迭代器。”了解更多信息:

还剩什么?

如果您不使用整数类型进行循环,还剩下什么?您自己的值,这些值取决于您的数据,但在您的情况下包括使用-1表示无效值。这很简单,使用带符号数。只需保持一致即可。

我非常支持使用自然类型,例如枚举,有符号整数适合此类情况。它们更符合我们的概念期望。当您的头脑和代码相对应时,您编写错误的可能性更小,表达正确、清晰的代码的可能性更大。


1
对于内存而言,最好使用std::intptr_tstd::uintptr_t,它们被定义为始终能够容纳指针。 - Morwenn
1
+1 迭代器。同时请注意,std::string::npos 在标准中被定义为 std::string::size_type(-1)。这是否意味着标准犯了一个错误?我不这么认为。使用 some_unsigned_type(-1) 是完全可以的。只要知道你将能够存储比 size_type 的大小少一个元素即可。 - rubenvb
谢谢回复!迭代器是个好主意,我经常尝试使用它。但当我将向量用作数组时,我必须关心输入索引或循环计数器(如我在帖子中的示例),我需要检查向量大小与索引是否匹配,或者我将循环计数器传递给另一个接受计数器的函数(这些值可能小于0)。在这种情况下,您会建议对向量大小进行类型转换吗?即 static_cast<int>(Vector.size()) - Marson Mao
5
迭代器和基于范围的for循环当然很好用,但有时您无法仅使用索引进行迭代,同时迭代2个或更多容器等操作。 (虽然可以使用迭代器完成此操作,但需要编写太多额外代码,实际上可能不太容易理解)。其实,让我烦恼的有符号/无符号问题是,如果您使用unsigned作为索引来循环,然后将其更改为以相反的顺序迭代,这将导致非明显的错误。 - Predelnik
2
Jamin Gray的答案并不是“上面”的。当提到本站其他答案时,使用“上面”或“下面”这样的参考词需要小心,因为这些参考可能会被投票无效化。 - Ruslan
显示剩余7条评论

4
使用容器返回的类型,这种情况下是size_t - 这是一个无符号整数类型。(技术上讲,它是std::vector::size_type,但通常定义为size_t,所以使用size_t是安全的。unsigned也可以)
但总的来说,要选择合适的工具来完成合适的工作。'index'是否可能为负数?如果不是,那就不要将其设为有符号数。
顺便提一下,您不必输入'unsigned int'。 'unsigned'是相同变量类型的简写:
int myVar1;
unsigned myVar2;

原问题链接中的页面说:
一些人,包括一些教科书作者,建议使用无符号类型来表示永远不为负的数字。这是作为自我文档的形式。然而,在 C 语言中,这种文档的优点被它可能引入的真正错误所抵消。
这不仅仅是自我文档,而是使用正确的工具完成正确的工作。说“无符号变量会导致错误,因此不要使用无符号变量”是愚蠢的。有符号变量也可能会导致错误。浮点数也可能比整数更容易出错。唯一保证没有错误的代码是不存在的代码。
他们举的无符号类型恶魔的例子是这个循环:
for (unsigned int i = foo.Length()-1; i >= 0; --i)

我在循环中迭代时经常犯错误,尤其是涉及到有无符号整数的情况。我该从size减去1吗?是大于或等于0,还是只是大于0?这是一个棘手的问题。
那么,如果你知道代码存在问题怎么办?你需要改变编码风格以解决问题,使代码更简洁易读、易记和易运行。他们发布的循环存在错误。这个错误是他们想要允许一个负值,但却选择了无符号变量,这是他们的错误。
但是有一个简单的技巧可以使它更易于阅读、记忆、编写和运行。对于无符号变量,下面是一个聪明的方法(显然,这是我的意见)。
for(unsigned i = myContainer.size(); i--> 0; )
{
    std::cout << myContainer[i] << std::endl;
}

这段代码是未签名的。它总是有效的。起始大小没有负面影响,也不用担心下溢。它就是有效的。它就是聪明的。如果大家曾经因为某人在for()循环中犯错而停止使用无符号变量,却没有训练自己不再犯错,那就要正确地做,不要停止使用无符号变量。

记住它的诀窍:

  1. 将'i'设置为大小。(不必担心减一)
  2. 像箭头一样使'i' 指向 0 。i --> 0(它是后递减 (i--) 和大于比较 (i > 0) 的结合)

更好的方法是教自己编写正确的代码技巧,而不是因为自己编写不正确的代码而放弃工具。

你想在你的代码中看到哪一种?

for(unsigned i = myContainer.size()-1; i >= 0; --i)

或者:

for(unsigned i = myContainer.size(); i--> 0; )

我使用缩写并不是因为它少打字(那太傻了),而是因为它减少了心理负担。在快速浏览代码时,更容易理解,也更容易发现错误。


请自行尝试代码


1
在64位机器上,size_t是一个64位无符号整数类型,而不是unsigned int。在x86/64 CPU上,size_t等同于unsigned long - user172818
2
@user172818 我说的是“无符号整数类型”,我并不是指‘unsigned int’。无符号长整型也是“无符号整数类型”。我会在答案中尽量澄清这一点,谢谢! - Jamin Grey
1
使用无符号类型仅仅因为有符号类型可能会出现错误是不合理的,如果你必须使用技巧来确保正确使用它们,那么这是一个危险信号,而不是说它们是可以接受的。有符号类型更自然,更安全,适用于更多情况。@MarsonMao,我认为你的文本关于错误超过优点是正确的。从上面回复的曲折性中,你可能可以看到它们并不可靠且难以正确使用。 - David
1
你想在你的代码中看到哪个?显然是(i--) > 0i-->0看起来很像i -> 0,让我想起了erlang代码。这样的语法会让维护人员感到困惑。"因为有人曾经说过他们犯了一个错误,没有训练自己",我认为这种态度是不健康的。除非你是一个单兵作战的软件开发者,否则你无法逃脱。你的代码是为人写的。而且有很大的可能性,阅读它的人比你技能低,甚至完全不称职。对于这些人,你的代码应该易于阅读。 - SigTerm
1
@JaminGrey:“你应该修复警告,而不是隐藏它们。” 你无法在所有编译器上修复所有警告。尝试使用 -WAll 编译 Microsoft 编译器的项目。即使在无害代码上,你也会得到 成千上万 的警告。当你看到一个警告时,你应该确保知道自己在做什么,并且如果它没有帮助,就隐藏这个警告。 - SigTerm
显示剩余12条评论

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