ChatGPT解决这个技术问题 Extra ChatGPT

如何打印出向量的内容?

如何将 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;
    }
}
对于信息,我发现“简洁”地做到这一点的唯一方法是黑客-->在 std 命名空间中添加 operator<< 的重载(以便它们被 ADL 拾取)并将调用转发到通用打印范围方法...我对这次讨论的结果非常感兴趣,感谢您的提问: )
如果你有异构类型,你可以混合 stl 容器和元组。使用 boost.fusion io 和漂亮的打印。 cout << vector<tuple<int,array<int,3>>>(...) << endl;

Z
Zorawar

如果您有 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 中的项目,以使这些更改在 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 循环中修改向量的内容,请使用 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)

请参阅Joshua's answer。您可以使用 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 at least version 10.1 上),否则即使您可能有一些可用的 C++20 功能,您也可能不会获得范围支持。

重载 std::ostream::operator<<

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

结论

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

附录

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


如果您从 0 循环到 vector::size() 并且向量未在循环内修改,则无需使用 at() 并产生额外的边界检查开销。也就是说,我会按照您的建议使用迭代器。
@Ed:是的,如果循环中没有任何内容修改向量,则使用 at 是没有意义的,但我想我会提到它以防万一向量 在循环中被修改(不推荐为那可能是)并且因为它从未被提及,并且至少知道它可能是有用的。
可以重写基于范围的 for 循环以使用引用,这在大型子对象的情况下可能很重要,如下所示:for (auto const &i: path) std::cout << i << ' ';
@underscore_d:谢谢。我已经清理了该部分,我希望它现在更完整,更清晰一些。
“重载运算符<<”不是一个好的解决方案;由于依赖于参数的查找,重载运算符的至少一个操作数应该是您的程序定义的类
L
Lightness Races in Orbit

更简单的方法是使用标准 copy algorithm

#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>

int main() {
    /* Set up vector to hold chars a-z */
    std::vector<char> path;
    for (int ch = 'a'; ch <= 'z'; ++ch)
        path.push_back(ch);

    /* Print path vector to console */
    std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));

    return 0;
}

ostream_iterator 就是所谓的迭代器适配器。它被模板化为要打印到流的类型(在本例中为 char)。 cout(又名控制台输出)是我们要写入的流,空格字符 (" ") 是我们要在存储在向量中的每个元素之间打印的内容。

这个标准算法很强大,许多其他算法也是如此。标准库为您提供的功能和灵活性是它如此出色的原因。想象一下:只需一行代码,您就可以将向量打印到控制台。您不必处理带有分隔符的特殊情况。您无需担心 for 循环。标准库为您完成所有工作。


如果我的向量是 vector<pair<int, struct node>> 类型怎么办。如何使用上述方法打印此矢量?
分隔符字符串写在每个元素之后,而不是在元素之间,即也在最后一个元素之后。如果您只希望它介于两者之间,即作为分隔符,则可能需要处理特殊情况。
@mtk,您可以为您的特定配对声明一个 operator<< 函数<>。
添加了 an answer showing a similar approach,但考虑了上面关于额外尾随分隔符的@Quigi:s 评论。
@ShoeLace没有别的办法吗?
S
Sven

此解决方案的灵感来自 Marcelo 的解决方案,但做了一些更改:

#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>

// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
    typedef TChar char_type;
    typedef TCharTraits traits_type;
    typedef std::basic_ostream<TChar, TCharTraits> ostream_type;

    pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
        : _stream(&stream), _delim(delim), _insertDelim(false)
    {
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
    {
        if( _delim != NULL )
        {
            // Don't insert a delimiter if this is the first time the function is called
            if( _insertDelim )
                (*_stream) << _delim;
            else
                _insertDelim = true;
        }
        (*_stream) << value;
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
    {
        return *this;
    }
private:
    ostream_type *_stream;
    const char_type *_delim;
    bool _insertDelim;
};

#if _MSC_VER >= 1400

// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};

#endif // _MSC_VER >= 1400

namespace std
{
    // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
    // These aren't necessary if you do actually include the headers.
    template<typename T, typename TAllocator> class vector;
    template<typename T, typename TAllocator> class list;
    template<typename T, typename TTraits, typename TAllocator> class set;
    template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}

// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };

// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };

// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };

// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };

// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };

// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
    typedef TChar char_type;
    const TChar *prefix;
    const TChar *delimiter;
    const TChar *postfix;
};

// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
    static const delimiters_values<TChar> values; 
};

// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" };

// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" };

// Delimiters for pair
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };

// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
    typedef TChar char_type;
    typedef TDelimiters delimiters_type;
    typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;

    print_container_helper(const T &container)
        : _container(&container)
    {
    }

    void operator()(ostream_type &stream) const
    {
        if( delimiters_type::values.prefix != NULL )
            stream << delimiters_type::values.prefix;
        std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
        if( delimiters_type::values.postfix != NULL )
            stream << delimiters_type::values.postfix;
    }
private:
    const T *_container;
};

// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
    helper(stream);
    return stream;
}

// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
    operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
    stream << print_container_helper<T, TChar, TCharTraits>(container);
    return stream;
}

// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
    if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;

    stream << value.first;

    if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;

    stream << value.second;

    if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
    return stream;    
}

// Used by the sample below to generate some values
struct fibonacci
{
    fibonacci() : f1(0), f2(1) { }
    int operator()()
    {
        int r = f1 + f2;
        f1 = f2;
        f2 = r;
        return f1;
    }
private:
    int f1;
    int f2;
};

int main()
{
    std::vector<int> v;
    std::generate_n(std::back_inserter(v), 10, fibonacci());

    std::cout << v << std::endl;

    // Example of using pretty_ostream_iterator directly
    std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
    std::cout << std::endl;
}

与 Marcelo 的版本一样,它使用 is_container 类型特征,该特征必须专门用于要支持的所有容器。可以使用 trait 来检查 value_typeconst_iteratorbegin()/end(),但我不确定我是否建议这样做,因为它可能匹配符合这些条件但不匹配的内容t 实际上是容器,例如 std::basic_string。也像 Marcelo 的版本一样,它使用可以专门指定要使用的分隔符的模板。

主要区别在于我围绕 pretty_ostream_iterator 构建了我的版本,它的工作方式类似于 std::ostream_iterator,但不会在最后一项之后打印分隔符。格式化容器由 print_container_helper 完成,它可以直接用于打印没有 is_container 特征的容器,或指定不同的分隔符类型。

我还定义了 is_container 和分隔符,因此它适用于具有非标准谓词或分配器的容器,以及 char 和 wchar_t。 operator<< 函数本身也被定义为与 char 和 wchar_t 流一起使用。

最后,我使用了 std::enable_if,它作为 C++0x 的一部分提供,适用于 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)及更高版本。这样就没有对 Boost 的依赖。


