提问人:forthewinwin 提问时间:5/25/2012 最后编辑:cigienforthewinwin 更新时间:8/8/2023 访问量:1190993
如何打印出矢量的内容?
How do I print out the contents of a vector?
问:
如何将 a 的内容打印到屏幕上?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;
}
}
答:
这是一个工作库,作为一个完整的工作程序呈现,我刚刚破解了它:
#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";
}
它目前仅适用于 和 ,但可以通过扩展专业化来使它适用于大多数容器。我没有过多考虑这个代码是否最小,但我不能立即想到任何我可以剥离为多余的东西。vector
set
IsContainer
编辑:只是为了踢球,我提供了一个处理数组的版本。我不得不排除 char 数组以避免进一步的歧义;它可能仍然会遇到麻烦。wchar_t[]
评论
std::map<>
operator<<
std::pair<>
Delims
operator<<
这已经编辑了几次,我们决定将包装集合的主类称为 。RangePrinter
一旦你写了一次性重载,这应该会自动适用于任何集合,除了你需要一个特殊的地图来打印对,并且可能想要在那里自定义分隔符。operator<<
您还可以在项目上使用一个特殊的“打印”函数,而不仅仅是直接输出它,有点像 STL 算法允许您传入自定义谓词。您将以这种方式使用它,并使用自定义打印机进行 .map
std::pair
您的“默认”打印机只会将其输出到流中。
好的,让我们在自定义打印机上工作。我将把我的外部类改为 .因此,我们有 2 个迭代器和一些分隔符,但尚未自定义如何打印实际项目。RangePrinter
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;
}
现在,默认情况下,只要键和值类型都是可打印的,并且您可以放入自己的特殊项目打印机,以备不时之需(就像使用任何其他类型一样),或者如果您不希望“=”作为分隔符。
我现在正在移动 free-function 来创建这些:
自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:
template<typename Collection>
RangePrinter<typename Collection::const_iterator> rangePrinter
( const Collection& coll, const char * delim=",",
const char * open="[", const char * close="]")
{
return RangePrinter< typename Collection::const_iterator >
( coll.begin(), coll.end(), delim, open, close );
}
然后,您可以将其用于std::set
std::cout << outputFormatter( mySet );
您还可以编写采用自定义打印机的自由功能版本和需要两个迭代器的版本。在任何情况下,它们都会为您解析模板参数,并且您将能够将它们作为临时参数传递。
评论
std::cout << outputFormatter(beginOfRange, endOfRange);
std::pair
std::map
此解决方案的灵感来自 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类型特征,该特征必须专门用于要支持的所有容器。也许可以使用特征来检查 、 、 /,但我不确定我是否建议这样做,因为它可能与符合这些条件但实际上不是容器的东西匹配,例如 .也像 Marcelo 的版本一样,它使用可以专门用于指定要使用的分隔符的模板。value_type
const_iterator
begin()
end()
std::basic_string
主要区别在于我围绕 构建了我的版本,它的工作方式类似于 ,但不会在最后一项之后打印分隔符。容器的格式设置由 完成,它可以直接用于打印没有is_container特征的容器,或指定不同的分隔符类型。pretty_ostream_iterator
std::ostream_iterator
print_container_helper
我还定义了is_container和分隔符,因此它适用于具有非标准谓词或分配器的容器,以及 char 和 wchar_t。运算符<<函数本身也被定义为同时处理 char 和 wchar_t 流。
最后,我使用了 ,它作为 C++0x 的一部分提供,适用于 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)及更高版本。这样就不依赖于 Boost。std::enable_if
评论
<i, j>
[i j]
print_container_helper
print_container_helper
operator<<
pair<int, int>
pair<double, string>
MyDels
std::cout << CustomPrinter<MyDels>(x);
std::cout << CustomDelims<"{", ":", "}">(x);
const char *
我的解决方案是simple.h,它是scc包的一部分。所有标准容器、映射、集合、c 阵列都是可打印的。
评论
i
std::set
问题可能出在上一个循环中:
(x = 17; isalpha(firstsquare); x++)
此循环将根本不运行(如果不是字母顺序)或将永远运行(如果它是字母顺序)。原因是它不会随着递增而改变。firstsquare
firstsquare
x
我看到两个问题。如中所述
for (x = 17; isalpha(firstsquare); x++)
要么有一个无限循环,要么根本没有执行过,如果入口字符与 Nothing 不同,则 Nothing 在推送到路径向量,使其为空,从而在屏幕上不打印任何内容。您可以测试后者检查或打印。if (entrance == 'S')
'S'
path.empty()
path.size()
无论哪种方式,使用字符串而不是向量不是更好吗?您也可以像数组一样访问字符串内容,查找字符,提取子字符串并轻松打印字符串(无需循环)。
用字符串做这一切可能是让它以一种不那么复杂的方式编写它的方法,并且更容易发现问题。
如果你有 C++ 11 编译器,我建议使用基于范围的 for 循环(见下文);或者使用迭代器。但是您有几种选择,我将在下面解释所有这些选择。
基于范围的 for 循环 (C++11)
在 C++11(及更高版本)中,您可以使用新的基于范围的 for 循环,如下所示:
std::vector<char> path;
// ...
for (char i: path)
std::cout << i << ' ';
for-loop 语句中的类型应为向量元素的类型,而不是整数索引类型。换言之,由于 是 类型,因此在基于范围的 for 循环中应该出现的类型是 。但是,您可能经常看到显式类型替换为占位符类型:char
path
path
std::vector<char>
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 循环中更改此复制值,您可以强制 of 的类型如下所示:i
i
const char
for (const auto i: path) {
i = '_'; // this will now produce a compiler error
std::cout << i << ' ';
}
如果要修改 中的项,以便这些更改保留在 for 循环之外,那么可以使用如下所示的引用:path
path
for (auto& i: path) {
i = '_'; // changes to 'i' will now also change the
// element in 'path' itself to that value
std::cout << i << ' ';
}
即使你不想修改,如果对象的复制成本很高,你也应该使用 const 引用而不是按值复制:path
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) / 自动 (C++11)
这不是另一种解决方案,而是对上述解决方案的补充。如果您使用的是 C++11 标准(或更高版本),则可以使用关键字来帮助提高可读性:iterator
auto
for (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
这里的类型将是非常量的(即,编译器将用作 的类型)。这是因为我们调用了该方法,因此编译器从中推断出 的类型。如果我们改用该方法(“c”表示 const),那么将是一个:i
std::vector<char>::iterator
i
begin
i
cbegin
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++ 编译器(或者出于某种原因不喜欢类型别名语法),那么您可以使用更传统的: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] << ' ';
为什么不优先使用它而不是解决方案呢?对于简单的情况,您可以这样做,但使用 an 会带来几个优点,我在上面简要概述了这些优点。因此,我的建议是避免使用这种方法,除非你有充分的理由。iterator
iterator
std::copy (C++ 11)
请看约书亚的回答。您可以使用 STL 算法将矢量内容复制到输出流中。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有充分的理由。std::copy
std::ranges::copy (C++20)
为了完整起见,C++ 引入了范围,它可以作用于 a 的整个范围,因此不需要 和 :std::vector
begin
end
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
std::vector<char> path;
// ...
std::ranges::copy(path, std::ostream_iterator<char>(std::cout, " "));
除非您有一个最新的编译器(在 GCC 上显然至少是 10.1 版),否则即使您可能有一些可用的 C++20 功能,您也可能没有范围支持。
重载 std::ostream::operator<<
另请参阅下面的 Chris 的回答。这更像是对其他答案的补充,因为您仍然需要在重载中实现上述解决方案之一,但好处是代码更简洁。以下是使用上述解决方案的方法:std::ranges::copy
#include <iostream>
#include <vector>
#include <iterator> // for std::ostream_iterator
#include <algorithm> // for std::ranges::copy depending on lib support
using Path = std::vector<char>; // type alias for std::vector<char>
std::ostream& operator<< (std::ostream& out, const Path& v) {
if ( !v.empty() ) {
out << '[';
std::ranges::copy(v, std::ostream_iterator<char>(out, ", "));
out << "\b\b]"; // use two ANSI backspace characters '\b' to overwrite final ", "
}
return out;
}
int main() {
Path path{'/', 'f', 'o', 'o'};
// will output: "path: [/, f, o, o]"
std::cout << "path: " << path << std::endl;
return 0;
}
现在,您可以像基本类型一样将对象传递到输出流。使用上述任何其他解决方案也应该同样简单。Path
结论
此处介绍的任何解决方案都将起作用。这取决于你(以及上下文或你的编码标准)哪一个是“最好的”。任何比这更详细的东西可能最好留给另一个问题,在那里可以正确评估利弊,但一如既往,用户偏好将始终发挥作用:提出的解决方案都不是客观错误的,但有些解决方案对每个编码人员来说看起来都更好。
补遗
这是我之前发布的解决方案的扩展解决方案。由于那篇帖子一直受到关注,我决定扩展它并参考这里发布的其他优秀解决方案,至少是我个人过去至少使用过一次的解决方案。但是,我鼓励读者查看下面的答案,因为可能有一些很好的建议我已经忘记或不知道了。
评论
0
vector::size()
at()
at
for (auto const &i: path) std::cout << i << ' ';
一个更简单的方法是使用标准复制算法:
#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>
int main() {
/* Set up vector to hold chars a-z */
std::vector<char> path;
for (int ch = 'a'; ch <= 'z'; ++ch)
path.push_back(ch);
/* Print path vector to console */
std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));
return 0;
}
ostream_iterator就是所谓的迭代器适配器。它被模板化到要打印到流的类型上(在本例中为 )。 (又名控制台输出)是我们要写入的流,空格字符 () 是我们希望在存储在向量中的每个元素之间打印的内容。char
cout
" "
这个标准算法很强大,许多其他算法也是如此。标准库为您提供的功能和灵活性使它如此出色。试想一下:您只需一行代码即可将矢量打印到控制台。您不必处理带有分隔符的特殊情况。您无需担心 for 循环。标准库为您完成所有工作。
评论
vector<pair<int, struct node>>
operator<<
我将在这里添加另一个答案,因为我提出了一种与上一个不同的方法,那就是使用语言环境方面。
基础知识在这里
从本质上讲,你要做的是:
- 创建一个派生自 的类。轻微的缺点是您需要在某个地方使用编译单元来保存其 ID。我们称之为 MyPrettyVectorPrinter。你可能会给它一个更好的名字,并为配对和地图创建一个。
std::locale::facet
- 在流函数中,您可以检查
std::has_facet< MyPrettyVectorPrinter >
- 如果返回 true,则使用
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
- 小面对象将具有分隔符的值,您可以读取它们。如果未找到分面,则打印函数 () 将提供默认分面。请注意,您可以对读取向量执行相同的操作。
operator<<
我喜欢这种方法,因为您可以使用默认打印,同时仍然能够使用自定义覆盖。
缺点是,如果在多个项目中使用,则需要为分面提供一个库(因此不能只是标题),并且您需要注意创建新区域设置对象的费用。
我把它写成一个新的解决方案,而不是修改我的另一个解决方案,因为我相信这两种方法都是正确的,你可以选择。
评论
在 C++11 中,您现在可以使用基于范围的 for 循环:
for (auto const& c : path)
std::cout << c << ' ';
评论
char
我认为最好的方法是通过将此函数添加到您的程序中来重载: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}
评论
last
size_t
size_t last = v.size() - 1;
看起来多余,你可以在链接之前使用条件if (i) out << ", ";
out << v[i];
if (i != last)
for_each
+ lambda 表达式怎么样:
#include <vector>
#include <algorithm>
// ...
std::vector<char> vec;
// ...
std::for_each(
vec.cbegin(),
vec.cend(),
[] (const char c) {std::cout << c << " ";}
);
// ...
当然,对于这项具体任务,基于范围的解决方案是最优雅的解决方案,但这个解决方案也提供了许多其他可能性。
解释
该算法采用一个输入范围和一个可调用对象,在该范围的每个元素上调用此对象。输入范围由两个迭代器定义。可调用对象可以是函数、指向函数的指针、重载类的对象,或者在本例中是 lambda 表达式。此表达式的参数与 vector 中的元素类型匹配。for_each
() operator
这种实现的美妙之处在于您从 lambda 表达式中获得的强大功能 - 您可以将这种方法用于更多的事情,而不仅仅是打印向量。
这里的目标是使用 ADL 来自定义我们的打印方式。
传入格式化程序标记,并覆盖标记命名空间中的 4 个函数(before、after、between 和 descend)。这改变了格式化程序在迭代容器时打印“装饰”的方式。
一个默认的格式化程序,用于映射、元组、字符串以及包含的其他所有内容。{(a->b),(c->d)}
(a,b,c)
"hello"
[x,y,z]
它应该“只适用于”第三方可迭代类型(并将它们视为“其他一切”)。
如果您想为第三方可迭代对象自定义装饰,只需创建自己的标签即可。处理地图下降需要一些工作(您需要重载才能返回)。也许有一种更干净的方法可以做到这一点,不确定。pretty_print_descend( your_tag
pretty_print::decorator::map_magic_tag<your_tag>
一个小库来检测可迭代性和元组性:
namespace details {
using std::begin; using std::end;
template<class T, class=void>
struct is_iterable_test:std::false_type{};
template<class T>
struct is_iterable_test<T,
decltype((void)(
(void)(begin(std::declval<T>())==end(std::declval<T>()))
, ((void)(std::next(begin(std::declval<T>()))))
, ((void)(*begin(std::declval<T>())))
, 1
))
>:std::true_type{};
template<class T>struct is_tupleoid:std::false_type{};
template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
// template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};
template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};
一个库,允许我们访问可迭代或元组类型对象的内容:
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
using std::begin; using std::end;
auto&& b = begin(c);
auto&& e = end(c);
if (b==e)
return;
std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
using std::begin; using std::end;
auto it = begin(c);
auto&& e = end(c);
if (it==e)
return;
it = std::next(it);
for( ; it!=e; it = std::next(it) ) {
f(*it);
}
}
namespace details {
template<class Tup, class F>
void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is, class Tup, class F>
void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
}
template<class Tup, class F>
void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
template<size_t... Is,class Tup, class F>
void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
int unused[] = {0,((void)(
f( std::get<Is>(std::forward<Tup>(tup)) )
),0)...};
(void)(unused);
}
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
一个漂亮的打印库:
namespace pretty_print {
namespace decorator {
struct default_tag {};
template<class Old>
struct map_magic_tag:Old {}; // magic for maps
// Maps get {}s. Write trait `is_associative` to generalize:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('{');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
s << CharT('}');
}
// tuples and pairs get ():
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT('(');
}
template<class CharT, class Traits, class Tup >
std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
s << CharT(')');
}
// strings with the same character type get ""s:
template<class CharT, class Traits, class...Xs >
void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
template<class CharT, class Traits, class...Xs >
void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
s << CharT('"');
}
// and pack the characters together:
template<class CharT, class Traits, class...Xs >
void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}
// map magic. When iterating over the contents of a map, use the map_magic_tag:
template<class...Xs>
map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
return {};
}
template<class old_tag, class C>
old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
return {};
}
// When printing a pair immediately within a map, use -> as a separator:
template<class old_tag, class CharT, class Traits, class...Xs >
void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
s << CharT('-') << CharT('>');
}
}
// default behavior:
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT('[');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(']');
}
template<class CharT, class Traits, class Tag, class Container >
void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
s << CharT(',');
}
template<class Tag, class Container>
Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
return std::forward<Tag>(tag);
}
// print things by default by using <<:
template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
os << std::forward<Scalar>(scalar);
}
// for anything visitable (see above), use the pretty print algorithm:
template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
visit_first( c, [&](auto&& elem) {
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
visit_all_but_first( c, [&](auto&& elem) {
pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
});
pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
}
}
测试代码:
int main() {
std::vector<int> x = {1,2,3};
pretty_print::print( std::cout, x );
std::cout << "\n";
std::map< std::string, int > m;
m["hello"] = 3;
m["world"] = 42;
pretty_print::print( std::cout, m );
std::cout << "\n";
}
这确实使用了 C++14 功能(一些别名和 lambda),但没有一个是必需的。_t
auto&&
评论
->
pair
map
在 C++ 11 中,基于范围的 for 循环可能是一个很好的解决方案:
vector<char> items = {'a','b','c'};
for (char n : items)
cout << n << ' ';
输出:
a b c
只需将容器复制到控制台即可。
std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));
应输出:
1 2 3 4
现在,该代码在多个场合被证明是方便的,并且由于使用率非常低,因此我觉得进行自定义的费用非常低。因此,我决定在 MIT 许可下发布它,并提供一个 GitHub 存储库,可以在其中下载标题和一个小示例文件。
http://djmuw.github.io/prettycc
0. 前言和措辞
就此答案而言,“装饰”是一组前缀字符串、分隔符字符串和后缀字符串。 其中前缀字符串在容器值之前插入到流中,后缀字符串在容器值之后插入(请参阅 2。目标容器)。 分隔符字符串插入到相应容器的值之间。
注意:实际上,这个答案并没有 100% 解决这个问题,因为修饰不是严格编译的时间常数,因为需要运行时检查来检查是否已将自定义修饰应用于当前流。尽管如此,我认为它有一些不错的功能。
注意2:可能有一些小错误,因为它还没有经过很好的测试。
1. 总体思路/用法
使用时无需额外代码
它要保持尽可能简单
#include <vector>
#include "pretty.h"
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
return 0;
}
易于定制...
...对于特定流对象
#include <vector>
#include "pretty.h"
int main()
{
// set decoration for std::vector<int> for cout object
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
或对于所有流:
#include <vector>
#include "pretty.h"
// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")
int main()
{
std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
return 0;
}
粗略描述
- 该代码包括一个类模板,该模板为任何类型提供默认修饰
- 可以专门用于更改 (a) 某些类型的默认装饰,它是
- 使用 / 提供的私有存储来保存指向特定流上特定类型的对象的指针。
ios_base
xalloc
pword
pretty::decor
如果未显式设置此流的对象,则调用该对象以获取给定类型的默认修饰。
该类将专门用于自定义默认装饰。pretty::decor<T>
pretty::defaulted<T, charT, chartraitT>::decoration()
pretty::defaulted
2. 目标对象/容器
此代码的“漂亮装饰”的目标对象是具有obj
- 重载和定义(包括 C 样式数组),
std::begin
std::end
- 拥有并通过 ADL 提供,
begin(obj)
end(obj)
- 属于类型
std::tuple
- 或类型 。
std::pair
该代码包括一个特征,用于标识具有范围特征 (/) 的类。
(不过,不包括检查是否为有效表达式。begin
end
begin(obj) == end(obj)
该代码在全局命名空间中提供 s,仅适用于没有更专用版本的可用类。
因此,例如,在此代码中使用运算符打印时,尽管具有有效的 / 对。operator<<
operator<<
std::string
begin
end
3. 利用和定制
可以为每种类型(不同的 s)和流(不是流类型!
(即,对于不同的流对象,可以有不同的装饰。tuple
std::vector<int>
A) 默认装饰
默认前缀为 (nothing),默认后缀为 (nothing),而默认分隔符为 (comma+space)。""
", "
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)
将宏用于流PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
char
宏扩展为
namespace pretty {
template< __VA_ARGS__ >
struct defaulted< TYPE > {
static decor< TYPE > decoration() {
return { PREFIX, DELIM, POSTFIX };
}
};
}
启用上述部分专业化重写为
PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)
或插入完整的专业化,例如
PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")
包含流的另一个宏:。wchar_t
PRETTY_DEFAULT_WDECORATION
C) 对溪流进行装饰
该函数用于对某个流进行装饰。
有超载采取
- 一个字符串参数作为分隔符(采用默认类中的前缀和后缀)
- 或三个字符串参数组合完整的装饰pretty::decoration
给定类型和流的完整装饰
float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");
// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}
// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2
自定义给定流的分隔符
PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")
std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}
v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}
v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))
4.特殊处理std::tuple
此代码不允许对每个可能的元组类型进行专用化,而是将任何可用的修饰应用于所有类型的元组。std::tuple<void*>
std::tuple<...>
5. 从流中删除自定义装饰
要返回到给定类型的默认修饰,请在流上使用函数模板。pretty::clear
s
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_
重载运算符<<:
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
这个答案是基于 Zorawar 的答案,但我无法在那里发表评论。
您可以通过改用 和 来制作 (C++11)/ 版本auto
typedef
const
cbegin
cend
for (auto i = path.cbegin(); i != path.cend(); ++i)
std::cout << *i << ' ';
使用但不带额外的尾随分隔符std::copy
使用 std
::copy 的替代/修改方法(最初用于@JoshuaKravtiz答案),但在最后一个元素后不包括额外的尾随分隔符:
#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 (auto i = path.begin(); i != path.end(); ++i)
std::cout << *i << ' ';
for(int i=0; i<path.size(); ++i)
std::cout << path[i] << ' ';
评论
在 C++23 中,您将能够使用 std::p rint
打印大多数标准类型,包括 .例如:std::vector
import std;
int main() {
auto v = std::vector{1, 2, 3};
std::print("{}", v);
}
指纹
[1, 2, 3]
自。stdout
同时,您可以使用 {fmt} 库,它基于:std::print
#include <vector>
#include <fmt/ranges.h>
int main() {
auto v = std::vector<int>{1, 2, 3};
fmt::print("{}", v);
}
Godbolt:https://godbolt.org/z/xEdz15
我不建议对标准容器进行超载,因为这可能会带来ODR违规。operator<<
免责声明:我是 {fmt} 的作者,并且 .std::format
std::print
评论
fmtlib print std::map
从最早的 BoostCon(现在称为 CppCon)中出来后,我和另外两个人合作开发了一个库来做到这一点。主要症结在于需要扩展。事实证明,对于提升库来说,这是不行的。namespace std
不幸的是,代码的链接不再起作用,但您可能会在讨论中找到一些有趣的花絮(至少那些没有谈论如何命名的花絮!
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;
}
}
如果 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';
}
评论
boost::algorithm::join
您可以使用 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";
}
我写了一个打印任何可迭代对象的 an,其中包括自定义容器、标准容器和具有已知边界的数组。需要 c++11:operator<<
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 ){
ostrm << "[";
for( int j = 0, n = vec.size(); j < n; ++j ){
ostrm << " " << vec[ j ] << " ,"[ j < n - 1 ];
}
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) 获取标头即可。
使用 C++23,您可以简单地编写:
std::print("{}", vec);
这很有效。使用 C++20,您可以使用 和 运算符:std::format
<<
std::cout << std::format("{}", vec);
当然,您需要包含适当的标准库标头(或 )。#include <print>
#include <format>
对于较旧的 C++ 版本 - 请参阅其他答案。
一个解决方案使用了该库nlohmann::json
// Create by [email protected] at 2023-08-08 10:14:27+0800
#include <nlohmann/json.hpp>
#include <iostream>
int main() {
std::cout << nlohmann::json(std::vector<int>{1, 2, 3}) << std::endl;
std::cout << nlohmann::json(std::vector<int>{1, 2, 3}).dump() << std::endl;
std::cout << nlohmann::json(std::vector<std::string>{"hello", "world"}) << std::endl;
std::cout << nlohmann::json(std::vector<std::string>{"hello", "world"}).dump() << std::endl;
}
控制台输出
[1,2,3]
[1,2,3]
["hello","world"]
["hello","world"]
下一个:对基类成员数据的派生模板类访问
评论
operator<<
std
boost.fusion
cout << vector<tuple<int,array<int,3>>>(...) << endl;