如何使用foreach语法在迭代时检查是否到达最后一个元素

38
例如:
for( auto &iter: item_vector ) {
     if(not_on_the_last_element) printf(", ");
}
或者
for( auto &iter: skill_level_map ) {
     if(not_on_the_last_element) printf(", ");
}

1
只是一个提示:你可以将打印“,”的操作移到循环体的开头,并检查是否为第一个元素,而不是检查是否为最后一个元素。这很容易做到,只需在循环之前设置bool first = true;,并在循环体内设置first = false;即可。 - user743382
1
为什么不使用来自基础知识的ostream_joinerostream_joiner - Kerrek SB
6个回答

74

你实际上不能。这就是range-for的一个要点,即您不需要迭代器。但您可以改变打印逗号的逻辑,使其在不是第一个时打印:

你不能真正地使用迭代器,这也是range-for的设计初衷,因为你不需要它们。但你可以通过更改打印逗号的逻辑,使其在非第一个元素时打印,来达到类似的效果:
bool first = true;
for (auto& elem : item_vector) {
    if (!first) printf(", ");
    // print elem
    first = false;
}

如果这本来就是循环的意图,或者您可以比较地址:

for (auto& elem : item_vector) {
    if (&elem != &item_vector.back()) printf(", ");
    // ...
}

3
酷招(第一段),加一! - vsoftco
我需要包含 auto& 吗? - CrazyVideoGamer
但是这假设向量中没有重复项,对吗? - Thanasis Petsas
1
@ThanasisPetsas 不,它并没有。 - Barry
对不起,你是正确的!我没有注意到第二个例子中引用(&)运算符的重要性! - Thanasis Petsas
显示剩余3条评论

11

没有绝对的好方法,但是如果我们可以轻松访问容器的最后一个元素...

std::vector<int> item_vector = ...;
for (auto & elem : item_vector) {
    ...
    if (&elem != &item_vector.back())
        printf(", ");
}

7
这种类型的循环最好使用“Loop and a Half”结构来编写:(循环加半)
#include <iostream>
#include <vector>

int main()
{
    auto somelist = std::vector<int>{1,2,3,4,5,6,6,7,8,9,6};

    auto first = begin(somelist), last = end(somelist);
    if (first != last) {                // initial check
        while (true) {
            std::cout << *first++;     
            if (first == last) break;   // check in the middle
            std::cout << ", ";
        }
    }
}
实时示例,输出:

1、2、3、4、5、6、6、7、8、9、6

即最后一个元素没有分隔符。

与do-while(前置检查)或for_each / range-based for(末尾检查)不同的是,中间的检查使其不同。试图在这些循环中强制使用常规for循环将引入额外的条件分支或重复的程序逻辑。


这如何比在循环结尾进行检查少了条件?你链接到的 Rosetta Code 上的示例通过迭代到 end(...) - 1 来避免条件。 - j b

6
这就像是一个状态模式。
#include <iostream>
#include <vector>
#include <functional>

int main() {
    std::vector<int> example = {1,2,3,4,5};

    typedef std::function<void(void)> Call;
    Call f = [](){};
    Call printComma = [](){ std::cout << ", "; };
    Call noPrint = [&](){ f=printComma; };
    f = noPrint;

    for(const auto& e:example){
        f();
        std::cout << e;
    }

    return 0;
}


Output:

1, 2, 3, 4, 5

第一次循环中,f 指向 noPrint ,这只是为了使 f 然后指向 printComma ,因此逗号仅在第二个及其后续项之前打印。


1
避免所有条件语句的绝妙方法!通过在数字前打印逗号,您避免了检查最后一个元素的需要。通过在第一个循环中修改执行路径,您可以避免所有条件语句。伟大的解决方案。它肯定更难理解,但它避免了所有条件语句。 - Gardener
1
因为在循环中处理不必要的条件语句可能需要很长时间,如果可以用单个函数调用替换它,则可以避免这种情况。话虽如此,这个答案有点过度使用状态模式。 - quamrana
"有点儿"是轻描淡写的表述。 - Silidrone
[&] 中的 & 是一个 lambda 捕获符,可以在 这里 找到详细解释。这意味着 lambda 需要知道的任何内容都是通过引用捕获的。因此,在 lambda 内部,fprintComma 都作为对应于 main() 中本地变量的引用而被访问。 - quamrana
1
一个更简单的替代方案是 const char* separator = ""; for (const auto& e : v) { std::cout << separator << e; separator = ", "; }。这可能更容易理解。甚至可以将其放在 C++20 的循环初始化器中。 - Etherealone
显示剩余2条评论

1

将这段代码存储在一个头文件中,并放在你的实用工具包中,确保安全保存:

namespace detail {
    template<class Iter>
    struct sequence_emitter
    {
        sequence_emitter(Iter first, Iter last, std::string sep)
        : _first(std::move(first))
        , _last(std::move(last))
        , _sep(std::move(sep))
        {}

        void write(std::ostream& os) const {
            bool first_element = true;
            for (auto current = _first ; current != _last ; ++current, first_element = false)
            {
                if (!first_element)
                    os << _sep;
                os << *current;
            }
        }

    private:
        Iter _first, _last;
        std::string _sep;
    };

    template<class Iter>
    std::ostream& operator<<(std::ostream& os, const sequence_emitter<Iter>& se) {
        se.write(os);
        return os;
    }
}

template<class Iter>
detail::sequence_emitter<Iter>
emit_sequence(Iter first, Iter last, std::string separator = ", ")
{
    return detail::sequence_emitter<Iter>(std::move(first), std::move(last), std::move(separator));
}

然后,您可以像这样发出任何容器的任何范围,而无需尾随分隔符:
vector<int> x { 0, 1, 2, 3, 4, 5 };
cout << emit_sequence(begin(x), end(x)) << endl;

set<string> s { "foo", "bar", "baz" };

cout << emit_sequence(begin(s), end(s), " comes before ") << endl;

预期输出:
0, 1, 2, 3, 4, 5
bar comes before baz comes before foo

0

基于范围的for循环是为了遍历整个范围而设计的。如果您不想这样做,为什么不使用常规的for循环呢?

auto end = vector.end() - 1;
for (auto iter = vector.begin(); iter != end; ++iter) {
   // do your thing
   printf(", ");
}
// do your thing for the last element

如果您不想重复编写代码来“做您的事情”,就像我一样,那么创建一个 lambda 函数并调用它即可:
auto end = vector.end() - 1;
// create lambda
for (auto iter = vector.begin(); iter != end; ++iter) {
   lambda(*iter);
   printf(", ");
}
lambda(vector.back());

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