如果我没看错的话,为了在一个函数中将一对打印为 <i, j> 并在另一个函数中打印为 [i j],您必须定义一个全新的类型,其中包含一些静态成员才能传递它键入 print_container_helper?这似乎过于复杂。为什么不使用实际对象,您可以根据具体情况设置字段,而专业化只是提供不同的默认值?
这样看:如果有一堆你个人喜欢的分隔符,你可以一劳永逸地创建几个带有静态成员的类,然后就使用它们。当然,您是对的,使用 print_container_helper 不如仅使用 operator<< 优雅。当然,您可以随时更改源代码,或者只是为您喜欢的容器添加明确的特化,例如为 pair<int, int>pair<double, string>。归根结底,这是一个权衡权力与便利性的问题。欢迎提出改进建议!
...并跟进这一点,如果您已经需要以不同格式对相同数据类型进行情境打印,那么您可能无论如何都必须编写至少一个小型包装器。这不是一个高度可配置的格式化库,而是一个零努力的明智默认库,它神奇地让您无需思考即可打印容器......(但如果您想要更多的全局灵活性,我们可能会添加一些 #macros 来制作默认值易于操作。)
真正的问题是,虽然我可以轻松地修改 print_container_helper 以使用自定义分隔符的参数,但除了专门指定分隔符模板之外,实际上没有任何方法可以为内部容器(或对)指定分隔符。实现这一点将非常复杂。
我几乎设法使用类型擦除来实现方便的自定义分隔符解决方案。如果您已经有一个分隔符类 MyDels,那么我可以说 std::cout << CustomPrinter<MyDels>(x);。我现在不能做的是说 std::cout << CustomDelims<"{", ":", "}">(x);,因为你不能有 const char * 模板参数。使定界符成为编译时常量的决定对那里的易用性造成了一些限制,但我认为这是值得的。
S
Shoe

在 C++11 中,您现在可以使用 range-based for loop

for (auto const& c : path)
    std::cout << c << ' ';

只有当向量的大小在循环范围的主体中没有改变时,这才有效。
@BrianP。是的。打印容器的元素不会修改容器的范围。
这里有什么可取的 - c 作为值副本或作为 const 引用以避免复制元素?
@kleinfreund 这取决于向量的内容。例如,对于 char 的向量,通过常量引用传递实际上比通过值传递的成本更高。但在这里我们谈论的是超微优化。
v
vitaut

您可以使用 the {fmt} library 打印容器以及范围和元组。例如:

#include <vector>
#include <fmt/ranges.h>

int main() {
  auto v = std::vector<int>{1, 2, 3};
  fmt::print("{}", v);
}

印刷

[1, 2, 3]

stdout (godbolt)。

我不建议为标准容器重载 operator<<,因为它可能会导致 ODR 违规。

免责声明:我是 {fmt} 的作者。


std::map 呢?我在文档中找不到任何内容
支持所有容器的格式化。
你能给我一个起点吗?我很难找到以 fmtlib print std::map 作为搜索词的 fmtlib 的用法。如果这算作菜鸟问题或类似 RTFM 的问题,我深表歉意 :)
以下是地图示例:godbolt.org/z/EG7aoE。如您所见,用法没有区别。
天啊!这太棒了godbolt.org/z/h7qxba
L
Leponzo

我认为最好的方法是通过将此函数添加到您的程序中来重载 operator<<

#include <vector>
using std::vector;
#include <iostream>
using std::ostream;

template<typename T>
ostream& operator<< (ostream& out, const vector<T>& v) {
    out << "{";
    size_t last = v.size() - 1;
    for(size_t i = 0; i < v.size(); ++i) {
        out << v[i];
        if (i != last) 
            out << ", ";
    }
    out << "}";
    return out;
}

然后您可以在任何可能的向量上使用 << 运算符,假设它的元素也定义了 ostream& operator<<

vector<string>  s = {"first", "second", "third"};
vector<bool>    b = {true, false, true, false, false};
vector<int>     i = {1, 2, 3, 4};
cout << s << endl;
cout << b << endl;
cout << i << endl;

输出:

{first, second, third}
{1, 0, 1, 0, 0}
{1, 2, 3, 4}

将 v.size() - 1 存储为 int 可能会损失精度。我在已接受的同行评审编辑 (stackoverflow.com/revisions/23397700/5) 中修复了此问题,但此后再次对其进行了编辑,恢复了可能的精度损失。我想这在实践中并不重要,因为向量通常不是那么大。
不将其存储为变量会降低代码的可读性,这是我不同意的编辑的一部分。我已将 last 的类型更改为 size_t
size_t last = v.size() - 1; 看起来多余,您可以在 out << v[i]; link 之前使用 if (i) out << ", "; 条件
ADL 找不到此运算符,因为它不在其任何参数的命名空间中。因此它将被任何其他命名空间的 operator<< 隐藏。 Example
如果您要这样做,为什么要在每次 in 循环时测试 if (i != last) ?相反,如果容器不为空,则 (a) 发送第一个元素,然后 (b) 循环发送 remaining 元素,打印分隔符 first (如前缀)。不需要内部循环测试(除了循环条件本身)。只需要一次外环测试。
c
cigien

这已经被编辑了几次,我们决定调用包装集合 RangePrinter 的主类。

一旦您编写了一次性 operator<< 重载,这应该会自动适用于任何集合,除非您需要一个特殊的映射来打印该对,并且可能希望在那里自定义分隔符。

您还可以在项目上使用特殊的“打印”函数,而不是直接输出它,有点像 STL 算法允许您传入自定义谓词。对于 map,您可以这样使用它,并为 std::pair 使用自定义打印机。

您的“默认”打印机只会将其输出到流中。

好的,让我们使用自定义打印机。我将把我的外部类更改为 RangePrinter。所以我们有 2 个迭代器和一些分隔符,但没有自定义如何打印实际项目。

struct DefaultPrinter
{
   template< typename T >
   std::ostream & operator()( std::ostream& os, const T& t ) const
   {
     return os << t;
   }

   // overload for std::pair
   template< typename K, typename V >
   std::ostream & operator()( std::ostream & os, std::pair<K,V> const& p)
   {
      return os << p.first << '=' << p.second;
   }
};

// some prototypes
template< typename FwdIter, typename Printer > class RangePrinter;

template< typename FwdIter, typename Printer > 
  std::ostream & operator<<( std::ostream &, 
        RangePrinter<FwdIter, Printer> const& );

template< typename FwdIter, typename Printer=DefaultPrinter >
class RangePrinter
{
    FwdIter begin;
    FwdIter end;
    std::string delim;
    std::string open;
    std::string close;
    Printer printer;

    friend std::ostream& operator<< <>( std::ostream&, 
         RangePrinter<FwdIter,Printer> const& );

public:
    RangePrinter( FwdIter b, FwdIter e, Printer p,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), printer( p ), open( o ), close( c )
    {
    } 

     // with no "printer" variable
    RangePrinter( FwdIter b, FwdIter e,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), open( o ), close( c )
    {
    } 

};


template<typename FwdIter, typename Printer>
std::ostream& operator<<( std::ostream& os, 
          RangePrinter<FwdIter, Printer> const& range )
{
    const Printer & printer = range.printer;

    os << range.open;
    FwdIter begin = range.begin, end = range.end;

    // print the first item
    if (begin == end) 
    { 
      return os << range.close; 
    }

    printer( os, *begin );

    // print the rest with delim as a prefix
    for( ++begin; begin != end; ++begin )
    {
       os << range.delim;
       printer( os, *begin );
    }
    return os << range.close;
}

