【问题标题】:Is there an equivalent to the "for ... else" Python loop in C++?C++ 中是否有与“for ... else”Python 循环等效的方法?
【发布时间】:2014-09-01 20:10:47
【问题描述】:

Python 有一个有趣的 for 语句,可让您指定 else 子句。

在这样的结构中:

for i in foo:
  if bar(i):
    break
else:
  baz()

else 子句在for 之后执行,但前提是for 正常终止(不是break)。

我想知道 C++ 中是否有等价物?我可以使用for ... else吗?

【问题讨论】:

  • 这是一个不时出现的有趣想法。出于好奇,这种结构在实际代码中的使用频率如何?
  • @KerrekSB 这是真正的问题。当我开始学习 Python 时,我发现这对初学者来说是微不足道的和困惑的。我找不到这个结构的任何紧迫要求。
  • @m.wasowski:真的吗?通常标准的提前返回对于类似查找的算法来说效果很好,不需要单独的“循环完成”块。
  • 我确实喜欢这种结构,并且我进行了搜索以查看是否可以在我的代码中找到它的一些用例来回答 @KerrekSB ,但我需要将其发布为答案,因为 cmets 是不够
  • @agomcas:从那以后我确信这种结构很有用。推理是这样的:if (c)while (c)for (...; c; ...) 中的所有三个都评估 c,但目前只有第一个允许您访问此表达式的结果。另外两个正在丢弃这些信息,这违反了不应丢弃作为计算的一部分获得的结果的原则。

标签: python c++ loops for-loop break


【解决方案1】:

这是我在 C++ 中的粗略实现:

bool other = true;
for (int i = 0; i > foo; i++) {
     if (bar[i] == 7) {
          other = false;
          break;
     }
} if(other)
     baz();

【讨论】:

  • 哎呀,解决了这个问题。
  • +1 因为我认为这个解决方案最清楚地说明了意图。
  • 为什么是i > foo?不应该是i < foo吗?
【解决方案2】:

是的,您可以通过以下方式达到相同的效果:

auto it = std::begin(foo);
for (; it != std::end(foo); ++it)
     if(bar(*it))
         break;
if(it == std::end(foo))
    baz();

