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
*, /, %
+, - 
<, >, <=, >=
==, !=
&&, ||
*=, /=, %=, +=, -=

2012. február 18., szombat

C++ 002 Beépített típusok


A C++ adattípusokból, operátorokból, kulcsszavakból és azonosítókból áll. Az azonosítókat a programozó hozza létre, a többi a nyelv részét képezi.

Beépített adattípusok:
int - egész szám. Értéke -2 147 483 648-tól +2 147 483 648-ig, memóriában 4 byte.
float - törtszám. Értéke 3.4*10^-38-tól 3.8*10^+38-ig, memóriában 4 byte.
double - kétszeres pontosságú törtszám. Értéke 2.2*10^-308-tól 1.8*10^306-ig, memóriában 8 byte
char - egyetlen karakter. Értéke bármilyen karakter lehet az ASCII kódtáblából, alfanumerikus (betűk, számok) és bizonyos szimblumok, jelek. Memóriában 1 byte.
bool - logikai típus. Értéke false (hamis) = 0, true (igaz) = 1, vagy bármi ami nem 0 (hamis), memóriában 1 byte helyet foglal.
enum - felsorolás típust hozhatunk vele létre, mely konstansokat tartalmaz. Szintaktikája:
enum autok {audi, BMW, chevrolet}
Itt autok lesz a felsorolás típusunk, és a konstansaink audi, BMW, chevrolet. A konstansok rendre értéket is kapnak, audi=0, BMW=1, chevrolet=2. Ám mi magunk is megadhatunk értéket, pl.:
enum abc {a=-10, b, c=20}
Itt a=-10 lesz, b automatikusan -11, míg a c 20.

Típusminősítők:
Vannak úgynevezett típusminősítők is a nyelvben, melyeket leginkább az int típusra értelmezünk, ezek:
long - hosszú 
short - rövid 
signed - előjeles
unsigned - előjel nélküli (Ha ezt használjuk, a negatív tartomány helyén 0-tól kezdődően pozitív számoknak foglalhatunk helyet)
Az előjel módosítókkal a típusminősítők szabadon keverhetők.
A szabály csak annyi ezeknek az alkalmazásánál, hogy helyfoglalásban igaz a következő összefüggés:
short int <= int <= long int. A tényleges értékeke az adott rendszertől függnek, de megközelítőleg írok néhány példát:
short int - Értéke -32 768-tól +32 767-ig, 2 byte
unsigned short int - Értéke 0-tól 65 535-ig, 2 byte
long int - Értéke -2 147 483 648-tól 2 147 483 647-ig, 4 byte
unsigned long int 0-tól 4 294 967 295-ig, 4 byte
A típusminősítők önmagukban is jelzik a típust:
short int = short 
long int = long 
unsigned int = unsigned
signed int = signed
signed long int = signed long
stb.

Típus szinonímák:
A typedef kulcsszóval típusoknak adhatunk szinonim neveket. Ennek a későbbiekben összetett típusoknál fogjuk hasznát látni, de lássunk egy egyszerűbb példát:
typedef unsigned long double uld;
uld nagytort;
Ekkor nem új típust hozunk létre, csak a hosszadalmasan gépelendő unsigned long double helyett a későbbiekben egy rövid uld-dal is létrehozhatjuk a nagytort nevű változónkat. Szerény véleményem szerint azonban az átláthatóság és olvashatóság, karbantarthatóság érdekében inkább a hosszú formát írjuk.

Konstansok:
A konstansokkal biztosíthatjuk, hogy egyes változók állandó értékeket tároljanak, ne lehessen őket megváltoztatni. Pl.:
const float PI = 3.14159265;

Érdekességképp:
A nem beépített adattípusokat header file-ok linkelésével tudjuk használni, így pl. a string típust, ami 255 karaktert tárol, 255 byte-on. Ezt a string.h file tárolja, és sok hasznos fgv-t is tartalmaz a string típushoz, mint az strlen(karakterlanc): visszaadja a karakterlánc hosszát; a strcpy(egyik, masik) bemásolja az egyik stringbe a masikat; strcmp(egyik, masik) összehasonlít két stringet, eredménye negatív, ha egyik<masik, pozitív, ha egyik>masik, és 0, ha egyenlőek.
A legfontosabb ilyen nem beépített típusokat tartalmazó, beágyazandó file-unk az iostream lesz (ld. próbalog), melynek 3 legfontosabb objektuma a cin (beolvasás), cout (kiírás), cerr (hiba-folyam).
Hasznos modul a math.h is: pow (hatványozás), sqrt (gyökvonás), sin, cos, tg fgv-eket tartalmaz, később a használatuknál kifejtem őket.
Van egy szabványosított tárolókat (konténereket), és algoritmusokat tartalmazó gyűjtemény is, ez az STL, azaz Standard Template Library, mely általános fgv-eket, úgynevezett sablonokat tartalmazó könyvtár. Ezekről is később lesz szó.

C++ 001 Első lépések


A program írását egy szövegszerkesztőben végezzük. Ez bármilyen szövegszerkesztő lehet, ami formázatlanul ment, ASCII szövegként. Ezt a szövegesállományt mentjük el C++ esetén .cpp illetve .h kiterjesztéssel, ez lesz a forrásfájlunk (source file). A file-t ezután egy fordítóprogrammal (compiler) lefordítjuk, ami tárgykódot állít elő, ennek kiterjesztése .o lesz (object code). Linux/Unix alatt pl. ezt egy parancssorral végezzük:
g++ <file-név> -o <futtatható állomány neve> , illetve 
c++ <file-név> -o <futtatható állomány neve>
Ez már gépi kódban van, ami azt jelenti, hogy a számítógép processzora értelmezni tudja. Ezután egy linker összekapcsolja a file-okat, és létrehozza a futtatható file-t (windowsban .exe kiterjesztéssel). Linux/Unix rendszer alatt annyi a különbség, hogy a g++/c++ után az összes file-nevet felsoroljuk, és a parancs össze is linkeli azokat.
Az újabb fordítók azonban beépített szövegszerkesztővel rendelkeznek, és project file-okban tárolják a linkeléshez szükséges adatokat. Ezeket integrált fejlesztői környezetnek IDE (Integrated Development Environment) -nek nevezzük. Például CodeBlocks program esetén ez a project file .cbp kiterjesztéssel lesz elmentve abba a könyvtárba, ahol a .cpp és .h file-jaink.
Miután a CodeBlocks-ot feltelepítettük, a munkát a File->New->Project..->Console Application-nal kezdjük. Klikkeljünk a Next-re, jelöljük ki a C++-t, megint Next, a Project title mezőbe írjuk a programunk nevét, a következő sorban jelöljük ki a könyvtárat, ahová a programunk file-jait menteni szeretnénk (...), majd Next. Ekkor megjelenik a CodeBlocks felülete, de még nem látunk semmit. A bal oldalon látszó Management feliratú ablakban a Projects fülre kattintva láthatjuk a projektünk nevét. Kibontva a kis + jellel a Sources mappában megtalálhatjuk a main.cp file-t, ami a programunk kiinduló forrásfile-ja lesz. Ezt átnevezhetjük, ha jobb gombbal ráklikkelünk, és a felugró menüben a Rename file.. -t választjuk. Figyelem, ezt csak egyetlen egyszer, azelőtt tehetjük meg mielőtt megnyitnánk a file-t. Erre kétszer klikkelve megnyílik a szövegszerkesztő egy keretprogrammal, ami nem csinál mást, mint kiírja a "Hello world!" feliratot:

#include <iostream>

using namespace std;

int main()
{
    cout << "Hello world!" << endl;
    return 0;
}

A továbbiakban ezt fogjuk módosítani, illetve a New->File...->Files menü kiválasztásával újabb c++ és header file-okat adhatunk majd a projektünkhöz. A fájlnév és a project mappa megadása után mind a Debug, mind a Release opciót jelöljük be. A későbbiek során a baloldali Management->Projects fülön váltogathatjuk az aktuálisan szerkesztendő file-okat. A header file-oknál is van egy keretprogram, amit azonban ajánlatos teljesen változatlanul hagyni, ezt header guard-nak nevezik, és arra szolgál, hogy linkelésnél a header file-unk csak egyszer, de egyszer szerepeljen, biztos ami biztos. Ez így fog kinézni:

#ifndef GS_H_INCLUDED
#define GS_H_INCLUDED



#endif // GS_H_INCLUDED

Mi csak a "#define GS_H_INCLUDED" és "#endif // GS_H_INCLUDED" között szerkesztjük majd a filet, ügyelve, hogy ez a keret megmaradjon.

Próbablog

A legtöbb C++ programozási tutorial azzal szokott kezdődni, hogy "Hello Word"-öt iratnak ki.
Szakítva a hagyománnyal, én az első leckében egy header file-t fogok bemutatni, abból a praktikus meggondolásból, hogy ezzel biztosan találkozik mindenki majd munkája során. Ha tovább olvasol megtudhatod mi az :).

Persze pár dolgot tisztázni kell, hogy pl. mi a deklaráció, definíció, típus, változó stb.
Talán a legfontosabb fogalom, amit először tisztázni kell, az az algoritmus. Az algoritmus nem más, mint egy feladat megoldásához szükséges, véges számú lépéssorozat. Az algoritmushoz szorosan kapcsolódik az implementáció fogalma. Az implementáció ugyanis adott programozási nyelv és platform alatt megírt működő algoritmus. A platform magában foglalja az adott hardware-t (mint pl. PC, mobiltelefon stb) és a rajta futó operációs rendszert (mint pl. Windows, Android stb).

Programot adott feladat megoldására írunk, amihez meg kell tervezni az algoritmust. Ezt a tervezést algoritmusleíró eszközökkel készítjük, ilyen lehet a folyamatábra, a struktorgram, a pszeudo-kód (ál-kód). Szerény véleményem szerint ez utóbbi a legpraktikusabb. Objektum-Orientált programozáshoz pedig a legjobb választás az UML (Unified Modeling Language).

A programunk futása során a memóriába adatok töltődnek be kis memóriarekeszekbe, melyek meg vannak címezve. Innen tudja majd a processzor, hogy honnan kell vegye az adatokat, amiket feldolgoz. Nos a típus leír egy ilyen tárhelyet, hogy hol, mennyi memóriát foglaljon egy adathoz. De a típus ennél több, azt is meghatározza, hogy milyen műveleteket végezhetünk azon az adaton. A változó a programozó - illetve a megoldandó feladat által megadott - neve a változónak. Mint a neve is mutatja, változik, azaz a program futása során a memóriában lévő adat változhat.
Érdekességképp: A memóriafoglalás helye az operációs rendszertől és hardware-től jelentősen függ.

A leggyakoribb típusok:
Számok: 
int (egész, -2 147 483 648-tól +2 147 483 648-ig, memóriában 4 byte)
float (törtszám, 3.4*10^-38-tól 3.8*10^+38-ig, memóriában 4 byte). Kis magyarázat: Ez utóbbi normálalakban van megadva, ami azt jelenti, hogy a 10-es számrendszerben minden felírható vmi szorozva a 10 a valahanyadikon formában. Pl. 200 = 2*10^2, vagy 0,2 = 2*10^-1. Itt a hatványozást a ^ jel jelenti. A programozásban ezzel a formával, az exponenciális alakkal nagy E-ként fogunk találkozni, tehát a float típus intervalluma [3.4E-38 .. 3.8E+38 ], ami elképesztően kicsi, és hatalmasan nagy szám.
Szöveg: 
char (egyetlen karakter, memóriában 1 byte)
string (255 karakterből álló karakterlánc, memóriában 255 * 1 byte)
Pl.:
int egesz;
Ezzel egy egész nevű változót hoztunk létre, aminek egész a típusa. A változónevek és azonosítók nem tartalmazhatnak ékezetet, és csak betűvel, vagy aláhúzásjellel kezdődhetnek. C++-ban a a típus után írjuk a változónevet pontosvesszővel lezárva.
Egyelőre ennyi, később részletezem őket, de fontos megemlíteni, hogy van határuk, hogy mekkora adatot tárolnak. Ezzel együtt jár a memóriahely foglalás is. Sajnos általános probléma, hogy a 

