Ebben a fejezetben megmutatom, hogyan lehet az eddig tanultak segítségével összetettebb
programokat írni. Háromféle vezérlési szerkezetet említek meg, a szekvenciát, az elágazást
és a ciklust. Említést teszek még a blokkokról is.
Nem titkolt cél, hogy a bevproggal küszködő gólyák valamihez kapcsolni tudják azt a sok absztrakt akármit.
Tartalomjegyzék
Ez a legegyszerűbb konstrukció, ami arról szól, hogy két vagy több utasítást közvetlenül egymás
után végrehajtunk.
A példában legyen az a feladat, hogy összeadunk két számot, és az összeg legyen hét.
Nyilván ezt a feladatot lebonthatjuk három részfeladatra,
mondjuk az egyik változó értéke legyen kettő (F1), a másiké legyen öt (F2),
majd adjuk össze ezeket (F3). A feladatot megoldó szekvencia tehát három programból fog állni (S1, S2, S3):
CODE
int a, b, c;
a = 2; // S1
b = 5; // S2
c = a + b; // S3
Ez egy három tagú szekvencia és rögtön látszik, hogy például S1 és S2 felcserélhető, de S2 és S3 nem. A megoldás során dekompozíciót alkalmaztam, azaz a feladatot kisebb feladatokra bontottam, amiket már könnyen meg lehet csinálni. Ennek párja a kompozíció, amikor már meglévő programegységekböl építjük fel a megoldást (megj.: nem ez a legmegfelelőbb példa rá, ld. inkább 5. fejezet - Alprogramok).
Ez már sokkal érdekesebb konstrukció, sőt már találkoztunk is vele az előző cikkben. Azt csinálja, hogy egy bizonyos
feltétel teljesülésétől függően hajt vagy nem hajt végre egy programot. A feltétel akkor lesz igaz, ha a benne szereplő
kifejezés értéke nem nulla, egyébként hamis lesz. Ha logikai kifejezés áll a feltétel helyén, akkor annak true
vagy false az értéke, ahol az utóbbi nulla, az előbbi meg nem nulla. A logikai értékek típusa a bool, ez a C++ -ban lett bevezetve.
CODE
int a;
std::cout << "Irj be egy szamot: ";
std::cin >> a;
if( a < 0 )
std::cout << "Ez a szam negativ\n";
Lehetőség van megadni a másik ágat is, vagyis azt hogy mi történjen akkor, amikor nem teljesül a feltétel. Ezt az else kulcsszóval tehetjük meg.
CODE
int a;
std::cout << "Irj be egy szamot: ";
std::cin >> a;
if( a < 0 )
std::cout << "Ez a szam negativ\n";
else
std::cout << "Ez a szam pozitiv\n";
Itt fel is merül egy probléma. Tekintsük az alábbi kódot:
CODE
if( a < 0 )
if( b > 5 )
// valami
else
// akkor ez most melyik if-hez tartozik?
Ezt úgy hívják, hogy csellengő else. C++ -ban mindig a legbelső if -hez tartozik az else, ha a külsőhöz akarod kapcsolni, akkor ki kell tenni a kapcsos zárójeleket. Vannak olyan nyelvek is, például a Python, amelyekben a behúzás mértéke határozza meg, hogy melyikhez tartozik (vagyis a példában a külsőhöz tartozna). Ha egy ágban nem akarsz semmit sem csinálni, akkor írhatsz egy sima ; -t. A kapcsos zárójeleket kitéve így néz ki a program:
CODE
if( a < 0 )
{
if( b > 5 )
;
}
else
;
Az elágazásoknak van egy másik fajtája is, amit esetkiválasztásos elágazásnak hívnak. A vezérlés arra az ágra fog ugrani, amelyik igazra értékelődött ki. Vigyázni kell azonban, ugyanis ha nem írsz az ág végére break -et, akkor a vezérlés tovább fog folyni a következő esetre (és ez egy fontos különbség a struktrogramoknál használt elágazáshoz képest). A szelektor típusa integral típusú kell legyen (pl. int, char, long).
CODE
int a;
std::cout << "Irj be egy szamot: ";
std::cin >> a;
switch( a )
{
case 3:
std::cout << "3-at irtal be\n";
break;
case 5:
std::cout << "5-ot irtal be\n";
default:
std::cout << "Nemtom mit irtal be\n";
}
Ha hármat írok be neki, akkor minden rendben, ha viszont ötöt, akkor azt is kiírja, hogy nem tudja mit írtam be. Ez azért történt mert az ötnél nem írtam break -et és átfolyt a vezérlés. Ennek persze lehet haszna is. Az is látható, hogy a nem lefedett esetekre lehet használni a default utasítást, ez akkor fut le, ha az összes többi hamisra értékelődött ki. C++ -ban nem kötelező minden esetet lefedni, de például Ada-ban igen.
Ugye mindenki álmából felriasztva keni-vágja a ciklus definícióját, levezetési szabályát és annak megfordítását? Miazhogy neeeem? Nyomás vissza bevprogot tanulni...
CODE
for( int i = 1; i < 11; ++i )
std::cout << i << " "; // ciklusmag
std::cout << "\n";
A for ciklus három részből áll, egy inicializáló lépésből, egy feltételből, és egy rákövetkezésből. Az inicializáló lépés egyszer fut le, itt állhat deklaráció is, azonban az itt deklarált változók a ciklusra nézve lokálisak lesznek (ld. következő alpont), és a végén felszabadulnak (amennyiben automaikusan lettek létrehozva, ld. későbbi cikk). Ha a feltétel teljesül, akkor végrehajtjuk a ciklusmagot, majd a végén a rákövetkezést, és ismét ellenőrizzük a feltételt. Így megy ez addig amíg a feltétel hamis nem lesz. Rögtön eszébe jut mindenkinek, hogy baromi könnyű végtelen ciklust csinálni, ami sosem áll le. Ha most bevprogos fejjel gondolkozunk, akkor a fenti ciklus invariánsa mondjuk az, hogy i > 0 (totál értelmetlen), a terminálási feltétele pedig az, hogy i >= 11. Egy másik fajta ciklus amit elöltesztelő vagy while ciklusnak hívnak a következőképpen fest:
CODE
int a, b;
bool fusson = true;
char ch;
while( fusson )
{
system("cls"); // linux alatt "clear"
std::cout << "Irj be ket szamot: ";
std::cin >> a >> b;
std::cout << "A ket szam osszege: " << a + b << "\n";
std::cout << "Akarod folytatni? y - igen, n - nem: ";
std::cin >> ch;
fusson = (ch == 'y');
}
Ez a ciklus is hasonlóan addig fog futni, amíg a feltétele igaz. Azért híjuk elöltesztelőnek, mert a feltételt a ciklusmag előtt értékeli ki. Felmerül a gyanú, hogy akkor van hátultesztelő ciklus is, valóban. Írjuk meg a programot szebben!
CODE
int a, b;
char ch;
do
{
system("cls"); // linux alatt "clear"
std::cout << "Irj be ket szamot: ";
std::cin >> a >> b;
std::cout << "A ket szam osszege: " << a + b << "\n";
std::cout << "Akarod folytatni? y - igen, n - nem: ";
std::cin >> ch;
}
while( ch == 'y' );
Megspóroltunk egy változót. Nyilván a feladattól függ, hogy mikor melyik ciklust alkalmazzuk. Ha egy ciklusból szeretnénk idő előtt kilépni, akkor a break utasítást kell használni. Ha szeretnénk visszaugrani a feltételhez, akkor a continue utasítást használhatjuk. Ezekkel megvalósítható a középen tesztelő ciklus is. Elvetelmülteknek mondom, hogy hátulgombolós ciklus nincs, csak hátulgombolós programozó. Írjunk egy olyan programot, ami kiírja 0 és 10 között a páros számokat és használjuk mind a két említett utasítást:
CODE
int i = -1;
while( true )
{
++i;
if( i > 10 )
break;
if( i % 2 == 1 )
continue;
std::cout << i << " ";
}
std::cout << "\n";
Kicsit erőltetett és nem túl olvasható példa, de legalább látható hogyan kell használni ezeket. Ha túllépte a 10-et, akkor kilép a ciklusból, egyébként ha a szám páratlan, akkor visszaugrik a ciklus elejére, ha meg páros akkor kiírja.
Ez nem vezérlési szerkezet (nem is megengedett konstrukció?), nem is tanultátok bevprogból, mert minek (egyébként leírható), de mégis egy fontos nyelvi elem.
A fentiekben is voltak blokkok, ilyenkor egy
új láthatósági szint jön létre, tehát az új blokkon belül újra lehet deklarálni már létező változókat, de azok elfedik az addigiakat.
CODE
int i = 2;
int main()
{
int i = 6;
{
int i = 3;
std::cout << i << "\n";
std::cout << ::i << "\n";
{
std::cout << ::i << "\n";
}
}
// output: 3 2 2
// ...
}
Egy blokkon belül deklarált változókat a blokkra nézve lokálisnak, az azon kívül deklaráltakat globálisnak hívjuk. Egy blokkon belül deklarált változók csak a blokkon belül láthatóak, a blokkból való kilépéskor már nem érhetőek el. Ez persze nem mindig jelenti azt, hogy meg is semmisülnek, erről majd lesz egy külön fejezet. Nyilván egy blokk lokális változói a beágyazott blokkokra nézve globálisak lesznek, tehát a beágyazott blokk látja a tartalmazó blokk lokális változóit. Ha ennek ismeretében nézzük meg az elágazást és a ciklust, akkor ugyanezt mondhatjuk el: egy elágazáson vagy cikluson belül deklarált változó lokális lesz arra nézve, a lefutása után már nem érhető el. A legkülső szinten (globális névtér) deklarált változókat elérhetjük a :: (scope) operátorral, de például közbülső szinteket nem tudjuk elérni. Az olyan nyelveket amikben van ilyen blokk-szerű nyelvi elem, blokkstrukturáltnak hívjuk. Például Pascalban a begin ... end is ilyen.
Van egy érdekes, három operandusú operátor, amivel ott is lehet elágazásokat írni, ahol egyébként nem. Például deklarációban.
Annyi megszorítás van, hogy a két ágnak konvertálhatónak kell lenni egymásra.
Ha két ilyet egymásba ágyaztok, akkor érdemes rendesen bezárójelezni.
CODE
int i;
std::cin >> i;
int j = (i < 0 ? 1 : (i > 5 ? 3 : 2));
Itt rögtön egymásba is ágyaztam két ilyen kifejezést; logikailag megfelel a következőnek (höfö átirni a C++ nyelvére): Ha i < 0, akkor j = 1, egyébként ha i > 5, akkor j = 3, egyébként j = 2. A fejezet végén még írok egy két szám legnagyobb közös osztóját (lnko) kiszámító programot. Ehhez gondoljuk végig mit is jelent ez. Azt jelenti, hogy m-szer is és n-szer is összeadom ugyanazt a számot (az lnko-t), és két különböző számot kapok. 2 + 2 + 2 = 6 2 + 2 + 2 + 2 = 8Most nézzük meg visszafelé mit jelent! Az ábrán is látható, hogy a nagyobbik számban legalább annyiszor megvan az lnko mint a kisebbikben. Tehát ha a nagyobbik számból kivonom a kisebbiket, akkor az így kapott számoknak még mindig ugyanaz az lnko-ja! 2 + 2 + 2 = 6 2 = 2Máris adódik, hogy mit kell csinálni: addig vonogatjuk ki a nagyobbikból a kisebbiket amíg egyenlő számokat nem kapunk. Az lesz az lnko. Már csak le kéne programozni. Ciklus és elágazás mindenképpen kelleni fog, nézzük is meg:
CODE
int main()
{
int a, b;
std::cout << "Irj be ket szamot: ";
std::cin > a > b;
while( a != b )
{
if( a > b )
a -= b;
else
b -= a;
}
std::cout << "\nA legnagyobb kozos oszto: " << a << "\n";
return 0;
}
Van ezzel néhány probléma, nevezetesen, hogy negatív számokra nem jó eredményt ad, illetve ha az egyik szám nulla, akkor szintén nem jó. Mi lehet a baj? Nyilván az, hogy negatív számoknál nem a nagyobból, hanem a kisebből kell kivonogatni. A nulla esetében pedig nem is kell számolni, hiszen az lnko a másik szám lesz. Az első problémára megmondom a megoldást. A feltétel nézzen ki a következőképpen:
CODE
// ...
if( a > 0 ? a > b : a < b )
a -= b;
else
b -= a;
A nullás esetnél a ciklus előtt érdemes ellenőrizni, hogy valamelyik nulla-e. Ez legyen höfö. Höfö:
Irodalomjegyzék http://en.cppreference.com/w/cpp http://www.cplusplus.com/ http://www.parashift.com/c++-faq-lite/ (ezt különösen ajánlott elolvasni) http://aszt.inf.elte.hu/~gsd/halado_cpp/ (ezt is) |