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

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个回答

4
为了避免在循环内部放置 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,但你可以使用一些助手来移除它。


4

我认为简单对我来说更好,所以在查看了所有答案之后,我准备了我的解决方案(需要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

酷,你甚至可以在C++20中使用这个一行代码:for (const char* delim = ""; auto&& v : nums) std::cout << std::exchange(delim, ", ") << v; - Etherealone

3
如果这些值是std::string,您可以使用range-v3以声明性风格很好地编写它们。
#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;
}

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;
    }
}

const_iterator是一个与非常量begin返回的普通iterator不兼容的独特类型。 - Potatoswatter
尽管在vector::iterator是一个简单指针的平台上可以编译通过,但在调试模式或更改编译器时可能会导致混淆和错误。 - Potatoswatter
@Potatoswatter:你在说什么呢?这将适用于所有编译器(假设它们是C++编译器)。如果您不修改容器的内容,您应始终优先使用const_iterator而不是iterator。 - Martin York
@Martin: 这就是我想到的。然后我想,“标准在哪里规定了 std::vector<std::string>::iterator 可以转换为 std::vector<std::string>::const_iterator?”现在我担心在调用 begin()end() 之前,您实际上必须将 data 强制转换为 const Container - Steve Jessop
@Steve(还有Martin):很抱歉,那完全是错误的。在§23.1的表格65中要求iterator可转换为const_iterator。我只是不习惯看到它写成这样。虽然它与重载无关。但是,const_iterator只提供了一个转换构造函数。通常我会尝试使用单个模板来实现iteratorconst_iterator,并使用SFINAE禁用不需要的构造函数。 - Potatoswatter
@Steve Jessop:之前走错了方向。回家后我会检查标准。但在SGI页面上:http://www.sgi.com/tech/stl/Container.html 上说:<quote>必须存在从迭代器类型到const迭代器类型的转换。</quote> - Martin York

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;

2
另一种可能的解决方案是避免使用if语句。
Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

取决于您在循环中正在做什么。


2

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

你可以尝试:

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

这样,++ 将在将迭代器与 keywords.end() 进行比较之前进行评估。

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();

如果序列为空,则 .end() - 1 是未定义行为。 - Xeverous

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

2
我认为@MarkB的答案变体在可读性、简洁性和简洁性方面达到了最佳平衡:
auto iter= keywords.begin();
if (iter!=keywords.end()) {
    out << *iter;
    while(++iter != keywords.end())
        out << "," << *iter;
}
out << endl;

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