设为首页 加入收藏

TOP

第1条 vector的使用(3)
2013-10-07 13:17:01 来源: 作者: 【 】 浏览:61
Tags:vector 使用

第1条  vector的使用(3)

(4) 避免无谓的重复求值。本例中v.end()所返回的值在整个循环的过程中是不会改变的,因此应当避免在每次判断循环条件时都调用一次v.end(),或许我们应当在循环之前预先将v.end()求出来。

注意,如果你的标准库实现中的vector::iterator就是int*,而且能够将end()进行内联及合理优化的话,原先的代码也许并无任何额外开销,因为编译器或许能够看出end()返回的值一直是不变的,从而安全地将求值提到循环外部。这是一种相当常见的情况。然而,如果你的标准库实现的vector::iterator并非int*(例如,在大多数调试版实现当中,其类型都是类类型的),或者end()之类的函数并没有内联,或者编译器并不能进行相应的优化,那么只有手动将这部分代码提出才能获得一定程度的性能提升。

(5) 尽量使用\n而不是endl。使用endl会迫使输出流刷新其内部缓冲区。如果该流的确有内部缓冲区,而且又确实不需要每次都刷新它的话,可以在整个循环结束之后写一行刷新语句,这样程序会执行得快很多。

最后一个意见稍微高级一些。

(6) 尽量使用标准库中的copy()和for_each(),而不是自己手写循环,因为利用标准库的设施,你的代码可以变得更为干净简洁。这里,风格跟美学判断起作用了。在简单的情况下,copy()和for_each()可以而且确实比手写循环的可读性要强。不过,也只有像本例这样的简单情形才会如此,如果情况稍微复杂一些的话,除非你有一个很好的表达式模板库,否则使用for_each()来写循环反而会降低代码的可读性,因为原先位于循环体中的代码必须被提到一个仿函数当中才能使用for_each()。有时候这种提取是件好事,但有时它只会导致混淆晦涩。
之所以说大家的口味可能各不相同,就是这个原因。另外,在本例中我倾向于将原先的手写循环替换成如下的形式:

  1. copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n")); 

此外,如果你如此使用copy(),那么原先关于!=、++、end()以及endl的问题就不用操心了,因为copy()已经帮你做了这些事情。(当然,我还是假定你并不希望在每输出一个int的时候都去刷新输出流,否则你只有手写循环了。)复用如果运用得当的话不但能够改善代码的可读性,而且还可以避开一些陷阱,从而让代码更佳。

你可以更进一步,编写一个基于容器的复制算法,也就是说,施加在整个容器(而不仅仅是迭代器区间)之上的算法。这种做法同样也可以自动纠正const_iterator问题。例如:

  1. template<class Container, class OutputIterator> 
  2. OutputIterator copy(const Container& c, OutputIterator result) {  
  3.  return std::copy(c.begin(), c.end(), result);  

这里,我们只需简单地包装std::copy(),让它对整个容器进行操作,此外由于我们是以const&来接受容器参数的,因而迭代器自然就是const_iterator了。

准则 确保const正确性。特别是不对容器内的元素做任何改动的时候,记得使用const_iterator。

尽量使用!=而不是<来比较两个迭代器。

养成默认情况下使用前缀形式的- -和++的习惯,除非你的确需要用到原来的值。

实施复用:尽量复用已有的算法,特别是标准库算法(例如for_each()),而不是手写循环。

接下来我们遇到下面这行代码:

  1. cout << v[0]; 

当程序执行这一行的时候,可能会打印出1。这是因为前面的程序以错误的方式改写了v[0]所引用的那块内存,只不过,这行代码也许并不会导致程序立即崩溃,真遗憾!

  1. v.reserve(100);  
  2. assert(v.capacity() == 100); 

同样,这里的断言表达式当中应该使用>=,而且和前面一样,这也是多余的。

  1. cout << v[0]; 

很奇怪!这次的输出结果可能为0,我们刚刚赋值的1神秘失踪了!

为什么?我们假设reserve(100)确实引发了一次内部缓冲区的重分配(即如果第一次reserve(2)并没有使内部缓冲区扩大到100或更多的话),这时v就只会将它确实拥有的那些元素复制到"新家"当中,而问题是实际上v认为它内部空空如也(因此不复制任何元素)!另一方面,新分配的内部缓冲区最初值可能为0(严格讲不确定),因此就出现了上述情况。

  1. v[2] = 3;  
  2. v[3] = 4;  
  3. // ……  
  4. v[99] = 100; 

毫无疑问,看到如上的代码你可能已经叹着气摇头了。这真是糟糕、糟糕、太糟糕了!但由于标准并不强制operator[]()进行越界检查,所以在大多数实现上这种代码或许会静悄悄地"正确"运行着,而不会立即导致异常或内存陷阱。

如果这样改写:

  1. v.at(2) = 3;  
  2. v.at(3) = 4;  
  3. // ……  
  4. v.at(99) = 100; 

那么问题就会变得明朗了,因为第一个调用语句就会抛出一个out_of_range异常。

  1. for(vector<int>::iterator i = v.begin(); i < v.end(); i++) {  
  2.  cout << *i << endl;  

再一次提醒,以上代码什么也不会打印出来,应当考虑将它改写成:

  1. copy(v.begin(), v.end(), ostream_iterator<int>(cout, "\n")); 

再次注意,这种复用自动地解决了!=、前缀++、end()以及endl问题,因此程序永远不会在这些方面犯错误。良好的复用通常也会让代码自动变得更快和更安全。

小结

了解size()和capacity()之间的区别,了解operator[]()跟at()之间的区别。如果需要越界检查,请使用at()而不是operator[]()。这么做可以帮助我们节省大量的调试时间。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇第11条 隐藏信息 下一篇第1条 vector的使用(2)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: