问答中心分类: C++如何打印出向量的内容?
0
匿名用户 提问 18小时 前

如何打印出一个内容std::vector到屏幕上?

实现以下内容的解决方案operator<<也会很好:

template
std::ostream & operator< & x)
{
  // ... What can I write here?
}

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

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int main()
{
    ifstream file("maze.txt");
    if (file) {
        vector vec(istreambuf_iterator(file), (istreambuf_iterator()));
        vector 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;
    }
}
Matthieu M. 回复 18小时 前

有关信息,我发现“简洁”地做到这一点的唯一方法是 hack -&gt; 添加重载operator<<std命名空间(以便它们被 ADL 拾取)并将调用转发到通用打印范围方法…我对这次讨论的结果非常感兴趣,感谢您的提问 🙂

Matthieu M. 回复 18小时 前

如果你有异构类型,你可以混合 stl 容器和元组。利用boost.fusionio 和漂亮的印刷品。cout << vector<tuple<int,array<int,3>>>(...) << endl;

28 Answers
0
Joshua Kravitz 回答 18小时 前

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

#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 循环。标准库为您完成所有工作。

mtk 回复 18小时 前

如果我的向量是类型怎么办vector<pair<int, struct node>>.如何使用上述方法打印此矢量?

Quigi 回复 18小时 前

分隔符字符串被写入每个元素,而不是之间,即,也在最后一个元素之后。如果您只希望它介于两者之间,即作为分隔符,则可能需要处理特殊情况。

ShoeLace 回复 18小时 前

@mtk 你可以声明一个operator<<您的特定配对&lt;&gt;的功能。

dfrib 回复 18小时 前

添加显示类似方法的答案但考虑到上面的@Quigi:s 评论,关于额外的尾随分隔符。

thegreatcoder 回复 18小时 前

@ShoeLace没有别的办法吗?

gerardw 回复 18小时 前

std::ostreambuf_iterator专门用于字符,可能表现更好。

šm98 回复 18小时 前

它适用于 char 类型向量,但您自己的数据类型如何。 Fe 我做了数据类型 Card:它有 value,它是 integer,和 suite,它是 string。这种方法不适用于我的示例。

0
Sven 回答 18小时 前

此解决方案的灵感来自 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_type,const_iterator,begin()/end(),但我不确定我是否会推荐它,因为它可能匹配符合这些条件但实际上不是容器的东西,比如std::basic_string.也像 Marcelo 的版本一样,它使用可以专门指定要使用的分隔符的模板。
主要的区别是我围绕一个pretty_ostream_iterator,其工作原理类似于std::ostream_iterator但不会在最后一项之后打印分隔符。格式化容器由print_container_helper,可直接用于打印没有 is_container 特征的容器,或指定不同的分隔符类型。
我还定义了 is_container 和分隔符,因此它适用于具有非标准谓词或分配器的容器,以及 char 和 wchar_t。 operator&lt;&lt; 函数本身也被定义为与 char 和 wchar_t 流一起使用。
最后,我用过std::enable_if,作为 C++0x 的一部分提供,适用于 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)及更高版本。这样就没有对 Boost 的依赖。

Dennis Zickefoose 回复 18小时 前

如果我没看错,为了让一对打印为<i, j>在一个功能中,并且作为[i j]在另一种情况下,您必须定义一个全新的类型,其中包含一些静态成员才能将该类型传递给print_container_helper?这似乎过于复杂。为什么不使用实际对象,您可以根据具体情况设置字段,而专业化只是提供不同的默认值?

Kerrek SB 回复 18小时 前

这样看:如果有一堆你个人喜欢的分隔符,你可以一劳永逸地创建几个带有静态成员的类,然后就使用它们。当然你是对的,使用print_container_helper不像刚才那样优雅operator<<.当然,您可以随时更改源代码,或者只是为您喜欢的容器添加明确的专业化,例如pair<int, int>并且对于pair<double, string>.归根结底,这是一个权衡权力与便利性的问题。欢迎提出改进建议!

Kerrek SB 回复 18小时 前