Érdekességképp: teljesen más módon tároljuk az egész (fixpontos tárolás) és törtszámokat (lebegőpontos tárolás).

Létezik struktúrált, és objektum orientált programozás. Ám a másodikhoz kell valamennyi az első ismeretéből is. A struktúrált programozás során függvényekkel (functions) és eljárásokkal (procedures) találkozunk. Mik is ezek? Egy szóval: alprogramok. Olyan kisebb egységek, melyekből fel tudjuk építeni nagyobb programunkat, egyszerűbben tervezhetővé, átláthatóbbá teszik a tervezést, a megoldáshoz vezető utat.

A függvény (function) olyan alprogram, melynek van visszatérési értéke, azaz visszaad egy bizonyos típusú adatot. A függvényeknek (procedúráknak) lehetnek paraméterei is, több is, hasonlóan a különböző változójú matematikai függvényekhez. Mikor ezeket a paramétereket, melyek csak szimbólikus változónevek, igazi értékekkel ruházzuk fel, argumentumként nevezzük őket. Azaz, a paraméterek helyébe argumentumok kerülnek.
A C++ csak függvényekkel dolgozik, jobban mondva az üres (void visszatérésű) függvény felel meg a procedúráknak.

A deklaráció az alprogramunk prototípusa, egy függvény visszatérési értékének típusát és a paramétereinek típusait soroljuk fel. A definícióban pedig a függvény működését, algoritmusát adjuk meg az adott programozási nyelven implementálva. Látni fogjuk, hogy a prototípusokat a header file-okban tároljuk majd, és ugyanolyan file-névvel ellátva a definíciókat egy .cpp file-ba tesszük majd. A függvény prototípusokat szignatúrának is nevezzük.

A moduláris programozás során a programunkat nem csak függvényekre, de különálló fizikai file-okra is bontjuk. Ezek a file-ok a winchesteren (HDD, merevlemez) tárolódnak. Ezekben a file-okban tároljuk majd a függvényeinket. Ez lehetőséget nyújt arra, hogy még bonyolultabb programokat írhassunk, melyben a programot modulokra, azon belül függvényekre bontsuk. De ezenkívül nagyobb jelentősége az, hogy bizonyos jól működő függvényeket egy file-ban tárolva újabb programjainkhoz is felhasználhassuk. Ezeket a file-okat, modulokat külön tudjuk fordítani (compiling), majd hozzácsatlakoztatni a programunkhoz (linking). Bizonyos ilyen file-ok már meg is vannak írva C++-ban, ezeket a file-okat az #include (tartalmazás) utasítás után írjuk. Pl. #include <iostream>. Itt a szabványos C++-hoz tartozó iostream.h file-t adtuk hozzá a programunkhoz. Ezt már előttünk megírták, méghozzá egy bizottság tagjainak jóváhagyásával, akik létrehozták az ANSI C++-t. Maga az ANSI C++ egy szabvány. Ezért előnye, hogy minden platformon működnie kell, minden programozási környezetben. Ez az iostream nagyon fontos modul, ugyanis e nélkül nem tudnánk adatokat beolvasni, illetve kiiratni (tudnánk, hisz ezt is megírta valaki, de egyelőre maradjunk annál, hogy ezt használjuk), mivel a c++ nyelvnek nem szerves része a beolvasáshoz használható cin és a kiiratáshoz használható cout objektumok. Alkalmazásukkor azonban átirányítást is felhasználunk, amit a >> és a << jel szimbolizál. Az iostream az Input-Output Stream rövidítése, azaz Bemeneti-Kimeneti Folyam. Az #include utasításokat mindig a kód elejére illesztjük.

Névterek (namespaces): a függvényeket tartalmazó modulokat névterekben csoportosíthatjuk. Az ANSI C++ által használt függvények és modulok a standard (alap) névtéren érhetők el, amit a using namespace std paranccsal jelzünk. A névterek lehetővé teszik, hogy pl. két olyan header file-t is használhassunk, mely tartalmaz egy ugyanolyan nevű deklarációt. Ezt a hatókör operátor felhasználásával tehetjük majd meg(). Pl. tegyük fel, hogy ír valaki egy cout-ot, ami már létezik az iostream.h-ban. Ha az új cout-ot tartalmazó header file-t pl. egy xyz névtérhez rendeljük, akkor a későbbiekben tudjuk őket egyszerre használni, méghozzá a névterük megkülönböztetésével, így az xyz::cout és std::cout egy forrásállományban is alkalmazható.

