2012. március 25., vasárnap

C ++ 004 Vezérlőszerkezetek

   A programunk utasítás-sorozatokból épül fel. Vannak összetettebb utasítások is, amiket utasításblokkokba foglalunk, ezeket a blokkokat a kapcsos nyitó-, és zárójel közé helyezzük: {utasítássorozatok}. Ezeket egymásba is ágyazhatjuk, mintegy szinteket létrehozva. A kapcsos záójeleket a függvénytörzseknél is, és az összetett típusok definíciójában is használjuk majd. Ezeknek a blokkoknak sajátossága, hogy "láthatóságuk" és hatókörük van, ami azt jelenti, hogy az utasításblokkokon belül definiált változókat csak azon blokkon belül használhatjuk és érhetjük el, amiben létrehoztuk, sőt, utasításblokkokon belül felül is írhatunk egy felsőbb szinten definiált változót, melynek élettartama saját blokkjának végéig létezik, majd a külső blokk
változóját fogjuk tudni csak elérni.
{
int szam;
<utasítások>
{
float szam;
string szoveg;
<utasítások>
// itt, ha bárhol hivatkozunk a szam változóra,
// az float típus lesz, és semmi köze az egész-
// ként definiált számhoz
}
<utasítások>
// Ha itt használjuk a szam változót, az már csak
// az egész típusú szám lehet, és a szoveg stringre
// nem hivatkozhatunk
}

Vezérlőszerkezetek

   A vezérlőszerkezetek elnevezés az elágazást (szelekció) és a ciklusokat (iteráció), illetve az ugróutasításokat foglalja össze. Az elágazással egy feltétel alapján eldönthetjük, hogy a programunk merre haladjon tovább, mit tegyen, ha az adott feltétel teljesül, azaz igaz, vagy ha nem teljesül, azaz hamis lesz. A ciklus pedig megkönnyati munkánkat azzal, hogy többször végrehajtandó művleteket, utasítássorozatokat rövid formába írjunk. Az ugróutasítások, vagy másnéven vezérlőátadó utasításokat részletezzük az alábbiakban. Minden vezérlőszerkezethez készítettem egy szemléltető folyamatábrát (flowchart).

Elágazás (Szelekció)

   Az elágazás lehet egyszerű elágazás (mikor csak egy feltétel alapján döntjük el, hogy hogyan lépjünk tovább), vagy összetett (mikor egy feltétel-kifejezés kimenetei alapján többféleképp folytathatjuk a programot).

Egyszerű elágazás (if)

Az egyszerű elágazások egyetlen feltételkifejezés alapján adják át a vezérlést valamely utasítássorozatok ágára. 
Formája a következő:

if ( <feltételkifejezés> )
{
  <utasítások>
}
   Ennél a formánál csak akkor hajtódik végre bármilyen változás a programban, ha a feltételünk igaz lesz, egyébként tovább folytatódik a program. Emlékezzünk, hogy az igaz érték valójában azt jelenti a c++-ban, hogy a kifejezés értéke nem 0. Így pl. ha egy aritmetikai kifejezést teszünk meg feltételnek, akkor ha 0-át kapunk, akkor az if-blokkban szereplő utasítás nem hajtódik végre.
if-else (ha-egyébként) pár:
if ( <feltételkifejezés> )
{
<utasítások1>
}
else
{
<utasítások2>
}
   Ha a feltételünk igaz, végrehajtódik az utasítások1 sorozata, ha hamis, akkor az utasítások2 sorozata hajtódik végre. Ezzel a feltételkifejezés minden lehetséges kimenetelét lefedjük.

   Természetesen ezek az utasítások is egymásba ágyazhatók, pl. előfordulhat, hogy egy feltételt többféle szempontból is vizsgálunk, ekkor beékelhetünk újabb if-eket, vagy if-else párokat:
if  ( x > 5 )
{
// utasítások, ha x>5
if  ( x > 7 )
      {
// Ide jönnek azok az utasítások, mikor
// x nem csak 5-nél, de hétnél is
// nagyobb
      }
}
else
{
  // ha x<=5-nél, akkor mi történjen
if ( x < -5 )
{
// Végrehajtódnak a -5-nél is kisebb
// x-re vonatkozó utasítások
}
}
   Az if-es elágazások egyik legjobban használható formája az else-if szerkezet: itt egyfajta kizáró feltételeket fogalmazunk meg,azaz olyan esetekben használható, mikor egy feltétel alapján sok különböző esetet vizsgálunk, ám azok mind elkülönülnek egymástól:
if ( x == 5 )
{
// utasítások, ha x értéke 5
}
else if ( x == 7 )
{
// utasítások, ha x értéke 7
}
else if ( x == -5 )
{
// utasítások, ha x értéke -5
}
else
{
// utasítsok minden más esetben,
// tehát, ha x nem 5, nem 7 és nem is -5
    Az utolsó else ágat (ami egyfajta alapértelmezés, azaz default) elhagyhatjuk, ha nem kívánjuk lefedni az összes esetet, itt pl, ha x értéke csak akkor jelentős a program futásában, mikor 5, 7, -5 lesz.

Többszörös elágazás ( switch )

   Kulcsszavai a switch (kapcsoló), case (eset-címke), opcionálisan a default (alapeset-címke) és az egyik legfontosabb a break (törés,szünet).
Formája:

switch <kifejezés> )
{
case konstans1 :
<utasítások1>
case konstans2 :
<utasítások2>
case konstans3 :
<utasítások3>
...
default :
<utasításokn>
}
   A switch után álló kifejezés konstans értékeiből sorolhatunk fel a case címkék után, s a megfelelő konstansértékhez tartozó utasítást hajtjuk végre. A program az összes eseten végigmegy, tehát, ha konstans1 teljesül, végrehajtódik az utasítások1, majd tovább vizsgálja a címkéket, és ha konstans3 is teljesül, akkor az utasítások3 is végrehajtódnik. Ha ezt nem szeretnénk, hogy tovább vizsgálja a switch a lehetőségeket, akkor egy break kulcsszót illesztünk az adott case végére. 

Például.:
enum edesseg cukorka, rago, sutemeny, csokolade };
switch ( edesseg )
{
case cukorka:
szopogat();     // fgv
break;
case rago:
ragozik();
break;
default:
eszik();
}
   Megeshet, hogy valaki egyszerre eszik cukorkát és rágózik is, de semmiképp nem eszik csokit és süteményt is mellé, ekkor a break-et elég egy pontra kitenni:
switch ( edesseg )
{
case cukorka:
szopogat(); // fgv
case rago:
ragozik();
break;
default:
eszik();
}

Ciklusok (Iterációk)

   A ciklusok vagy iterációk egy művelet, vagy utasítás-sorozat többszöri, általában adott számszori végrehajtását teszik lehetővé egymás után. Az ismételni kívánt utasítások az úgynevezett ciklusmagban hajtódnak végre, és a ciklusból valamilyen feltétel, illetve egy kifejezés igaz vagy hamis volta alapján lépünk ki. A ciklusból való kilépést egy változó növelésével vagy csökkentésével érjük el, amíg az a ciklusfeltételben lévő kifejezéssel meg nem egyezik. Ezt a változót a ciklusváltozó névvel illetjük. Több típusuk van: az elöltelsztelős, számlálós, hátulteltesztelős. Az elöltesztelős és számlálós ciklusok könnyen át is írhatók egymásba.

Előltesztelős ciklus (while ciklus)

   Az előltesztelős ciklus a nevében is hordozza, hogy még a ciklusba, ciklusmagba való belépés előtt ellenőrizzük a ciklusfeltételt. Így, ha az hamis, be sem lépünk a ciklusmagba. Amíg a ciklusfeltétel igaz, addig a ciklusmag újra és újra végrehajtódik. Fontos, hogy ennél a ciklusfajtánál a ciklusváltozót mi magunk kell, hogy inícializáljuk, ellássuk egy kezdőértékkel a ciklus előtt, valamint, hogy a ciklusmag belsejében növeljük vagy csökkentsük.
Formája:

ciklusváltozó = kezdőérték;
while ( <feltételkifejezés> )
{
<utasítások> // ciklusváltozó növelésével/csökkentésével, azaz egy léptetőkifejezéssel
}

Számlálós ciklus (for ciklus)

   A számlálós ciklus az előltesztelős egy speciális formája, melyben pontosan tudjuk a ciklusmag végrahajtási számát. Ilyenkor a for kulcsszó megadása után egy zárójelben felsorolva adjuk meg a ciklusváltozó inícializálását, a feltételt és a ciklusváltozó növelésének/csökkentésének mértékét.
