在for循环中声明两个不同类型的变量是否可行?

319

在 C++ 的 for 循环初始化体中,能否声明两个不同类型的变量?

例如:

for(int i=0,j=0 ...

定义了两个整数。我能在初始化主体中定义一个intchar吗?如何做到呢?


6
еңЁg++-4.4 (-std=c++0x)дёӯпјҢеҸҜд»ҘдҪҝз”Ёfor(auto i=0, j=0.0; ...зҡ„еҪўејҸпјҢдҪҶжҳҜеңЁg++-4.5дёӯиҝҷз§ҚеҸҜиғҪжҖ§иў«з§»йҷӨд»ҘдёҺc++0xж–Үжң¬дёҖиҮҙгҖӮ - rafak
1
由于这个问题是许多想在C中寻找相同问题的人首先遇到的,在这里是C语言的等效版本。 - RobertS supports Monica Cellio
注意:阅读 https://dev59.com/WnE85IYBdhLWcg3wnU-p#2687427。 - VimNing
8个回答

314

不行 - 但技术上有一个变通方法(除非被迫,否则我实际上不会使用它):

for(struct { int a; char b; } s = { 0, 'a' } ; s.a < 5 ; ++s.a) 
{
    std::cout << s.a << " " << s.b << std::endl;
}

31
使用C++11,你可以使用默认值将此示例缩短:struct { int a=0; char b='a'; } s; - Ryan Haining
1
@TrevorBoydSmith:这太丑了,变量到处散落。 - VimNing
3
谢谢。我刚刚因为这个代码段而狂笑不已:for(struct { std::vector<float>::iterator it; size_t count; } v { vec.begin(), 1 }; v.it < vec.end(); ++v.it, ++v.count) { ... } - adfriedman

250

不能实现,但您可以这样做:

float f;
int i;
for (i = 0,f = 0.0; i < 5; i++)
{
  //...
}

或者,使用额外的括号明确限定fi的作用域:

{
    float f; 
    int i;
    for (i = 0,f = 0.0; i < 5; i++)
    {
       //...
    }
}

21
@fizzisist明确限制f和i的范围,仅限于它们在代码中使用的部分。 - MK.
1
@MK。谢谢,这正是我所怀疑的。我编辑了你的回答来解释这一点。 - ford
只有一个问题:为什么会这样? :O - rohan-patel
但这会让你的缩进看起来非常丑陋。 - VimNing
在我看来,这应该是被接受的答案。仅仅为了限制作用域而使用元组(等)的结构化绑定是非常晦涩难懂的。如果你想/需要限制作用域,只需使用花括号即可。这就是它们存在的意义。 - Oliver Schönrock
显示剩余2条评论

239

C++17: 是的! 你应该使用结构化绑定声明。自gcc-7和clang-4.0以来,这种语法已经得到了支持(clang实时示例)。这使我们可以像这样解包元组:

for (auto [i, f, s] = std::tuple{1, 1.0, std::string{"ab"}}; i < N; ++i, f += 1.5) {
    // ...
}

以上代码将会给你以下结果:
  • int i 被设置为 1
  • double f 被设置为 1.0
  • std::string s 被设置为 "ab"
请确保在使用这种声明时添加#include <tuple>
如果你想要命名类型,可以像我使用std::string那样在tuple中指定所有类型。例如:
auto [vec, i32] = std::tuple{std::vector<int>{3, 4, 5}, std::int32_t{12}}

这个的具体应用是在遍历一个映射时,获取键和值。
std::unordered_map<K, V> m = { /*...*/ };
for (auto& [key, value] : m) {
   // ...
}

点击此处查看实时示例。


C++14: 你可以通过类型为基础的std::get来实现与C++11(下面)相同的功能。因此,在下面的示例中,可以使用std::get<int>(t)代替std::get<0>(t)


C++11: std::make_pair可以让您做到这一点,而std::make_tuple可以用于两个以上对象。

for (auto p = std::make_pair(5, std::string("Hello World")); p.first < 10; ++p.first) {
    std::cout << p.second << '\n';
}

std::make_pair 函数会返回一个由两个参数组成的 std::pair 对象。可以使用 .first.second 访问这两个元素。

当有超过两个对象时,需要使用 std::tuple

for (auto t = std::make_tuple(0, std::string("Hello world"), std::vector<int>{});
        std::get<0>(t) < 10;
        ++std::get<0>(t)) {
    std::cout << std::get<1>(t) << '\n'; // cout Hello world
    std::get<2>(t).push_back(std::get<0>(t)); // add counter value to the vector
}

std::make_tuple是一个可变参模板,可以构造任意数量的参数元组(当然有一些技术上的限制)。元素可以通过索引使用std::get<INDEX>(tuple_object)进行访问。

在for循环体中,您可以轻松地给对象起别名,但在for循环条件和更新表达式中仍需要使用.firststd::get

for (auto t = std::make_tuple(0, std::string("Hello world"), std::vector<int>{});
        std::get<0>(t) < 10;
        ++std::get<0>(t)) {
    auto& i = std::get<0>(t);
    auto& s = std::get<1>(t);
    auto& v = std::get<2>(t);
    std::cout << s << '\n'; // cout Hello world
    v.push_back(i); // add counter value to the vector
}

C++98和C++03,您可以显式命名std::pair的类型。但是,没有标准的方法可以将其推广到超过两个类型:

for (std::pair<int, std::string> p(5, "Hello World"); p.first < 10; ++p.first) {
    std::cout << p.second << '\n';
}

8
如果你在使用 C++17,甚至可以省略make_并直接写成std::pair(1, 1.0) - Marc Glisse
1
C++14风格的元组/对的代码看起来有些奇怪,但是功能都很好(可能会被点赞)。 - mlvljr
4
简而言之:是的,这是可能的,但不会很美观。 - Some programmer dude
我知道这是“惯用”的C++(至少在C++17时代),但天哪,那真的很丑陋。整个std命名空间以自命不凡的姿态存在于语言中,这只是朝着错误方向迈出的一大步,在我看来跨越了太远的距离。 - Armen Michaeli
1
此时,我会忘记整个在for循环初始化中声明多个不同类型变量的事情,只是在循环之前声明它们。 - code

15

你无法在初始化时声明多个类型,但是可以将值分配给多个类型,例如:

{
   int i;
   char x;
   for(i = 0, x = 'p'; ...){
      ...
   }
}

在它们自己的作用域中声明它们即可。


但这会让你的缩进看起来非常丑陋。 - VimNing

3
我认为最好的方法是使用xian的回答

但是...


# 嵌套的for循环

这种方法虽然有点糟糕,但可以解决所有版本的问题。

所以,在宏函数中我经常使用它。

for(int _int=0, /* make local variable */ \
    loopOnce=true; loopOnce==true; loopOnce=false)

    for(char _char=0; _char<3; _char++)
    {
        // do anything with
        // _int, _char
    }

补充1。

它还可以用于声明局部变量初始化全局变量

float globalFloat;

for(int localInt=0, /* decalre local variable */ \
    _=1;_;_=0)

    for(globalFloat=2.f; localInt<3; localInt++) /* initialize global variable */
    {
        // do.
    }

附加2.

好的例子:使用宏函数。

(如果因为使用了for-loop-macro而无法使用最佳方法,那么请参考此示例)

#define for_two_decl(_decl_1, _decl_2, cond, incr) \
for(_decl_1, _=1;_;_=0)\
    for(_decl_2; (cond); (incr))


    for_two_decl(int i=0, char c=0, i<3, i++)
    {
        // your body with
        // i, c
    }

# If语句小技巧

if (A* a=nullptr);
else
    for(...) // a is visible

如果您想将其初始化为0nullptr,可以使用这个技巧。

但是我不建议这样做,因为它很难阅读。

而且看起来像一个错误。


1
请注意,在这里“break”和“continue”将不能按预期工作。 - Michaël
@Michaël:为什么?在所有的例子中,难道不应该继续执行最内层的“for”循环吗? - VimNing
还有一种方式:for(int i = 0; i < whatever; i++) if (A & a = get_a(i)),它不会破坏breakcontinue,并且是透明的。缺点是A必须实现一个显式的operator bool返回true。 - xryl669
非常棒的if语句技巧,谢谢。 - Sam Liddicott

0
参见 "在for循环中定义两种类型的变量有没有办法?",这是另一种涉及嵌套多个for循环的方法。与Georg的"结构技巧"相比,另一种方法的优点是它(1)允许您拥有静态和非静态的局部变量,并且(2)允许您拥有不可复制的变量。缺点是它可读性较差,可能效率也较低。

-3

在C++中,你也可以像下面这样使用。

int j=3;
int i=2;
for (; i<n && j<n ; j=j+2, i=i+2){
  // your code
}

-6

定义一个宏:

#define FOR( typeX,x,valueX,  typeY,y,valueY,  condition, increments) typeX x; typeY y; for(x=valueX,y=valueY;condition;increments)

FOR(int,i,0,  int,f,0.0,  i < 5, i++)
{
  //...
}

请记住,这种方式下你的变量作用域也不会在for循环内部。


你可以通过使用 {} 将宏中的代码包装在单独的作用域中来轻松克服这个限制。 - Nathan Osman
4
不行。他的宏没有将循环体包裹起来。他可以添加一个额外的左括号,但使用该宏时会需要一个“额外”的右括号来闭合。 - John
4
这是一个有趣的想法,但在考虑这个之前,我更愿意使用其他答案中的任何一个。 - gregn3

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