如何打印由逗号分隔的元素列表?

69

我知道如何在其他语言中实现,但不知道如何在C++中实现,而这里必须使用C++。

我有一组字符串(keywords),需要作为列表打印到out,字符串之间需要逗号分隔,但不能有尾随逗号。例如在Java中,我会使用StringBuilder构建字符串后,删除末尾的逗号。在C++中应该怎么做呢?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{
    out << *iter << ", ";
}
out << endl;

我最初尝试插入以下代码块来完成它(将逗号打印移至此处):

if (iter++ != keywords.end())
    out << ", ";
iter--;

8
我知道使用for(auto iter = ...;可以让代码行数更少,但除非你明确打算在循环之后使用它,否则你应该将iter与循环的作用域绑定。 - user229044
@πάνταῥεῖ 虽然它们是重复的,但为什么闭包不是另一种方式?这篇文章肯定看起来更像是一个更好的重复目标。 - Passer By
36个回答

0

结合C++11 lambda和宏:

#define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()

使用方法:

for(const auto& k: keywords)
    INFIX_PRINTER(out, ", ") << k;

0

我喜欢使用基于范围的for循环,并进行is_last_elem测试。在我看来,这种方式非常易读:

for (auto& e : range)
{
    if (!is_last_elem(e, range)) [[likely]] 
        os << e << ", ";
    else
        os << e;
}
os << std::endl;

完整代码:

C++20:

#include <iostream>
#include <list>
#include <ranges>
#include <utility>
#include <type_traits>
#include <memory>

template <std::ranges::bidirectional_range R>
bool is_last_elem(const std::ranges::range_value_t<R>& elem, const R& range)
{
    auto last_it = range.end();
    std::advance(last_it, -1);
    return std::addressof(elem) == std::addressof(*last_it);
}

template <std::ranges::bidirectional_range R, class Stream = std::ostream>
void print(const R& range, std::ostream& os = std::cout)
{
    for (auto& e : range)
    {
        if (!is_last_elem(e, range)) [[likely]] 
            os << e << ", ";
        else
            os << e;
    }
    os << std::endl;
}

int main()
{
    std::list<int> v{1, 2, 3, 4, 5};
    print(v);
}

C++17:

#include <iostream>
#include <list>
#include <utility>
#include <type_traits>
#include <memory>

template <class Range>
using value_type_t = std::remove_reference_t<decltype(*std::begin(std::declval<Range>()))>;

template <class Range>
bool is_last_elem(const value_type_t<Range>& elem, const Range& range)
{
    auto last_it = range.end();
    std::advance(last_it, -1);
    return std::addressof(elem) == std::addressof(*last_it);
}

template <class Range, class Stream = std::ostream>
void print(const Range& range, std::ostream& os = std::cout)
{
    for (auto& e : range)
    {
        if (!is_last_elem(e, range))
            os << e << ", ";
        else
            os << e;
    }
    os << std::endl;
}

int main()
{
    std::list<int> v{1, 2, 3, 4, 5};
    print(v);
}

0

C++20 带来了格式化库。然而截至目前(2021年4月),gcc和clang都还没有实现它。但我们可以使用基于它的fmt库:

std::list<int> v{1, 2, 3, 4, 5};
fmt::print("{}", fmt::join(v, ", "));

0

我使用这个:

template<class T>
void print(vector<T> v, ostream& f, const char* separator) {
std::copy(v.begin(), v.end()-1, std::ostream_iterator<T>(f, separator));
f << *(v.end()-1) << endl;}

你可以这样调用它:

print(v, std::cout, ",");

-1

自从C++11以来,您可以使用partition_copy进行有条件的输出。

std::vector<int> arr{0, 1, 2, 3, 4};

//C++11 example
int count = arr.size();
std::partition_copy(
    arr.begin(), arr.end(),
    std::ostream_iterator<int>(std::cout),
    std::ostream_iterator<int>(std::cout, ", "),
    [count] (int) mutable {
        return (--count) == 0;
    }
);

//after C++14, it support lambda capture initialization
std::partition_copy(
    arr.begin(), arr.end(),
    std::ostream_iterator<int>(std::cout),
    std::ostream_iterator<int>(std::cout, ", "),
    [count = arr.size()] (int) mutable {
        return (--count) == 0;
    }
);

实时演示


-1

自从 C++20,如果你正在寻找一种紧凑的解决方案,并且你的编译器尚未支持 bolov 的解决方案,你可以使用一个带有 init-statement 的 基于范围的 for 循环 作为 first 标志,以及一个 条件运算符,如下所示:

std::set<std::string> keywords {"these", "are", "my", "keywords"};

for (bool first{true}; auto const& kw : keywords)
    std::cout << (first ? first = false, "" : ", ") << kw;

输出:

这些是我的关键字

注意:我在cppreference.com的此页面的示例部分中找到了这个解决方案。

在Wandbox上查看代码


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接