我有一组要打印到列表中的字符串,它们需要每个逗号之间的逗号,而不是结尾逗号。例如,在java中,我将使用stringbuilder并在构建字符串后将逗号删除。如何在C ++中实现?
我讨厌小事情绊倒我。
编辑:谢谢大家。这就是为什么我在这里发布这样的东西。这么多好的答案,并以不同的方式解决。在学习了Java和汇编(不同的类)一学期之后,不得不花4天时间做一个C ++项目使我陷入了循环。我不仅得到了答案,而且有机会思考解决此类问题的不同方法。太棒了。
#1 楼
使用infix_iterator:// 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, ","));
评论
凉。像那样。为什么在Boost中没有这样的东西?
–马丁·约克
10年8月16日在20:46
@Martin:因为我从来都不想提交?我可能应该考虑一下...
–杰里·科芬(Jerry Coffin)
10年8月16日在20:59
提交。 :)您应该发布到邮件列表,并询问是否有人需要类似的迭代器。
– GManNickG
2010年8月17日,0:18
@ T.E.D .:使用中,它是您发布的代码长度的四分之一,并且功能也更加丰富(例如,如果您希望使用制表符分隔的输出,那么这就是零额外的工作)。简而言之,额外的长度主要是给出了如何完成工作的大致思路的代码与合理完成并准备使用的代码之间的差异。
–杰里·科芬(Jerry Coffin)
2011-10-10 14:35
这种功能将在带有std :: experimental :: ostream_joiner的C ++ 17中可用,并且当前在Wandbox上的GCC 6.0-SVN和Clang 3.9-SVN上可用。看到我的新答案。
–TemplateRex
16-3-14在21:14
#2 楼
在即将推出的具有实验性C ++ 17的编译器中,您可以使用std::experimental::ostream_joiner
:使用GCC 6.0 SVN和Clang 3.9 SVN的实时示例评论
我喜欢这个。这是简洁明了的。有没有一种方法可以在不使用std :: transform的情况下应用所需的中间存储创建转换?
–kshenoy
18年4月30日在22:04
#3 楼
因为每个人都决定使用while循环来执行此操作,所以我将举一个for循环示例。for (iter = keywords.begin(); iter != keywords.end(); iter++) {
if (iter != keywords.begin()) cout << ", ";
cout << *iter;
}
评论
这是规范的容器打印机循环。如果您厌倦了每次编写它的麻烦,我们制作了一个魔术助手标头,它对所有容器都做到了。唯一的评论是,如果一切都足够恒定,并且在循环后不需要迭代器,请将循环头更改为for(auto iter = keyword.begin(),end = keyword.end(); iter!= end; ++ iter)。
– Kerrek SB
2011-6-7 23:42
另外,如果您确实想避免每次都进行比较,则可以自动执行= = keyword.begin();。 if(it!= keyword.end())cout << it ++;然后使用body cout <<“,” << it;运行循环。就个人而言,我更喜欢将所有内容都放在一个地方。
– Kerrek SB
2011年6月7日23:50
@Kerrek SB-...或者您可以只使用经过中间测试的循环。每当您发现自己用“在第一个(或最后一个)迭代上也执行此操作”逻辑编写循环时,很有可能您手上就有一个经过自然测试的自然循环。
– T.E.D.
2011年6月8日在13:32
@TED:什么是“中间测试循环”?
– Kerrek SB
2011年6月8日下午13:43
#4 楼
假设输出流模糊不清,因此向其写入空字符串确实没有任何作用:const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
out << padding << *iter;
padding = ", "
}
评论
两年后,我有机会再次考虑这个问题,我更喜欢这种方法。聪明。
– T.E.D.
2012年6月29日18:52
#5 楼
一种常见的方法是在循环之前打印第一个项目,并仅在剩余项目上循环,在每个剩余项目之前预打印逗号。或者,您应该能够创建自己的流保持行的当前状态(在endl之前)并将逗号放在适当的位置。
EDIT:
您还可以使用TED建议的中间测试循环就像这样:
if(!keywords.empty())
{
auto iter = keywords.begin();
while(true)
{
out << *iter;
++iter;
if(iter == keywords.end())
{
break;
}
else
{
out << ", ";
}
}
}
我首先提到了“在循环前打印第一项”方法,因为它使循环体确实很简单,但是任何一种方法都有效很好。
评论
您确实应该提到使用中间测试循环的(更好的IMHO)选项。
– T.E.D.
10年8月16日在22:42
您无需在if检查中使用else子句,因为true分支无论如何都会使您的控制逻辑脱离循环。我会把它写得更像if(iter == keyword.end())break;。也。如果您要在每次循环迭代中增加某些内容,那么如果继续进行并使用for循环并将迭代放入迭代槽中,人们会更容易阅读,这样人们就可以看到它并确切地知道您在做什么。
– T.E.D.
2011年6月8日13:40
...当然,完成所有这些清理工作之后,您基本上会在下面得到我的答案。它用五行文本(如果我正确添加了empty()检查,则是六行)来完成,它占据了17行。我认为它也更容易理解。它将迭代放在标准位置,并消除了围绕逗号生成代码的两个完整嵌套级别。
– T.E.D.
2011-6-8 13:52
@ T.E.D。我没有使用for循环并在第三条语句中放入增量的原因是,这行不通:我需要先打印项目,然后再进行增量测试,然后再进行最终测试。
– Mark B
11年8月8日在20:33
这就是为什么将最终测试放在循环的中间。请参阅下面的答案。
– T.E.D.
2011年8月9日12:46
#6 楼
有很多聪明的解决方案,太多的解决方案使代码难以救赎而又不让编译器执行工作。显而易见的解决方案是特殊化第一次迭代:
bool first = true;
for (auto const& e: sequence) {
if (first) { first = false; } else { out << ", "; }
out << e;
}
这是一种简单的死模式:
不弄乱循环:乍一看,每个元素都会迭代。
由于
else
块和循环体可以包含任意语句,因此不仅仅可以放置分隔符或实际打印列表,还可以。它可能不是绝对最有效的代码,但单个预测良好的分支的潜在性能损失很可能被
std::ostream::operator<<
的庞然大物所掩盖。#7 楼
这样的东西吗?while (iter != keywords.end())
{
out << *iter;
iter++;
if (iter != keywords.end()) cout << ", ";
}
评论
不能低估,但是我对这种解决方案的问题是,它每次迭代两次对完全相同的条件执行一次检查。
– T.E.D.
10年8月16日在20:52
两次测试同一件事比对每次迭代的开始和结束进行测试要好。如果编译器确定cout <<“,”不会更改关键字或进行迭代,则可以消除第二项测试。如果您确实想要DRY,则使用if(test)break;或执行{} while(test && cout <<“,”);但是那些通常被认为是糟糕的风格。
–马铃薯
2012年6月23日在12:49
#8 楼
我做分隔符(使用任何语言)的典型方法是使用经过中期测试的循环。 C ++代码为:for (;;) {
std::cout << *iter;
if (++iter == keywords.end()) break;
std::cout << ",";
}
(注意:如果关键字可能为空,则在循环之前需要进行额外的
if
检查)所示的大多数其他解决方案最终都会在每个循环迭代中进行整个额外的测试。您正在执行I / O,因此花费的时间不是一个大问题,但这会冒犯我的敏感性。
评论
该条件不会在第一次通过测试。那确实是一个do循环。
–马铃薯
10年8月16日在21:50
@Potatoswatter-我想这取决于您选择如何定义术语。对我来说,循环可以是顶级测试,底层测试或中间测试。此循环是中间测试的。至于实现它,在C语法语言中,我通常更喜欢使用for()循环,除非碰巧是其他形式之一(while或do)完全匹配的特殊情况。不过那只是一个品味问题。
– T.E.D.
10年8月16日在22:33
说到冒犯性的敏感性,您已经省略了开始时必须进行的一次性测试,以确保keyword.size()> 0或等效值。这使您的代码看起来比实际的简单。鬼;-)
–史蒂夫·杰索普(Steve Jessop)
10年8月16日在22:46
顺便说一句:要了解需要做的事情有多么稀少,请参阅已关闭的问题stackoverflow.com/questions/3347001/do-while-vs-while/…。
– T.E.D.
10年8月16日在22:47
@Mark B-嗯...我明白了。固定按照您的顺序进行。我从您的代码中获取它,您希望自己看到iter ++,所以请假装我这样做了。 :-)其他排列也是可能的。
– T.E.D.
2011年8月9日在13:07
#9 楼
在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示例不同,其中
" "
是字符串,而keywords
必须是字符串可迭代的,在此C ++示例中,分隔符和keywords
可以是任何可流式传输的内容,例如cout << join('\n', keywords) << endl;
评论
自C ++ 11起,甚至可以将typename V :: const_iterator替换为auto。
– Jarod42
2015年9月20日在11:49
我同意,但是我倾向于使事情与C ++ 11以前的版本兼容,因为我被旧的Linux安装和旧的编译器所咬住了太多次了。
– Darko Veberic
2015年9月21日在16:34
我认为这个答案比大多数具有for(...){if(...){...} else {...}}的答案要好,至少该答案摆脱了for中的跳转指令循环,感觉更轻,更好。
–r0ng
18-2-15在3:57
#10 楼
试试这个: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是与非const开始返回的普通迭代器不同的,不兼容的类型。
–马铃薯
10年8月16日在21:57
尽管这将在vector :: iterator是简单指针的平台上进行编译,但令人困惑的破损可能会导致调试模式或更改编译器。
–马铃薯
10年8月16日在21:58
@Potatoswatter:你在说什么。这将适用于所有编译器(假设它们是C ++编译器)。如果不修改容器的内容,则应始终首选使用const_iterator而不是迭代器。
–马丁·约克
10年8月16日在23:02
@马丁:这就是我的想法。然后我想,“在标准中什么地方说std :: vector
–史蒂夫·杰索普(Steve Jessop)
2010年8月16日23:07
@史蒂夫(和马丁):对不起,那是完全错误的。 §23.1中的表65要求迭代器可转换为const_iterator。我只是不习惯看到这样写。 (不过,它与重载无关。const_iterator只是提供了一个转换构造函数。通常,我尝试使用单个模板实现iterator和const_iterator,并使用SFINAE禁用不需要的构造函数。)
–马铃薯
2010年8月17日,0:08
#11 楼
为避免将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
,但是您可以使用一些辅助工具将其删除。 />#12 楼
您正在使用的++
运算符有一个小问题。您可以尝试:将迭代器与
++
进行比较。#13 楼
我为此使用了一个辅助类: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;
#14 楼
请执行以下操作:- 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为UB
–大量
19年5月5日,3:18
#15 楼
我建议您仅在lambda的帮助下切换第一个字符。std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };
for (auto &k : keywords)
std::cout << f() << k;
评论
这是stackoverflow.com/a/35373017/4818802的较差版本
–大量
19年5月5日,下午3:17
#16 楼
如果值是std::string
s,则可以使用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;
}
#17 楼
修复此问题非常容易(摘自我的回答):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
#18 楼
我认为这应该有效while (iter != keywords.end( ))
{
out << *iter;
iter++ ;
if (iter != keywords.end( )) out << ", ";
}
评论
这行是怎么回事:; iter ++?另外,这是错误的-您要双重添加逗号。这将产生word1,word2,word3,
–黄洁仪
10年8月16日在20:24
#19 楼
使用boost: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));
,您将获得一个包含以逗号分隔的向量的字符串。
EDIT:
以删除最后一个逗号,只需发出:
add_str = add_str.substr(0, add_str.size()-1);
#20 楼
另一种可能的解决方案是避免使用if
Char comma = '[';
for (const auto& element : elements) {
std::cout.put(comma) << element;
comma = ',';
}
std::cout.put(']');
,这取决于您在循环中正在执行的操作。
#21 楼
可能是这样..bool bFirst = true;
for (auto curr = keywords.begin(); curr != keywords.end(); ++curr) {
std::cout << (bFirst ? "" : ", ") << *curr;
bFirst = false;
}
评论
为什么有条件而不是?
–马铃薯
10年8月16日在21:48
我喜欢简洁。否则我可以被说服。
– JohnMcG
2010年8月17日15:22
每次都修改bFirst!
– Kerrek SB
2011年6月7日23:43
#22 楼
我认为@MarkB答案的这种变体实现了可读性,简单性和简洁性的最佳平衡:auto iter= keywords.begin();
if (iter!=keywords.end()) {
out << *iter;
while(++iter != keywords.end())
out << "," << *iter;
}
out << endl;
#23 楼
我会选择类似这样的简单解决方案,并且应该适用于所有迭代器。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;
}
#24 楼
您可以使用do
循环,为第一次迭代重写循环条件,并使用短路&&
运算符和有效流为true
的事实。auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;
评论
似乎这样会写两个逗号。
–迈克尔·马修斯(Michael Mathews)
10年8月16日在22:17
@Michael:哇哦,复制粘贴从原始代码中保留下来。固定。
–马铃薯
2010年8月17日,0:11
#25 楼
这会重载流运算符。是的,全局变量是邪恶的。#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;
}
#26 楼
可以使用函子:#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;
}
#27 楼
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;
#28 楼
这是您可以使用的两种方法,它们本质上是相同的想法。我喜欢这些方法,因为它们不包含任何不必要的条件检查或赋值操作。我将第一个称为打印优先方法。方法1:打印优先方法
if (!keywords.empty()) {
out << *(keywords.begin()); // First element.
for (auto it = ++(keywords.begin()); it != keywords.end(); it++)
out << ", " << *it; // Every subsequent element.
}
这是我最初使用的方法。它通过自己打印容器中的第一个元素来工作,然后在每个后续元素前打印逗号和空格。它很简单,简洁,如果您需要做的就是那么好用。一旦您想做更多的事情,例如在最后一个元素之前添加“和”,此方法就不可用了。您必须检查每个循环迭代是否在最后一个元素上。不过,在列表后添加句点或换行符并不是很糟糕。您可以在for循环之后再添加一行,以将所需的内容追加到列表中。
第二种方法我更喜欢。我将之称为print last方法,因为它与第一个方法相同,但顺序相反。
方法2: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.
}
此方法通过打印除最后一个元素外的所有元素(带有逗号和空格)来工作,允许您选择添加“和”在它之前,之后和/或换行符。如您所见,此方法为您提供了更多如何处理最后一个元素而不影响循环性能或添加大量代码的选择。
如果麻烦您离开第一部分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";
}
评论
我知道它使行更短,但是您确实应该使用for(auto iter = ...;将iter绑定到循环的范围,除非您以后明确打算使用它。@πάνταῥεῖ当它们是重复项时,为什么闭包不是相反?这篇文章肯定看起来像是更好的候选对象。