…并跟进,如果您已经需要情境打印相同的不同格式的数据类型,无论如何您可能必须编写至少一个小的包装器。这不是一个高度可配置的格式化库,而是一个零努力的明智默认库,它神奇地让您无需思考即可打印容器……(但如果您想要更多全球的灵活性,我们可能会添加一些#macros 以使默认值易于操作。)

Sven 回复 18小时 前

真正的问题是,虽然我可以轻松地修改 print_container_helper 以使用自定义分隔符的参数,但除了专门指定分隔符模板之外,实际上没有任何方法可以为内部容器(或对)指定分隔符。实现这一点将非常复杂。

Kerrek SB 回复 18小时 前

我几乎设法使用类型擦除来实现方便的自定义分隔符解决方案。如果您已经有一个分隔符类MyDels,那么我可以说std::cout << CustomPrinter<MyDels>(x);.我什么不能此刻做的是说std::cout << CustomDelims<"{", ":", "}">(x);,因为你不能拥有const char *模板参数。使定界符成为编译时常量的决定对那里的易用性造成了一些限制,但我认为这是值得的。

Kerrek SB 回复 18小时 前

(当然你是对的,这并不容易自定义分隔符。我认为在这种情况下简单地专门化所需的类型应该足够容易,而且,如果你需要超级定制的打印,你可能会编写自己的循环。这只是一个方便快速检查的库。)

Dennis Zickefoose 回复 18小时 前

我正在可视化的分隔符接口涉及一个提供默认值的模板化工厂类。用户可以根据自己的喜好对工厂进行专业化。辅助函数接受一个带有默认参数的实际分隔符对象,该参数只查询该工厂。然后,您可以编写一个基本的操纵器来提供一组自定义分隔符。所以你最终得到cout << v;或者cout << delims("(", "-", ")") << v;或者cout << pretty_container_helper(v, delims("(", "-", ")");.这样,分隔符对象本身就不必是模板,一切都保持相对简单。

Dennis Zickefoose 回复 18小时 前

@Kerrek:ideone.com/aTHOQ

Kerrek SB 回复 18小时 前

@Dennis:感谢您的代码。据我所知,您所做的是使分隔符成为对象范围的常量而不是编译时(即类静态)常量,对吗?所以区别在于用户可以在运行时提供分隔符。但是控制嵌套输出的问题仍然存在,即我无法根据具体情况彻底自定义嵌套容器的打印。 (不过,如果您愿意,我可以将您的基于对象的分隔符添加到代码中。但请查看我的顶级帖子以获取最新版本。)

Dennis Zickefoose 回复 18小时 前

@Kerrek:我已经有用于此任务的实用程序功能,所以不要更改我帐户上的代码。如果您不喜欢该设计,那是您的决定:-)

BaCh 回复 18小时 前

事实证明,在一些极端情况下,使用 gcc5.3 并没有为我编译接受的答案。例如,在具有两个不相关结构但具有运算符的命名空间中

Brian 回复 18小时 前

只有当向量的大小在循环范围的主体中没有改变时,这才有效。

Shoe 回复 18小时 前

@BrianP。是的。打印容器的元素不会修改容器的范围。

kleinfreund 回复 18小时 前

这里有什么可取的 – c 作为值副本或作为 const 引用以避免复制元素?

Shoe 回复 18小时 前

@kleinfreund 这取决于向量的内容。例如对于一个向量chars,有可能通过常量引用传递实际上比按值传递更昂贵。但在这里我们谈论的是超微优化。

Karthik Nishanth 回复 18小时 前

std::map 呢?我在文档中找不到任何内容

vitaut 回复 18小时 前

格式化全部支持容器。

Karthik Nishanth 回复 18小时 前

你能给我一个起点吗?我很难找到 fmtlib 的用法fmtlib print std::map作为搜索词。如果这算作菜鸟问题或类似 RTFM 的问题,我深表歉意 🙂

vitaut 回复 18小时 前

这是一个地图的例子:godbolt.org/z/EG7aoE.如您所见,用法没有区别。

Karthik Nishanth 回复 18小时 前

天啊!这真太了不起了godbolt.org/z/h7qxba

JDiMatteo 回复 18小时 前

将 v.size() – 1 存储为 int 可能会损失精度。我在接受的同行评审编辑中修复了这个问题(stackoverflow.com/revisions/23397700/5),但此后再次对其进行编辑,以恢复可能的精度损失。我想这在实践中并不重要,因为向量通常不是那么大。

Chris Redford 回复 18小时 前

不将其存储为变量会降低代码的可读性,这是我不同意的编辑的一部分。我改变了类型lastsize_t.

Vladimir Gamalyan 回复 18小时 前

size_t last = v.size() - 1;看起来多余,你可以使用if (i) out << ", ";之前的条件out << v[i]; 关联

M.M 回复 18小时 前

ADL 找不到此运算符,因为它不在其任何参数的命名空间中。所以它将被任何其他命名空间隐藏operator<<.例子

WhozCraig 回复 18小时 前

如果你要这样做,为什么要测试if (i != last)每一次循环?相反,如果容器不为空,则 (a) 发送第一个元素,然后 (b) 循环发送其余的元素,打印分隔符第一的(作为前缀)。不需要内部循环测试(除了循环条件本身)。只需要一次外环测试。

Kerrek SB 回复 18小时 前

我懂了。这和马塞洛·坎托斯的想法很像,不是吗?我将尝试将其变成一个工作示例,谢谢!

Björn Pollex 回复 18小时 前

我发现这个解决方案比 Marcelo 的解决方案干净得多,而且它提供了同样的灵活性。我喜欢必须将输出显式包装到函数调用中的方面。真的很酷,您可以添加对直接输出一系列迭代器的支持,这样我就可以做到std::cout << outputFormatter(beginOfRange, endOfRange);.

Matthieu M. 回复 18小时 前

@CashCow:这个解决方案有一个问题,它似乎不适用于递归集合(即集合的集合)。std::pair是“内部集合”最基本的例子。

Kerrek SB 回复 18小时 前

我非常喜欢这个答案,因为它没有依赖关系,也不需要知道它支持的容器。我们可以弄清楚它是否可以处理std::maps 很容易,如果它适用于集合的集合?不过,我很想接受这个作为答案。我希望马塞洛不介意,他的解决方案也有效。

CashCow 回复 18小时 前

@Matthieu M。这取决于您如何打印内部集合。如果您只使用 os &lt;&lt; open &lt;&lt; *iter &lt;&lt; close 那么您会遇到问题,但是如果您允许您的用户按照我的建议传入自定义打印机,那么您可以打印任何您喜欢的东西。

Kerrek SB 回复 18小时 前

哦 – 我们可以将这一切都包装成一个通用打印机功能吗?对于原始类型,它只是“

CashCow 回复 18小时 前

我已经开始使用您的自定义打印机了。当然,您可以使用函数来创建项目打印机。您也可以在向量上添加部分专业化,但是您必须走上分别专门化每个专业化的道路。最好有一个集合打印机,你甚至可以在 RangePrinter 上重载 operator() 以适应作为内部打印机的类型。

CashCow 回复 18小时 前

@Kerrek。如果 Marcelo 和我结对编程,我们可能会在这里想出完美的解决方案。您将使用特征类型来做到这一点。 boost 可能比 Marcelo 的 IsContainer 有更好的东西,尽管它会查看类型的特征以查看它是否充当容器。

CashCow 回复 18小时 前

@Matthieu 我至少看到了您的一项更正。是的,它是一个更专业的重载,但是如果你传入一个 std::pair 它的效果是它是一个更接近的匹配,所以编译器会选择它而不是一般的,所以有点像部分专业化。

CashCow 回复 18小时 前

如果你想要完全递归的东西,你很可能需要采用打印机并使用聪明的 SFINAE 特征技术(如果项目有 begin() 和 end() 方法,它就是一个集合……如果它是一个共享指针,打印它指向的内容to…等),这假设您想在每个级别使用相同的分隔符/开启符/关闭符,并且您还需要决定如何键值。如果有很大的兴趣尝试这个,我们可以把它变成一个社区维基吗?你的集合到底有多嵌套?

Kerrek SB 回复 18小时 前

这是我无法编译的示例程序:pastebin.com/Vx6hAWuL我想在现实生活中,您通常不会拥有多于一个向量左右的向量,因此这种嵌套可能不如对向量对的支持和对地图的支持那么重要。

CashCow 回复 18小时 前

你做了一些修改,我不知道什么不能编译。我明天测试一下。我不确定第一个模板声明是否也需要默认参数。它究竟在哪里编译失败?你看到什么错误?

Kerrek SB 回复 18小时 前

很抱歉,当我用你的新版本更新我的本地代码时,我很抱歉。你是对的,现在它工作得更好了。我已经更新了示例:pastebin.com/ewMHBMnB矢量仍然有一些问题&gt; 不过……

Kerrek SB 回复 18小时 前

在注意到 RangePrinter 对 std::strings 的工作令人惊讶之后,我也添加了对数组的支持,就像 Marcelo 所做的那样:pastebin.com/ZhBc0XhR这让你说std::cout << outputFormatter("Hello World") << std::endl;

Kerrek SB 回复 18小时 前

还有一个问题:要专门化分隔符,比如 std::set,我是否必须重写 RangePrinter 的整个定义?可以以某种方式避免这种情况吗?

CashCow 回复 18小时 前

问题是我的打印机有一个接口,而 RangePrinter 有一个不同的接口。 RangePrinter 是一个用于打印单个范围的对象,您可以指定如何打印其中的内容。我们可能想要具有 operator() 并在下面使用 RangePrinter 的 CollectionPrinter。

CashCow 回复 18小时 前

我看到了这个问题:我们应该将分隔符规范与打印的内容分离,即它是什么的集合。当你有 vector&lt; vector 时,这是可能的&gt; 将分隔符规范传递给外部向量的每个项目以打印内部向量。

CashCow 回复 18小时 前

这将是一个相当大的重新编辑,但我现在看到如下: Delims 应该是一个具有 3 个字符串的结构。我们需要一个打印机函子(RangeItemPrinter),它将分隔符作为参数并保持状态知道它是否正在打印第一个对象,并且我们需要另一个类来实际打印项目(与它们在一个范围内无关),即打印机=此处的默认打印机。 RangePrinter 只是所有这些类的包装器,并通过操作符进行流式传输从现有的类。

Marcelo Cantos 回复 18小时 前

@Nawaz:正如我所说,这只是解决方案的开始。你可以支持std::map<>要么通过专门化操作符,要么通过定义一个operator<<为了std::pair<>.

Nawaz 回复 18小时 前

但是,使用 +1Delims类模板!

Kerrek SB 回复 18小时 前

@MC:哦,很好。这看起来很有希望! (顺便说一句,你需要返回类型“std::ostream &amp;”,我一开始忘记了。)

Kerrek SB 回复 18小时 前

嗯,我在 std::vector 上尝试这个时得到“模棱两可的过载”和 std::set…

Marcelo Cantos 回复 18小时 前

是的,我目前正在研究如何防止歧义,这是由于operator<<模板几乎可以匹配任何东西。

Marcelo Cantos 回复 18小时 前

呸,那是一种折磨!我在弄清楚 SFINAE 时认输了(它每次都打败我,该死的),然后就使用了boost::enable_if,它将所有 SFINAE goo 隐藏在一个简单的模板后面。

Kerrek SB 回复 18小时 前

好吧,这行得通——那么,有什么要求? Boost/utility.hpp,并且每个可接受的容器都必须添加到 IsContainer 特化中?好吧,这当然是一个可以打包到某个地方的单个文件的巧妙解决方案。谢谢!

Marcelo Cantos 回复 18小时 前

我刚刚添加了代码来处理T[N].我可能可以将这两个运算符合并为一个,但这实际上可能会增加总代码大小而获得的收益微乎其微。

Kerrek SB 回复 18小时 前

让我直截了当地说:使用这种方法,我是否需要主动将我想使用的每种容器类型列入白名单?

CashCow 回复 18小时 前

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

Yakk - Adam Nevraumont 回复 18小时 前

@KerrekSB 工作版本,有一些变化。大部分代码是一般的“访问元组/迭代”,以及花哨的格式(包括->pairmaps) 在这一点上。漂亮的打印库的核心很好而且很小,这很好。我试图使它易于扩展,不确定是否成功。

Kerrek SB 回复 18小时 前

有趣的。我喜欢容器的模板模板方法,但它是否适用于具有非标准谓词或分配器的自定义容器和 STL 容器? (我做了类似的尝试在 C++0x 中实现 bimap使用可变参数模板。)此外,您似乎没有在打印例程中普遍使用迭代器;为什么明确使用计数器i?

Leonid Volnitsky 回复 18小时 前

什么是带有非标准谓词的容器?将打印匹配签名的自定义容器。目前不支持非标准分配器,但很容易修复。我只是暂时不需要这个。

Leonid Volnitsky 回复 18小时 前

没有充分的理由使用索引而不是迭代器。历史原因。当我有时间时会修复它。

Kerrek SB 回复 18小时 前

“带有非标准谓词的容器”是指类似于std::set使用自定义比较器,或使用自定义相等的 unordered_map。支持这些结构非常重要。

Yashas 回复 18小时 前

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

phuclv 回复 18小时 前

你们怎么了?没有人使用boost::algorithm::join

HolyBlackCat 回复 18小时 前

1.fprintf(stdout, "%s\n", &test[0]);std::cout << test.data(),两者都需要一个空终止向量。 2.“但我会将其包装在 ostream 运算符中” <<修改右操作数的运算符是一个非常糟糕的主意。

alexpanter 回复 18小时 前

我用过fprintf(stdout, "%s\n", &test[0]);在代码中很长时间没有给我带来任何麻烦。有趣的!而且我同意在ostream操作员,但我不喜欢手动循环使用迭代器。不知何故,我觉得像打印一个简单的操作std::vector<char>标准库应该隐藏这些东西。但 C++ 正在不断发展,它可能很快就会到来。

0
Shoe 回答 18小时 前

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

for (auto const& c : path)
std::cout << c << ' ';
Brian 回复 18小时 前

只有当向量的大小在循环范围的主体中没有改变时,这才有效。

Shoe 回复 18小时 前

@BrianP。是的。打印容器的元素不会修改容器的范围。

kleinfreund 回复 18小时 前

这里有什么可取的 – c 作为值副本或作为 const 引用以避免复制元素?

Shoe 回复 18小时 前

@kleinfreund 这取决于向量的内容。例如对于一个向量chars,有可能通过常量引用传递实际上比按值传递更昂贵。但在这里我们谈论的是超微优化。

0
vitaut 回答 18小时 前

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

#include <vector>
#include <fmt/ranges.h>
int main() {
auto v = std::vector<int>{1, 2, 3};
fmt::print("{}", v);
}

印刷

[1, 2, 3]

stdout(好螺栓)。
我不建议重载operator<<对于标准容器,因为它可能会引入 ODR 违规。
免责声明: 我是 {fmt} 的作者。

Karthik Nishanth 回复 18小时 前

std::map 呢?我在文档中找不到任何内容

vitaut 回复 18小时 前

格式化全部支持容器。

Karthik Nishanth 回复 18小时 前

你能给我一个起点吗?我很难找到 fmtlib 的用法fmtlib print std::map作为搜索词。如果这算作菜鸟问题或类似 RTFM 的问题,我深表歉意 🙂

vitaut 回复 18小时 前

这是一个地图的例子:godbolt.org/z/EG7aoE.如您所见,用法没有区别。

Karthik Nishanth 回复 18小时 前

天啊!这真太了不起了godbolt.org/z/h7qxba

0
Chris Redford 回答 18小时 前

我认为最好的方法就是超载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}
JDiMatteo 回复 18小时 前

将 v.size() – 1 存储为 int 可能会损失精度。我在接受的同行评审编辑中修复了这个问题(stackoverflow.com/revisions/23397700/5),但此后再次对其进行编辑,以恢复可能的精度损失。我想这在实践中并不重要,因为向量通常不是那么大。

Chris Redford 回复 18小时 前

不将其存储为变量会降低代码的可读性,这是我不同意的编辑的一部分。我改变了类型lastsize_t.

Vladimir Gamalyan 回复 18小时 前

size_t last = v.size() - 1;看起来多余,你可以使用if (i) out << ", ";之前的条件out << v[i]; 关联

M.M 回复 18小时 前

ADL 找不到此运算符,因为它不在其任何参数的命名空间中。所以它将被任何其他命名空间隐藏operator<<.例子

WhozCraig 回复 18小时 前

如果你要这样做,为什么要测试if (i != last)每一次循环?相反,如果容器不为空,则 (a) 发送第一个元素,然后 (b) 循环发送其余的元素,打印分隔符第一的(作为前缀)。不需要内部循环测试(除了循环条件本身)。只需要一次外环测试。