现在默认情况下,只要键和值类型都是可打印的,并且您可以放入自己的特殊项目打印机(与任何其他类型一样),或者如果您不想要,则默认情况下它适用于地图“=”作为分隔符。

我现在将自由功能移动到最后:

一个自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:

template<typename Collection>
RangePrinter<typename Collection::const_iterator> rangePrinter
    ( const Collection& coll, const char * delim=",", 
       const char * open="[", const char * close="]")
{
   return RangePrinter< typename Collection::const_iterator >
     ( coll.begin(), coll.end(), delim, open, close );
}

然后,您可以将其用于 std::set

 std::cout << outputFormatter( mySet );

您还可以编写带有自定义打印机和带有两个迭代器的自由功能版本。在任何情况下,它们都会为您解析模板参数,并且您将能够将它们作为临时对象传递。


我懂了。这和马塞洛·坎托斯的想法很像,不是吗?我将尝试将其变成一个工作示例,谢谢!
我发现这个解决方案比 Marcelo 的解决方案干净得多,而且它提供了同样的灵活性。我喜欢必须将输出显式包装到函数调用中的方面。更酷的是,您可以添加对直接输出一系列迭代器的支持,这样我就可以做到 std::cout << outputFormatter(beginOfRange, endOfRange);
@CashCow:这个解决方案有一个问题,它似乎不适用于递归集合(即集合的集合)。 std::pair 是“内部集合”的最基本示例。
我非常喜欢这个答案,因为它没有依赖关系,也不需要知道它支持的容器。我们能否确定它是否可以轻松处理std::map,以及它是否适用于集合的集合?不过,我很想接受这个作为答案。我希望马塞洛不介意,他的解决方案也有效。
@Matthieu M。这取决于您如何打印内部集合。如果您只使用 os << open << *iter << close 那么您会遇到问题,但是如果您允许您的用户按照我的建议传入自定义打印机,那么您可以打印任何您喜欢的东西。
c
cigien

for_each + lambda 表达式怎么样:

#include <vector>
#include <algorithm>
// ...
std::vector<char> vec;
// ...
std::for_each(
              vec.cbegin(),
              vec.cend(),
              [] (const char c) {std::cout << c << " ";} 
              );
// ...

当然,基于范围的 for 是这个具体任务的最优雅的解决方案,但这个解决方案也提供了许多其他可能性。

解释

for_each 算法采用 输入范围可调用对象,在范围的每个元素上调用此对象。 输入范围由两个迭代器定义。 可调用对象可以是函数、指向函数的指针、重载 () operator 的类的对象,或者在本例中为 lambda 表达式。此表达式的参数与 vector 中元素的类型相匹配。

这种实现的美妙之处在于您从 lambda 表达式中获得的强大功能——您可以将这种方法用于更多的事情,而不仅仅是打印向量。


M
Marcelo Cantos

这是一个工作库,作为一个完整的工作程序呈现,我刚刚一起破解:

#include <set>
#include <vector>
#include <iostream>

#include <boost/utility/enable_if.hpp>

// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.                                                                                                             
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};

template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T>    > { enum { value = true }; };

template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
  o << Delims<C>::delim[0];
  for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
    {
      if (i != x.begin()) o << Delims<C>::delim[1];
      o << *i;
    }
  o << Delims<C>::delim[2];
  return o;
}

template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };

template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
  o << "[";
  for (int i = 0; i != N; ++i)
    {
      if (i) o << ",";
      o << x[i];
    }
  o << "]";
  return o;
}

int main()
{
  std::vector<int> i;
  i.push_back(23);
  i.push_back(34);

  std::set<std::string> j;
  j.insert("hello");
  j.insert("world");

  double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };

  std::cout << i << "\n" << j << "\n" << k << "\n";
}

它目前仅适用于 vectorset,但可以通过扩展 IsContainer 特化使其适用于大多数容器。我没有过多考虑这段代码是否是最小的,但我无法立即想到我可以删除的任何多余的东西。

编辑: 只是为了好玩,我包含了一个处理数组的版本。我不得不排除 char 数组以避免进一步的歧义; wchar_t[] 可能仍会出现问题。


@Nawaz:正如我所说,这只是解决方案的开始。您可以通过专门化运算符或为 std::pair<> 定义 operator<< 来支持 std::map<>
但是,使用 Delims 类模板 +1!
@MC:哦,很好。这看起来很有希望! (顺便说一句,你需要返回类型“std::ostream &”,我一开始忘记了。)
嗯,在 std::vector 和 std::set 上尝试此操作时,我得到“模棱两可的重载”...
是的,我目前正在研究如何防止歧义,这是由于 operator<< 模板几乎匹配任何内容而引起的。
Y
Yoon5oo

该代码现在多次被证明很方便,我觉得进行定制的费用很低,因为使用率很低。因此,我决定在 MIT 许可下发布它,并提供一个 GitHub 存储库,可以在其中下载标头和一个小示例文件。

http://djmuw.github.io/prettycc

0. 前言和措辞

就这个答案而言,“装饰”是一组前缀字符串、分隔符字符串和后缀字符串。前缀字符串插入到流之前,后缀字符串插入到容器的值之后(请参阅 2. 目标容器)。分隔符字符串插入到相应容器的值之间。

注意:实际上,这个答案并没有解决 100% 的问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。不过,我认为它有一些不错的功能。

注意2:可能有一些小错误,因为它还没有经过很好的测试。

1. 总体思路/用法

使用所需的零附加代码

保持简单

#include <vector>
#include "pretty.h"

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
  return 0;
}

轻松定制...

...关于特定的流对象

#include <vector>
#include "pretty.h"

int main()
{
  // set decoration for std::vector<int> for cout object
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

或关于所有流:

#include <vector>
#include "pretty.h"

// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

粗略的描述

代码包含一个类模板,为任何类型提供默认装饰

它可以专门用于更改(a)某些类型的默认装饰,它是

使用 ios_base 提供的私有存储,使用 xalloc/pword 来保存指向 pretty::decor 对象的指针,该对象专门装饰特定流上的特定类型。

如果没有明确设置此流的 pretty::decor<T> 对象,则调用 pretty::defaulted<T, charT, chartraitT>::decoration() 来获取给定类型的默认装饰。 pretty::defaulted 类将专门用于定制默认装饰。

2.目标对象/容器

此代码的“漂亮装饰”的目标对象 obj 是具有以下任一属性的对象

重载 std::begin 和 std::end 定义(包括 C 样式数组),

通过 ADL 获得 begin(obj) 和 end(obj),

是 std::tuple 类型

或 std::pair 类型。

该代码包含一个特征,用于识别具有范围特征的类 (begin/end)。 (不过,没有检查 begin(obj) == end(obj) 是否为有效表达式。)

该代码在全局命名空间中提供了 operator<<,这些 operator<< 仅适用于没有更专业版本的 operator<< 可用的类。因此,例如 std::string 没有使用此代码中的运算符打印,尽管具有有效的 begin/end 对。

3.利用和定制

可以为每种类型(不同的 tuple 除外)和流(不是流类型!)单独施加装饰。 (即 std::vector<int> 可以对不同的流对象有不同的修饰。)

A) 默认装饰

