C++ for循环- size_type vs. size_t

22

在《C++ Primer》书籍的第三章中,有以下用于将向量中的元素重置为零的 for 循环:

for (vector<int>::size_type ix = 0; ix ! = ivec.size(); ++ix)
ivec[ix] = 0;

为什么要使用 vector<int>::size_type ix = 0?不能直接写成 int ix = 0 吗?使用第一种形式相对于使用第二种形式有何好处?

谢谢。

4个回答

19
C++标准规定:

 size_type  |  无符号整数类型  | 一种类型,能够表示分配模型中最大对象的大小

然后它补充说:

本国际标准中描述的容器实现可以假设其Allocator模板参数满足以下两个附加要求,超出了表32中的要求。

  • typedef成员pointer、const_pointer、size_type和difference_type需要分别是T*、T const*、size_t和ptrdiff_t

因此,size_type很可能是size_t的typedef。

而标准确实将其定义为:

template <class T> 
class allocator 
{
   public:
       typedef size_t size_type;
       //.......
};

因此,需要注意的最重要的几点是:

  • size_type 是无符号整数类型,而 int 不一定是无符号的。 :-)
  • 由于它是无符号的,所以可以表示最大的索引。

当引用说明分配器的size_type需要是size_t,以及在 C++14 之后,如果size()不能适应 size_t,则程序无效,那么我们为什么需要同时有size_t和size_type呢?也许这会引起一些不必要的混淆(我们知道C++就是喜欢这样)。 - user3063349
1
关于“int不一定是无符号的”这个问题,实际上int类型永远不会是无符号的。 - Ayxan Haqverdili

16

是的,你可以使用int,但只有类型为vector<int>::size_type的类型保证其类型可用于索引所有向量元素。

它可能与int的大小不同。例如,在编译64位Windows时,int宽度为32位,而vector<int>::size_type将为64位。

您可以使用std::size_t代替冗长的vector<int>::size_type,因为前者是后者的typedef。然而,如果您改变容器类型,则其size_type可能是不同的类型,如果它使用std::size_t,则您可能需要修改代码。


3
除此之外,size_type 很可能永远不会是 int 类型。一般来说,它会是 size_t(无符号整数类型)。 - rkellerm
1
是的,我的意思是它可能与int大小相同。 - Daniel Gehriger
大小可以是除了 intunsigned int 之外的其他类型吗? - Simplicity
2
int 的大小取决于具体的实现。例如,对于 VisualC++,请参见此处:http://msdn.microsoft.com/en-us/library/s3f49ktz(VS.80).aspx。正如您所看到的,`int` 是32位的,但指针是64位的。还可以参考http://blogs.msdn.com/oldnewthing/archive/2005/01/31/363790.aspx。 - Daniel Gehriger
2
不要使用相当冗长的vector<int>::size_type,可以使用std::size_t,因为前者是后者的typedef。但这并不总是正确的。它可能是相同的,但没有必要这样做。 - NathanOliver
显示剩余4条评论

9

vector<int>::size_type是一种类型,保证可以容纳您可能拥有的最大vector的大小,因此它保证了您可以索引vector的所有元素(因为索引从0到大小-1);它是所有vector方法中用于索引和大小的类型。

如果您有非常大的数组,这可能实际上很重要,因为其他整数类型可能会溢出(如果它们是带符号的类型,事情可能会变得相当奇怪);即使您永远不会获得如此巨大的数组,这基本上也是一种代码清理方式;此外,您的ix具有与ivec.size()相同的类型,因此您不会收到比较有符号和无符号整数的警告。

背景vector<T>::size_type通常是size_ttypedef(我在某个地方读到过实际上标准隐含强制使用它是size_t - 编辑:根本就不是隐含的,请参见@Nawaz的答案),而sizeof 运算符的返回类型是size_t。这暗示它可以容纳可用于C++应用程序的最大对象的大小,因此它肯定(仅)足够大,以索引任何类型的数组。

实际上,我也将size_t(在<cstddef>中定义)用作C风格数组的索引,并且出于完全相同的原因,我认为这是一个好习惯。


顺便说一句,您还可以完全忘记用于索引的类型,而只需使用迭代器:

for (vector<int>::iterator it = ivec.begin(); it != ivec.end(); ++it)
    *it = 0;

或者使用迭代器和 <algorithm> 库:

std::fill(ivec.begin(), ivec.end(), 0);

这两个选项适用于任何容器ivec,因此如果您决定更改容器类型,则无需更改代码。

使用vector还可以使用assign方法(如其他答案中建议的):

ivec.assign(ivec.size(), 0);

你使用 size_t 还是 std::size_t?;-) - fredoverflow
在这个答案的顶部有一个隐式的using namespace std;(但不在我的代码中):Pstd::size_t - Matteo Italia
当你说vector<int>::size_type是一种可以确保指定你可能拥有的最大向量大小的类型时,你能再解释一下吗?谢谢。 - Simplicity
@user588855:抱歉措辞不当,将“指定”替换为“保持”;但是,我的意思是保证类型size_type可以存储您实际可以分配的最大数组大小(=项目数),因此,无论您的数组大小如何,使用size_type作为索引迭代它时,您可以确信不会出现任何整数溢出问题。 - Matteo Italia

4
您不应该使用int,因为vector<int>::size_type是无符号类型,也就是说,向量将其元素用无符号类型索引。然而,int是有符号类型,混合使用有符号和无符号类型可能会导致奇怪的问题。(尽管在您的示例中对于较小的n这不会成为问题。)
请注意,我认为只使用size_t(而不是T::size_type)更清晰 - 打字更少,应该适用于所有实现。
还要注意您发布的for循环:
for(size_t ix=0; ix != ivec.size(); ++ix) ...

最好写成:

for(size_t i=0, e=ivec.size(); i!=e; ++ix) ...

-- 不需要在每次迭代中调用size()函数。

实际上,如果第一个版本的for循环的汇编代码比第二个更紧凑,我会非常惊讶。vector<T>::size()的调用将被内联。 - Daniel Gehriger
@Fred:一般情况下我同意你的观点。但对于模板化的std::vector来说,影响向量大小的所有代码对编译器都是可见的,因为编译器必须针对元素类型进行特化。我快速运行了一次测试,事实上并没有差别。 - Daniel Gehriger
@Fred:我相信我们都同意。请注意,编译器不必对som_funcv的操作做出任何假设。对v.size()的调用始终可以作为从存储向量大小的内存位置加载的内联操作来执行。但是,编译器不能像使用OP的优化时那样将其放入寄存器中。 - Daniel Gehriger
2
@Daniel:我不想在这里讨论优化器的问题。简单地说,我发布的第二种形式是*C++*for循环的推荐用法。我发现它很清晰,也使意图明确,在for循环期间大小肯定不会改变。 - Martin Ba
1
@Martin:太好了!我明白了。我自己也这么做。比如在可能的情况下,即使是简单的数据类型,也要使用预增 ++i - Daniel Gehriger
显示剩余3条评论

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