【讨论】:

  • 请注意,当 Python 循环遍历可迭代 foo 的元素时,这会从 0 计数到 foo。不过可能很容易调整(与 end() 相比)。
  • 没有。 Python 循环根本不算数。 i 可能永远不是整数。它更像是 C++11 for (auto i : foo)
  • for(auto it = begin(something); it != end(something); ++i) ... if (it == end(something))`... 另外请记住,每次不必要地增加后缀,一只小猫就会死去。
  • @Joeytje50 i++ 可能会慢很多,因为它涉及创建一个新对象;使用它是一个坏习惯,你不会在可以使用++i 的合适代码中找到它。就个人而言,它迫使我不得不放慢速度,思考是否有理由使用它,或者它只是草率的代码。即使有一秒钟,也可能很烦人。
  • @Joeytje50 请注意,这是针对不同编程语言的答案。例如,迭代器不是像int 这样的原始类型。另请参阅下面的 cmets,您会发现应将 ++i 用作默认值的所有原因。恕我直言,为了保持一致性,原始类型也应该使用相同的,例如int
【解决方案3】:

类似:

auto it = foo.begin(), end = foo.end();
while ( it != end && ! bar( *it ) ) {
    ++ it;
}
if ( it != foo.end() ) {
    baz();
}

应该做的伎俩,它避免了非结构化的break

【讨论】:

  • break 恕我直言很好,无论如何,这种变化与实际的肉完全正交(it != foo.end())。
  • @BenVoigt 好点。这就是打字太快的结果。应该是while;我会解决的。
【解决方案4】:

如果不介意使用goto 也可以通过以下方式完成。这节省了额外的if 检查和更高范围的变量声明。

for(int i = 0; i < foo; i++)
     if(bar(i))
         goto m_label;
baz();

m_label:
...

【讨论】:

  • @MatthiasB 据我了解,问题不在于使用什么,而是关于 C++ 中可能的实现。
  • @MatthiasB 而且我不认为'goto'已经过时了。因为仍然有一些字段需要省略额外的“if”检查和声明以考虑性能和内存,同时仍然使用 C++ 进行编程。
  • goto 部分还不错。这就是我在这种情况下使用的。我认为它实际上比基于标志的解决方案更具可读性。
  • 如果我们考虑语言等效性,那么这是唯一有效的答案。 Python 没有 goto,而 C++ 有,所有其他示例,例如使用 return、临时变量和 break,或者异常都可以用 Python 完成。此外,这种情况(for else)以及打破嵌套循环可能是在 C++ 中有效应用 goto 的唯一两种情况。
  • goto 定义明确,通常是复杂循环处理的最易理解(即最可维护)的解决方案。这个解决方案有我的投票(我会给出一个类似的)。
【解决方案5】:

表达实际逻辑的更简单方法是使用std::none_of

if (std::none_of(std::begin(foo), std::end(foo), bar))
    baz();

如果 C++17 的范围提议被接受,希望这将简化为:

if (std::none_of(foo, bar)) baz();

【讨论】:

  • 不完全等价。例如,它不允许通过“栏”修改容器的元素。更不用说,它让我(一个 C++ 开发人员)去帮助中查找它,而原始的 OP python 示例我立即理解没有问题。 stl变成了一堆泥巴。
  • 我指的是此处的帮助页面:cplusplus.com/reference/algorithm/none_of 或此处en.cppreference.com/w/cpp/algorithm/all_any_none_of。两者都说谓词不得修改参数。
  • @0kcats: hmmm... 25.1/8 确实说 “函数对象 pred 不应通过取消引用的迭代器应用任何非常量函数。”,所以你'重新正确。有趣的!相关讨论here。干杯
【解决方案6】:

C++ 中没有这样的语言结构,但是,由于预处理器的“魔力”,您可以自己制作一个。例如,像这样(C++11):

#include <vector>
#include <iostream>
using namespace std;

#define FOR_EACH(e, c, b) auto e = c.begin(); for (; e != c.end(); ++e) {b} if (e == c.end()) {}

int main()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);

    FOR_EACH(x, v, {
        if (*x == 2) {
            break;
        }        
        cout << "x = " << *x << " ";
    })
    else {
        cout << "else";
    }

    return 0;
}

这应该输出x = 1 else

如果将if (*x == 2) { 更改为if (*x == 3) {,则输出应为x = 1 x = 2

如果你不喜欢在当前作用域中添加了一个变量,你可以稍微改变一下:

#define FOR_EACH(e, c, b, otherwise) {auto e = c.begin(); for (; e != c.end(); ++e) {b} if (e == c.end()) {} otherwise }

那么使用将是:

FOR_EACH(x, v, {
    if (*x == 2) {
        break;
    }        
    cout << "x = " << *x << " ";
},
else {
    cout << "else";
})

当然,它并不完美,但如果小心使用,可以节省一些打字时间,如果大量使用,它会成为项目“词汇表”的一部分。

【讨论】:

  • 牺牲可读性来节省 5 秒的打字时间通常是一个非常糟糕的主意。
  • 嗯,可读性是主观的,而打字不是。这是一个权衡,就像其他一切一样。我不提倡使用它,只是展示了如何在没有 goto 的情况下在 C++ 中实现“for each...else”(这有其自身的问题)。
  • 这里的 goto 模式比声明宏要好得多。
  • 我不确定我对宏在我的作用域中留下一个新变量的感觉。在您的示例中,将在 else 语句之后留下一个迭代器 x。没有一个很好的方法来解决这个问题......
  • 我添加了一个示例来避免将新变量带入当前范围。我不是说它很好,但是,它有效。 :)
【解决方案7】:

我不知道在 C/C++ 中实现这一点的优雅方式(不涉及标志变量)。建议的其他选项比这更可怕......

为了回答@Kerrek SB 关于现实生活中的用法,我在我的代码中找到了一些(简化的 sn-ps)

示例 1:典型的查找/失败

for item in elements:
    if condition(item):
        do_stuff(item)
        break
else: #for else
    raise Exception("No valid item in elements")

示例 2:尝试次数有限

for retrynum in range(max_retries):
    try:
        attempt_operation()
    except SomeException:
        continue
    else:
        break
else: #for else
    raise Exception("Operation failed {} times".format(max_retries))

【讨论】:

  • 第一个例子可以用python内置的anymap或迭代器理解替换为if语句。
  • @agomcas:这很有趣,与等效 C++ 的对比也是如此,例如:for (int retrynum = 0; ; ++retrynum) { try { attempt_operation(); } catch (const SomeException&amp;) { if (retrynum == max_retries) throw ...; } }。不知道我更喜欢哪个。
  • @АндрейБеньковский 但我相信使用 any() 构造没有办法恢复哪个元素满足条件。在 for...else 情况下,您将它存储在 item 中,并且可以用它做其他事情。
  • @agomcas 我继续我的实验。 link。我不确定这两种解决方案中哪一种更具可读性。
  • @agomcas 稍微改进的版本:melpon.org/wandbox/permlink/xiQBiPYY9zBV0ceY
【解决方案8】:

您可以为此使用 lambda 函数:

[&](){
  for (auto i : foo) {
    if (bar(i)) {
      // early return, to skip the "else:" section.
      return;
    }
  }
  // foo is exhausted, with no item satisfying bar(). i.e., "else:"
  baz();
}();

这应该与 Python 的“for..else”完全一样,并且与其他解决方案相比,它具有一些优势:

  • 它是“for..else”的真正替代品:“for”部分可能有副作用(与 none_of 不同,其谓词不得修改其参数),并且它可以访问外部范围。
  • 它比定义一个特殊的宏更具可读性。
  • 它不需要任何特殊的标志变量。

但是...我自己会使用笨重的标志变量。

【讨论】:

    【解决方案9】:

    可能没有一个解决方案最适合所有问题。在我的情况下,标志变量和基于范围的for 循环与auto 说明符效果最好。下面是相关代码的等价物:

    bool none = true;
    for (auto i : foo) {
      if (bar(i)) {
        none = false;
        break;
      }
    }
    if (none) baz();
    

    它比using iterators 少打字。特别是,如果你使用for 循环来初始化一个变量,你可以使用它来代替布尔标志。

    感谢auto 键入它比std::none_of 更好,如果您想内联条件而不是调用bar()(并且如果您不使用C++14)。

    我遇到了两种情况都发生的情况,代码如下所示:

    for (auto l1 : leaves) {
      for (auto x : vertices) {
        int l2 = -1, y;
        for (auto e : support_edges[x]) {
          if (e.first != l1 && e.second != l1 && e.second != x) {
            std::tie(l2, y) = e;
            break;
          }
        }
        if (l2 == -1) continue;
    
        // Do stuff using vertices l1, l2, x and y
      }
    }
    

    这里不需要迭代器,因为v表示break是否发生。

    使用 std::none_of 需要在 lambda 表达式的参数中明确指定 support_edges[x] 元素的类型。

    【讨论】:

    【解决方案10】:

    这不仅在 C++ 中是可能的,在 C 中也是可能的。不过,我将坚持使用 C++ 以使代码易于理解:

    for (i=foo.first(); i != NULL || (baz(),0); i = i.next())
    {
        if bar(i):
            break;
    }
    

    我怀疑我是否会让它通过代码审查,但它有效且高效。在我看来,这也比其他一些建议更清楚。

    【讨论】:

      【解决方案11】:

      直接回答:不,您可能不能,或者它充其量是基于编译器的。但是这里有一个宏的破解方法!

      几点说明:

      我通常使用 Qt 进行编程,所以我习惯于使用 foreach 循环,而不必直接处理迭代器。

      我使用 Qt 的编译器 (v 5.4.2) 对此进行了测试,但它应该可以工作。出于多种原因,这很糟糕,但通常可以满足您的需求。我不容忍这样的编码,但只要你注意语法,它就没有理由不工作。

      #include <iostream>
      #include <vector>
      
      #define for_else(x, y) __broke__ = false; for(x){y} if (__broke__) {}
      #define __break__ __broke__ = true; break
      
      bool __broke__;  // A global... wah wah.
      
      class Bacon {
        public:
          Bacon(bool eggs);
      
          inline bool Eggs() {return eggs_;}
      
        private:
          bool eggs_;
      };
      
      Bacon::Bacon(bool eggs) {
        eggs_ = eggs;
      }
      
      bool bar(Bacon *bacon) {
        return bacon->Eggs();
      }
      
      void baz() {
        std::cout << "called baz\n";
      }
      
      int main()
      {
        std::vector<Bacon *>bacons;
      
        bacons.push_back(new Bacon(false));
        bacons.push_back(new Bacon(false));
        bacons.push_back(new Bacon(false));
      
        for_else (uint i = 0; i < bacons.size(); i++,
            std::cout << bacons.at(i)->Eggs();
            if (bar(bacons.at(i))) {
              __break__;
            }
        ) else {
          baz();
        }
      
        bacons.push_back(new Bacon(true));
        bacons.push_back(new Bacon(false));
      
        for_else (uint i = 0; i < bacons.size(); i++,
            std::cout << bacons.at(i)->Eggs();
            if (bar(bacons.at(i))) {
              __break__;
            }
        ) else {
          baz();
        }
      
        return EXIT_SUCCESS;
      }
      

      【讨论】:

        【解决方案12】:

        通过定义两个宏,您几乎可以像在 Python 中一样使用 for-else:

        #define BREAK {CONTINUETOELSE = false; break;}
        #define FORWITHELSE(x, y) {bool CONTINUETOELSE = true; x if(!CONTINUETOELSE){} y}
        

        现在您将forelse 放在FORWITHELSE 宏中,用逗号分隔,并使用BREAK 而不是break。这是一个例子:

        FORWITHELSE(
            for(int i = 0; i < foo; i++){
                if(bar(i)){
                    BREAK;
                }
            },
            else{
                baz();
            }
        )
        

        您需要记住两件事:在else 之前放置一个逗号以及使用BREAK 而不是break

        【讨论】:

          【解决方案13】:

          我来到这里是因为我有同样的问题,但在 C 语言中。 我出来的最好的东西是

          bool notTerminated = true;
          for (int i = 0; i < 50 || (notTerminated = false); i++)
              if (bar(i))
                  break;
          if (! notTerminated)
              baz();
          

          说明:(notTerminated = false) 是一个始终返回 false 值的赋值,它永远不会影响条件,并且如果条件为真,则会对其进行评估。

          【讨论】:

            【解决方案14】:

            我会用一个简单的辅助变量来完成这个:

            #include <stdio.h>
            #include <stdbool.h>
            
            int main()
            
            {
                bool b;
                printf("Numbers which are multiples of 7:\n");
            
                for (int i=8; b=(i<12); i++)
                {
                    if (i%7==0)
                    {
                        printf("%d", i);
                        break;
                    }
                }
                if (!b)
                {
                    printf("no numbers found\n");
                }
                return 0;
            }
            

            这样,你只需要在一个地方实现条件(在上面的例子中i&lt;12)。

            【讨论】:

              猜你喜欢
              • 2015-03-14
              • 1970-01-01
              • 2017-04-15
              • 2010-12-29
              • 2019-03-07
              • 2015-04-30
              • 2021-02-02
              • 1970-01-01
              相关资源
              最近更新 更多