默认前缀是 ""(无)和默认后缀一样,而默认分隔符是 ", "(逗号+空格)。

B) 通过专门化 pretty::defaulted 类模板来自定义类型的默认装饰

struct defaulted 有一个静态成员函数 decoration(),它返回一个 decor 对象,其中包括给定类型的默认值。

使用数组的示例:

自定义默认数组打印:

namespace pretty
{
  template<class T, std::size_t N>
  struct defaulted<T[N]>
  {
    static decor<T[N]> decoration()
    {
      return{ { "(" }, { ":" }, { ")" } };
    }
  };
}

打印一个数组:

float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)

对字符流使用 PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) 宏

宏扩展为

namespace pretty { 
  template< __VA_ARGS__ >
  struct defaulted< TYPE > {
    static decor< TYPE > decoration() {
      return { PREFIX, DELIM, POSTFIX };
    } 
  }; 
} 

使上述部分专业化重写为

PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)

或插入一个完整的专业,如

PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")

包含 wchar_t 流的另一个宏:PRETTY_DEFAULT_WDECORATION

C) 对溪流进行装饰

函数 pretty::decoration 用于对某个流进行装饰。有重载采用 - 一个字符串参数作为分隔符(采用默认类的前缀和后缀) - 或三个字符串参数组装完整的装饰

给定类型和流的完整装饰

float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");

// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}

// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2

为给定流自定义分隔符

PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")

std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}

v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}

v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))

4、std::tuple的特殊处理

此代码不允许对每种可能的元组类型进行专门化,而是将可用于 std::tuple<void*> 的任何修饰应用于所有类型的 std::tuple<...>

5.从流中删除自定义装饰

要返回给定类型的默认装饰,请使用流 s 上的 pretty::clear 函数模板。

s << pretty::clear<std::vector<int>>();

5. 进一步的例子

使用换行符打印“类似矩阵”

std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
std::cout << pretty::decoration<std::vector<std::vector<int>>>("\n");
std::cout << m;

印刷

1, 2, 3
4, 5, 6
7, 8, 9

在 ideone/KKUebZ 上查看

6. 代码

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

#include <string>
#include <iostream>
#include <type_traits>
#include <iterator>
#include <utility>

#define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE > {\
    static decor< TYPE > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

#define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE, wchar_t, std::char_traits<wchar_t> > {\
    static decor< TYPE, wchar_t, std::char_traits<wchar_t> > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

namespace pretty
{

  namespace detail
  {
    // drag in begin and end overloads
    using std::begin;
    using std::end;
    // helper template
    template <int I> using _ol = std::integral_constant<int, I>*;
    // SFINAE check whether T is a range with begin/end
    template<class T>
    class is_range
    {
      // helper function declarations using expression sfinae
      template <class U, _ol<0> = nullptr>
      static std::false_type b(...);
      template <class U, _ol<1> = nullptr>
      static auto b(U &v) -> decltype(begin(v), std::true_type());
      template <class U, _ol<0> = nullptr>
      static std::false_type e(...);
      template <class U, _ol<1> = nullptr>
      static auto e(U &v) -> decltype(end(v), std::true_type());
      // return types
      using b_return = decltype(b<T>(std::declval<T&>()));
      using e_return = decltype(e<T>(std::declval<T&>()));
    public:
      static const bool value = b_return::value && e_return::value;
    };
  }

  // holder class for data
  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct decor
  {
    static const int xindex;
    std::basic_string<CharT, TraitT> prefix, delimiter, postfix;
    decor(std::basic_string<CharT, TraitT> const & pre = "",
      std::basic_string<CharT, TraitT> const & delim = "",
      std::basic_string<CharT, TraitT> const & post = "")
      : prefix(pre), delimiter(delim), postfix(post) {}
  };

  template<class T, class charT, class traits>
  int const decor<T, charT, traits>::xindex = std::ios_base::xalloc();

  namespace detail
  {

    template<class T, class CharT, class TraitT>
    void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
    {
      using deco_type = decor<T, CharT, TraitT>;
      if (evt == std::ios_base::erase_event)
      { // erase deco
        void const * const p = s.pword(idx);
        if (p)
        {
          delete static_cast<deco_type const * const>(p);
          s.pword(idx) = nullptr;
        }
      }
      else if (evt == std::ios_base::copyfmt_event)
      { // copy deco
        void const * const p = s.pword(idx);
        if (p)
        {
          auto np = new deco_type{ *static_cast<deco_type const * const>(p) };
          s.pword(idx) = static_cast<void*>(np);
        }
      }
    }

    template<class T> struct clearer {};

    template<class T, class CharT, class TraitT>
    std::basic_ostream<CharT, TraitT>& operator<< (
      std::basic_ostream<CharT, TraitT> &s, clearer<T> const &)
    {
      using deco_type = decor<T, CharT, TraitT>;
      void const * const p = s.pword(deco_type::xindex);
      if (p)
      { // delete if set
        delete static_cast<deco_type const *>(p);
        s.pword(deco_type::xindex) = nullptr;
      }
      return s;
    }

    template <class CharT> 
    struct default_data { static const CharT * decor[3]; };
    template <> 
    const char * default_data<char>::decor[3] = { "", ", ", "" };
    template <> 
    const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" };

  }

  // Clear decoration for T
  template<class T>
  detail::clearer<T> clear() { return{}; }
  template<class T, class CharT, class TraitT>
  void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; }

  // impose decoration on ostream
  template<class T, class CharT, class TraitT>
  std::basic_ostream<CharT, TraitT>& operator<<(
    std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h)
  {
    using deco_type = decor<T, CharT, TraitT>;
    void const * const p = s.pword(deco_type::xindex);
    // delete if already set
    if (p) delete static_cast<deco_type const *>(p);
    s.pword(deco_type::xindex) = static_cast<void *>(new deco_type{ std::move(h) });
    // check whether we alread have a callback registered
    if (s.iword(deco_type::xindex) == 0)
    { // if this is not the case register callback and set iword
      s.register_callback(detail::manage_decor<T, CharT, TraitT>, deco_type::xindex);
      s.iword(deco_type::xindex) = 1;
    }
    return s;
  }

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct defaulted
  {
    static inline decor<T, CharT, TraitT> decoration()
    {
      return{ detail::default_data<CharT>::decor[0],
        detail::default_data<CharT>::decor[1],
        detail::default_data<CharT>::decor[2] };
    }
  };

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  decor<T, CharT, TraitT> decoration(
    std::basic_string<CharT, TraitT> const & prefix,
    std::basic_string<CharT, TraitT> const & delimiter,
    std::basic_string<CharT, TraitT> const & postfix)
  {
    return{ prefix, delimiter, postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(
      std::basic_string<CharT, TraitT> const & delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      delimiter, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const prefix,
      CharT const * const delimiter, CharT const * const postfix)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<typename T, std::size_t N, std::size_t L>
  struct tuple
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &delimiter)
    {
      s << std::get<N>(value) << delimiter;
      tuple<T, N + 1, L>::print(s, value, delimiter);
    }
  };

  template<typename T, std::size_t N>
  struct tuple<T, N, N>
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &) {
      s << std::get<N>(value);
    }
  };

}

