C++ implementálási alapok


Alapelv: modularizáció -> C++ egy objektum orientált programozási nyelv, tehát a cél a kívánt programkód implementálása minél kisebb, funkcionális működését ellenőrizhető egységekkel.

Modularizáció

Alapvető szerkezeti eszköze: project file: több különálló állomány használatát teszi lehetővé.

Célok:

1.          procedurális: hasonló funkciójú függvények elkülönítései

2.          objektum orientált: osztálydefiníciók elkülönítései

Előnyök: csoportos fejlesztés, áttekinthetőség, újrahasznosítás

Implementációs megállapodások:

1.          minden a program által használt állományt tartalmaz a project fájl

2.          állományok között ne legyen globális változó (ellenkezne a modularitás értelmével)

3.          forrásállományon belül globális változót csak nagyon indokolt esetben használjunk

4.          a fejállományokat az azt használó állományok inklúdolják

5.          a megfelelő "könyvtárak" /(library) lásd.: prognyelvek 2/ elemeire való közvetlen hivatkozás a "::" jellel lehetséges, de névtérbe is bevehető. leggyakoribb a standard library (std::)

6.          megállapodás szerint vegyük be a névtérbe az std -t, hiszen gyakorlatilag arra építjük az első két félév programozását, ez mondhatjuk, hogy a C++ (és nagyrészt a C) programozási nyelvi elemek metszete. (using namespace std;)

Alapvető szerkezet

Minden forrásfájlban megtalálható az ún. main függvény, melynek két paramétere az indítási paraméterek száma (int argc) és a paramétereket tartalmazó tömb (char* argc[]). Elemi alkalmazások fejlesztése 1 tantárgy keretein belül hármas tagolódást kell mutatnia a függvénynek: adatok beolvasása, számítások elvégzése, majd az eredmény kiírása, de esetenként ez összemosódhat.

Hibák

Implementációs jótanács: fordítani, fordítani, fordítani, minél kisebb egységenként ellenőrizni a program helyes működését!

Hibafajták: fordítóidejű és bug. A fordítóidejű problémát a compiler jelzi, fatális esetben (error) nem is hoz létre futtatható állományt, míg egyéb esetben figyelmeztetés (warning) mellett létrehozza azt. (Megjegyzés: a fordító hibaüzenete a hiba észlelési helyét jelzi, nem azt, hogy mi és hol okozza!) A bug felderítése problémásabb, az ugyanis a program elvi felépítéséből adódó hiba. Ilyenkor a nyomkövetés a legcélravezetőbb módja, hogy lépésről lépésre nyomon követhessük a program működését.

A tárgy első két félévben olyan programokat kellene implementálni, amelyek sem error, sem warning fordítóidejű jelzést nem adnak.

Szintaxis

Vannak szintaktikai követelmények, melyeket az adott nyelv tesz kötelezővé (szintaxis) és a fordító kényszerít ki, valamint vannak megállapodások, amiket a gyakorlatvezetők tesznek kötelezővé. Ez utóbbi a program funkcionális működését nem befolyásolja.

Általános szintaktikai megállapodások:

1.          minden utasítást új sorba írunk, pontosvesszővel a végén, kivéve a szimultán értékadásokat

2.          blokkok után nem teszünk pontosvesszőt (kapcsoszárójellel határoltak)

3.          az állapottérváltozókat a main() függvény elején deklaráljuk, a segédváltozókat a blokkjukban (ezt egyes programozási nyelvek (pl ADA, C) meg is követelik)

4.          a szimultán értékadásokat egy sorba írt egyszerű értékadásokkal helyettesítjük

5.          az elágazás ágait és a ciklusmagot blokkokba tesszük ({}), kivéve, ha egy utasításból áll, akkor viszont egy sorba a szerkezeti utasítással

6.          többágú elágazást if()else if()...else() –el valósítunk meg (speciális esetben használható az ún. switch is)

7.          általában while ciklust, esetenként for -t és do-while -t használunk

8.          tabulátoros tagolással elkülönítjük a blokkokat

Absztrakt programból C++ forráskódok implementálása

Típusmegfeleltetés: az állapottérnek megfelelő (típushelyes) változókat deklarálunk

Probléma: nem minden állapottérkomponensnek van megfelelő beépített típusa standard C++ -ban. Kifejtését lásd lejjebb!

Algoritmus kódolása stuktogramm alapján, kívülről befelé haladva

Eredmény megjelenítése

