如果你有一个c++ 11编译器,我建议使用一个基于范围的for循环(见下文);或者使用迭代器。但是你有几个选择,我将在下面解释。
基于范围的for循环(c++ 11)
在c++ 11(以及以后的版本)中,你可以使用新的基于范围的for循环,它看起来像这样:
std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';
for-loop语句中的type char应该是vector路径元素的类型,而不是整数索引类型。换句话说,因为path的类型是std::vector<char>,所以应该出现在基于范围的for循环中的类型是char。然而,你可能会经常看到显式类型被auto占位符类型取代:
for (auto i: path)
std::cout << i << ' ';
无论您使用的是显式类型还是auto关键字,对象i都有一个值,该值是path对象中实际项的副本。因此,循环中对i的所有更改都不会保存在path本身中:
std::vector<char> path{'a', 'b', 'c'};
for (auto i: path) {
i = '_'; // 'i' is a copy of the element in 'path', so although
// we can change 'i' here perfectly fine, the elements
// of 'path' have not changed
std::cout << i << ' '; // will print: "_ _ _"
}
for (auto i: path) {
std::cout << i << ' '; // will print: "a b c"
}
如果你也想禁止在for循环中更改i的复制值,你可以强制i的类型为const char,如下所示:
for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}
如果你想修改path中的项,以便这些更改在for循环之外的path中持续存在,那么你可以像这样使用引用:
for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}
即使你不想修改path,如果对象的复制是昂贵的,你应该使用const引用,而不是按值复制:
for (const auto& i: path)
std::cout << i << ' ';
迭代器
在c++ 11之前,规范的解决方案是使用迭代器,这仍然是完全可以接受的。它们的使用方法如下:
std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
如果你想在for循环中修改vector的内容,那么使用iterator而不是const_iterator。
补充:typedef / type alias (c++ 11) / auto (c++ 11)
这不是另一个解决方案,而是对上述迭代器解决方案的补充。如果你正在使用c++ 11标准(或更高版本),那么你可以使用auto关键字来帮助提高可读性:
for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
这里i的类型将是非const(即,编译器将使用std::vector<char>::iterator作为i的类型)。这是因为我们调用了begin方法,所以编译器由此推导出i的类型。如果我们改为调用cbegin方法(“c”表示const),则i将是std::vector<char>::const_iterator:
for (auto i = path.cbegin(); i != path.cend(); ++i) {
*i = '_'; // will produce a compiler error
std::cout << *i << ' ';
}
如果你不习惯编译器推断类型,那么在c++ 11中,你可以使用类型别名来避免一直输入向量(养成一个好习惯):
using Path = std::vector<char>; // C++11 onwards only
Path path; // 'Path' is an alias for std::vector<char>
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
如果你不能使用c++ 11编译器(或者出于某种原因不喜欢类型别名语法),那么你可以使用更传统的typedef:
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
注:
在这一点上,您以前可能遇到过迭代器,也可能没有听说过迭代器是您“应该”使用的,并且可能想知道为什么。答案并不容易理解,但是,简而言之,迭代器是一种抽象,可以使您免受操作细节的影响。
It is convenient to have an object (the iterator) that does the operation you want (like sequential access) rather than you writing the details yourself (the "details" being the code that does the actual accessing of the elements of the vector). You should notice that in the for-loop you are only ever asking the iterator to return you a value (*i, where i is the iterator) -- you are never interacting with path directly itself. The logic goes like this: you create an iterator and give it the object you want to loop over (iterator i = path.begin()), and then all you do is ask the iterator to get the next value for you (*i); you never had to worry exactly how the iterator did that -- that's its business, not yours.
OK, but what's the point? Well, imagine if getting a value wasn't simple. What if it involves a bit of work? You don't need to worry, because the iterator has handled that for you -- it sorts out the details, all you need to do is ask it for a value. Additionally, what if you change the container from std::vector to something else? In theory, your code doesn't change even if the details of how accessing elements in the new container does: remember, the iterator sorts all the details out for you behind the scenes, so you don't need to change your code at all -- you just ask the iterator for the next value in the container, same as before.
所以,虽然这看起来像是对vector循环的过度使用,但迭代器的概念背后有很好的理由,所以你最好习惯使用它们。
索引
你也可以使用整数类型显式地在for循环中为vector元素建立索引:
for (int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
如果要这样做,最好使用容器的成员类型,如果它们可用且合适的话。vector有一个名为size_type的成员类型:它是size方法返回的类型。
typedef std::vector<char> Path; // 'Path' now a synonym for std::vector<char>
for (Path::size_type i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
为什么不优先使用迭代器解决方案呢?对于简单的情况,您可以这样做,但是使用迭代器有几个优点,我在上面简要介绍了这些优点。因此,我的建议是避免使用这种方法,除非你有充分的理由。
std::复制(C + + 11)
请看约书亚的回答。可以使用STL算法std::copy将向量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有什么好的理由。
std::范围:复制(C + + 20)
为了完整起见,c++ 20引入了range,它可以作用于std::vector的整个范围,因此不需要begin和end:
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));
除非您有一个最新的编译器(在GCC上显然至少是版本10.1),否则即使您可能有一些c++ 20的特性可用,也可能没有范围支持。
过载std::上ostream::操作符< <
下面是克里斯的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上面的解决方案之一,但好处是代码更简洁。这是你如何使用std::ranges::copy上面的解决方案:
#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
using Path = std::vector<char>; // type alias for std::vector<char>
std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}
int main() {
Path path{'/', 'f', 'o', 'o'};
// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;
return 0;
}
现在可以像基本类型一样将Path对象传递到输出流。使用上述任何其他解决方案也应该同样简单。
结论
这里提供的任何解决方案都可以工作。这取决于你(环境或你的编码标准),哪一个是“最好的”。任何比这更详细的问题可能最好留给另一个问题,在那里可以正确地评估利弊,但一如既往,用户偏好总是起作用的:所提出的解决方案在客观上都是错误的,但有些对每个程序员来说都更好。
齿顶高
这是我之前发布的一个解决方案的扩展。由于这篇文章一直受到关注,我决定扩展它,并参考这里发布的其他优秀解决方案,至少是那些我个人过去至少使用过一次的解决方案。然而,我想鼓励读者看看下面的答案,因为其中可能有我已经忘记或不知道的好建议。