template<class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class CharT, class TraitT, class ... T>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  pretty_tuple::print(s, v, d ? d->delimiter : 
    defaulted_type::decoration().delimiter);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class T, class U, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v)
{
  using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << v.first;
  s << (d ? d->delimiter : defaulted_type::decoration().delimiter);
  s << v.second;
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
  typename std::enable_if < pretty::detail::is_range<T>::value,
  std::basic_ostream < CharT, TraitT >> ::type & operator<< (
    std::basic_ostream<CharT, TraitT> &s, T const & v)
{
  bool first(true);
  using deco_type = pretty::decor<T, CharT, TraitT>;
  using default_type = pretty::defaulted<T, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto d = static_cast<pretty::decor<T, CharT, TraitT> const * const>(p);
  s << (d ? d->prefix : default_type::decoration().prefix);
  for (auto const & e : v)
  { // v is range thus range based for works
    if (!first) s << (d ? d->delimiter : default_type::decoration().delimiter);
    s << e;
    first = false;
  }
  s << (d ? d->postfix : default_type::decoration().postfix);
  return s;
}

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_

J
JeJo

只需将容器复制到控制台即可。

std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));

应该输出:

1 2 3 4

d
dfrib

使用 std::copy 但没有额外的尾随分隔符

使用 std::copy 的替代/修改方法(最初在 @JoshuaKravtiz answer 中使用)但在最后一个元素之后不包括额外的尾随分隔符:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template <typename T>
void print_contents(const std::vector<T>& v, const char * const separator = " ")
{
    if(!v.empty())
    {
        std::copy(v.begin(),
                  --v.end(),
                  std::ostream_iterator<T>(std::cout, separator));
        std::cout << v.back() << "\n";
    }
}

// example usage
int main() {
    std::vector<int> v{1, 2, 3, 4};
    print_contents(v);      // '1 2 3 4'
    print_contents(v, ":"); // '1:2:3:4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // '1'
    return 0;
}

应用于自定义 POD 类型容器的示例用法:

// includes and 'print_contents(...)' as above ...

class Foo
{
    int i;
    friend std::ostream& operator<<(std::ostream& out, const Foo& obj);
public:
    Foo(const int i) : i(i) {}
};

std::ostream& operator<<(std::ostream& out, const Foo& obj)
{
    return out << "foo_" << obj.i; 
}

int main() {
    std::vector<Foo> v{1, 2, 3, 4};
    print_contents(v);      // 'foo_1 foo_2 foo_3 foo_4'
    print_contents(v, ":"); // 'foo_1:foo_2:foo_3:foo_4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // 'foo_1'
    return 0;
}

c
cigien

问题可能出在上一个循环中:

(x = 17; isalpha(firstsquare); x++)

此循环将根本不运行(如果 firstsquare 是非字母)或将永远运行(如果它是字母)。原因是 firstsquare 不会随着 x 的增加而改变。


V
Vadim Kotov

在 C++11 中,基于范围的 for 循环可能是一个很好的解决方案:

vector<char> items = {'a','b','c'};
for (char n : items)
    cout << n << ' ';

输出:

a b c 

i
ivanmara

重载运算符<<:

template<typename OutStream, typename T>
OutStream& operator<< (OutStream& out, const vector<T>& v)
{
    for (auto const& tmp : v)
        out << tmp << " ";
    out << endl;
    return out;
}

用法:

vector <int> test {1,2,3};
wcout << test; // or any output stream

C
CashCow

我将在这里添加另一个答案,因为我想出了与以前的方法不同的方法,那就是使用语言环境方面。

基础是 here

基本上你所做的是:

创建一个派生自 std::locale::facet 的类。轻微的缺点是你需要一个编译单元来保存它的 id。我们称它为 MyPrettyVectorPrinter。您可能会给它一个更好的名称,并为 pair 和 map 创建名称。在您的流函数中,您检查 std::has_facet< MyPrettyVectorPrinter > 如果返回 true,请使用 std::use_facet< MyPrettyVectorPrinter >( os.getloc() ) 提取它 您的构面对象将具有分隔符的值,您可以读取它们.如果未找到该构面,则您的打印函数 (operator<<) 会提供默认值。请注意,您可以执行相同的操作来读取矢量。

我喜欢这种方法,因为您可以使用默认打印,同时仍然可以使用自定义覆盖。

缺点是如果在多个项目中使用你的方面需要一个库(所以不能只是标题),而且你需要注意创建新语言环境对象的费用。

我把它写成一个新的解决方案,而不是修改我的另一个,因为我相信这两种方法都是正确的,你可以选择。


让我直截了当地说:使用这种方法,我是否需要主动将我想使用的每种容器类型列入白名单?
好吧,除了自己的类型之外,真的不应该扩展 std,但是您为每个容器类型(向量、映射、列表、双端队列)加上您希望能够打印的对编写 operator<< 的重载。当然有些可能共享一个方面(例如,您可能希望打印列表、向量和双端队列)。您提供“默认”打印方法,但允许用户在打印之前创建构面和区域设置并灌输。有点像 boost 打印他们的 date_time 的方式。也可以将他们的方面加载到全局语言环境中,以默认方式打印。
c
cigien

此答案基于 answer from Zorawar,但我无法在此处发表评论。

您可以改用 cbegincend 来制作 auto (C++11)/typedef 版本 const

for (auto i = path.cbegin(); i != path.cend(); ++i)
    std::cout << *i << ' ';

c
cigien

我看到两个问题。正如在

for (x = 17; isalpha(firstsquare); x++)

要么存在无限循环,要么根本不执行,并且在 if (entrance == 'S') 中,如果入口字符与 'S' 不同,则不会将任何内容推送到路径向量,使其为空,因此不会在屏幕上打印任何内容。您可以测试后者检查 path.empty() 或打印 path.size()

无论哪种方式,使用字符串而不是向量不是更好吗?您也可以像访问数组一样访问字符串内容、查找字符、提取子字符串并轻松打印字符串(无需循环)。

用字符串来做这一切可能是以一种不那么复杂的方式编写它并且更容易发现问题的方法。


Y
Yakk - Adam Nevraumont

这里的目标是使用 ADL 来定制我们漂亮的打印方式。

您传入一个格式化程序标签,并覆盖标签命名空间中的 4 个函数(之前、之后、之间和下降)。这会改变格式化程序在迭代容器时打印“装饰”的方式。

一个默认的格式化程序,对地图执行 {(a->b),(c->d)},对元组执行 (a,b,c),对字符串执行 "hello",对其他所有内容执行 [x,y,z]

它应该与 3rd 方可迭代类型“正常工作”(并将它们视为“其他一切”)。

