【问题标题】:Should I use int or unsigned int when working with STL container?使用 STL 容器时应该使用 int 还是 unsigned int?
【发布时间】:2013-06-19 03:48:27
【问题描述】:

参考本指南:
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() 之间的警告相同。


如果我将indexi 声明为unsigned int,则警告关闭,但类型声明会传播,然后我必须将更多变量声明为unsigned int,然后它与指南相矛盾并失去一致性.

我能想到的最好方法是使用这样的演员表:

if (index &gt; static_cast&lt;int&gt;(someExternalVector.size())

for (int i = 0; i &lt; static_cast&lt;int&gt;(someExternalVector.size()); ++i)

但我真的不喜欢演员表。

有什么建议吗?


下面有一些详细的想法:

仅使用有符号整数的好处是:我可以避免有符号/无符号警告、强制转换,并确保每个值都可以是负数(保持一致),因此 -1 可用于表示无效值。

在很多情况下,循环计数器的使用与其他一些常量或结构成员混合使用。因此,如果有符号/无符号不一致,就会有问题。将充满警告和强制。

【问题讨论】:

  • C++11 的 autodecltype 肯定会对此有所帮助。您还应该阅读 Herb Sutter 的 this 文章。
  • 谢谢。但是 C++11 现在可能不是我的解决方案……我们的项目不能支持 :(
  • 你的编译器是什么? C++ 本身并不规定警告。顺便说一句,您是否尝试过使用 64 位或更大的整数类型?关于 int 如何与 unsigned 大小和更大的类型交互存在一些怪癖......
  • 我使用 Visual Studio 2008 作为编译器。
  • 你有什么理由要遵循那个所谓的“指南”吗?

标签: c++ stl casting integer warnings


【解决方案1】:

无符号类型具有三个特征,其中之一是定性“好”,其中之一是定性“差”:

  • 它们可以容纳两倍于相同大小的有符号类型的值(好)
  • size_t 版本(即 32 位机器上的 32 位,64 位机器上的 64 位等)可用于表示内存(地址、大小等)(中性)
  • 它们在 0 之下回绕,因此在循环中减去 1 或使用 -1 来表示无效索引可能会导致错误(坏)。Signed types wrap 也是。

由于上面的前两点,STL 使用无符号类型:为了不限制类似数组的类的潜在大小,例如 vectordeque(尽管您必须质疑您想要 4294967296 的频率数据结构中的元素);因为负值永远不会是 most 数据结构的有效索引;并且因为 size_t 是用于表示与内存有关的任何事情的正确类型,例如结构的大小,以及相关的事情,例如字符串的长度(见下文。)这不一定是使用的好理由它用于索引或其他非内存目的,例如循环变量。在 C++ 中这样做是最佳实践的原因是一种反向构造,因为它是容器以及其他方法中使用的内容,并且一旦使用,其余代码必须匹配以避免遇到相同的问题。

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

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

在处理内存大小时应该使用size_tsizeof 的结果,通常是字符串长度等)它通常被选为默认使用的无符号类型,因为它与编译代码的平台相匹配。例如,字符串的长度是size_t,因为字符串只能有 0 个或多个元素,并且没有理由将字符串的长度方法任意限制为比平台上可以表示的长度更短,例如 16 - 32 位平台上的位长 (0-65535)。注意(感谢评论者Morwenstd::intptr_tstd::uintptr_t,它们在概念上是相似的——总是适合你的平台的大小——如果你想要一些不是指针的东西,应该将它们用于内存地址。注意 2(感谢评论者 rubenvb)由于 npos 的值,字符串只能包含 size_t-1 元素。详情如下。

这意味着如果你使用 -1 来表示一个无效的值,你应该使用有符号整数。如果您使用循环对数据进行迭代向后,如果您不确定循环构造是否正确,则应考虑使用有符号整数(并且如其他答案之一所述,它们是容易出错。)IMO,您不应该不使用技巧来确保代码正常工作 - 如果代码需要技巧,那通常是一个危险信号。此外,对于那些关注您并阅读您的代码的人来说,这将更难理解。这两个都是不遵循上述@Jasmin Gray 回答的原因。

迭代器

但是,在 C++ 中使用基于整数的循环来迭代数据结构的内容是错误的,因此从某种意义上说,关于有符号与无符号 for 循环的争论是没有意义的。您应该改用迭代器:

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 作为无效值。这很简单。使用签名。保持一致即可。

我非常相信使用自然类型,例如枚举,并且有符号整数适合这一点。它们更符合我们的概念预期。当您的思想和代码保持一致时,您编写错误代码的可能性就会降低,而更有可能写出正确、干净的代码。

【讨论】:

  • 对于内存,最好使用std::intptr_tstd::uintptr_t,它们被定义为始终能够保存指针。
  • +1 用于迭代器。另请注意,std::string::npos 在标准中定义为std::string::size_type(-1)。这是否意味着标准犯了错误?我不这么认为。使用some_unsigned_type(-1) 非常好。只要知道您将能够存储一个小于size_type 大小的元素。
  • 谢谢@rubenvb:我编辑提到这一点并参考您的评论。在写这个答案时,我已经忘记了所有关于 npos 的事情并且没有考虑它的影响。使用 some_unsigned_type(-1) 很好(我对标准没有任何顾虑!),只要它是故意的,而不是错误......
  • 嗯,迭代器和基于范围的fors 当然很好,有时你不能仅仅使用索引,同时迭代 2 个或更多容器以及类似的东西。 (它可以用迭代器来完成,但有太多的附加代码实际上可能不太容易理解)。实际上,困扰我的有符号/无符号问题是,如果您使用unsigned 作为索引进行循环,然后将其更改为以相反的顺序进行迭代,则会导致不明显的错误。
  • Jamin Gray 的回答不是高于。使用“以上”或“以下”来指代本网站上的其他答案时必须小心,因为此类引用可能会因投票而无效。
【解决方案2】:

使用容器返回的类型。在这种情况下,size_t - 这是一个无符号整数类型。 (从技术上讲,它是 std::vector&lt;MyType&gt;::size_type,但它通常定义为 size_t,因此使用 size_t 是安全的。unsigned 也可以)

但总的来说,为正确的工作使用正确的工具。 “指数”是否应该为负数?如果没有,请不要签名。

顺便说一句,您不必输入“unsigned int”。 'unsigned' 是相同变量类型的简写:

int myVar1;
unsigned myVar2;

原始问题中链接到的页面说:

有些人,包括一些教科书作者,建议使用无符号 类型来表示从不为负的数字。这是意在 作为一种自我记录的形式。然而,在 C 中,这样的优点 它可能引入的真正错误超过了文档。

这不仅仅是自我记录,而是使用正确的工具完成正确的工作。说“无符号变量会导致错误,所以不要使用无符号变量”是愚蠢的。有符号变量也可能导致错误。浮点数也可以(超过整数)。唯一保证没有错误的代码是不存在的代码。

他们为什么 unsigned 是邪恶的例子是这个循环:

for (unsigned int i = foo.Length()-1; i >= 0; --i)

我很难在循环中向后迭代,而且我通常会犯错误(有符号或无符号整数)。我要从尺寸中减去一个吗?我是让它大于等于 0,还是仅仅大于?一开始就是草率的情况。

那么你会如何处理你知道你有问题的代码呢?你改变你的编码风格来解决问题,让它更简单,更容易阅读,更容易记住。他们发布的循环中有一个错误。错误是,他们希望允许低于零的值,但他们选择将其设为无符号。这是他们的错误。

但这里有一个简单的技巧,可以让阅读、记忆、书写和运行变得更容易。带有无符号变量。这是智能要做的事情(显然,这是我的看法)。

for(unsigned i = myContainer.size(); i--> 0; )
{
    std::cout << myContainer[i] << std::endl;
}

它没有签名。它总是有效的。对起始尺寸没有负面影响。不用担心下溢。它只是工作。这很聪明。做对了,不要停止使用无符号变量,因为有人曾经说过他们在 for() 循环中犯了错误并且未能训练自己不犯错误

记住它的诀窍:

  1. 将“i”设置为大小。 (不用担心减一)
  2. 让'i'指向 0,就像一个箭头。 i --&gt; 0(它是后递减 (i--) 和大于比较 (i > 0) 的组合)

最好自学正确编码的技巧,然后因为编码不正确而丢弃工具。

您希望在代码中看到哪些内容?

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

或者:

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

不是因为输入的字符更少(那会很愚蠢),而是因为它减少了精神上的混乱。浏览代码时更容易进行心理解析,也更容易发现错误。


Try the code yourself

【讨论】:

  • 在 64 位机器上,size_t 是 64 位无符号整数类型,而不是 unsigned int。在 x86/64 CPU 上,size_t 等价于 unsigned long
  • @MarsonMao 您使用的是 C++,而不是 C。是的,制作有缺陷的代码可能(令人惊讶!)有缺陷。您可以使用有符号或无符号整数制作错误代码。这不是不使用无符号整数的理由。在他们给出的负迭代示例中,有一个正确迭代的简单技巧,我将在答案中发布。
  • @user172818 我说它是“无符号整数类型”——我不是指“无符号整数”。 unsigned long 也是“无符号整数类型”。我会尝试在答案中澄清这一点,谢谢!
  • --&gt; - ugh and 'wtf', to quote. 仅仅因为已签名也可能有错误而使用未签名是没有道理的,如果您必须使用技巧来确保您可以正确使用它们,那是一个危险信号 - 不是说它们的理由'没事。签名是一种更自然的类型,在更多情况下更安全。 @MarsonMao,IMO,您的文字是正确的,错误超过了优势。从上述回复中的卷积中,您可能可以看出它们是如何可靠地正确使用的。
  • “你想在你的代码中看到什么?” (i--) &gt; 0,很明显。 i--&gt;0 看起来很像 i -&gt; 0,让我想起了 erlang 代码。这种语法会让维护人员感到困惑。 “因为有人曾经在某个地方说他们犯了错误,没有训练自己”我认为这种态度是不健康的。除非您是一名单兵软件开发人员,否则您将无法逃脱。您的代码是为人们编写的。很有可能会阅读它的人比你熟练,甚至完全不称职。对于这些人来说,您的代码应该易于阅读。
猜你喜欢
  • 1970-01-01
  • 2010-09-08
  • 1970-01-01
  • 2021-12-13
  • 2011-08-14
  • 2012-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多