这个问题在这里已经有了答案:如何打印以逗号分隔的元素列表? (33 个回答) 使用 foreach 语法 [重复] (6 个回答) 6 年前关闭时,如何检查我是否在最后一个元素上。
每个人都会在某个时候遇到这个问题:
for(const auto& item : items) {
cout << item << separator;
}
...最后你会得到一个你不想要的额外分隔符。有时它不是打印,而是执行一些其他动作,但是相同类型的连续动作需要一些分隔符动作 - 但最后一个不需要。
现在,如果你使用老式的 for 循环和数组,你会做
for(int i = 0; i < num_items; i++)
cout << items[i];
if (i < num_items - 1) { cout << separator; }
}
(或者你可以特殊情况下循环中的最后一项。)如果你有任何允许非破坏性迭代器的东西,即使你不知道它的大小,你可以这样做:
for(auto it = items.cbegin(); it != items.cend(); it++) {
cout << *it;
if (std::next(it) != items.cend()) { cout << separator; }
}
我不喜欢最后两个的美学,喜欢范围内的 for 循环。我能否获得与最后两个相同的效果,但使用更漂亮的 C++11ish 构造?
这个
for(const auto& item : items) {
cout << item;
} and_between {
cout << separator;
}
join
。这是一个巨大的耻辱。
fold
不会帮助您-它将对每个元素应用操作,包括第一个和最后一个。
std::accumulate
,但是一个元素应该没问题(单个元素作为 init
传递,最初是 begin == end
,导致没有折叠)
我的方式(没有额外的分支)是:
const auto separator = "WhatYouWantHere";
const auto* sep = "";
for(const auto& item : items) {
std::cout << sep << item;
sep = separator;
}
从迭代中排除一个结束元素是 Ranges 提案旨在简化的事情。 (请注意,有更好的方法来解决字符串连接的特定任务,将元素从迭代中分离只会产生更多需要担心的特殊情况,例如当集合已经为空时。)
当我们等待标准化的 Ranges 范式时,我们可以使用现有的 ranged-for 和一个小助手类来做到这一点。
template<typename T> struct trim_last
{
T& inner;
friend auto begin( const trim_last& outer )
{ using std::begin;
return begin(outer.inner); }
friend auto end( const trim_last& outer )
{ using std::end;
auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};
template<typename T> trim_last<T> skip_last( T& inner ) { return { inner }; }
现在你可以写了
for(const auto& item : skip_last(items)) {
cout << item << separator;
}
演示:http://rextester.com/MFH77611
对于使用 ranged-for 的 skip_last
,需要一个双向迭代器,对于类似的 skip_first
,有一个 Forward 迭代器就足够了。
bool
类型的纯右值可以转换为 int
类型的纯右值,其中 false
变为零,true
变为一。”
join
,而不是跳过最后一个元素。 (只应保留最后一个分隔符)。
你知道Duff's device吗?
int main() {
int const items[] = {21, 42, 63};
int const * item = items;
int const * const end = items + sizeof(items) / sizeof(items[0]);
// the device:
switch (1) {
case 0: do { cout << ", ";
default: cout << *item; ++item; } while (item != end);
}
cout << endl << "I'm so sorry" << endl;
return 0;
}
希望我没有毁了每个人的一天。如果你不想,那么永远不要使用它。
(咕哝)对不起……
处理空容器(范围)的设备:
template<typename Iterator, typename Fn1, typename Fn2>
void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) {
switch ((from == to) ? 1 : 2) {
case 0:
do {
butFirst(*from);
case 2:
always(*from); ++from;
} while (from != to);
default: // reached directly when from == to
break;
}
}
int main() {
int const items[] = {21, 42, 63};
int const * const end = items + sizeof(items) / sizeof(items[0]);
for_the_device(items, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << endl << "I'm (still) so sorry" << endl;
// Now on an empty range
for_the_device(end, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << "Incredibly sorry." << endl;
return 0;
}
foreach
,而是为每个 元素做一些事情。大多数时候,一个循环就是一个循环。
goto
?
我不知道有什么特殊的成语。但是,我更喜欢先对特殊情况进行处理,然后再对其余项目执行操作。
#include <iostream>
#include <vector>
int main()
{
std::vector<int> values = { 1, 2, 3, 4, 5 };
std::cout << "\"";
if (!values.empty())
{
std::cout << values[0];
for (size_t i = 1; i < values.size(); ++i)
{
std::cout << ", " << values[i];
}
}
std::cout << "\"\n";
return 0;
}
输出:"1, 2, 3, 4, 5"
for( const auto& item : skip_first(values) )
... 以满足问题的要求。
skip_first
是我们必须定义的(假设的)函数,还是有一个 C++ 函数可以满足需要?
通常我会以相反的方式进行操作:
bool first=true;
for(const auto& item : items) {
if(!first) cout<<separator;
first = false;
cout << item;
}
我喜欢简单的控制结构。
if (first == last) return;
while (true) {
std::cout << *first;
++first;
if (first == last) break;
std::cout << separator;
}
根据您的喜好,您可以将增量和测试放在一行中:
...
while (true) {
std::cout << *first;
if (++first == last) break;
std::cout << separator;
}
int a[3] = {1,2,3};
int size = 3;
int i = 0;
do {
std::cout << a[i];
} while (++i < size && std::cout << ", ");
输出:
1, 2, 3
目标是使用评估 &&
的方式。如果第一个条件为真,则评估第二个条件。如果不成立,则跳过第二个条件。
我不认为您可以在 somewhere 处使用特殊情况...例如,Boost 的 String Algorithms Library 有一个 join 算法。如果您查看它的 implementation,您会看到第一项的特殊情况(没有继续分隔符),并且在每个后续元素之前添加了 then 分隔符。
您可以定义一个函数 for_each_and_join 以两个函子作为参数。第一个函子适用于每个元素,第二个函子适用于每对相邻元素:
#include <iostream>
#include <vector>
template <typename Iter, typename FEach, typename FJoin>
void for_each_and_join(Iter iter, Iter end, FEach&& feach, FJoin&& fjoin)
{
if (iter == end)
return;
while (true) {
feach(*iter);
Iter curr = iter;
if (++iter == end)
return;
fjoin(*curr, *iter);
}
}
int main() {
std::vector<int> values = { 1, 2, 3, 4, 5 };
for_each_and_join(values.begin(), values.end()
, [](auto v) { std::cout << v; }
, [](auto, auto) { std::cout << ","; }
);
}
我不知道“惯用语”,但 C++11 为双向迭代器提供了 std::prev
和 std::next
函数。
int main() {
vector<int> items = {0, 1, 2, 3, 4};
string separator(",");
// Guard to prevent possible segfault on prev(items.cend())
if(items.size() > 0) {
for(auto it = items.cbegin(); it != prev(items.cend()); it++) {
cout << (*it) << separator;
}
cout << (*prev(items.cend()));
}
}
items
的大小。
我喜欢 boost::join
功能。因此,对于更一般的行为,您需要一个为每对项目调用的函数并且可以具有持久状态。您可以将其用作带有 lambda 的函数调用:
foreachpair (range, [](auto left, auto right){ whatever });
现在您可以使用 range filters 回到基于范围的常规 for
循环!
for (auto pair : collection|aspairs) {
Do-something_with (pair.first);
}
在这个想法中,pair
被设置为原始集合的一对相邻元素。如果你有“abcde”,那么在第一次迭代中你会得到 first='a' 和 second='b';下次通过 first='b' 和 second='c';等等
您可以使用类似的过滤器方法来准备一个元组,该元组使用 /first/middle/last/ 迭代的枚举标记每个迭代项,然后在循环内进行切换。
要简单地省略最后一个元素,请使用范围过滤器作为 all-but-last。我不知道这是否已经在 Boost.Range 中,或者 Rangev3 正在提供什么,但这是使常规循环发挥作用并使其“整洁”的一般方法。
这是我喜欢使用的一个小技巧:
对于双向可迭代对象: for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (it == items.end()-1 ? "" : sep); };
使用三元 ?
运算符,我将迭代器的当前位置与 item.end()-1
调用进行比较。由于 item.end()
返回的迭代器指向最后一个元素之后 的位置,因此我们将其递减一次以获取我们实际的最后一个元素。
如果该项不是可迭代对象中的最后一个元素,我们返回分隔符(在别处定义),或者如果它是最后一个元素,我们返回一个空字符串。
对于单向迭代(使用 std::forward_list 测试): for ( auto it = items.begin(); it != items.end(); it++ ) { std::cout << *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };
在这里,我们使用当前迭代器位置和可迭代对象的结尾调用 std::distance 来替换之前的三元条件。
请注意,此版本适用于双向可迭代对象和单向可迭代对象。
编辑:我知道您不喜欢 .begin()
和 .end()
类型的迭代,但是如果您希望保持 LOC 倒计时,在这种情况下您可能不得不避开基于范围的迭代。
如果您的比较逻辑相对简单,则“技巧”只是将比较逻辑包装在一个三元表达式中。
std::distance
除了一直迭代到结束之外没有办法知道答案,并且您在每次循环迭代中都在这样做。
不定期副业成功案例分享
separator
需要成为std::string
而不是char const*
的上下文中使用它,则直接移植此模式将导致每次迭代都需要昂贵的复制。这可以通过以下方式避免:也有一个空常量std::string
,首先使sep
成为指向它的指针,输出*sep
,最后在下一次迭代之前使sep
成为指向separator
的指针。