繁体   English   中英

如何打印出矢量的内容?

[英]How do I print out the contents of a vector?

如何将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?
}

这是我到目前为止所拥有的,没有单独的 function:

#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 循环(见下文); 或者使用迭代器。 但是你有几个选择,我将在下面解释所有这些。

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

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

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

for 循环语句中的char类型应该是向量path的元素类型,而不是整数索引类型。 换句话说,由于path的类型是std::vector<char> ,因此应该出现在基于范围的 for 循环中的类型是char 但是,您可能会经常看到显式类型被auto占位符类型替换:

for (auto i: path)
    std::cout << i << ' ';

无论您使用显式类型还是auto关键字,对象i都有一个值,该值是path对象中实际项目的副本。 因此,循环中对i所有更改都不会保留在path本身中:

std::vector<char> path{'a', 'b', 'c'};

for (auto i: path) {
    i = '_'; // 'i' is a copy of the element in 'path', so although
             // we can change 'i' here perfectly fine, the elements
             // of 'path' have not changed
    std::cout << i << ' '; // will print: "_ _ _"
}

for (auto i: path) {
    std::cout << i << ' '; // will print: "a b c"
}

如果您也希望禁止在 for 循环中更改i这个复制值,您可以强制i的类型为const char如下所示:

for (const auto i: path) {
    i = '_'; // this will now produce a compiler error
    std::cout << i << ' ';
}

如果您想修改path的项目,以便这些更改在 for 循环之外的path持续存在,那么您可以使用如下引用:

for (auto& i: path) {
    i = '_'; // changes to 'i' will now also change the
             // element in 'path' itself to that value
    std::cout << i << ' ';
}

即使您不想修改path ,如果对象的复制很昂贵,您也应该使用 const 引用而不是按值复制:

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

迭代器

在 C++11 之前,规范的解决方案是使用迭代器,这仍然是完全可以接受的。 它们的用法如下:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

如果要在 for 循环中修改向量的内容,请使用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方法(const 为“c”),那么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会带来几个优点,我在上面简要概述了这些优点。 因此,我的建议是避免使用这种方法,除非您有充分的理由。

std::copy (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<<

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

结论

此处介绍的任何解决方案都将起作用。 哪个是“最好的”取决于您(以及上下文或您的编码标准)。 任何比这更详细的内容可能最好留给另一个可以正确评估利弊的问题,但与往常一样,用户偏好始终会发挥作用:所提出的解决方案都没有客观上是错误的,但有些解决方案对每个编码员来说看起来更好.

附录

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

一个更简单的方法是使用标准复制算法

#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 循环。 标准库为您完成这一切。

这个解决方案的灵感来自 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 类型特征,该特征必须专用于所有要支持的容器。 可以使用特征来检查value_typeconst_iteratorbegin() / end() ,但我不确定我会推荐它,因为它可能匹配符合这些条件但实际上不是容器的东西,像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 的依赖。

在 C++11 中,您现在可以使用基于范围的 for 循环

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

我认为最好的方法是通过将这个函数添加到你的程序中来重载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}

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

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

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

印刷

{1, 2, 3}

stdoutgodbolt )。

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

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

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 表达式中获得的力量——你可以使用这种方法来做更多的事情,而不仅仅是打印向量。

这已经被编辑了几次,我们决定调用包装集合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 by

 std::cout << outputFormatter( mySet );

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

这是一个工作库,以完整的工作程序形式呈现,我只是将其组合在一起:

#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[]

该代码现在多次被证明很方便,我觉得进入定制的成本很低,因为使用率很低。 因此,我决定在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来保存一个指向pword pretty::decor对象的指针,该对象专门装饰某个流上的某个类型。

如果没有为该流显式设置pretty::defaulted<T, charT, chartraitT>::decoration() pretty::decor<T>对象pretty::defaulted<T, charT, chartraitT>::decoration()则调用pretty::defaulted<T, charT, chartraitT>::decoration()以获取给定类型的默认装饰。 pretty::defaulted专门用于自定义默认装饰。

2. 目标对象/容器

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

  • 重载std::beginstd::end定义(包括 C 样式数组),
  • 通过 ADL 可以使用begin(obj)end(obj)
  • 属于std::tuple类型
  • 或类型std::pair

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

该代码在全局命名空间中提供了operator<< s,它们仅适用于没有更专门的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{ { "(" }, { ":" }, { ")" } };
    }
  };
}

打印一个 arry 数组:

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)

char流使用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<...> s。

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_

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

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

应该输出:

1 2 3 4

问题可能出在前面的循环中:

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

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

使用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++11 中,基于范围的 for 循环可能是一个很好的解决方案:

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

输出:

a b c 

重载运算符<<:

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

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

基础知识在这里

基本上你要做的是:

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

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

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

我已将此作为新解决方案编写,而不是修改我的另一个解决方案,因为我相信这两种方法都是正确的,您可以选择。

我看到两个问题。 正如所指出的

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

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

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

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

这个答案基于Zorawar答案,但我无法在那里发表评论。

您可以使auto (C ++ 11)/ typedef版本const使用cbegincend代替

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

这里的目标是使用 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> )。也许有更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";
}

活生生的例子

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

在 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] << ' ';

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

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

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

这是我在 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;
    }
}

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

您可以使用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";
}

Godbolt 上的演示

如何将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;
    }
}

如果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';

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

#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';
}

Godbolt 上的演示

我编写了一个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;
}
template <typename T>
std::ostream& operator<<( std::ostream& ostrm, const std::vector<T>& vec ){
    for( int j = 0, n = vec.size(); j < n; ++j ){
        ostrm << ",["[ !j ] << " " << vec[ j ];
    }
    return ostrm << " ]";
}

[ 1, 2, 3, 4 ]

您可以使用perr.h作为起点:

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

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

您只需要从 GitHub ( https://github.com/az5112/perr ) 中获取 header。

如何将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::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;
    }
}

对于那些感兴趣的人:我写了一个通用的解决方案,它充分利用了两全其美的优势,更广泛地适用于任何类型的范围,并在非算术类型(需要类似字符串的类型)周围加上引号。 此外,这种方法不应该有任何 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<> ,但为了简单起见,我在这里省略了它。

如何将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;
    }
}

对于想要没有环的单衬的人:

我不敢相信没有人想到这一点,但也许是因为更像 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 循环与迭代器一起使用可能会更快。

模板集合:

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

std::vectorstd::arraystd::tuple

由于在 cpp 中打印向量的工作量惊人(至少与此任务的基本程度相比)并且作为再次解决同一问题的一个步骤,当使用其他容器时,这里有一个更通用的解决方案......

模板收藏内容

此模板集合处理 3 种容器类型: std::vectorstd::arraystd::tuple 它为那些定义了std::to_string()并且可以通过std::cout << container;直接打印它们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;

代码

您可以以交互方式测试此代码:此处

#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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM