4. Vezérlő utasítások
4.3. Iterációs utasítások
A következő példában bemutatjuk, hogyan lehet több esethez ugyanazt a programrészletet rendelni. A programban a válaszkaraktert feldolgozó switch utasításban az 'i' és 'I', illetve az 'n' és 'N' esetekhez tartozó case címkéket egymás után helyeztük el.
#include <iostream>
A programozási nyelveken utasítások automatikus ismétlését biztosító programszerkezetet iterációnak vagy ciklusnak (loop) nevezzük. A C++ nyelv ciklusutasításai egy ún. ismétlési feltétel függvényében mindaddig ismétlik a megadott utasítást, amíg a feltétel igaz.
while (feltétel) utasítás
for (inicializáló kifejezésopt; feltételopt; léptető kifejezésopt) utasítás do utasítás while (feltétel)
A for utasítás esetén az opt index arra utal, hogy a megjelölt kifejezések használata opcionális.
A ciklusokat csoportosíthatjuk a vezérlőfeltétel feldolgozásának helye alapján. Azokat a ciklusokat, amelyeknél az utasítás végrehajtása előtt értékelődik ki vezérlőfeltétel, az elöltesztelő ciklusok. A ciklus utasítása csak akkor hajtódik végre, ha a feltétel igaz. A C++ elöltesztelő ciklusai a while és a for.
Ezzel szemben a do ciklus utasítása legalább egyszer mindig lefut, hisz a vezérlőfeltétel ellenőrzése az utasítás végrehajtása után történik – hátultesztelő ciklus.
Mindhárom esetben a helyesen szervezett ciklus befejezi működését, amikor a vezérlőfeltétel hamissá (0) válik.
Vannak azonban olyan esetek is, amikor szándékosan vagy véletlenül olyan ciklust hozunk létre, melynek vezérlőfeltétele soha sem lesz hamis. Ezeket a ciklusokat végtelen ciklusnak nevezzük:
for (;;) utasítás;
while (true) utasítás;
do utasítás while (true);
A ciklusokból a vezérlőfeltétel hamis értékének bekövetkezte előtt is ki lehet ugrani (a végtelen ciklusból is).
Erre a célra további utasításokat biztosít a C++ nyelv, mint a break, a return, illetve a ciklus törzsén kívülre irányuló goto. A ciklus törzsének bizonyos utasításait átugorhatjuk a continue utasítás felhasználásával. A continue hatására a ciklus következő iterációjával folytatódik a program futása.
I.10. ábra - A while ciklus működésének logikai vázlata
4.3.1. A while ciklus
A while ciklus mindaddig ismétli a hozzá tartozó utasítást (a ciklus törzsét), amíg a vizsgált feltétel értéke true (igaz, nem 0). A vizsgálat mindig megelőzi az utasítás végrehajtását. A while ciklus működésének folyamata az elöző ábrán (I.10. ábra - A while ciklus működésének logikai vázlata) követhető nyomon.
while (feltétel) utasítás
A következő példaprogramban meghatározzuk az első n természetes szám összegét:
#include <iostream>
using namespace std;
int main() {
int n = 2012;
cout<<"Az elso "<<n<<" egesz szam ";
unsigned long sum = 0;
while (n>0) { sum += n;
n--;
}
cout<<"osszege: "<<sum<<endl;
}
A megoldás while ciklusát tömörebben is felírhatjuk, természetesen a program olvashatóságának rovására:
while (n>0) { sum += n;
n--;
}
while (n>0) sum += n--;
while (sum += n--, n);
A C++ nyelv megengedi, hogy a változók deklarációját bárhova helyezzük a program kódján belül. Egyetlen feltétel, hogy a változót mindenképpen deklarálnunk (definiálnunk) kell a felhasználása előtt. Bizonyos esetekben a változó-definíciót a ciklusutasítások fejlécébe is bevihetjük, amennyiben a változót azonnal inicializáljuk, például véletlen számmal.
Az alábbi példaprogram while ciklusa az első 10-el osztható véletlen számig fut. A megoldásban az
srand((unsigned)time(NULL));
utasítás a véletlen szám generátorát az aktuális idővel inicializálja, így minden futtatáskor új számsort kapunk. A véletlen számot a rand() függvény szolgáltatja 0 és RAND_MAX (32767) értékhatáron belül.
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main() {
srand((unsigned)time(NULL));
while (int n = rand()%10) cout<< n<< endl;
}
Felhívjuk a figyelmet arra, hogy az így definiált n változó csak a while cikluson belül érhető el, vagyis a while ciklusra nézve lokális.
4.3.2. A for ciklus
A for utasítást általában akkor használjuk, ha a ciklusmagban megadott utasítást adott számsor mentén kívánjuk végrehajtani (I.11. ábra - A for ciklus működésének logikai vázlata). A for utasítás általános alakjában feltüntettük az egyes kifejezések szerepét:
for (inicilizáció; feltétel; léptetés) utasítás
A for utasítás valójában a while utasítás speciális alkalmazása, így a fenti for ciklus minden további nélkül átírható while ciklussá:
inicilizáció;
while (feltétel) { utasítás;
léptetés;
}
I.11. ábra - A for ciklus működésének logikai vázlata
Példaként a for ciklusra, tekintsük a már megoldott, természetes számok összegét meghatározó programot!
Azonnal látható, hogy a megoldásnak ez a változata sokkal áttekinthetőbb és egyszerűbb:
#include <iostream>
using namespace std;
int main() {
unsigned long sum;
int i, n = 2012;
cout<<"Az elso "<<n<<" egesz szam ";
for (i=1, sum=0 ; i<=n ; i++) sum += i;
cout<<"osszege: "<<sum<<endl;
}
A példában szereplő ciklus magjában csak egyetlen kifejezés-utasítás található, ezért a for ciklus az alábbi tömörebb formákban is felírható:
for (i=1, sum=0 ; i<=n ; sum += i, i++) ; illetve
for (i=1, sum=0 ; i<=n ; sum += i++) ;
A ciklusokat egymásba is ágyazhatjuk, hisz a ciklus utasítása újabb ciklusutasítás is lehet. A következő példában egymásba ágyazott ciklusokat használunk a megadott méretű piramis megjelenítésére. Mindhárom ciklusban lokálissá tettük a ciklusváltozót:
#include <iostream>
using namespace std;
int main () {
const int maxn = 12;
for (int i=0; i<maxn; i++) { for (int j=0; j<maxn-i; j++) { cout <<" ";
}
for (int j=0; j<i; j++) { cout <<"* ";
} cout << endl;
} }
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * * * * * * * * * *
4.3.3. A do-while ciklus
A do-while utasításban a ciklus törzsét képező utasítás végrehajtása után kerül sor a tesztelésre (I.12. ábra - A do-while ciklus működési logikája), így a ciklus törzse legalább egyszer mindig végrehajtódik.
I.12. ábra - A do-while ciklus működési logikája
do
utasítás while (feltétel);
Első példaként készítsük el a természetes számokat összegző program do-while ciklust használó változatát!
#include <iostream>
cout<<"Az elso "<<n<<" egesz szam ";
unsigned long sum = 0;
do {
sum += n;
n--;
} while (n>0);
cout<<"osszege: "<<sum<<endl;
}
Az alábbi ciklus segítségével ellenőrzött módon olvasunk be egy egész számot 2 és 100 között:
int m =0;
do {
cout<<"Kerek egy egesz szamot 2 es 100 kozott: ";
cin >> m;
} while (m < 2 || m > 100);
Az utolsó példánkban egész kitevőjű hatványt számolunk:
#include <iostream>
cout <<"a hatvany erteke: " << hatvany << endl;
}
Igen gyakori programozási hiba, amikor a ciklusok fejlécét pontosvesszővel zárjuk. Nézzük az 1-től 10-ig a páratlan egész számok kiírására tett kísérleteket!
int i = 1;
i+=2;
}
} i+=2;
} while(i<10);
A while esetén az üres utasítást ismétli vég nélkül a ciklus. A for ciklus lefut az üres utasítás ismétlésével, majd pedig kiírja a ciklusváltozó kilépés utáni értékét, a 11-et. A do-while ciklus esetén a fordító hibát jelez, ha a do után pontosvesszőt teszünk, így a közölt kód teljesen jó megoldást takar.
4.3.4. A brake utasítás a ciklusokban
Vannak esetek, amikor egy ciklus szokásos működésébe közvetlenül be kell avatkoznunk. Ilyen feladat például, amikor adott feltétel teljesülése esetén ki kell ugrani egy ciklusból. Erre ad egyszerű megoldást a break utasítás, melynek hatására az utasítást tartalmazó legközelebbi while, for és do-while utasítások működése megszakad, és a vezérlés a megszakított ciklus utáni első utasításra kerül.
Az alábbi while ciklus kilép, ha megtalálja a megadott két egész szám legkisebb közös többszörösét:
#include <iostream>
cout << "Legkisebb kozos tobbszoros: " << lkt << endl;
}
A break utasítás használata kiküszöbölhető, ha a hozzá kapcsolódó if utasítás feltételét beépítjük a ciklus feltételébe, ami ezáltal sokkal bonyolultabb lesz:
while (lkt<=a*b && !(lkt % a == 0 && lkt % b == 0)) { lkt++;
}
Ha a break utasítást egymásba ágyazott ciklusok közül a belsőben használjuk, akkor csak a belső ciklusból lépünk ki vele. Az alábbi példában megjelenítjük a prímszámokat 2 és maxn között. A kilépés okát egy logikai (flag, jelző) változó (prim) segítségével adjuk a külső ciklus tudtára:
#include <iostream>
}
Ha feladat úgy hangzik, hogy keressük meg a legelső Pitagoraszi számhármast a megadott intervallumban, akkor találat esetén egyszerre két for ciklusból kell kilépnünk. Ekkor a legegyszerűbb megoldáshoz egy jelző (talalt) bevezetésével jutunk:
#include <iostream>
#include<cmath>
using namespace std;
int main () { int bal, jobb;
cout <<"bal = "; cin >> bal;
cout <<"jobb = "; cin >> jobb;
bool talalt = false;
for(int a = bal, c, c2; a<=jobb && !talalt; a++) { for(int b = bal; b<=jobb && !talalt; b++) { c2 = a*a + b*b;
c = static_cast<int>(sqrt(float(c2)));
if (c*c == c2) { talalt = true;
cout << a << ", " << b << ", " << c << endl;
} // if } // for } // for } // main()
4.3.5. A continue utasítás
A continue utasítás elindítja a while, a for és a do-while ciklusok soron következő iterációját. Ekkor a ciklustörzsben a continue után elhelyezkedő utasítások nem hajtódnak végre.
A while és a do-while utasítások esetén a következő iteráció a ciklus feltételének ismételt kiértékelésével kezdődik. A for ciklus esetében azonban, a feltétel feldolgozását megelőzi a léptetés elvégzése.
A következő példában a continue segítségével értük el, hogy a 1-től maxn-ig egyesével lépkedő ciklusban csak a 7-tel vagy 12-gyel osztható számok jelenjenek meg:
#include <iostream>
using namespace std;
int main(){
const int maxn = 123;
for (int i = 1; i <= maxn; i++) { if ((i % 7 != 0) && (i % 12 != 0)) continue;
cout<<i<<endl;
} }
A break és a continue utasítások gyakori használata rossz programozói gyakorlat. Mindig érdemes átgondolnunk, hogy meg lehet-e valósítani az adott programszerkezetet vezérlésátadó utasítások nélkül. Az előző példában ezt könnyedén megtehetjük az if feltételének megfordításával:
for (int i = 1; i <= maxn; i++) { if ((i % 7 == 0) || (i % 12 == 0)) cout<<i<<endl;
}