Formája:

for ( <inícializálókifejezés>; <feltételkifejezés>; <léptetőkifejezés> )
{
<utasítások>
}

A folyamatábra számlálós ciklus szimbólumával egyszerűbben:

Hátultesztelős ciklus (do-while ciklus)

A hátultesztelős ciklus olyan ciklus, mely a ciklusmag végrehajtása után ellenőrzi csak a kilépő feltételt, így a ciklusba belépve a mag legalább egyszer biztosan végrehajtódik.
Formája:
do
{
<utasítások>
}
while ( <feltételkifejezés> )

Ugró utasítások

A programunkba (ciklusokba is) becsempésztethetünk ugró utasításokat is, melyeknek vezérlésátadó szerepük van. Ugyanezt tettük a switch utasításnál is, mikor a break kulcsszóval megakadályoztuk, hogy újabb címkét vizsgáljon a fordító. A break (szünet, vagy törés) utasítás a saját blokkjában leállítja a további utasítások végrehajtását.
A continue (folytatás) utasítás ciklusmagban való elhelyezésével pl. egy ciklusra erőltethetjük annak továbbfutását egy adott ponttól, ez azt fogja eredményezni, hogy a ciklusmag további részeit átugorva a ciklus újabb futtatása következik. Ezt legtöbbször vmilyen feltétel alapján tesszük, és csak igen indokolt esetben.
Végül, van nekünk egy goto utasításunk is, melyet azonban ajánlatos elkerülni, mert gyakori használata áttekinthetetlen kódot eredményezhet. A goto utasítást címkékkel együtt használjuk. A címke szintaxisa igen egyszerű, egy név és utána kettőspont, a vezérlés a címke utáni utasításokra fog kerülni. Az alábbi módon, ha van egy x változónk, melynek értéke az <utasítások1> hatására 5 lesz, akkor kihagyjuk az <utasítások2>-t, és csak az <utasítások3> hajtódik végre.
A goto formája:
<utasítások1>
if (x==5)
     goto cimke1;
}
<utasítások2>
cimke1:
<utasítások3>

2012. március 4., vasárnap

C ++ 003 Operátorok


Ha az előző bejegyzés szerint deklarált változóazonosítókat kifejezésben használjuk, operátorandusokként is nevezzük őket, míg a rajtuk végzett műveletek szimbólumai az operátorok lesznek. 
Alapvetően két féle operátort különböztetünk meg: aritmetikai ("számolós") és relációs ("összehasonlító") operátorokat. Külön szokták említeni még a mutató operátorokat (pointer operator). A pointereknek, mutatóknak fontos szerep jut a C++ nyelvben, később részletezzük.
Először tisztázzunk két fontos fogalmat:
Kiértékelési sorrend: A fordítóprogram a kifejezésekben szereplő műveleteket meghatározott sorrendben hajtja végre. A kiértékelési sorrend határozza meg ezt, a sorban előbb álló művelet előbb hajtódik végre mint a sorban mögötte álló. Ezt a matematikában zárójelezve asszociativitásnak is nevezzük.
Precedencia: elsőbbséget jelent. A precedenciasorban előbb álló operátorok előbb fognak kiértékelődni, mint az utánuk következőek. Vannak azonos precedenciájú operátorok is, köztük a kiértékelési sorrend határozza meg a műveletek végrehajtási sorrendjét.

