63

我知道如何用其他语言做到这一点,但不知道在 C++ 中,我不得不在这里使用它。

我有一组字符串 ( keywords) 作为列表打印到out,字符串之间需要逗号,而不是尾随逗号。例如,在 Java 中,我会使用 aStringBuilder并在构建字符串后删除末尾的逗号。我怎样才能在 C++ 中做到这一点?

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

我最初尝试插入以下块来执行此操作(在此处移动逗号打印):

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

32 回答 32

56

使用中缀迭代器:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin's 's prefix_ostream_iterator 
#if !defined(INFIX_ITERATOR_H_) 
#define  INFIX_ITERATOR_H_ 
#include <ostream> 
#include <iterator> 
template <class T, 
          class charT=char, 
          class traits=std::char_traits<charT> > 
class infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag,void,void,void,void> 
{ 
    std::basic_ostream<charT,traits> *os; 
    charT const* delimiter; 
    bool first_elem; 
public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 
    infix_ostream_iterator(ostream_type& s) 
        : os(&s),delimiter(0), first_elem(true) 
    {} 
    infix_ostream_iterator(ostream_type& s, charT const *d) 
        : os(&s),delimiter(d), first_elem(true) 
    {} 
    infix_ostream_iterator<T,charT,traits>& operator=(T const &item) 
    { 
        // Here's the only real change from ostream_iterator: 
        // Normally, the '*os << item;' would come before the 'if'. 
        if (!first_elem && delimiter != 0) 
            *os << delimiter; 
        *os << item; 
        first_elem = false; 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator*() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++() { 
        return *this; 
    } 
    infix_ostream_iterator<T,charT,traits> &operator++(int) { 
        return *this; 
    } 
};     
#endif 

用法类似于:

#include "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
于 2010-08-16T20:24:48.953 回答
40

在即将推出的实验性 C++17 编译器中,您可以使用std::experimental::ostream_joiner

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

使用GCC 6.0 SVNClang 3.9 SVN的实时示例

于 2016-03-14T21:18:08.877 回答
30

因为每个人都决定用 while 循环来做这件事,所以我将举一个 for 循环的例子。

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}
于 2010-08-16T20:29:21.580 回答
24

假设一个模糊的正常输出流,因此向它写入一个空字符串确实什么都不做:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
于 2010-08-16T23:05:11.210 回答
17

一种常见的方法是在循环之前打印第一个项目,并且只循环剩余的项目,在每个剩余项目之前预先打印一个逗号。

或者,您应该能够创建自己的流来维护行的当前状态(在 endl 之前)并将逗号放在适当的位置。

编辑:您还可以使用 TED 建议的中间测试循环它会是这样的:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

我首先提到了“在循环之前打印第一项”方法,因为它使循环体非常简单,但是任何方法都可以正常工作。

于 2010-08-16T20:22:28.333 回答
12

有很多聪明的解决方案,有太多会在不让编译器完成工作的情况下破坏代码而无法挽救。

显而易见的解决方案是对第一次迭代进行特殊处理:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

这是一个非常简单的模式:

  1. 不会破坏循环:一目了然,每个元素都将被迭代。
  2. 不仅允许放置分隔符或实际打印列表,因为else块和循环体可以包含任意语句。

它可能不是绝对最有效的代码,但单个预测良好的分支的潜在性能损失很可能被庞大的std::ostream::operator<<.

于 2016-02-14T15:11:59.877 回答
7

像这样的东西?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
于 2010-08-16T20:23:45.907 回答
5

我做分隔符的典型方法(在任何语言中)是使用中间测试循环。C++ 代码将是:

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

if(注意:如果关键字可能为空,则需要在循环之前进行额外检查)

显示的大多数其他解决方案最终都会在每次循环迭代时进行完整的额外测试。你在做 I/O,所以花费的时间不是一个大问题,但它冒犯了我的感受。

于 2010-08-16T20:49:21.067 回答
5

在python中我们只写:

print ", ".join(keywords)

那为什么不呢:

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

然后像这样使用它:

cout << join(", ", keywords) << endl;

与上面的 python 示例不同,其中 the" "是一个字符串,并且keywords必须是字符串的可迭代,在这个 C++ 示例中,分隔符和keywords可以是任何可流式传输的,例如

cout << join('\n', keywords) << endl;
于 2015-09-17T07:52:35.713 回答
4

我建议您在 lambda 的帮助下简单地切换第一个字符。

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;
于 2017-08-18T14:18:50.303 回答
3

试试这个:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}
于 2010-08-16T20:25:36.730 回答
3

为了避免if在循环内放置一个,我使用这个:

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

它取决于矢量类型,int但您可以使用一些帮助程序将其删除。

于 2016-08-23T07:28:07.297 回答
3

如果值为s,您可以使用range-v3std::string以声明式风格很好地编写它

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

对于必须转换为字符串的其他类型,您只需添加转换调用即可to_string

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}
于 2018-05-08T20:30:20.840 回答
2

++您使用的运算符有一点问题。

你可以试试:

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

这样,++将在将迭代器与keywords.end().

于 2010-08-16T20:23:30.383 回答
2

我为此使用了一个小助手类:

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

要使用它:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;
于 2011-07-11T14:37:22.163 回答
2

