如何将 a 的内容打印std::vector到屏幕上?
std::vector
实现以下内容的解决方案也operator<<很好:
operator<<
template<container C, class T, String delim = ", ", String open = "[", String close = "]"> std::ostream & operator<<(std::ostream & o, const C<T> & x) { // ... What can I write here? }
这是我到目前为止所拥有的,没有单独的功能:
#include <iostream> #include <fstream> #include <string> #include <cmath> #include <vector> #include <sstream> #include <cstdio> using namespace std; int main() { ifstream file("maze.txt"); if (file) { vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>())); vector<char> path; int x = 17; char entrance = vec.at(16); char firstsquare = vec.at(x); if (entrance == 'S') { path.push_back(entrance); } for (x = 17; isalpha(firstsquare); x++) { path.push_back(firstsquare); } for (int i = 0; i < path.size(); i++) { cout << path[i] << " "; } cout << endl; return 0; } }
如果您有 C++11 编译器,我建议使用基于范围的 for 循环(见下文);或者使用迭代器。但是你有几个选择,我将在下面解释所有这些。
在 C++11(及更高版本)中,您可以使用新的基于范围的 for 循环,如下所示:
std::vector<char> path; // ... for (char i: path) std::cout << i << ' ';
for 循环语句中的类型char应该是向量元素的类型,path而不是整数索引类型。换句话说,因为pathis 是 type std::vector<char>,所以应该出现在基于范围的 for 循环中的 type 是char。但是,您可能经常会看到显式类型替换为auto占位符类型:
char
path
std::vector<char>
auto
for (auto i: path) std::cout << i << ' ';
无论您使用显式类型还是auto关键字,对象i的值都是对象中实际项的副本path。i因此,循环中的所有更改path本身都不会保留:
i
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" }
如果你想禁止i在 for 循环中更改这个复制的值,你可以强制的类型i是const char这样的:
const char
for (const auto i: path) { i = '_'; // this will now produce a compiler error std::cout << i << ' '; }
如果您想修改其中的项目,path以便这些更改在pathfor 循环之外持续存在,那么您可以使用如下引用:
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 循环中修改向量的内容,请使用iterator而不是const_iterator.
iterator
const_iterator
这不是另一种解决方案,而是对上述iterator解决方案的补充。如果您使用的是 C++11 标准(或更高版本),那么您可以使用auto关键字来提高可读性:
for (auto i = path.begin(); i != path.end(); ++i) std::cout << *i << ' ';
这里的类型i将是非常量的(即编译器将std::vector<char>::iterator用作的类型i)。这是因为我们调用了begin方法,所以编译器从中推断出类型i。如果我们cbegin改为调用该方法(“c”表示 const),那么i将是 a std::vector<char>::const_iterator:
std::vector<char>::iterator
begin
cbegin
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
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 << ' ';
在这一点上,您以前可能遇到过迭代器,也可能没有,您可能听说过或可能没有听说过迭代器是您“应该”使用的,并且可能想知道为什么。答案并不容易理解,但简而言之,这个想法是迭代器是一种抽象,可以保护您免受操作细节的影响。
拥有一个执行您想要的操作(如顺序访问)的对象(迭代器)而不是您自己编写详细信息(“详细信息”是实际访问向量元素的代码)是很方便的。您应该注意到,在 for 循环中,您只是要求迭代器返回一个值(迭代器*i在哪里i)——您永远不会path直接与自身交互。逻辑是这样的:你创建一个迭代器并给它你想要循环的对象(iterator i = path.begin()),然后你所做的就是让迭代器为你获取下一个值(*i);您永远不必担心迭代器是如何做到这一点的——这是它的业务,而不是您的业务。
*i
iterator i = path.begin()
好的,但有什么意义呢?好吧,想象一下,如果获得价值并不简单。如果它涉及一些工作怎么办?你不必担心,因为迭代器已经为你处理了——它整理了细节,你需要做的就是向它询问一个值。此外,如果您将容器从std::vector其他东西更改怎么办?理论上,即使访问新容器中的元素的细节发生了变化,您的代码也不会改变:请记住,迭代器会在幕后为您整理所有细节,因此您根本不需要更改代码– 你只需向迭代器询问容器中的下一个值,和以前一样。
因此,虽然这看起来像是对遍历向量的混淆过度杀伤,但迭代器的概念背后有充分的理由,因此您不妨习惯使用它们。
您还可以使用整数类型在 for 循环中显式地索引向量的元素:
for (int i=0; i<path.size(); ++i) std::cout << path[i] << ' ';
如果您打算这样做,最好使用容器的成员类型,如果它们可用且合适的话。std::vector有一个size_type为此作业调用的成员类型:它是size方法返回的类型。
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] << ' ';
为什么不优先使用它而不是iterator解决方案?对于简单的情况,您可以这样做,但使用 aniterator会带来一些优势,我在上面已经简要概述了这些优势。因此,我的建议是避免这种方法,除非你有充分的理由。
见约书亚的回答。您可以使用 STL 算法std::copy将矢量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有什么好的理由。
std::copy
为了完整起见,C++20 引入了范围,它可以作用于 a 的整个范围std::vector,因此不需要beginand end:
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::ranges::copy解决方案的方式:
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像基本类型一样将对象传递给输出流。使用上述任何其他解决方案也应该同样简单。
Path
此处介绍的任何解决方案都可以使用。哪个是“最好的”取决于您(以及上下文或您的编码标准)。任何比这更详细的东西可能最好留给另一个可以正确评估优点/缺点的问题,但一如既往,用户偏好总是会发挥作用:提出的解决方案都不是客观错误的,但有些解决方案对每个编码员来说看起来更好.
这是我发布的早期解决方案的扩展解决方案。由于该帖子一直受到关注,因此我决定对其进行扩展并参考此处发布的其他出色解决方案,至少是我过去亲自使用过至少一次的那些。但是,我鼓励读者查看下面的答案,因为可能有一些我已经忘记或不知道的好建议。