Можно ли объявить две переменные разных типов в цикле for?


240

Можно ли объявить две переменные разных типов в теле инициализации цикла for в C ++?

Например:

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

определяет два целых числа Могу ли я определить intи charв теле инициализации? Как это будет сделано?


3
Это возможно в g ++ - 4.4 ( -std=c++0x) в форме for(auto i=0, j=0.0; ..., но эта возможность была удалена в g ++ - 4.5, чтобы совпадать с текстами c ++ 0x.
Рафак

Ответы:


133

C ++ 17 : Да! Вы должны использовать объявление структурированной привязки . Синтаксис поддерживается в gcc и clang в течение многих лет (начиная с 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>для такого рода деклараций.

Вы можете указать точные типы внутри tuple, напечатав их все как у меня std::string, если вы хотите назвать тип. Например:

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 : вы можете сделать то же самое, что и C ++ 11 (ниже), с добавлением типов на основе std::get. Таким образом, вместо std::get<0>(t)приведенного ниже примера, вы можете иметь std::get<int>(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 << std::endl;
}

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) << std::endl; // 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 вы можете легко создавать псевдонимы для объектов, хотя вам все равно нужно использовать .firstили std::getдля условия цикла for и выражения update

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 << std::endl; // cout Hello world
    v.push_back(i); // add counter value to the vector
}

C ++ 98 и C ++ 03 Вы можете явно назвать типы a std::pair. Не существует стандартного способа обобщить это более чем на два типа:

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

5
Если вы делаете C ++ 17, вы можете даже оставить make_и написать std::pair(1, 1.0).
Марк Глисс

Волосатый бизнес кортеж / пара в стиле C ++ 14 - все хорошо (вероятно, проголосовало), но выглядит странно :)
mlvljr

3
Короче говоря: да, это возможно, но не будет красиво.
Какой-то программист чувак

Да, не красиво, но это дурь! Абсолютно понравился кортеж. :) Но на самом деле это очень неинтуитивное синтаксическое качество циклов for в C ++ и дало мне головную боль более получаса, чтобы наконец понять, что нужно гуглить ...
aderchox

@aderchox, если вы можете прояснить свое недоразумение, я могу обновить ответ
Райан

276

Нет, но технически есть обходной путь (не то, чтобы я использовал его, если бы не был вынужден):

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

3
Это не компилируется на VS 2008, но на Comeau онлайн ;-)
JRL

7
@JRL: Да, и VS2005 тоже. Еще одна особенность несоответствия в VC ++, я думаю.
Георг Фрицше

3
Я сделал эквивалент в Perl. Однако я не пробовал пробраться через что-то подобное в обзоре кода на C ++.
Джон

21
с C ++ 11 I вы можете сделать этот пример короче, используя значения по умолчаниюstruct { int a=0; char b='a'; } s;
Райан Хейнинг

1
Этот ответ соответствует требованиям ответа, но из удобочитаемости POV я предпочитаю @MK. ответ. Решение МК даже обращается к области видимости, добавляя фигурные скобки.
Тревор Бойд Смит

221

Не возможно, но вы можете сделать:

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

Или явно ограничьте область применения fи iиспользуйте дополнительные скобки:

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

Я знаю, что это очень старый вопрос, но можете ли вы объяснить, почему некоторые делают это с дополнительными скобками, как в вашем втором примере?
Форд

13
@fizzisist, чтобы явно ограничить область действия f и i только частями кода, где они используются.
МК.

1
@MK. Спасибо, это то, что я подозревал. Я отредактировал твой ответ, чтобы объяснить это.
Форд

Только один вопрос: почему так? : O
Рохан-Патель

Я полагаю, потому что это работает как 'int a = 0, b = 4'. Тем не менее, область видимости f и i, вероятно, будет полезна только для предотвращения повторного использования этих имен (что является достаточной причиной), но сгенерированный код обычно будет таким же на современном компиляторе (в данном случае).
Асу

14

Вы не можете объявить несколько типов при инициализации, но вы можете назначить несколько типов EG

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

Просто объявите их в своей области видимости.


3

Я думаю, что лучший подход - это ответ Сианя .

но...


# Вложено для цикла

Этот подход грязен, но может решить любую версию.

поэтому я часто использую его в макросах.

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.

Это также может быть использовано для declare local variablesи initialize global variables.

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)

#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

Если вы хотите инициализировать в 0или nullptr, вы можете использовать этот трюк.

но я не рекомендую это из-за жесткого чтения.

и это похоже на ошибку.


Меня не перестает удивлять то, как разные люди думают о других. Я бы никогда не подумал о таких странностях. Интересные идеи.
Доктор Человек Персонал II

1

См. « Есть ли способ определить переменные двух типов в цикле for? », Чтобы узнать о другом способе, заключающемся во вложении нескольких циклов for. Преимущество другого способа перед «структурным трюком» Георга состоит в том, что он (1) позволяет вам иметь смесь статических и нестатических локальных переменных и (2) позволяет иметь непереписываемые переменные. Недостатком является то, что он гораздо менее читабелен и может быть менее эффективным.


-2

Определите макрос:

#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.


Вы можете легко преодолеть это ограничение, поместив код в макрос в отдельную область, используя {и }.
Натан Осман

4
Нет, он не мог. Его макрос не оборачивает тело цикла. Он мог бы добавить дополнительную открывающую скобку, но это потребовало бы «дополнительной» закрывающей скобки при использовании макроса.
Джон

3
Это интересная идея, но я бы скорее воспользовался любыми другими ответами, прежде чем обсуждать это.
gregn3

-2

Также вы можете использовать, как показано ниже в C ++.

int j=3;
int i=2;
for (; i<n && j<n ; j=j+2, i=i+2){
  // your code
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.