Aritmetikai operátorok: =, +, -, *, /, %
Az aritmetikai operátorok kiértékelése jobbról balra, visszafelé történik.
Precedenciájuk (az azonosak egy sorban, felül a magasabb):
*, /, %
+, -
=
Az = jel az értékadást jelöli. A baloldalon álló változó a jobboldalon álló kifejezés értékét fogja kapni. A kifejezés lehet természetesen egy másik változó valamilyen műveletek, vagy akár egy konstans érték is.
A + az összeadás, - kivonás, * szorzás, / osztás, % maradékképzés.
Világos, hogy amint azt említettem, a típusok és rajtuk végzett műveletek összefüggésben vannak, így pl. 2 gész szám összege, vagy különbsége nyílván egész szám, osztásnál ez már nem biztos, hogy teljesül, ezt úgy nevezzük, kivezet a művelet az értelmezési tartományunkból. Az operátorokat tehát függvényként is felfoghatjuk, értelmezési tartomány lesz a két egészünk, de a művelet, az osztás függvényének eredménye, értékkészlete már tört szám lenne. A fordítóprogram azonban kicsit másképp dolgozik: ha két int-et kap operandusként, és az eredmény tört lenne, akkor egyszerűen levágja a törtrészt, tehát a számításunk pontatlan lesz. Ekkor még kerekítésre sem számíthatunk! Tehát az operandusok típusa fogja meghatározni a kifejezés típusát. Ez néha bosszantó hibákhoz vezethet.
Érdekességképp: Matematikában két egész szám osztásának eredményét racionális számnak neveznénk, és tudjuk, hogy például a gyökvonás művelete már ebből is kivezethet, az irracionális számokba, azaz a gyökvonás értékkészlete a valós számok halmaza. Ám itt, programozáskor nem választjuk őket szét, a tört szám törtszám, a float és double típus magát a teljes valós számok körét jelentené, bár ez a memóriahatárok miatt lehetetlen. 
A % a maradékképzés operátorának első operandusa az osztandó, a második az osztó, míg az eredmény a maradék lesz. Operandusai egész típusúak kell legyenek.
Az itt leírtak megértéséhez és gyakorlásához írjunk egy rövid programot:
#include <iostream>

using namespace std;

int main()
{
    int a=22;
    int b=3;
    cout << "Egesz a=22, b=3" << endl;
    cout << "a+b=" << a+b << endl;
    cout << "a-b=" << a-b << endl;
    cout << "Ket egesz osztasa: a/b=" << a/b << endl;
    float c=22;
    float d=3;
    cout << "Valos c=22, d=3" << endl;
    cout << "Ket valos osztasa: c/d=" << c/d << endl;
    cout << "Maradekkepzes: a%b=" << a%b << endl;
    return 0;
}

Relációs operátorok: ==, !=, <, >, <=, >=
A relációs operátorok kiértékelése balról jobbra történik.
Precedenciájuk (az azonosak egy sorban, felül a magasabb):
<, >, <=, >=
==, !=
A címben szereplők sorban: egyenlőségvizsgálat, nemegyenlőség vizsgálat, baloldali érték kisebb-e a jobboldalinál, baloldali érték nagyobb-e a jobboldalinál, a baloldali érték kisebb, vagy egyenlő a jobboldalinál, a baloldali érték nagyobb vagy egyenlő a jobboldalinál. 
Ezeket az egyszerű kifejezéseket a logikai operátorokkal bővíthetjük:

Logikai operátorok: !, ||, &&
! : logikai tagadás
|| : "vagy" operátor
&& : "és" operátor
A logikai operátorok kiértékelése a tagadáson kívül balról jobbra történik, míg a logikai tagadás jobbról balra értékelődik ki.
Precedenciájuk (felül a magasabb):
!
&&
||
Működésüket táblázatban mutatom be:
Erre is írhatunk egy kis egyszerű programot, amiben gyakorolhatjuk a bool logikai típus működését is:
#include <iostream>

using namespace std;

