Czy można zadeklarować dwie zmienne różnych typów w pętli for?

240

Czy możliwe jest zadeklarowanie dwóch zmiennych różnych typów w treści inicjalizacyjnej pętli for w C ++?

Na przykład:

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

definiuje dwie liczby całkowite. Mogę zdefiniować inta charw organizmie inicjalizacji? Jak by to było zrobione?

Nathan Osman
źródło
3
Możliwe jest w g ++ - 4.4 ( -std=c++0x) w formie for(auto i=0, j=0.0; ..., ale ta możliwość została usunięta w g ++ - 4.5, aby pokrywać się z tekstami c ++ 0x.
rafak

Odpowiedzi:

133

C ++ 17 : Tak! Należy użyć ustrukturyzowanej wiążącej deklaracji . Składnia jest obsługiwana w gcc i clang od lat (od gcc-7 i clang-4.0) ( przykład na żywo clang ). To pozwala nam rozpakować krotkę w następujący sposób:

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

Powyższe daje ci:

  • int i Ustawić 1
  • double f Ustawić 1.0
  • std::string s Ustawić "ab"

Upewnij się, że #include <tuple>dla tego rodzaju deklaracji.

Możesz podać dokładne typy wewnątrz tuple, wpisując je wszystkie tak jak ja std::string, jeśli chcesz nazwać typ. Na przykład:

auto [vec, i32] = std::tuple{std::vector<int>{3, 4, 5}, std::int32_t{12}}

Specyficznym zastosowaniem tego jest iteracja po mapie, uzyskanie klucza i wartości,

std::unordered_map<K, V> m = { /*...*/ };
for (auto& [key, value] : m) {
   // ...
}

Zobacz przykład na żywo tutaj


C ++ 14 : Możesz zrobić to samo co C ++ 11 (poniżej) z dodatkiem opartego na typie std::get. Zamiast tego std::get<0>(t)w poniższym przykładzie możesz mieć std::get<int>(t).


C ++ 11 : std::make_pairpozwala to zrobić, a także std::make_tupledla więcej niż dwóch obiektów.

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

std::make_pairzwróci dwa argumenty w std::pair. Dostęp do elementów można uzyskać za pomocą .firsti .second.

W przypadku więcej niż dwóch obiektów musisz użyć 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_tupleto szablon variadic, który zbuduje krotkę dowolnej liczby argumentów (oczywiście z pewnymi ograniczeniami technicznymi). Dostęp do elementów można uzyskać za pomocą indeksu za pomocąstd::get<INDEX>(tuple_object)

W obiektach pętli for można łatwo aliasować obiekty, choć nadal trzeba użyć .firstlub std::getdla warunku pętli for i zaktualizować wyrażenie

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 i C ++ 03 Możesz jawnie nazwać typy a std::pair. Nie ma jednak standardowego sposobu na uogólnienie tego na więcej niż dwa typy:

for (std::pair<int, std::string> p(5, "Hello World"); p.first < 10; ++p.first) {
    std::cout << p.second << std::endl;
}
Ryan Haining
źródło
5
Jeśli korzystasz z C ++ 17, możesz nawet upuścić make_i pisać std::pair(1, 1.0).
Marc Glisse,
Owłosiona krotka / para w stylu C ++ 14 - wszystko dobrze (prawdopodobnie, pozytywnie oceniany), ale wygląda dziwnie :)
mlvljr
3
W skrócie: Tak, jest to możliwe, ale nie będzie ładne.
Jakiś programista koleś
Tak, nie ładna, ale to zajebiste! Absolutnie podobał mi się ten krotek. :) Ale tak naprawdę to bardzo nieintuicyjna jakość syntaktyczna pętli for w C ++ i sprawiła mi ból głowy przez ponad pół godziny, aby w końcu zdać sobie sprawę z tego, co musiało być Googled ...
aderchox
@aderchox, jeśli możesz wyjaśnić swoje nieporozumienie, mogę zaktualizować odpowiedź
Ryan Haining
276

Nie - ale technicznie istnieje obejście (nie żebym go użył, chyba że byłbym zmuszony):