A fordítás azt jelenti, hogy a programunkat az adott programozási nyelv fordítóprogramja leellenőrzi szintaktikailag. Szintaxis: mint a nyelvtanban, azt jelenti, hogy alakilag megfelel-e a megírt kódunk a programnyelvnek, azaz nincs-e vmi elírva, minden amit deklaráltunk, az ugyanolyan néven szerepel-e mindenhol, és a programnyelv utasításai megfelelő formájúak-e. Így mondhatjuk, hogy bár lehet, hogy sok bosszúságot fog okozni számunkra egy program fordításakor a sok figyelmeztetés, hibajelzés, az mégis segítséget ad, hogy javíthassuk hibáinkat. Azaz mondhatjuk úgy is egyszerűbben, hogy a szintaxis a a programnyelv nyelvtani, alaki helyessége. FIGYELEM! A szintaktikus helyesség mellett létezik egy szemantikai helyesség is, amit azonban nem ellenőriz helyettünk semmi, mikor programot írunk. Tehát semmilyen segítséget nem kapunk hozzá. Ez a hiba tartalmi hiba, azaz a program írása közben lehet, hogy mindent megfelelően jelöltünk, használtunk, ám a programunk nem azt csinálja, amit szeretnénk, saját hibánkból. 

Mielőtt belekezdenénk egy példába, fontos letisztázni, hogy C++-ban a program az úgynevezett főfüggvénnyel kezdődik, melynek a neve main. Ebből csak egyetlen egy lehet, és ha moduláris a programunk, célszerű az őt tartalmazó file-t a programunk nevével menteni. Innen indul majd minden, minden függvényhívás, és minden alprogram végrehajtás. A munka megkezdése előtt a neten szét lehet nézni, rengeteg ingyenesen használható fordítóprogramot lehet találni, legnépszerűbbbek: 
devcpp: http://www.bloodshed.net/dev/devcpp.html
CodeBlocks: http://www.codeblocks.org/downloads/26 
de a Microsoft Visual C++-nak is és a Borland cég fordítóprogramjának is van kipróbálható verziója. Én alapból a CodeBlocks-ot használom.


Végre lássunk egy példát:


A main.cpp file tartalma:
#include <iostream>
// Használjuk az iostream.h file-t
#include "fgv.h"
// Használjuk az általunk megírt fgv.h file-t

using namespace std;
// Használjuk az iostream-et tartalmazó standard névterületet

int main()  // Ez a fõfüggvényünk
{
    int a, b;   // a és b változó deklarálása egész számnak
    cout << "Kerek egy szamot: ";
    // Szöveg kiiratása, idézőjelek között
    cin >> a;   // az a változó beolvasása
    cout << "Kerek meg egy szamot: ";
    cin >> b;   // a b változó beolvasása
    cout << "Osszeguk: "<<szamol(a,b)<<"."<<endl;
    // az általunk megírt szamol fgv használata, a sor végét
    // endl-lel (endline=sorvége) zárjuk
    return 0;
    // minden fgv visszatérési értékét a return(visszatérés)
    // után írjuk. A main fgv visszatérési értéke az operációs
    // rendszernek szól, a 0 azt jelzi, hiba nélkül futott
    // le a program.
}
A fgv.h file tartalma:
#ifndef FGV_H_INCLUDED
#define FGV_H_INCLUDED
// ide kerül a szamol fgv-ünk prototípusa:
// visszatérési érték típusa, fgvazonosító, zárójelben a paraméterek
// típusai vesszõvel elválasztva, és az utasítást lezáró pontosvesszõ
 int szamol( int, int );

#endif // FGV_H_INCLUDED
A fgv.cpp file tartalma:
#include "fgv.h"
// ide kerül a szamol fgv-ünk megvalósítása:

int szamol( int _a, int _b )
{
    return (_a + _b);
};