另一种可能的解决方案,它避免了if

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

取决于你在循环中做什么。

于 2013-07-22T16:15:13.887 回答
2

以下应该做: -

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();
于 2016-12-28T22:33:44.150 回答
2

我认为@MarkB 答案的这种变体在可读性、简单性和简洁性之间达到了最佳平衡:

auto iter= keywords.begin();
if (iter!=keywords.end()) {
    out << *iter;
    while(++iter != keywords.end())
        out << "," << *iter;
}
out << endl;
于 2019-11-13T18:11:56.013 回答
2

很容易解决这个问题(取自我在这里的回答):

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

我在许多编程语言中使用这个习语(模式?),以及所有需要从列表(如输入)构造分隔输出的任务。让我用伪代码给出摘要:

empty output
firstIteration = true
foreach item in list
    if firstIteration
        add delimiter to output
    add item to output
    firstIteration = false

在某些情况下,甚至可以完全省略firstIteration指标变量:

empty output
foreach item in list
    if not is_empty(output)
        add delimiter to output
    add item to output
于 2020-10-07T20:18:35.320 回答
1

我认为这应该有效

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}
于 2010-08-16T20:23:22.843 回答
1

可以这样。。

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}
于 2010-08-16T21:43:18.273 回答
1

使用提升:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

并且您获得一个包含向量的字符串,以逗号分隔。

编辑:要删除最后一个逗号,只需发出:

add_str = add_str.substr(0, add_str.size()-1);
于 2012-06-05T11:33:18.047 回答
1

我认为简单对我来说更好,所以在查看了所有答案后,我准备了我的解决方案(需要 c++14):

#include <iostream>
#include <vector>
#include <utility> // for std::exchange c++14

int main()
{    
    std::vector nums{1, 2, 3, 4, 5}; // c++17
    
    const char* delim = "";
    for (const auto value : nums)
    {
        std::cout << std::exchange(delim, ", ") << value;
    }
}

输出示例:

1, 2, 3, 4, 5
于 2021-08-19T09:55:00.197 回答
0

您可以使用do循环,为第一次迭代重写循环条件,并使用短路&&运算符和有效流是true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
于 2010-08-16T21:55:49.380 回答
0

我会选择这样的东西,一个简单的解决方案,应该适用于所有迭代器。

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}
于 2010-08-16T23:14:46.800 回答
0

这个重载了流运算符。是的,全局变量是邪恶的。

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}
于 2014-02-18T02:28:19.867 回答
0

可以使用函子:

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

例子:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}
于 2016-10-25T07:07:18.987 回答
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;
于 2019-08-29T07:24:01.370 回答
0

这是您可以使用的两种方法,它们本质上是相同的想法。我喜欢这些方法,因为它们不包含任何不必要的条件检查或赋值操作。我将第一个称为 print first 方法

方法一:先打印法

if (!keywords.empty()) {
    out << *(keywords.begin()); // First element.
    for (auto it = ++(keywords.begin()); it != keywords.end(); it++)
        out << ", " << *it; // Every subsequent element.
}

这是我一开始使用的方法。它通过自己打印容器中的第一个元素来工作,然后打印每个后续元素,前面有逗号和空格。如果这就是您需要做的所有事情,它很简单,简洁并且效果很好。一旦你想做更多的事情,比如在最后一个元素之前添加一个“and”,这个方法就会失败。您必须检查每个循环迭代是否在最后一个元素上。不过,在列表后添加句点或换行符也不会那么糟糕。您可以在 for 循环之后再添加一行,以将您想要的任何内容添加到列表中。

第二种方法我更喜欢。我将调用print last 方法,因为它与第一个方法相同,但顺序相反。

方法二:最后打印方法

if (!keywords.empty()) {
    auto it = keywords.begin(), last = std::prev(keywords.end());
    for (; it != last; it++) // Every preceding element.
        out << *it << ", ";
    out << "and " << *it << ".\n"; // Last element.
}

这个工作原理是打印除最后一个元素之外的每个元素,并使用逗号和空格,允许您选择在它之前添加一个“and”,在它之后添加一个句点和/或换行符。如您所见,此方法为您提供了更多选项,让您可以在不影响循环性能或添加大量代码的情况下处理最后一个元素。

如果将 for 循环的第一部分留空让您感到困扰,您可以这样写:

if (!keywords.empty()) {
    auto it, last;
    for (it = keywords.begin(), last = std::prev(keywords.end()); it != last; it++)
        out << *it << ", ";
    out << "and " << *it << ".\n";
}
于 2019-11-28T06:37:25.517 回答
0

我喜欢基于范围的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);
}
于 2021-04-21T06:35:25.633 回答
0

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

std::list<int> v{1, 2, 3, 4, 5};
fmt::print("{}", fmt::join(v, ", "));
于 2021-04-21T06:45:03.677 回答
0

C++20开始,如果您正在寻找一个紧凑的解决方案,并且您的编译器尚不支持bolov 的解决方案,您可以使用基于范围的 for 循环,该循环带有一个用于first标志的 init 语句和一个条件运算符,如下所示:

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本页的示例部分找到了这个解决方案。

魔杖盒上的代码

于 2021-04-21T15:11:39.033 回答