如何打印向量的内容?
如何将 std::vector
的内容打印到屏幕上?
实现以下运算符<<的解决方案也很好:
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;
}
}
How do I print out the contents of a std::vector
to the screen?
A solution that implements the following operator<<
would be nice as well:
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?
}
Here is what I have so far, without a separate 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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(30)
如果您有 C++11 编译器,我建议使用基于范围的 for 循环(见下文);或者使用迭代器。但您有多种选择,我将在下文中解释所有这些选择。
基于范围的 for 循环 (C++11)
,您可以使用新的基于范围的 for 循环,如下所示:
在 C++11(及更高版本)中 for-loop 语句应该是向量路径元素的类型,而不是整数索引类型。换句话说,由于
path
的类型为std::vector
,因此应出现在基于范围的 for 循环中的类型为char< /代码>。但是,您可能经常会看到显式类型被替换为
auto
占位符类型:无论您使用显式类型还是
auto
关键字,对象i
的值是path
对象中实际项目的副本。因此,循环中对i
的所有更改都不会保留在path
本身中:如果您希望禁止更改
i
的此复制值在 for 循环中,您也可以将i
的类型强制为const char
,如下所示:如果您想修改
中的项目path
以便这些更改保留在 for 循环之外的path
中,然后您可以使用如下引用:即使您不想修改
path< /code>,如果对象的复制成本很高,您应该使用 const 引用而不是按值复制:
迭代器
在 C++11 之前,规范的解决方案是使用迭代器,这仍然是完全可以接受的。它们的用法如下:
如果要在 for 循环中修改向量的内容,请使用
iterator
而不是const_iterator
。补充: typedef / type alias (C++11) / auto (C++11)
这不是另一种解决方案,而是对上述迭代器解决方案的补充。如果您使用的是 C++11 标准(或更高版本),那么您可以使用
auto
关键字来提高可读性:这里::iterator 作为
i
的类型将是非 - const (即编译器将使用 std::vectori
的类型)。这是因为我们调用了begin
方法,因此编译器从中推导出i
的类型。如果我们改为调用cbegin
方法(“c”代表 const),则i
将是一个std::vector::const_iterator
:如果您对编译器推导类型感到不舒服,那么在 C++11 中,您可以使用类型别名来避免一直键入向量(这是一个好习惯):
如果您不这样做可以访问 C++11 编译器(或者出于某种原因不喜欢类型别名语法),那么您可以使用更传统的
typedef
:旁注:
此时,您可以或以前可能没有遇到过迭代器,您可能听说过也可能没有听说过迭代器是您“应该”使用的,并且可能想知道为什么。答案并不容易理解,但简而言之,其思想是迭代器是一种抽象,可以让您免受操作细节的影响。
有一个对象(迭代器)来执行您想要的操作(如顺序访问),而不是您自己编写详细信息(“详细信息”是实际访问向量元素的代码),这很方便。您应该注意到,在 for 循环中,您只要求迭代器返回一个值(
*i
,其中i
是迭代器)——您永远不会直接与path
本身交互。逻辑是这样的:您创建一个迭代器并给它您想要循环的对象(iterator i = path.begin()
),然后您所做的就是要求迭代器获取您的下一个值 (*i
);你永远不必担心迭代器到底是如何做到这一点的——那是它的事,而不是你的事。好吧,但是有什么意义呢?好吧,想象一下获取一个值是否并不简单。如果涉及一些工作怎么办?您不必担心,因为迭代器已经为您处理了这些问题——它会整理细节,您所需要做的就是向它询问一个值。此外,如果将容器从
std::vector
更改为其他内容会怎样?理论上,即使如何访问新容器中的元素的详细信息发生变化,您的代码也不会改变:请记住,迭代器会在幕后为您整理所有详细信息,因此您根本不需要更改代码-- 您只需向迭代器询问容器中的下一个值,与以前相同。因此,虽然这对于循环遍历向量来说似乎有些令人困惑,但迭代器的概念背后有充分的理由,因此您不妨习惯使用它们。
索引
您还可以使用整数类型显式地对 for 循环中向量的元素进行索引:
如果要执行此操作,最好使用容器的成员类型(如果它们可用且合适)。
std::vector
有一个名为size_type
的成员类型用于此作业:它是size
方法返回的类型。为什么不优先使用它而不是迭代器解决方案呢?对于简单的情况,您可以这样做,但是使用迭代器会带来几个优点,我在上面简要概述了这些优点。因此,我的建议是避免使用这种方法,除非您有充分的理由。
std::copy (C++11)
请参阅约书亚的回答。您可以使用 STL 算法 std::copy 将向量内容复制到输出流上。我没有什么要补充的,只是说我不使用这种方法;但除了习惯之外,没有什么充分的理由。
std::ranges::copy (C++20)
为了完整性,C++20 引入了范围,它可以作用于
std::vector
的整个范围,因此不需要begin
和end
:除非你有一个最新的编译器(显然在 GCC 上至少版本 10.1),即使您可能有一些可用的 C++20 功能,您也可能不会获得范围支持。
重载 std::ostream::operator<<
另请参阅下面克里斯的回答。这更多的是对其他答案的补充,因为您仍然需要在重载中实现上述解决方案之一,但好处是代码更干净。这就是您如何使用上面的
std::ranges::copy
解决方案:现在您可以像基本类型一样将
Path
对象传递到输出流。使用上述任何其他解决方案也应该同样简单。结论
这里提出的任何解决方案都可以工作。哪一个是“最好的”取决于您(以及上下文或您的编码标准)。任何比这更详细的内容可能最好留给另一个问题,可以正确评估优缺点,但一如既往,用户偏好始终会发挥作用:所提出的解决方案都不是客观上错误的,但有些解决方案对每个编码员来说看起来会更好。
附录
这是我之前发布的解决方案的扩展解决方案。由于该帖子不断受到关注,我决定对其进行扩展,并参考此处发布的其他优秀解决方案,至少是我个人过去至少使用过一次的解决方案。然而,我鼓励读者查看下面的答案,因为可能有一些我已经忘记或不知道的好建议。
If you have a C++11 compiler, I would suggest using a range-based for-loop (see below); or else use an iterator. But you have several options, all of which I will explain in what follows.
Range-based for-loop (C++11)
In C++11 (and later) you can use the new range-based for-loop, which looks like this:
The type
char
in the for-loop statement should be the type of the elements of the vectorpath
and not an integer indexing type. In other words, sincepath
is of typestd::vector<char>
, the type that should appear in the range-based for-loop ischar
. However, you will likely often see the explicit type replaced with theauto
placeholder type:Regardless of whether you use the explicit type or the
auto
keyword, the objecti
has a value that is a copy of the actual item in thepath
object. Thus, all changes toi
in the loop are not preserved inpath
itself:If you would like to proscribe being able to change this copied value of
i
in the for-loop as well, you can force the type ofi
to beconst char
like this:If you would like to modify the items in
path
so that those changes persist inpath
outside of the for-loop, then you can use a reference like so:and even if you don't want to modify
path
, if the copying of objects is expensive you should use a const reference instead of copying by value:Iterators
Before C++11 the canonical solution would have been to use an iterator, and that is still perfectly acceptable. They are used as follows:
If you want to modify the vector's contents in the for-loop, then use
iterator
rather thanconst_iterator
.Supplement: typedef / type alias (C++11) / auto (C++11)
This is not another solution, but a supplement to the above
iterator
solution. If you are using the C++11 standard (or later), then you can use theauto
keyword to help the readability:Here the type of
i
will be non-const (i.e., the compiler will usestd::vector<char>::iterator
as the type ofi
). This is because we called thebegin
method, so the compiler deduced the type fori
from that. If we call thecbegin
method instead ("c" for const), theni
will be astd::vector<char>::const_iterator
:If you're not comfortable with the compiler deducing types, then in C++11 you can use a type alias to avoid having to type the vector out all the time (a good habit to get into):
If you do not have access to a C++11 compiler (or don't like the type alias syntax for whatever reason), then you can use the more traditional
typedef
:Side note:
At this point, you may or may not have come across iterators before, and you may or may not have heard that iterators are what you are "supposed" to use, and may be wondering why. The answer is not easy to appreciate, but, in brief, the idea is that iterators are an abstraction that shield you from the details of the operation.
It is convenient to have an object (the iterator) that does the operation you want (like sequential access) rather than you writing the details yourself (the "details" being the code that does the actual accessing of the elements of the vector). You should notice that in the for-loop you are only ever asking the iterator to return you a value (
*i
, wherei
is the iterator) -- you are never interacting withpath
directly itself. The logic goes like this: you create an iterator and give it the object you want to loop over (iterator i = path.begin()
), and then all you do is ask the iterator to get the next value for you (*i
); you never had to worry exactly how the iterator did that -- that's its business, not yours.OK, but what's the point? Well, imagine if getting a value wasn't simple. What if it involves a bit of work? You don't need to worry, because the iterator has handled that for you -- it sorts out the details, all you need to do is ask it for a value. Additionally, what if you change the container from
std::vector
to something else? In theory, your code doesn't change even if the details of how accessing elements in the new container does: remember, the iterator sorts all the details out for you behind the scenes, so you don't need to change your code at all -- you just ask the iterator for the next value in the container, same as before.So, whilst this may seem like confusing overkill for looping through a vector, there are good reasons behind the concept of iterators and so you might as well get used to using them.
Indexing
You can also use a integer type to index through the elements of the vector in the for-loop explicitly:
If you are going to do this, it's better to use the container's member types, if they are available and appropriate.
std::vector
has a member type calledsize_type
for this job: it is the type returned by thesize
method.Why not use this in preference to the
iterator
solution? For simple cases, you can do that, but using aniterator
brings several advantages, which I have briefly outlined above. As such, my advice would be to avoid this method unless you have good reasons for it.std::copy (C++11)
See Joshua's answer. You can use the STL algorithm
std::copy
to copy the vector contents onto the output stream. I don't have anything to add, except to say that I don't use this method; but there's no good reason for that besides habit.std::ranges::copy (C++20)
For completeness, C++20 introduced ranges, which can act on the whole range of a
std::vector
, so no need forbegin
andend
:Unless you have a recent compiler (on GCC apparently at least version 10.1), likely you will not have ranges support even if you might have some C++20 features available.
Overload std::ostream::operator<<
See also Chris's answer below. This is more a complement to the other answers since you will still need to implement one of the solutions above in the overloading, but the benefit is much cleaner code. This is how you could use the
std::ranges::copy
solution above:Now you can pass your
Path
objects to your output stream just like fundamental types. Using any of the other solutions above should also be equally straightforward.Conclusion
Any of the solutions presented here will work. It's up to you (and context or your coding standards) on which one is the "best". Anything more detailed than this is probably best left for another question where the pros/cons can be properly evaluated, but as always user preference will always play a part: none of the solutions presented are objectively wrong, but some will look nicer to each coder.
Addendum
This is an expanded solution of an earlier one I posted. Since that post kept getting attention, I decided to expand on it and refer to the other excellent solutions posted here, at least those that I have personally used in the past at least once. I would, however, encourage the reader to look at the answers below because there are probably good suggestions that I have forgotten, or do not know, about.
更简单的方法是使用标准的复制算法:
ostream_iterator 就是所谓的迭代器适配器。它通过打印到流的类型进行模板化(在本例中为
char
)。cout
(又名控制台输出)是我们要写入的流,空格字符(" "
)是我们要在存储在向量中的每个元素之间打印的内容。这个标准算法非常强大,许多其他算法也是如此。标准库为您提供的强大功能和灵活性使其如此出色。想象一下:只需一行代码即可将向量打印到控制台。您不必使用分隔符处理特殊情况。您无需担心 for 循环。标准库为您完成这一切。
A much easier way to do this is with the standard copy algorithm:
The ostream_iterator is what's called an iterator adaptor. It is templatized over the type to print out to the stream (in this case,
char
).cout
(aka console output) is the stream we want to write to, and the space character (" "
) is what we want printed between each element stored in the vector.This standard algorithm is powerful and so are many others. The power and flexibility the standard library gives you are what make it so great. Just imagine: you can print a vector to the console with just one line of code. You don't have to deal with special cases with the separator character. You don't need to worry about for-loops. The standard library does it all for you.
在 C++23 中,您可以使用
std::print
打印大多数标准类型,包括 std::vector 。例如:打印
到
stdout
。在
std::print
广泛使用之前,您可以使用{fmt} 库 ,它基于:godbolt: https://godbolt.org/z/xEdz15
我不会建议对您无法控制的类型(例如标准容器)重载
operator<<
。免责声明:我是 {fmt}、
std::format
和std::print
的作者。In C++23 you can use
std::print
to print most standard types includingstd::vector
. For example:prints
to
stdout
.Until
std::print
is widely available you can use the {fmt} library, it is based on:godbolt: https://godbolt.org/z/xEdz15
I wouldn't recommend overloading
operator<<
for types you don't control such as standard containers.Disclaimer: I'm the author of {fmt},
std::format
andstd::print
.该解决方案的灵感来自 Marcelo 的解决方案,并进行了一些更改:
与 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。运算符<<函数本身也被定义为可以使用 char 和 wchar_t 流。
最后,我使用了
std::enable_if
,它作为 C++0x 的一部分提供,并且适用于 Visual C++ 2010 和 g++ 4.3(需要 -std=c++0x 标志)以及稍后。这样就不再依赖Boost。This solution was inspired by Marcelo's solution, with a few changes:
Like Marcelo's version, it uses an is_container type trait that must be specialized for all containers that are to be supported. It may be possible to use a trait to check for
value_type
,const_iterator
,begin()
/end()
, but I'm not sure I'd recommend that since it might match things that match those criteria but aren't actually containers, likestd::basic_string
. Also like Marcelo's version, it uses templates that can be specialized to specify the delimiters to use.The major difference is that I've built my version around a
pretty_ostream_iterator
, which works similar to thestd::ostream_iterator
but doesn't print a delimiter after the last item. Formatting the containers is done by theprint_container_helper
, which can be used directly to print containers without an is_container trait, or to specify a different delimiters type.I've also defined is_container and delimiters so it will work for containers with non-standard predicates or allocators, and for both char and wchar_t. The operator<< function itself is also defined to work with both char and wchar_t streams.
Finally, I've used
std::enable_if
, which is available as part of C++0x, and works in Visual C++ 2010 and g++ 4.3 (needs the -std=c++0x flag) and later. This way there is no dependency on Boost.在 C++11 中,您现在可以使用基于范围的 for 循环:
In C++11 you can now use a range-based for loop:
我认为最好的方法是通过将此函数添加到您的程序中来重载
operator<<
:然后您可以在任何操作上使用
<<
运算符可能的向量,假设其元素也有 ostream&运算符<< 定义:输出:
I think the best way to do this is to just overload
operator<<
by adding this function to your program:Then you can use the
<<
operator on any possible vector, assuming its elements also haveostream& operator<<
defined:Outputs:
for_each
+ lambda 表达式怎么样:当然,基于范围的 for 是针对此具体任务的最优雅的解决方案,但是这个给出了还有许多其他可能性。
说明
for_each
算法采用一个输入范围和一个可调用对象,在该范围的每个元素上调用此对象。 输入范围由两个迭代器定义。 可调用对象可以是一个函数、指向函数的指针、重载()运算符
的类的对象,或者在本例中,一个 lambda 表达式< /强>。该表达式的参数与向量中元素的类型匹配。这种实现的美妙之处在于您从 lambda 表达式中获得的强大功能 - 您可以使用这种方法做更多的事情,而不仅仅是打印向量。
How about
for_each
+ lambda expression:Of course, a range-based for is the most elegant solution for this concrete task, but this one gives many other possibilities as well.
Explanation
The
for_each
algorithm takes an input range and a callable object, calling this object on every element of the range. An input range is defined by two iterators. A callable object can be a function, a pointer to function, an object of a class which overloads() operator
or as in this case, a lambda expression. The parameter for this expression matches the type of the elements from vector.The beauty of this implementation is the power you get from lambda expressions - you can use this approach for a lot more things than just printing the vector.
这已经被编辑了几次,我们决定调用包装集合
RangePrinter
的主类。一旦您编写了一次性运算符<<重载,这应该会自动适用于任何集合,除非您需要一个特殊的映射来打印该对,并且可能需要自定义分隔符那里。
您还可以在项目上使用特殊的“打印”函数,而不是直接输出它,有点像 STL 算法允许您传入自定义谓词。对于
map
,您可以通过这种方式使用它,并为std::pair
使用自定义打印机。您的“默认”打印机只会将其输出到流中。
好的,让我们开始定制打印机。我将把我的外部类更改为
RangePrinter
。所以我们有 2 个迭代器和一些分隔符,但没有定制如何打印实际项目。现在默认情况下,只要键和值类型都可打印,它就适用于地图,并且您可以在它们不可打印时放入您自己的特殊项目打印机(就像您可以使用任何其他类型一样),或者如果您不想要“=”作为分隔符。
我现在将自由函数移动到最后来创建它们:
自由函数(迭代器版本)看起来像这样,你甚至可以有默认值:
然后你可以将它用于
std::set
您还可以编写带有自定义打印机的自由功能版本和带有两个迭代器 自由功能版本。无论如何,他们都会为您解析模板参数,并且您将能够将它们作为临时参数传递。
This has been edited a few times, and we have decided to call the main class that wraps a collection
RangePrinter
.This should work automatically with any collection once you have written the one-time
operator<<
overload, except that you will need a special one for maps to print the pair, and may want to customize the delimiter there.You could also have a special "print" function to use on the item instead of just outputting it directly, a bit like STL algorithms allow you to pass in custom predicates. With
map
you would use it this way, with a custom printer for thestd::pair
.Your "default" printer would just output it to the stream.
Ok, let's work on a custom printer. I will change my outer class to
RangePrinter
. So we have 2 iterators and some delimiters but have not customized how to print the actual items.Now by default it will work for maps as long as the key and value types are both printable and you can put in your own special item printer for when they are not (as you can with any other type), or if you do not want "=" as the delimiter.
I am moving the free-function to create these to the end now:
A free-function (iterator version) would look like something this and you could even have defaults:
You could then use it for
std::set
byYou can also write free-function version that take a custom printer and ones that take two iterators. In any case they will resolve the template parameters for you, and you will be able to pass them through as temporaries.
这是一个工作库,作为一个完整的工作程序呈现,我刚刚将其组合在一起:
它目前仅适用于
vector
和set
,但可以与大多数人一起使用容器,只需扩展 IsContainer 专业化即可。我没有过多考虑这段代码是否是最小的,但我无法立即想到可以删除的任何冗余代码。编辑:只是为了好玩,我包含了一个处理数组的版本。我必须排除 char 数组以避免进一步的歧义;它可能仍然会遇到
wchar_t[]
的麻烦。Here is a working library, presented as a complete working program, that I just hacked together:
It currently only works with
vector
andset
, but can be made to work with most containers, just by expanding on theIsContainer
specializations. I haven't thought much about whether this code is minimal, but I can't immediately think of anything I could strip out as redundant.EDIT: Just for kicks, I included a version that handles arrays. I had to exclude char arrays to avoid further ambiguities; it might still get into trouble with
wchar_t[]
.事实证明,该代码现在在很多情况下都很方便,而且我觉得由于使用率很低而进行定制的费用非常低。因此,我决定在 MIT 许可下发布它,并提供一个 GitHub 存储库,可以在其中下载标头和一个小示例文件。
http://djmuw.github.io/prettycc
0. 前言和措辞
就这个答案而言,'装饰' 是前缀字符串、分隔符字符串和后缀字符串的集合。
其中前缀字符串插入到容器值之前的流中,后缀字符串插入到容器值之后(请参阅 2. 目标容器)。
分隔符字符串插入到相应容器的值之间。
注意:实际上,这个答案并不能 100% 解决问题,因为装饰不是严格编译的时间常数,因为需要运行时检查来检查自定义装饰是否已应用于当前流。
尽管如此,我认为它有一些不错的功能。
注2:由于尚未经过充分测试,可能存在小错误。
1. 总体思路/用法 使用
时需要零额外代码
它应保持简单,如
轻松定制......
...相对于特定流对象
或相对于所有流:
粗略描述
ios_base
使用xalloc
/pword
提供的私有存储,以保存指向pretty::decor
的指针专门装饰特定流上特定类型的对象。如果没有显式设置此流的
pretty::decor
对象,则调用pretty::defaulted::decoration()
获取给定类型的默认装饰。pretty::defaulted
类专门用于自定义默认装饰。2. 目标对象/容器
此代码的“漂亮装饰”的目标对象
obj
是具有重载std::begin
和 < code>std::end 定义(包括 C 样式数组),begin(obj)
和end(obj)
,std::tuple
std::pair
。该代码包含一个用于识别具有范围特征的类的特征(
begin
/end
)。(不过,没有检查
begin(obj) == end(obj)
是否是有效的表达式。)该代码在全局中提供了
运算符<<
命名空间仅适用于没有更专门版本的operator<<
可用的类。因此,例如,尽管具有有效的
begin
/end
对,但在此代码中不会使用运算符打印std::string
。3. 使用和定制
可以为每种类型(不同的元组除外)和流(不是流类型!)单独施加装饰。
(即
std::vector
对于不同的流对象可以有不同的装饰。)A) 默认装饰
默认前缀是
""
(什么也没有),就像默认后缀,默认分隔符为", "
(逗号+空格)。B) 通过专门化
pretty::defaulted
类模板来自定义类型的默认装饰struct defaulted
有一个静态成员函数decoration()
返回一个decor
对象,其中包含给定类型的默认值。使用数组的示例:
自定义默认数组打印:
打印 arry 数组:
对
char
流使用PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
宏宏扩展为
允许将上述部分特化重写为
或插入完整特化,例如
包含用于
wchar_t
流的另一个宏:PRETTY_DEFAULT_WDECORATION
。C) 对流进行装饰
函数
pretty::decoration
用于对某个流进行装饰。存在过载情况
- 一个字符串参数作为分隔符(采用默认类中的前缀和后缀)
- 或三个字符串参数组装完整的装饰
给定类型和流的完整装饰
给定流的分隔符的自定义
4.
std::tuple
的特殊处理这不是允许对每个可能的元组类型进行专门化,而是代码将
std::tuple
可用的任何修饰应用于所有类型的std::tuple<...>
。5. 从流中删除自定义装饰
要返回给定类型的默认装饰,请在流
s
上使用pretty::clear
函数模板。5. 更多示例
使用换行符分隔符打印“类似矩阵”
打印
请参见 ideone/KKUebZ
6. 代码
The code proved to be handy on several occasions now and I feel the expense to get into customization as usage is quite low. Thus, I decided to release it under MIT license and provide a GitHub repository where the header and a small example file can be downloaded.
http://djmuw.github.io/prettycc
0. Preface and wording
A 'decoration' in terms of this answer is a set of prefix-string, delimiter-string, and a postfix-string.
Where the prefix string is inserted into a stream before and the postfix string after the values of a container (see 2. Target containers).
The delimiter string is inserted between the values of the respective container.
Note: Actually, this answer does not address the question to 100% since the decoration is not strictly compiled time constant because runtime checks are required to check whether a custom decoration has been applied to the current stream.
Nevertheless, I think it has some decent features.
Note2: May have minor bugs since it is not yet well tested.
1. General idea/usage
Zero additional code required for usage
It is to be kept as easy as
Easy customization ...
... with respect to a specific stream object
or with respect to all streams:
Rough description
ios_base
usingxalloc
/pword
in order to save a pointer to apretty::decor
object specifically decorating a certain type on a certain stream.If no
pretty::decor<T>
object for this stream has been set up explicitlypretty::defaulted<T, charT, chartraitT>::decoration()
is called to obtain the default decoration for the given type.The class
pretty::defaulted
is to be specialized to customize default decorations.2. Target objects / containers
Target objects
obj
for the 'pretty decoration' of this code are objects having eitherstd::begin
andstd::end
defined (includes C-Style arrays),begin(obj)
andend(obj)
available via ADL,std::tuple
std::pair
.The code includes a trait for identification of classes with range features (
begin
/end
).(There's no check included, whether
begin(obj) == end(obj)
is a valid expression, though.)The code provides
operator<<
s in the global namespace that only apply to classes not having a more specialized version ofoperator<<
available.Therefore, for example
std::string
is not printed using the operator in this code although having a validbegin
/end
pair.3. Utilization and customization
Decorations can be imposed separately for every type (except different
tuple
s) and stream (not stream type!).(I.e. a
std::vector<int>
can have different decorations for different stream objects.)A) Default decoration
The default prefix is
""
(nothing) as is the default postfix, while the default delimiter is", "
(comma+space).B) Customized default decoration of a type by specializing the
pretty::defaulted
class templateThe
struct defaulted
has a static member functiondecoration()
returning adecor
object which includes the default values for the given type.Example using an array:
Customize default array printing:
Print an arry array:
Using the
PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...)
macro forchar
streamsThe macro expands to
enabling the above partial specialization to be rewritten to
or inserting a full specialization like
Another macro for
wchar_t
streams is included:PRETTY_DEFAULT_WDECORATION
.C) Impose decoration on streams
The function
pretty::decoration
is used to impose a decoration on a certain stream.There are overloads taking either
- one string argument being the delimiter (adopting prefix and postfix from the defaulted class)
- or three string arguments assembling the complete decoration
Complete decoration for given type and stream
Customization of delimiter for given stream
4. Special handling of
std::tuple
Instead of allowing a specialization for every possible tuple type, this code applies any decoration available for
std::tuple<void*>
to all kind ofstd::tuple<...>
s.5. Remove custom decoration from the stream
To go back to the defaulted decoration for a given type use
pretty::clear
function template on the streams
.5. Further examples
Printing "matrix-like" with newline delimiter
Prints
See it on ideone/KKUebZ
6. Code
只需将容器复制到控制台即可。
应该输出:
Just copy the container to the console.
Should output :
使用
std::copy
但没有额外的尾随分隔符使用
std::copy
(最初用于@JoshuaKravtiz 答案)但在最后一个元素之后不包含额外的尾部分隔符:应用于自定义 POD 类型容器的示例用法:
Using
std::copy
but without extra trailing separatorAn alternative/modified approach using
std::copy
(as originally used in @JoshuaKravtiz answer) but without including an additional trailing separator after the last element:Example usage applied to container of a custom POD type:
问题可能出在前面的循环中:
这个循环根本不会运行(如果
firstsquare
是非字母的)或者将永远运行(如果它是字母的)。原因是firstsquare
不会随着x
的增加而改变。The problem is probably in the previous loop:
This loop will run not at all (if
firstsquare
is non-alphabetic) or will run forever (if it is alphabetic). The reason is thatfirstsquare
doesn't change asx
is incremented.在 C++11 中,基于范围的 for 循环可能是一个很好的解决方案:
输出:
In C++11, a range-based for loop might be a good solution:
Output:
我将在这里添加另一个答案,因为我提出了一种与之前的方法不同的方法,那就是使用语言环境方面。
基础知识在此处
本质上您要做的是:
std::has_facet
std::has_facet
std::has_facet< MyPrettyVectorPrinter >
std::use_facet
提取它。 MyPrettyVectorPrinter >( os.getloc() )
operator<<
)将提供默认的构面。请注意,您可以对读取向量执行相同的操作。我喜欢这种方法,因为您可以使用默认打印,同时仍然能够使用自定义覆盖。
缺点是如果在多个项目中使用,您的方面需要一个库(因此不能只是标题),而且您需要注意创建新区域设置对象的费用。
我将其编写为一种新的解决方案,而不是修改我的另一个解决方案,因为我相信这两种方法都是正确的,您可以选择。
I am going to add another answer here, because I have come up with a different approach to my previous one, and that is to use locale facets.
The basics are here
Essentially what you do is:
std::locale::facet
. The slight downside is that you will need a compilation unit somewhere to hold its id. Let's call it MyPrettyVectorPrinter. You'd probably give it a better name, and also create ones for pair and map.std::has_facet< MyPrettyVectorPrinter >
std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
operator<<
) provides default ones. Note you can do the same thing for reading a vector.I like this method because you can use a default print whilst still being able to use a custom override.
The downsides are needing a library for your facet if used in multiple projects (so can't just be headers-only) and also the fact that you need to beware about the expense of creating a new locale object.
I have written this as a new solution rather than modify my other one because I believe both approaches can be correct and you take your pick.
重载运算符<<:
用法:
overload operator<<:
Usage:
这个答案基于Zorawar的答案,但我无法在那里发表评论。
您可以使用
cbegin
和将
auto
(C++11)/typedef
版本设为const
改为 cendThis answer is based on the answer from Zorawar, but I couldn't leave a comment there.
You can make the
auto
(C++11)/typedef
versionconst
by usingcbegin
andcend
instead我看到两个问题。正如中所指出的,
要么是无限循环,要么根本不会执行,并且如果入口字符与
'S' 不同,则
if (entrance == 'S')
中也指出。 > 然后没有任何内容被推送到路径向量,使其变空,从而在屏幕上不打印任何内容。您可以测试后者检查path.empty()
或打印path.size()
。不管怎样,使用字符串而不是向量不是更好吗?您也可以像数组一样访问字符串内容、查找字符、提取子字符串并轻松打印字符串(无需循环)。
使用字符串来完成这一切可能是一种以不太复杂的方式编写的方法,并且更容易发现问题。
I see two problems. As pointed out in
there's either an infinite loop or never executed at all, and also in
if (entrance == 'S')
if the entrance character is different than'S'
then nothing in pushed to the path vector, making it empty and thus printing nothing on screen. You can test the latter checking forpath.empty()
or printingpath.size()
.Either way, wouldn't it be better to use a string instead of a vector? You can access the string contents like an array as well, seek characters, extract substrings and print the string easily (without a loop).
Doing it all with strings might be the way to have it written in a less convoluted way and make it easier to spot the problem.
如果您可以将较新的 C++ 标准与 std::views 一起使用,那么就有
std:: range::views::join
/std::ranges::join_view
Demo on Godbolt
If you're using an older C++ standard but boost 是一个选项,那么您可以使用
boost::algorithm::join
。例如,要打印std::string
向量:对于其他类型的向量,您需要 transform to string first
上帝演示螺栓
If you can use newer C++ standards with std::views then there's
std::ranges::views::join
/std::ranges::join_view
Demo on Godbolt
If you're using an older C++ standard but boost is an option then you can use
boost::algorithm::join
. For example to print out a vector ofstd::string
:For vectors of other types you'll need to transform to string first
Demo on Godbolt
这里的目标是使用 ADL 来定制我们漂亮的打印方式。
您传入一个格式化程序标签,并覆盖该标签命名空间中的 4 个函数(之前、之后、之间和下降)。这改变了格式化程序在迭代容器时打印“装饰”的方式。
默认格式化程序,为映射执行
{(a->b),(c->d)}
,为 tupleoids 执行(a,b,c)
,< code>"hello" 表示字符串,[x,y,z]
表示其他所有内容。它应该“只适用于”第 3 方可迭代类型(并将它们视为“其他所有类型”)。
如果您想要为第 3 方可迭代对象定制装饰,只需创建您自己的标签即可。处理地图下降需要一些工作(您需要重载
pretty_print_descend( your_tag
以返回pretty_print::decorator::map_magic_tag
)。也许有一个更干净的方法来做到这一点,不确定。一个检测可迭代性和元组性的小库:
一个让我们访问可迭代或元组类型对象的内容的库:
一个漂亮的打印库:
测试代码:
实时示例
这确实使用了 C++14 功能(一些
_t
别名,以及auto&&
lambdas),但没有一个是必需的。The goal here is to use ADL to do customization of how we pretty print.
You pass in a formatter tag, and override 4 functions (before, after, between and descend) in the tag's namespace. This changes how the formatter prints 'adornments' when iterating over containers.
A default formatter that does
{(a->b),(c->d)}
for maps,(a,b,c)
for tupleoids,"hello"
for strings,[x,y,z]
for everything else included.It should "just work" with 3rd party iterable types (and treat them like "everything else").
If you want custom adornments for your 3rd party iterables, simply create your own tag. It will take a bit of work to handle map descent (you need to overload
pretty_print_descend( your_tag
to returnpretty_print::decorator::map_magic_tag<your_tag>
). Maybe there is a cleaner way to do this, not sure.A little library to detect iterability, and tuple-ness:
A library that lets us visit the contents of an iterable or tuple type object:
A pretty printing library:
Test code:
live example
This does use C++14 features (some
_t
aliases, andauto&&
lambdas), but none are essential.我的解决方案是 simple.h,它是 scc 包。所有 std 容器、地图、集合、c 数组都是可打印的。
My solution is simple.h, which is part of scc package. All std containers, maps, sets, c-arrays are printable.
参加完第一届 BoostCon(现在称为 CppCon)后,我和另外两个人开发了一个库来完成此任务。主要症结在于需要扩展
namespace std
。事实证明,这对于 boost 库来说是行不通的。不幸的是,代码的链接不再有效,但您可能会在讨论中发现一些有趣的花絮(至少是那些没有讨论如何命名的花絮!)
http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming- td2619544.html
Coming out of one of the first BoostCon (now called CppCon), I and two others worked on a library to do just this. The main sticking point was needing to extend
namespace std
. That turned out to be a no-go for a boost library.Unfortunately the links to the code no longer work, but you might find some interesting tidbits in the discussions (at least those that aren't talking about what to name it!)
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
Here is my version of implementation done in 2016
Everything is in one header, so it's easy to use
https://github.com/skident/eos/blob/master/include/eos/io/print.hpp
在 C++11 中
In C++11
您可以使用
std::experimental::make_ostream_joiner
< /a>:Godbolt 上的演示
You can use
std::experimental::make_ostream_joiner
:Demo on Godbolt
[ 1, 2, 3, 4 ]
[ 1, 2, 3, 4 ]
我编写了一个运算符<<,它可以打印任何可迭代对象,其中包括自定义容器、标准容器和具有已知边界的数组。需要 c++11:
I wrote a an
operator<<
which prints any iterable, which includes custom containers, standard containers, and arrays with known bounds. Requires c++11:您可以使用
perr.h
作为起点:您只需从 GitHub 获取标头 (https://github.com/az5112/perr)。
You could use
perr.h
as a starting point:You just need to grab the header from GitHub (https://github.com/az5112/perr).
使用 C++23,您可以简单地编写:
就可以了。对于 C++20,您可以使用
std::format
和<<
运算符:当然,您需要包含适当的标准库标头(
#include
或#include
)。对于较旧的 C++ 版本 - 请参阅其他答案。
With C++23, you can simply write:
and that works. With C++20, you could use
std::format
and the<<
operator:Of course, you'll need to have included the appropriate standard library header (
#include <print>
or#include <format>
).For older C++ versions - consult other answers.
一种解决方案使用
nlohmann::json
库nlohmann::json Github 主页
控制台输出
One solution used the
nlohmann::json
librarynlohmann::json Github home page
Console Output