int main()
{
    bool valasz;
    cout << "A kerdesekre adott valaszok a bool tipusnak megfeleloen: ";
    cout << "0, ha hamis, 1, ha igaz" << endl;
    cout << "15 egyenlo-e 15-tel? ";
    valasz = 15 == 15;
    cout << valasz << endl;
    cout << "15 nemegyenlo-e 15-tel? ";
    valasz = 15 != 15;
    cout << valasz << endl;
    cout << "15 kisebb-egyenlo-e 15-nel? ";
    valasz = 15 <= 15;
    cout << valasz << endl;
    cout << "15 nagyobb-egyenlo-e 14-nel? ";
    valasz = 15 >= 14;
    cout << valasz << endl;
    cout << "15 nagyobb-egyenlo-e 17-nel? ";
    valasz = 15 >= 17;
    cout << valasz << endl;
    cout << "15 egyenlo-e 15-tel ES 15 nagyobb-egyenlo-e 14-nel? ";
    valasz = 15 == 15 && 15 >= 14;
    cout << valasz << endl;
    cout << "15 nemegyenlo-e 15-tel ES 15 nagyobb-egyenlo-e 14-nel? ";
    valasz = 15 != 15 && 15 >= 14;
    cout << valasz << endl;
    cout << "15 egyenlo-e 15-tel VAGY 15 nagyobb-egyenlo-e 14-nel? ";
    valasz = 15 == 15 || 15 >= 14;
    cout << valasz << endl;
    // Változók használata esetén mindenképp
    // ajánlatos zárójeleznünk összetettebb
    // kifejezéseknél, átláthatóbb is, és
    // kevesebb hibát véthetünk:
    int a=15;
    cout << "15 nemegyenlo-e 15-tel VAGY 15 kisebb-egyenlo-e 14-nel? ";
    valasz = a != a || a <= 14;
    cout << valasz << endl;
    // Most lássunk néhány igazán összetett
    // logikai kifejezést:
    cout << "(15 == 15 && 15 >= 14) VAGY (15 != 15 && 15 >= 14)? ";
    valasz = (15 == 15 && 15 >= 14) || (15 != 15 && 15 >= 14);
    cout << valasz << endl;
    cout << "(15 == 15 && 15 <= 14) VAGY (15 != 15 && 15 >= 14)? ";
    valasz = (15 == 15 && 15 <= 14) || (15 != 15 && 15 >= 14);
    cout << valasz << endl;
    cout << "(15 != 15 || 15 >= 14) ES (15 == 15 || 15 <= 14)? ";
    valasz = (15 != 15 || 15 >= 14) && (15 == 15 || 15 <= 14);
    cout << valasz << endl;
    cout << "(15 == 15 || 15 >= 14) ES (15 != 15 || 15 <= 14)? ";
    valasz = (15 == 15 || 15 >= 14) && (15 != 15 || 15 <= 14);
    cout << valasz << endl;
    return 0;
}

Léptető operátorok:
A C++-ban gyakran használatos 
++ : növelő operátor (increment) 
-- : csökkentő operátor (decrement)
Precedenciájuk megegyezik a fenti sorrenddel.
Ezeknek az operátoroknak van úgynevezett előrevetett (prefixes) és hátravetett (postfixes) formájuk. A postfixes forma kiértékelése előbb történik, mint a prefixesé. Alapvetően megegyeznek az eggyel való növeléssel, vagy csökkentéssel. Az x++ és ++x megegyezik azzal, hogy x=x+1; az x-- és --x pedig egyezik az x=x-1 kifejezéssel. Azon kívül, hogy egyszerűbb leírni, az x++ gyorsabban hajtódik végre, sőt, a ++x még gyorsabban. Mégis miért van két alakjuk? Előre és hátravetett formájuk? 
Ha kifejezésben használjuk őket, a hátravetett forma a kifejezés kiértékelése után növeli a változó értékét, míg az előrevetett forma esetén már a kifejezés végrehajtásakor a növelt értéket fogja használni.
Pl.:
Ha az a változónak a 15 értéket adtuk, akkor az alábbi kifejezésekben az első esetben 35-öt kapunk eredményül, miközben az a értéke 16-tá változik, míg a második esetben 36-ot kapunk eredményül, és az a értéke ugyancsak 16 lesz. Sőt, ha a két kifejezést egymás után írjuk egy programon belül, akkor először 35-öt kapunk, a=16 mellett, majd 37-et, a=17 mellett:
20 + a++;
20 + ++a;

Aritmetikai operátoroknak van összevont formája:
Az aritmetikai operátoroknak van összevont formájuk is, amit akkor használunk, ha az eredményt a kifejezésben szereplő egyik operandusban szeretnénk tárolni. Ezt is gyorsabb leírni, valamint gyorsabb kódot is eredményez. Kiértékelésük jobbról balra történik, és precedenciájuk megegyezik. Lássuk mit is tudnak, ha van egy a és egy b operandusunk:
a*=b; megegyezik azzal, hogy a=a*b;
a/=b; // a=a/b;
a%=b; // a=a%b;
a+=b; // a=a+b;
a-=b; // a=a-b;

Összefoglalva az összes csoportba tartozó operátort, lássuk a teljes precedencia sorrendet:
!, ++ prefix, -- prefix
*, /, %
+, - 
<, >, <=, >=
==, !=
&&, ||
*=, /=, %=, +=, -=