在for循环的初始化器中是否有一种方法可以定义两种不同类型的变量?

7

你可以在for循环中定义两个相同类型的变量:

int main() {
  for (int i = 0, j = 0; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

但是定义不同类型的变量是非法的:

int main() {
  for (int i = 0, float j = 0.0; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

有没有一种方法可以做到这一点?(我不需要在循环内使用,只需使用j。)
如果您有完全破解和模糊的解决方案,对我来说是可以的。
在这个人为的例子中,我知道你可以只使用double作为两个变量。我正在寻找一个通用答案。
请不要建议将任何变量移动到for主体外面,对我来说可能无法使用,因为其中一个是迭代器,必须在循环后立即消失,并且for语句要包含在我的foreach宏中:
#define foreach(var, iter, instr) {                  \
    typeof(iter) var##IT = iter;                     \
    typeof(iter)::Element var = *var##IT;            \
    for (; var##_iterIT.is_still_ok(); ++var##IT, var = *var#IT) {  \
      instr;                                         \
    }                                                \
  }

它可以这样使用:

foreach(ii, collection, {
  cout << ii;
}). 

但是我需要一个可以像这样使用的东西:

foreach(ii, collection)
  cout << ii;

请不要引入任何运行时开销(但编译可能会慢)。

1
你意识到可能没有任何解决方案能满足你提出的所有条件吗? - David Z
6
请不要建议移动任何变量到for循环体之外,因为这对我来说可能无法使用,因为迭代器在循环后必须消失。你似乎创建了一个人为的要求。你关心的是迭代器的作用域,而不是它是否在for循环体内。只需解释你的要求,而不要添加你关于作用域的先入为主的观念。这样更容易让人理解你的问题并给予帮助。 - dss539
3
每当有人提出解决方案时,你都会补充一句“请不要说……”,并评论“没用……会改变身体……”。你为什么这样做? - Johannes Schaub - litb
5
我需要的是类似于这样的东西:foreach(ii, collection) cout << ii;这是我最近看到的最大的胡说八道。请停止制定任何新的人为要求。顺便说一句,foreach宏毫无关系,与在for循环中定义2个变量的问题无关。 - lothar
1
嗯...最简单的问题:#include <boost/foreach.hpp>,然后使用:BOOST_FOREACH( int & x, container ) - David Rodríguez - dribeas
显示剩余8条评论
13个回答

27

请不要建议将任何变量移动到for循环体外,对我来说可能无用,因为迭代器在循环后必须消失。

你可以这样做:

#include <iostream>

int main( int, char *[] ) {
    {
        float j = 0.0;

        for ( int i = 0; i < 10; i += 1, j = 2*i ) {
            std::cout << j << std::endl;
        }
    }

    float j = 2.0; // works

    std::cout << j << std::endl;

    return 0;
}

11
为什么它不够用?它满足了你所有的要求。 - Michael Myers
2
@Łukasz:为什么不呢?这违反了你的哪个条件? - Reunanen
2
鉴于您想要满足的所有条件,我认为没有更实际的解决方案了。 - xian
3
人工作用域强制变量j在作用域结束时被“销毁”。这与将j定义为与i同时的方式完全相同。 - Naaff
1
我不知道为什么这个更好的答案建议简单地打开和关闭一个临时作用域却没有被标记,而原帖作者却认为用多个冗余的“for”循环来模仿作用域是真正的解决方案。打开和关闭作用域的能力是有原因的。像这样的事情。“for”循环存在的目的是循环,而不是在粗糙、毫无意义的近似临时作用域中被粘在一起。 - underscore_d
显示剩余2条评论

11

嗯,这很丑陋。但你可以使用pair。

int main() {
  for (std::pair<int,float> p(0,0.0f); 
       p.first < 10; 
       p.first += 1, p.second = 2*p.first) {
    cout << p.second << endl;
  }
}

1
不错的想法!然后通过使用boost::tuple将其推广到两种以上类型。 - Reunanen
好主意,但它强制更改循环体,所以我不能接受它。 - Łukasz Lew
3
希望使用 boost::tuple 回答,但你已经比我先回答了 :) 但是为什么不使用 for (std::pair<int,float> p(0,0.0f); p.first < 10; p.first++, p.second = 2*p.first) { ... } ? - Johannes Schaub - litb
3
对于这段美妙的代码爆炸(这是原帖作者所要求的),我给予赞赏。 - dss539
1
#define i p.first #define j p.second现在,您不需要更改循环的主体。 - Matt Cruikshank

10

这里提供一种使用boost预处理器的版本(仅供娱乐,真正的解决方案请参见上面@kitchen的答案):

FOR((int i = 0)(int j = 0.0), i < 10, (i += 1, j = 2 * i)) { 

}
第一部分指定了一系列声明:(a)(b)...。后面声明的变量可以引用先前声明的变量。第二部分和第三部分与通常一样。在第二部分和第三部分中出现逗号时,可以使用括号来防止它们分隔宏参数。
我知道有两个技巧用于声明变量,这些变量后来在添加到宏外的复合语句中可见。第一个使用条件,例如 if:
if(int k = 0) ; else COMPOUND_STATEMENT

然后k是可见的。自然地,它总是必须计算为false。因此它不能被我们使用。另一个上下文是这个:

for(int k = 0; ...; ...) COMPOUND_STATEMENT

这就是我要在这里使用的方法。我们必须注意只执行一次COMPOUND_STATEMENT。实际的for循环执行增量和条件检查必须放在最后,以便追加的复合语句与其相关。

#include <boost/preprocessor.hpp>
#include <iostream>

#define EMIT_DEC_(R,D,DEC) \
    for(DEC; !_k; ) 

#define FOR(DECS, COND, INC) \
    if(bool _k = false) ; else \
      BOOST_PP_SEQ_FOR_EACH(EMIT_DEC_, DECS, DECS) \
        for(_k = true; COND; INC)

int main() {
    FOR((int i = 0)(float j = 0.0f), i < 10, (i += 1, j = 2 * i)) {
        std::cout << j << std::endl;
    }
}

这将创建一堆嵌套的for语句。它会被展开为:

if(bool _k = false) ; else
  for(int i = 0; !_k; )
    for(float j = 0.0f; !_k; )
      for(_k = true; i < 10; (i += 1, j = 2 * i)) {
        std::cout << j << std::endl;
      }

结果发现第一个if和两个for循环被优化掉了。 - Łukasz Lew

8
{
  int i = 0;
  float j = 0.0;
  for ( ; i < 10; i += 1, j = 2*i) {
    cout << j << endl;
  }
}

变量在块结束后会“消失”。

1
这是在宏中创建变量的规范方法。 - Chris Arguin

7
这将使迭代器(或在这种情况下,浮点数)在不再需要时消失:
int main() {
  // some code...

  {
    float j = 0.0;
    for (int i = 0; i < 10; i += 1, j = 2*i) {
      cout << j << endl;
    }
  }

  // more code...
}

6

如果你在使用宏时遇到问题,有一个标准的do..while技巧可以完美解决:

#define MYFOR(init, test, post, body) \
    do \
    { \
        init \
        for( ; test; post) \
            body \
    } while(0)

使用方法如下:

MYFOR( int i = 0; float j = 0.0f; , i < 10 , (i += 1, j = 2.0f * i),
    {
         cout << j << endl;
    } );

虽然它不太美观,但它确实可以满足您的需求:从宏定义中,ij的作用域受到do..while循环的限制,并且在结尾需要加上分号,这样在if/else语句的条件中使用时就不会有问题。


这是我想到的最好的方法。但我需要将主体与宏分开。 - Łukasz Lew

5
这个例子也不太好看,但是提供了一种在for循环中声明多个具有相同名称和类型的变量的通用方式。
int main() {
  for (struct { int i; float j; } x = { };
       x.i < 10; x.i += 1, x.j = 2 * x.i) {
    cout << x.j << endl;
  }
}

4

编辑:问题又有所变化。现在明确要求实现一个foreach循环。最简单的答案:

#include <boost/foreach.hpp>
void( std::vector<int>& v ) {
   BOOST_FOREACH( int & x, v ) {
      x = x*2;
   }
}

将变量注入代码块

本文并非答案,而是展示一种更通用的技术,用于将变量注入代码块。虽然它似乎会导致一些额外的开销,但OP试图定义的宏可能会使用到它。

有几个地方可以定义具有不同作用域的变量。你可以在任何代码块内定义一个变量,其生命周期将持续到该特定块的结束。你可以在for循环的括号中定义一个变量,其作用域将是循环块。你也可以在if块内定义一个变量,其作用域将是该if语句(包括else子句)。

你可以将上述选项结合起来,创建外部并将变量注入代码块,而不创建一个生命周期超过该块的变量。一个实际的例子是定义一个foreach循环(简化为仅适用于STL容器)。调用语法如下:

void f( std::vector<int>& container ) 
{
   INTVECTOR_FOREACH( int & x, container )
   {
      x = x*2;
   }
}

该函数与其他语言中的 foreach 语义类似:将x引用到容器中的每个元素,因此该函数实际上会将整数向量中的每个值都加倍。

现在,这是简化宏的代码:

#define INTVECTOR_FOREACH( variable, container ) \
   for ( std::vector<int>::iterator it = container.begin(); it!=container.end(); ++it ) \
      if ( bool condition=false ) {} else \
         for ( variable = *it; !condition; condition=true )

将宏泛化为任何容器和类型需要一些元编程,这超出了问题的上下文,但是其工作原理(我希望)不太难理解。外部的循环迭代容器,在每次迭代中,我们执行另一个for,只定义迭代变量(样例代码中的int&x)一次。我们需要一个条件来控制内部循环的迭代次数(1),并且该条件通过if注入。我们选择使if失败,以确保如果用户在循环后写else语句时不会得到意外结果...宏很棘手。

但这会破坏 breakcontinue。请注意,您可以直接在 if 部分声明变量,它将起作用。您的变量必须测试为 true。 - xryl669

3

请不要建议将任何变量移动到for循环体外面,对我来说可能无法使用,因为迭代器必须在循环后立即消失。

你仍然可以这样做,并将整个内容放在花括号中,以使额外的变量超出范围。

int main() 
{
  {
    float j = 0.0;
    for (int i = 0; i < 10; i += 1, j = 2*i) 
    {
      cout << j << endl;
    }
  }
  // more code...
}

这样,j将在循环后立即超出作用域。

2

根据您的要求,我能想到的最简单的代码是:

for ( int i = 0; i < 10; ++i )
{
   float f = i * 2;
   std::cout << f << std::endl;
}

你只使用i的两倍作为f的值。生存期仅限于循环中,并且(至少在你提供的简化问题中)创建浮点数很便宜(正好与分配一样便宜)。
如果构建真实的float(我假设i不是int,因此f可能也不是float)比重新分配值更昂贵,则将其封装在额外的花括号对中以限制范围的其他解决方案将是最佳选择。

啊!被你一秒钟给抢先了!(根据SO) - Reunanen

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