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);
};