Можно ли объявить две переменные разных типов в цикле 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.