A típushelyesség problémája: az állapottér egyes komponenseinek lehet megfelelő típusa C++ -ban, amit deklarálhatunk is, de a gyakorlatban általában származtatnunk kell ezeket, illetve megszorításokat kell tennünk, hogy megfeleljenek a típusspecifikációnak. Pl.: egész számok halmaza az állapottérkomponens, akkor az "int" típusból képzett változó megfelelően reprezentálja az egész számok halmazán értelmezett állapottérváltozót, de ha a feladat állapotterében pl. a páros egészek halmaza szerepel, akkor kénytelenek vagyunk egy logikai megkötést végezni a bemenő adatok között, tehát egy "int" típusú bemenő változót ellenőrizni kell, le kell szűkíteni a megfelelő halmazra. (Gyakorlatilag ezt az absztrakt program specifikációjában is elvégezhetjük úgy, hogy állapottér transzformációt hajtunk végre és az előfeltételbe bevesszük a szűkítést.) Egy másik példa, ha egy két dimenziós, egész alapú koordinátarendszer pontjainak halmazát akarjuk implementálni, ekkor kénytelenek vagyunk egyedi, származtatott típust (jelen esetben rekord típust) használni, melybe két "int" típusú változó kerül. (struct{int x, int y}). A fenti példák mind integer (egész) típusra épülő típusmegvalósítások.

Megjegyzés: ebben a félévben csak statikus reprezentációjú típusokat használunk (nem pointeres)...

Alapvető struktúrák, szolgáltatott típusműveletekkel:

struct:      szelektorok szerinti hivatkozás lehetősége

enum:       felsorolás, ahol a típusértékek között csak összehasonlító művelet létezik, amit a felsorolásban vett sorszámok közötti általános relációk adnak

union:      nincs típusmeghatározási művelet, nem használjuk

Osztályok (class)

Ha saját, nem C++ által szolgáltatott típusműveletekre van szükség, akkor osztályt (class) kell használni, ahol a típusdeklarációt és a típusváltozókkal kapcsolatos metódusokat egy összefüggő modulban implementálhatjuk.

Az osztály tehát a típusmegvalósítás eszköze. Elemeit hozzáférhetőség szempontjából a következő csoportok valamelyikébe soroljuk: public (kívülről hozzáférhető), private (csak a saját, belső metódusok használhatják), protected (származtatott osztályok esetén nem érik el az osztály példányai). Kiemelt fontosságú metódusai a konstruktor és a destruktor, ezekről később még lesz szó.

Általában külön forrásban/forrásokban helyezzük el (.h, .cpp, hpp), egyforrású esetben a következő megállapodást tartjuk be: a típus és metódusdeklarációs rész (osztálydeklaráció) a main() függvény és egyébb függvénydeklarációk előtt, a metódusok konkrét definíciói pedig a main() függvény után helyezkednek el!

Implementációs megállapodások:

1.          public -ban deklaráljuk a típusműveleteket

2.          private -ban deklaráljuk a típus változóit, melyekkel az osztály felvehet egy típusértéket reprezentáló állapotot

3.          private -ban deklaráljuk a kizárólag belső metódusok használatára szánt metódusokat

4.          legalább egy konstruktor -t definiálunk (feladata, hogy a reprezentáló elemeket olyan értékekkel töltsük fel, hogy kielégítsék a típusinvariánst)

5.          statikus repr. oszt. esetén nincs szükség copy konstruktorra, sem értékadás operátorra

6.          statikus repr. oszt. esetén akkor definiálunk destruktort az alapértelmezett helyett, ha a konstruktorban használtunk olyan inicializáló utasítást, melynek van lezáró párja (pl open-close)

7.          beágyazott definíciót csak ritkán és rövid metódusdefiníció esetén használunk

Függvények

Használata áttekinthető, tömörített programot eredményez. Gyakran végrehajtott utasítássorozatokra javasolt eszköz. A deklarációja meghatározza a visszatérési értékének típusát (vagy void, ha nincs visszatérési értéke), valamint a várt paramétereket. Módosító jelek: &, ami közvetlen paraméterátadást okoz és const, ami megköveteli, hogy a végrehajtás során a változó értéke ne változzon meg.

Implementációs megállapodások

1.          ha egy kimenő adatra van szükség, akkor visszatérési értéket használunk, több kimenő adat esetén void és paraméterátadást végzünk, kivéve, ha létezik kitüntetett visszatérési érték (általában speciális logikai érték), ekkor azzal visszatérünk, a többit átadjuk

2.          mindig legyen return utasítás

3.          ha programozási tételre vezetünk vissza, akkor a programozási tételeket önálló blokkba implementáljuk

4.          összetett I/O műveleteket külön függvénybe implementáljuk

5.          a függvénydeklarációt a main() függvény előtt, a definíciót pedig utána helyezzük el

Nem ellenőrzött anyag! Hiteles információt, valamint egyéb hasznos segédanyagot a következő linkre kattintva találhat: http://people.inf.elte.hu/gt/eaf/eaf1/eaf1.html.