for(struct { int a; char b; } s = { 0, 'a' } ; s.a < 5 ; ++s.a) 
{
    std::cout << s.a << " " << s.b << std::endl;
}
Georg Fritzsche
źródło
3
Nie kompiluje się w VS 2008, ale w Comeau online ;-)
JRL
7
@JRL: Och, nie ma też VS2005. To chyba kolejna funkcja niezgodności w VC ++.
Georg Fritzsche,
3
Zrobiłem równoważny w Perlu. Jednak nie próbowałem wymyślić czegoś takiego poprzez przegląd kodu w C ++.
John
21
z c ++ 11 I możesz skrócić ten przykład, używając wartości domyślnychstruct { int a=0; char b='a'; } s;
Ryan Haining
1
Ta odpowiedź spełnia wymagania odpowiedzi, ale z punktu widzenia czytelności wolę @MK. odpowiedź. Rozwiązanie MK rozwiązuje nawet problem określania zakresu, dodając nawiasy klamrowe.
Trevor Boyd Smith
221

Niemożliwe, ale możesz zrobić:

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

Lub wyraźnie ogranicz zakres fi istosowanie dodatkowych nawiasów:

{
    float f; 
    int i;
    for (i = 0,f = 0.0; i < 5; i++)
    {
       //...
    }
}
MK.
źródło
Wiem, że to bardzo stare pytanie, ale czy możesz wyjaśnić, dlaczego niektórzy zrobiliby to z dodatkowymi nawiasami wokół niego, jak w twoim drugim przykładzie?
ford
13
@fizzisist, aby wyraźnie ograniczyć zakres f i i tylko do części kodu, w których są używane.
MK.
1
@MK. Dzięki, tak podejrzewałam. Zredagowałem twoją odpowiedź, aby to wyjaśnić.
ford
Tylko jedno pytanie: dlaczego tak? : O
rohan-patel
Ponieważ działa jak „int a = 0, b = 4”, zakładam. Biorąc to pod uwagę, określanie zakresu fi będzie prawdopodobnie przydatne tylko w celu zapobiegania ponownemu użyciu tych nazw (co jest słusznym powodem), ale generowany kod będzie zwykle taki sam w nowoczesnym kompilatorze (w tym przypadku).
Asu
14

Nie można zadeklarować wielu typów podczas inicjalizacji, ale można przypisać wiele typów EG

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

Po prostu zadeklaruj je we własnym zakresie.

zmbush
źródło
3

Myślę, że najlepszym podejściem jest odpowiedź Xian .

ale...


# Zagnieżdżone dla pętli

To podejście jest brudne, ale można je rozwiązać we wszystkich wersjach.

więc często używam go w funkcjach makro.

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
    }

Dodatkowe 1.

Może być również używany do declare local variablesi 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.
    }

Dodatkowe 2.

Dobry przykład: z funkcją makro.

(Jeśli nie można zastosować najlepszego podejścia, ponieważ jest to makro dla pętli)

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

# Sztuczka z instrukcją If

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

Jeśli chcesz zainicjować 0lub nullptr, możesz użyć tej sztuczki.

ale nie polecam tego z powodu trudnego czytania.

i wygląda na błąd.

mgcation
źródło
Nigdy nie przestaje mnie zadziwiać, jak różni są niektórzy ludzie od innych. Nigdy bym nie pomyślał o takich osobliwościach. Ciekawe pomysły
Dr Osoba Osoba II
1

Zobacz „ Czy istnieje sposób definiowania zmiennych dwóch typów w pętli for? ”, Aby poznać inny sposób zagnieżdżania wielokrotności pętli for. Zaletą drugiej strony w porównaniu z „sztuczką strukturalną” Georga jest to, że (1) pozwala na połączenie statycznych i niestatycznych zmiennych lokalnych i (2) pozwala mieć zmienne niemożliwe do skopiowania. Minusem jest to, że jest znacznie mniej czytelny i może być mniej wydajny.

tgoodhart
źródło
-2

Zdefiniuj makro:

#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++)
{
  //...
}

Pamiętaj tylko, że zakresy zmiennych również nie będą znajdować się w pętli for.

Ryan Favale
źródło
Można łatwo obejść to ograniczenie, zawijając kod w makrze w osobnym zakresie za pomocą {i }.
Nathan Osman,
4
Nie, nie mógł. Jego makro nie zawija ciała pętli. Mógłby dodać dodatkowy wspornik otwierający, ale wymagałoby to dodatkowego wspornika zamykającego podczas korzystania z makra.
John
3
To ciekawy pomysł, ale wcześniej zastanowiłbym się nad którąkolwiek z pozostałych odpowiedzi.
gregn3
-2

Możesz także użyć jak poniżej w C ++.

int j=3;
int i=2;
for (; i<n && j<n ; j=j+2, i=i+2){
  // your code
}
Loyola
źródło