如何打印出向量的内容?

新手上路,请多包涵

如何将 std::vector 的内容打印到屏幕上?


实现以下 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;
    }
}

原文由 forthewinwin 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

如果您有 C++11 编译器,我建议使用基于范围的 for 循环(见下文);或者使用迭代器。但是你有几个选择,我将在下面解释所有这些。

基于范围的 for 循环 (C++11)

在 C++11(及更高版本)中,您可以使用新的基于范围的 for 循环,如下所示:

 std::vector<char> path;
// ...
for (char i: path)
    std::cout << i << ' ';

for循环语句中的类型 char 应该是向量元素的类型 path 而不是整数索引类型。换句话说,由于 pathstd::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 中的项目,以便这些更改在 path 在 for 循环之外持续存在,那么您可以使用如下引用:

 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

补充:typedef/类型别名(C++11)/auto(C++11)

这不是另一种解决方案,而是对上述 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 将是 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 << ' ';

边注:

在这一点上,您以前可能遇到过迭代器,也可能没有,您可能听说过或可能没有听说过迭代器是您“应该”使用的,并且可能想知道为什么。答案并不容易理解,但简而言之,这个想法是迭代器是一种抽象,可以保护您免受操作细节的影响。

拥有一个执行您想要的操作(如顺序访问)的对象(迭代器)而不是您自己编写详细信息(“详细信息”是实际访问向量元素的代码)是很方便的。您应该注意到,在 for 循环中,您只要求迭代器返回一个值( *i ,其中 i 是迭代器) - 您永远不会与 path 直接自己。逻辑是这样的:你创建一个迭代器并给它你想要循环的对象( iterator i = path.begin() ),然后你所做的就是让迭代器为你获取下一个值( *i );您永远不必担心迭代器是如何做到这一点的——这是它的业务,而不是您的业务。

好的,但有什么意义呢?好吧,想象一下,如果获得价值并不简单。如果它涉及一些工作怎么办?你不必担心,因为迭代器已经为你处理了——它整理了细节,你需要做的就是向它询问一个值。此外,如果您将容器从 std::vector 更改为其他内容怎么办?从理论上讲,即使访问新容器中的元素的细节发生了变化,您的代码也不会改变:请记住,迭代器会在幕后为您整理所有细节,因此您根本不需要更改代码-- 你只需向迭代器询问容器中的下一个值,和以前一样。

因此,虽然这看起来像是对循环向量的混淆过度杀伤,但迭代器的概念背后有充分的理由,因此您不妨习惯使用它们。

索引

您还可以使用整数类型在 for 循环中显式地索引向量的元素:

 for (int i=0; i<path.size(); ++i)
    std::cout << path[i] << ' ';

如果您打算这样做,最好使用容器的成员类型,如果它们可用且合适的话。 std::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] << ' ';

为什么不优先使用 iterator 解决方案?对于简单的情况,您可以这样做,但使用 iterator 会带来几个优势,我在上面已经简要概述了这些优势。因此,我的建议是避免这种方法,除非你有充分的理由。

标准::复制 (C++11)

约书亚的回答。您可以使用 STL 算法 std::copy 将向量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有什么好的理由。

std::ranges::copy (C++20)

为了完整起见,C++20 引入了范围,它可以作用于 std::vector 的整个范围,因此不需要 beginend

 #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::operator<<

另请参阅 下面 Chris 的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上述解决方案之一,但好处是代码更简洁。这就是您可以使用 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 对象传递给您的输出流。使用上述任何其他解决方案也应该同样简单。

结论

此处介绍的任何解决方案都可以使用。哪个是“最好的”取决于您(以及上下文或您的编码标准)。任何比这更详细的东西可能最好留给另一个可以正确评估优点/缺点的问题,但一如既往,用户偏好总是会发挥作用:所提出的解决方案都不是客观错误的,但有些解决方案对每个编码人员来说看起来更好.

附录

这是我发布的早期解决方案的扩展解决方案。由于该帖子一直受到关注,因此我决定对其进行扩展并参考此处发布的其他出色解决方案,至少是那些我过去至少亲自使用过一次的解决方案。但是,我鼓励读者查看下面的答案,因为可能有一些我已经忘记或不知道的好建议。

原文由 Zorawar 发布,翻译遵循 CC BY-SA 4.0 许可协议

# include <iostream>
# include <vector>

template <typename T>
std::ostream& operator<<( std::ostream& os, const std::vector<T>& v ){
    for( int j = 0; j < v.size(); j++ ){
        os << "[,"[ j > 0] << v[ j];
    }
    return os << "]";
}

原文由 baz 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题