如果您想要为您的 3rd 方迭代定制装饰,只需创建您自己的标签。处理地图下降需要一些工作(您需要重载 pretty_print_descend( your_tag 以返回 pretty_print::decorator::map_magic_tag<your_tag>)。也许有一种更清洁的方法可以做到这一点,不确定。

一个检测可迭代性和元组的小库:

namespace details {
  using std::begin; using std::end;
  template<class T, class=void>
  struct is_iterable_test:std::false_type{};
  template<class T>
  struct is_iterable_test<T,
    decltype((void)(
      (void)(begin(std::declval<T>())==end(std::declval<T>()))
      , ((void)(std::next(begin(std::declval<T>()))))
      , ((void)(*begin(std::declval<T>())))
      , 1
    ))
  >:std::true_type{};
  template<class T>struct is_tupleoid:std::false_type{};
  template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
  template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
  // template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};

template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};

一个允许我们访问可迭代或元组类型对象内容的库:

template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto&& b = begin(c);
  auto&& e = end(c);
  if (b==e)
      return;
  std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto it = begin(c);
  auto&& e = end(c);
  if (it==e)
      return;
  it = std::next(it);
  for( ; it!=e; it = std::next(it) ) {
    f(*it);
  }
}

namespace details {
  template<class Tup, class F>
  void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is, class Tup, class F>
  void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
  }
  template<class Tup, class F>
  void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is,class Tup, class F>
  void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    int unused[] = {0,((void)(
      f( std::get<Is>(std::forward<Tup>(tup)) )
    ),0)...};
    (void)(unused);
  }
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
  details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
  details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}

一个漂亮的打印库:

namespace pretty_print {
  namespace decorator {
    struct default_tag {};
    template<class Old>
    struct map_magic_tag:Old {}; // magic for maps

    // Maps get {}s. Write trait `is_associative` to generalize:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('{');
    }

    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('}');
    }

    // tuples and pairs get ():
    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT('(');
    }

    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT(')');
    }

    // strings with the same character type get ""s:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    // and pack the characters together:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}

    // map magic. When iterating over the contents of a map, use the map_magic_tag:
    template<class...Xs>
    map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
      return {};
    }
    template<class old_tag, class C>
    old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
      return {};
    }

    // When printing a pair immediately within a map, use -> as a separator:
    template<class old_tag, class CharT, class Traits, class...Xs >
    void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
      s << CharT('-') << CharT('>');
    }
  }

  // default behavior:
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT('[');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(']');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(',');
  }
  template<class Tag, class Container>
  Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
    return std::forward<Tag>(tag);
  }

  // print things by default by using <<:
  template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
  std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
    os << std::forward<Scalar>(scalar);
  }
  // for anything visitable (see above), use the pretty print algorithm:
  template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
  std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
    pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
    visit_first( c, [&](auto&& elem) {
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    visit_all_but_first( c, [&](auto&& elem) {
      pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
  }
}

测试代码:

int main() {
  std::vector<int> x = {1,2,3};

  pretty_print::print( std::cout, x );
  std::cout << "\n";

  std::map< std::string, int > m;
  m["hello"] = 3;
  m["world"] = 42;

  pretty_print::print( std::cout, m );
  std::cout << "\n";
}

live example

这确实使用了 C++14 功能(一些 _t 别名和 auto&& lambda),但没有一个是必不可少的。


@KerrekSB 工作版本,有一些变化。大部分代码是通用的“访问元组/可迭代对象”和花哨的格式(包括 mappair 内的 ->)。漂亮的打印库的核心很好而且很小,这很好。我试图使它易于扩展,不确定是否成功。
L
Leonid Volnitsky

我的解决方案是 simple.h,它是 scc 包的一部分。所有标准容器、地图、集合、c 数组都是可打印的。


有趣的。我喜欢容器的模板模板方法,但它是否适用于具有非标准谓词或分配器的自定义容器和 STL 容器? (我为使用可变参数模板尝试 implement a bimap in C++0x 做了类似的事情。)此外,您似乎没有在打印例程中通用地使用迭代器。为什么要显式使用计数器 i
什么是带有非标准谓词的容器?将打印匹配签名的自定义容器。目前不支持非标准分配器,但很容易修复。我只是暂时不需要这个。
没有充分的理由使用索引而不是迭代器。历史原因。当我有时间时会修复它。
“带有非标准谓词的容器”是指带有自定义比较器的 std::set 或带有自定义相等性的 unordered_map 之类的东西。支持这些结构非常重要。
c
cigien

从第一个 BoostCon(现在称为 CppCon)中走出来,我和另外两个人开发了一个库来做到这一点。主要的症结在于需要扩展 namespace std。事实证明,对于 boost 库来说,这是不行的。

不幸的是,代码的链接不再有效,但您可能会在讨论中发现一些有趣的花絮(至少那些不讨论如何命名的花絮!)

http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html


c
cigien

这是我在 2016 年完成的实施版本

一切都在一个标题中,因此易于使用 https://github.com/skident/eos/blob/master/include/eos/io/print.hpp

/*! \file       print.hpp
 *  \brief      Useful functions for work with STL containers. 
 *          
 *  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
 *  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
 *  map, multimap, unordered_map, array
 *
 *  \author     Skident
 *  \date       02.09.2016
 *  \copyright  Skident Inc.
 */

#pragma once

// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
    #define MODERN_CPP_AVAILABLE 1
#endif


#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>

#ifdef MODERN_CPP_AVAILABLE
    #include <array>
    #include <unordered_set>
    #include <unordered_map>
    #include <forward_list>
#endif


#define dump(value) std::cout << (#value) << ": " << (value) << std::endl

#define BUILD_CONTENT                                                       \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss << *it << elem_separator;                                    \
        }                                                                   \


#define BUILD_MAP_CONTENT                                                   \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss  << it->first                                                \
                << keyval_separator                                         \
                << it->second                                               \
                << elem_separator;                                          \
        }                                                                   \


#define COMPILE_CONTENT                                                     \
        std::string data = ss.str();                                        \
        if (!data.empty() && !elem_separator.empty())                       \
            data = data.substr(0, data.rfind(elem_separator));              \
        std::string result = first_bracket + data + last_bracket;           \
        os << result;                                                       \
        if (needEndl)                                                       \
            os << std::endl;                                                \



////
///
///
/// Template definitions
///
///

//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE                                          \
    template<                                                           \
        template<class T,                                               \
                 class Alloc = std::allocator<T> >                      \
        class Container, class Type, class Alloc>                       \

#define SET_TEMPLATE                                                    \
    template<                                                           \
        template<class T,                                               \
                 class Compare = std::less<T>,                          \
                 class Alloc = std::allocator<T> >                      \
            class Container, class T, class Compare, class Alloc>       \

#define USET_TEMPLATE                                                   \
    template<                                                           \
template < class Key,                                                   \
           class Hash = std::hash<Key>,                                 \
           class Pred = std::equal_to<Key>,                             \
           class Alloc = std::allocator<Key>                            \
           >                                                            \
    class Container, class Key, class Hash, class Pred, class Alloc     \
    >                                                                   \


#define MAP_TEMPLATE                                                    \
    template<                                                           \
        template<class Key,                                             \
                class T,                                                \
                class Compare = std::less<Key>,                         \
                class Alloc = std::allocator<std::pair<const Key,T> >   \
                >                                                       \
        class Container, class Key,                                     \
        class Value/*, class Compare, class Alloc*/>                    \


#define UMAP_TEMPLATE                                                   \
    template<                                                           \
        template<class Key,                                             \
                   class T,                                             \
                   class Hash = std::hash<Key>,                         \
                   class Pred = std::equal_to<Key>,                     \
                   class Alloc = std::allocator<std::pair<const Key,T> >\
                 >                                                      \
        class Container, class Key, class Value,                        \
        class Hash, class Pred, class Alloc                             \
                >                                                       \


#define ARRAY_TEMPLATE                                                  \
    template<                                                           \
        template<class T, std::size_t N>                                \
        class Array, class Type, std::size_t Size>                      \



namespace eos
{
    static const std::string default_elem_separator     = ", ";
    static const std::string default_keyval_separator   = " => ";
    static const std::string default_first_bracket      = "[";
    static const std::string default_last_bracket       = "]";


    //! Prints template Container<T> as in Python
    //! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    template<class Container>
    void print( const Container& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections with one template argument and allocator as in Python.
    //! Supported standard collections: vector, deque, list, forward_list
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    VECTOR_AND_CO_TEMPLATE
    void print( const Container<Type>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Type>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:set<T, Compare, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    SET_TEMPLATE
    void print( const Container<T, Compare, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    USET_TEMPLATE
    void print( const Container<Key, Hash, Pred, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:map<T, U> as in Python
    //! supports generic objects of std: map, multimap
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    MAP_TEMPLATE
    void print(   const Container<Key, Value>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints classes like std:unordered_map as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    UMAP_TEMPLATE
    void print(   const Container<Key, Value, Hash, Pred, Alloc>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:array<T, Size> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    ARRAY_TEMPLATE
    void print(   const Array<Type, Size>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
            )
    {
        typename Array<Type, Size>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Removes all whitespaces before data in string.
    //! \param str string with data
    //! \return string without whitespaces in left part
    std::string ltrim(const std::string& str);

    //! Removes all whitespaces after data in string
    //! \param str string with data
    //! \return string without whitespaces in right part
    std::string rtrim(const std::string& str);

    //! Removes all whitespaces before and after data in string
    //! \param str string with data
    //! \return string without whitespaces before and after data in string
    std::string trim(const std::string& str);



    ////////////////////////////////////////////////////////////
    ////////////////////////ostream logic//////////////////////
    /// Should be specified for concrete containers
    /// because of another types can be suitable
    /// for templates, for example templates break
    /// the code like this "cout << string("hello") << endl;"
    ////////////////////////////////////////////////////////////



#define PROCESS_VALUE_COLLECTION(os, collection)                            \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

#define PROCESS_KEY_VALUE_COLLECTION(os, collection)                        \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_keyval_separator,                                       \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

    ///< specialization for vector
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for deque
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for set
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for multiset
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

#ifdef MODERN_CPP_AVAILABLE
    ///< specialization for unordered_map
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for forward_list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for array
    template<class T, std::size_t N>
    std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }
#endif

    ///< specialization for map, multimap
    MAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for unordered_map
    UMAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }
}

c
cigien

在 C++11 中

for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';

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

与已经存在的答案相比,这个答案没有提供任何额外的信息。
p
phuclv

如果 boost 是一个选项,那么您可以使用 boost::algorithm::join。例如打印出 std::string 的向量:

#include <boost/algorithm/string/join.hpp>

std::vector<std::string> vs { "some", "string", "vector" };
std::cout << boost::algorithm::join(vs, " | ") << '\n';

对于其他类型的向量,您需要先 transform 字符串

#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>

int main()
{
    using boost::adaptors::transformed;
    using boost::algorithm::join;

    // Generate the vector
    std::vector<int> vi(10);
    std::iota(vi.begin(), vi.end(), -3);

    // Print out the vector
    std::cout << join(vi |
                 transformed(static_cast<std::string(*)(int)>(std::to_string)),
                 ", ")
              << '\n';
}

Demo on Godbolt


你们怎么了?没有人使用 boost::algorithm::join 发布答案
p
phuclv

您可以使用 std::experimental::make_ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
 
int main()
{
    std::vector<int> vi(12);
    std::iota(vi.begin(), vi.end(), -5);
    std::cout << "Int vector:\n";
    std::copy(std::begin(vi),
              std::end(vi),
              std::experimental::make_ostream_joiner(std::cout, ", "));

    std::cout <<"\nString vector:\n[";
    std::vector<std::string> vs { "some", "string", "vector" };
    std::copy(std::begin(vs),
              std::end(vs),
              std::experimental::make_ostream_joiner(std::cout, "] - ["));
    std::cout << "]\n";
}

Demo on Godbolt


C
Captain Hatteras

我编写了一个 operator<< 来打印任何可迭代对象,其中包括自定义容器、标准容器和具有已知边界的数组。需要 c++11:

template<typename Container, typename = 
    std::enable_if_t<std::is_same_v<std::void_t<
        decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cbegin)),
        decltype(static_cast<typename Container::const_iterator (*)(const Container&)>(&std::cend))>, void>
        && !std::is_same_v<std::string, Container>>>
std::ostream& operator<<(std::ostream& out, const Container &vec)
{
    std::cout << "[ ";
    for(const auto& t: vec){
        std::cout << t << " ";
    }
    std::cout << "] ";
    return out;
}

d
darune

对于那些感兴趣的人:我编写了一个综合解决方案,它充分利用了两全其美,更适用于任何类型的范围,并在非算术类型周围加上引号(对于类似字符串的类型来说是理想的)。此外,这种方法不应该有任何 ADL 问题,也应该避免“意外”(因为它是根据具体情况明确添加的):

template <typename T>
inline constexpr bool is_string_type_v = std::is_convertible_v<const T&, std::string_view>;

template<class T>
struct range_out {
  range_out(T& range) : r_(range) {
  }
  T& r_;
  static_assert(!::is_string_type_v<T>, "strings and string-like types should use operator << directly");
};

template <typename T>
std::ostream& operator<< (std::ostream& out, range_out<T>& range) {
  constexpr bool is_string_like = is_string_type_v<T::value_type>;
  constexpr std::string_view sep{ is_string_like ? "', '" : ", " };

  if (!range.r_.empty()) {
    out << (is_string_like ? "['" : "[");
    out << *range.r_.begin();
    for (auto it = range.r_.begin() + 1; it != range.r_.end(); ++it) {
      out << sep << *it;
    }
    out << (is_string_like ? "']" : "]");
  }
  else {
    out << "[]";
  }

  return out;
}

现在它在任何范围内都相当容易使用:

std::cout << range_out{ my_vector };

类似字符串的检查留有改进的空间。我也有 static_assert 签入我的解决方案以避免 std::basic_string<>,但为了简单起见,我在此处省略了它。


a
alexpanter

对于想要没有循环的单行代码的人:

我不敢相信没有人知道这一点,但也许是因为更像 C 的方法。无论如何,在没有循环的情况下这样做是完全安全的,在单行中,假设 std::vector<char> 是空终止的:

std::vector<char> test { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0' };
std::cout << test.data() << std::endl;

但为了安全起见,我会将其包装在 ostream 运算符中,正如@Zorawar 所建议的那样:

template <typename T>std::ostream& operator<< (std::ostream& out, std::vector<T>& v)
{
    v.push_back('\0'); // safety-check!
    out << v.data();
    return out;
}

std::cout << test << std::endl; // will print 'Hello, world!'

我们可以通过使用 printf 来实现类似的行为:

fprintf(stdout, "%s\n", &test[0]); // will also print 'Hello, world!'

笔记:

重载的 ostream 运算符需要接受向量作为非常量。这可能会使程序不安全或引入误用代码。此外,由于附加了空字符,因此可能会发生 std::vector 的重新分配。因此,使用带有迭代器的 for 循环可能会更快。


1. fprintf(stdout, "%s\n", &test[0]);std::cout << test.data() 没有什么不同,都需要一个空终止向量。 2. “但我会将它包装在 ostream 运算符中” << 修改右操作数的运算符是一个非常糟糕的主意。
我在代码中使用 fprintf(stdout, "%s\n", &test[0]); 很长时间了,它从来没有给我带来任何麻烦。有趣的!而且我同意在 ostream 运算符中修改向量并不是很好,但我不喜欢手动循环 使用迭代器。不知何故,我觉得对于像打印 std::vector<char> 这样的简单操作,标准库应该将这些东西隐藏起来。但 C++ 正在不断发展,它可能很快就会到来。
M
Markus Dutschke

模板集合:

应用 std::cout << 和 std::to_string

到 std::vector、std::array 和 std::tuple

由于在 cpp 中打印向量结果出乎意料的多(至少与这项任务的基本相比),并且作为一个步骤再次解决相同的问题,当使用其他容器时,这里有一个更通用的解决方案......

模板集合内容

此模板集合处理 3 种 容器 类型:std::vectorstd::arraystd::tuple。它为这些定义了 std::to_string(),并可以通过 std::cout << container; 直接打印出来。

此外,它定义了 << std::string << container 的运算符。这样就可以以紧凑的方式构造包含这些容器类型的字符串。

std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);

我们到

std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;

代码

您可以交互测试此代码:here

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <array>

namespace std
{   
    // declations: needed for std::to_string(std::vector<std::tuple<int, float>>)
    std::string to_string(std::string str);
    std::string to_string(const char *str);
    template<typename T, size_t N>
    std::string to_string(std::array<T, N> const& arr);
    template<typename T>
    std::string to_string(std::vector<T> const& vec);
    template<typename... Args>
    std::string to_string(const std::tuple<Args...>& tup);
    
    std::string to_string(std::string str)
    {
        return std::string(str);
    }
    std::string to_string(const char *str)
    {
        return std::string(str);
    }

    template<typename T, size_t N>
    std::string to_string(std::array<T, N> const& arr)
    {
        std::string s="{";
        for (std::size_t t = 0; t != N; ++t)
            s += std::to_string(arr[t]) + (t+1 < N ? ", ":"");
        return s + "}";
    }

    template<typename T>
    std::string to_string(std::vector<T> const& vec)
    {
        std::string s="[";
        for (std::size_t t = 0; t != vec.size(); ++t)
            s += std::to_string(vec[t]) + (t+1 < vec.size() ? ", ":"");
        return s + "]";
    }
    
    // to_string(tuple)
    // https://en.cppreference.com/w/cpp/utility/tuple/operator%3D
    template<class Tuple, std::size_t N>
    struct TupleString
    {
        static std::string str(const Tuple& tup)
        {
            std::string out;
            out += TupleString<Tuple, N-1>::str(tup);
            out += ", ";
            out += std::to_string(std::get<N-1>(tup));
            return out;
        }
    };
    template<class Tuple>
    struct TupleString<Tuple, 1>
    {
        static std::string str(const Tuple& tup)
        {
            std::string out;
            out += std::to_string(std::get<0>(tup));
            return out;
        }
    };
    template<typename... Args>
    std::string to_string(const std::tuple<Args...>& tup)
    {
        std::string out = "(";
        out += TupleString<decltype(tup), sizeof...(Args)>::str(tup);
        out += ")";
        return out;
    }
} // namespace std


/**
 * cout: cout << continer
 */
template <typename T, std::size_t N> // cout << array
std::ostream& operator <<(std::ostream &out, std::array<T, N> &con)
{
    out <<  std::to_string(con);
    return out;
}
template <typename T, typename A> // cout << vector
std::ostream& operator <<(std::ostream &out, std::vector<T, A> &con)
{
    out <<  std::to_string(con);
    return out;
}
template<typename... Args> // cout << tuple
std::ostream& operator <<(std::ostream &out, std::tuple<Args...> &con)
{
    out <<  std::to_string(con);
    return out;
}

/**
 * Concatenate: string << continer
 */
template <class C>
std::string operator <<(std::string str, C &con)
{
    std::string out = str;
    out += std::to_string(con);
    return out;
}
#define STR() std::string("")

int main()
{
    std::array<int, 3> arr {1, 2, 3};
    std::string sArr = std::to_string(arr);
    std::cout << "std::array" << std::endl;
    std::cout << "\ttest to_string: " << sArr << std::endl;
    std::cout << "\ttest cout <<: " << arr << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << arr) << std::endl;
    
    std::vector<std::string> vec {"a", "b"};
    std::string sVec = std::to_string(vec);
    std::cout << "std::vector" << std::endl;
    std::cout << "\ttest to_string: " << sVec << std::endl;
    std::cout << "\ttest cout <<: " << vec << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << vec) << std::endl;
    
    std::tuple<int, std::string> tup = std::make_tuple(5, "five");
    std::string sTup = std::to_string(tup);
    std::cout << "std::tuple" << std::endl;
    std::cout << "\ttest to_string: " << sTup << std::endl;
    std::cout << "\ttest cout <<: " << tup << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << tup) << std::endl;
    
    std::vector<std::tuple<int, float>> vt {std::make_tuple(1, .1), std::make_tuple(2, .2)};
    std::string sVt = std::to_string(vt);
    std::cout << "std::vector<std::tuple>" << std::endl;
    std::cout << "\ttest to_string: " << sVt << std::endl;
    std::cout << "\ttest cout <<: " << vt << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << vt) << std::endl;
    
    std::cout << std::endl;
    
    std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);
    std::cout << s1 << std::endl;
    
    std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;
    std::cout << s2 << std::endl;

    return 0;
}

输出

std::array
    test to_string: {1, 2, 3}
    test cout <<: {1, 2, 3}
    test string <<: {1, 2, 3}
std::vector
    test to_string: [a, b]
    test cout <<: [a, b]
    test string <<: [a, b]
std::tuple
    test to_string: (5, five)
    test cout <<: (5, five)
    test string <<: (5, five)
std::vector<std::tuple>
    test to_string: [(1, 0.100000), (2, 0.200000)]
    test cout <<: [(1, 0.100000), (2, 0.200000)]
    test string <<: [(1, 0.100000), (2, 0.200000)]

s1: {1, 2, 3}; [a, b]; (5, five)
s2: {1, 2, 3}; [a, b]; (5, five)