2016. augusztus 24., szerda

Absztrakció

      Az 1990-es évek óta az objektum-orientált programozási stílus előtérbe került a strukturált programozással szemben. A strukturált program adatból és algoritmusból épül fel, míg az objektum-orientált program emellett absztrakciót is végez. Az absztrakció az objektumok révén valósul meg, melyek leegyszerűsítik az adatok és algoritmusok kezelését. Az objektumokat tulajdonságaik alapján osztályozni lehet, az adott osztályhoz tartozó objektum pedig az osztály példányának tekinthető.



1. Osztályok és objektumok
      Példaképp adott egy négyzethálós kép. Minden négyzet egy objektum, melynek van egy pozíciója és egy színe. Ezzel a két adattal művelet hajtható végre, például megváltoztatható a szín vagy újra rajzolható a négyzet. Mivel minden négyzetnek van színe és pozíciója, ezért egy osztályba sorolható, a Négyzet osztályba.


A Négyzet osztály létrehozása egy adattípus létrehozásával ér fel, mert lehetővé teszi a Négyzet típusú változók deklarálását. Ezeket a változókat a Négyzet osztály objektumainak hívjuk. Például a Négyzet obj  utasítás az obj objektumot deklarálja, amely Négyzet típusú, és rendelkezik a szín és pozíció tagváltozókkal és a Színez, Rajzol tagfüggvényekkel.


2. Öröklés
      Nem csak négyzetek, hanem háromszögek is részesei lehetnek a képnek. Ezeknek ugyanazok a tulajdonságai vannak és ugyanazok a műveletek végezhetőek el velük, de mégsem teljesen ugyanazok, mert például az újrarajzoló algoritmus más. Továbbá beiktatható egy új tulajdonság ami méginkább megkülünbözteti a háromszöget a négyzettől: a csúcsok száma.


A hasonlóságok miatt ezt a két osztályt egy felsőbb absztrakciós szinten közös osztályba lehet sorolni: a Formák osztályba.


A Formák osztály még absztraktabb a Négyzet és Háromszög osztályoknál, mert méginkább kiiktatja a kevésbé fontos információkat. Nem foglalkozik azzal, hogy a forma négyzet-e vagy háromszög, az majd lentebb eldől. Magába foglalja a közös tulajdonságokat, így ezeket nem kell külön deklarálni mindenik alsó osztályban. Az alsó osztályok (Négyzet és Háromszög) megöröklik a Formák tulajdonságait. Ami nem egyforma azt felülírja mindenik a maga osztályában (például a csúcs tagváltozót és a Rajzol tagfüggvényt). A Négyzet és a Háromszög osztályok származtatott (vagy derivált) osztályai a Formák osztálynak. A Formák osztály a származtatott osztályok ősosztálya (vagy alaposztálya). Az öröklődés célja, hogy közös tulajdonságok általánosabbá váljanak kezelhetőbbé téve az információt.


3. Struktúrák
      A hagyományos strukturált programozási nyelv a C. A Pascal, a Deplphi és a C++ csupán támogatják az objektum-orientált módszereket, azonban alapjában ezek is strukturált programozási nyelvek. Tisztán objektum-orientált például a Java és a C#. A C++-ban használt osztályok a C-ben használt struktúrákból fejlődtek ki. Összesített adattípust jelentenek, azaz különböző típusú adatokra és függvényekre lehet egyetlen változóval hivatkozni, amit objektumoknak hívunk.

Példa:
#include <iostream>
using namespace std;

struct Time
{
     int hour;   //0-23
     int minute; //0-59
     int second; //0-59
     void printShort();
     void printLong();
};

void Time::printShort() //13:30
{
     cout << (hour < 10 ? "0" : "") << hour
          << ":"
          << (minute < 10 ? "0" : "") << minute;
}

void Time::printLong()  //1:30:10 PM
{
     cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)
          << ":"
          << (minute < 10 ? "0" : "") << minute
          << ":"
          << (second < 10 ? "0" : "") << second
          << " "
          << (hour < 12 ? "AM" : "PM");
}

int main()
{
     Time timeObject,           // Time típusú objektum
          *timePtr,             // Time típusú mutató
          &timeRef = timeObject;// Time típusú referencia
            
     timeObject.hour = 6;
               
     cout << "timeObject.hour = " << timeObject.hour << endl;
     cout << "timeRef.hour = " << timeRef.hour << endl;
    
     timePtr = &timeObject;
     cout << "timePtr->hour = " << timePtr->hour << endl << endl;   
    
     Time Lunch;
     Lunch.hour = 13;
     Lunch.minute = 30;
     Lunch.second = 10;
    
     cout << "Ebéd idő: ";
     Lunch.printShort();
     cout << " azaz ";
     Lunch.printLong();
    
     return 0;
}

A fenti programban egy Time nevű struktúra van definiálva, mely 3 változóval és 2 függvénnyel rendelkezik. A függvényeket lehetne a struktúrán belül is definiálni, azonban szebb ha külön, a struktúrán kívül definiáljuk. Mivel több struktúrának is lehet azonos nevű függvénye, a függvény neve elé dupla kettősponttal oda kell írni a struktúra nevét is ahová tartozik.
A program elején három darab Time típus van deklarálva: egy objektum, egy mutató és egy referencia. A struktúra definiálása önmagában nem foglal semmi memóriát, ha nem használjuk akkor szabadon marad a neki szánt tárhely. Amikor azonban egy Time típusú objektumot deklarálunk, akkor lefoglalódnak a struktúrában deklarált változók helyei. A struktúra tagjai a pont (.) és a nyíl (->) operátorokkal érhetőek el. A pontot az objektumon vagy az objektum referenciáján keresztül való elérésénél használjuk, míg a nyilat akkor, ha mutató segítségével hivatkozunk az objektumra. A program kimenete a következő:

timeObject.hour = 6
timeRef.hour = 6
timePtr->hour = 6

Ebéd idő: 13:30 azaz 1:30:10 PM

A struktúrák a lehetőségek szempontjából korlátozottabbak az osztályoknál, például nem lehet privát változókat deklarálni, melyeket csak a tagfüggvények érhetnének el, mert alapból minden publikus, azaz kívülről hozzáférhető. A programírás felügyelete pedig sokkal korlátlanabb a struktúráknál, például megengedi, hogy inicializálatlanul hagyjuk a változókat vagy hogy ne ellenőrizzük le annak helyességét (pl. egy interfészen keresztül). Az osztályok esetén az ilyen hibákra már a fordításnál fény derül és ezzel csökken a hibás programok írásának esélye. A gyakorlott programozók a struktúrákkal is meg tudják oldani mindazt amit az osztályokkal lehet, ám sokkal nagyobb erőfeszítést és figyelmet igényel.


4. Osztályok
      Az osztályok implementálása nagyon hasonló a struktúrákéhoz. Lássuk az előző programot osztállyal:

#include <iostream>
using namespace std;

class Time
{
     public:               // bárhol elérhető
           Time();         // konstruktor
           void printShort();
           void printLong ();
           void setTime(int, int, int);
     private:              // csak a tagfüggvények látják
           int hour;
           int minute;
           int second;
          
};

Time::Time()
{
     hour = minute = second = 0; //inicializálás
}

void Time::printShort()
{
     cout << (hour < 10 ? "0" : "") << hour
          << ":"
          << (minute < 10 ? "0" : "") << minute;
}

void Time::printLong()
{
     cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)
          << ":"
          << (minute < 10 ? "0" : "") << minute
          << ":"
          << (second < 10 ? "0" : "") << second
          << " "
          << (hour < 12 ? "AM" : "PM");
}

void Time::setTime(int h, int m, int s)
{
     hour = (h >= 0 && h < 24) ? h : 0;
     minute = (m >= 0 && m < 60) ? m : 0;
     second = (s >= 0 && s < 60) ? s : 0;
}

int main()
{           
     Time t;    // a tagváltozók lenullázódnak a konstruktorban
    
     cout << "\nRövid változat: ";
     t.printShort();
     cout << "\nHosszú változat: ";
     t.printLong();
    
     t.setTime(13,27,6);
     cout << "\nRövid változat a setTime után: ";
     t.printShort();
     cout << "\nHosszú változat a setTime után: ";
     t.printLong();
    
     return 0;
}

A struktúráktól eltérően az osztályok rendelkeznek private, public és protected specifikációkkal. Ezek a tagokhoz való hozzáférést határozzák meg. A privát tagokhoz csak a tagfüggvények férnek hozzá (a főprogramban érhetőek el), a publikushoz mindenki hozzáfér aki az objektumot is eléri, a protected tagokat pedig csak az örökös osztályok érhetik el. A tagfüggvényeket közvetlenül a deklarálásnál is lehet definiálni, de szebb, ha csak a prototípust (deklarálást) írjuk az osztályba, a definiálást pedig azon kívül, akár a struktúrák esetében. A tagfüggvények összessége az osztály interfészét képezi, hiszen csak azokon keresztül érhetőek el és módosíthatók a privát tagváltozók. Publikus tagváltozókat ritkán használunk. A tagfüggvények listájába tartozik az osztály nevével azonos nevű függvény, a konstruktor. Ez minden alkalommal automatikusan meghívódik valahányszor létrehozunk egy objektumot, soha nem térít vissza semmit, ezért ebbe a függvénybe a tagváltozók inicializálását szokás beírni.
A fenti program a következőt írja ki:

Rövid változat: 00:00
Hosszú változat: 12:00:00 AM
Rövid változat a setTime után: 13:27
Hosszú változat a setTime után: 1:27:06 PM

Pluszban van a setTime interfész tagfüggvény, ami a privát tagokhoz ad hozzáférést és közben ellenőrzi, hogy a megadott értékek a megfelelő tartományban vannak-e. A tagfüggvények olyan módon is megírhatók, hogy azok közvetlen hozzáférést adjanak a privát változókhoz, ez viszont helytelen taktika, ugyanis kiiktatja a változók ellenőrzését. Tegyük be az int& badSetHour(int); függvényt a publikus tagfüggvények. Ez egy referenciát fog visszatéríteni, a hour változó referenciáját:

int& Time::badSetHour(int h)
{
     int& ora = hour; // az "ora" a hour tagváltozó referenciája
     ora = (h >= 0 && h < 24) ? h : 0; // ellenőrzés
     return ora;
}

A hour tagváltozó kiirásához a következő publikus tagfüggvény használható:

int Time::getHour(){ return hour; }

Ezek segítségével a main()függvény végén közvetlenül lehet módosítani a hour tagváltozót akár hibás értékkel is:

int& hourRef = t.badSetHour(20); // itt még van belső ellenőrzés
hourRef = 87; //a hour tagváltozó módosítása - ellenőrzés nélkül
cout << "\nA módosított óra: " << t.getHour();

A program kimenete:

Rövid változat: 00:00
Hosszú változat: 12:00:00 AM
Rövid változat a setTime után: 13:27
Hosszú változat a setTime után: 1:27:06 PM
A módosított óra: 87


5. Kódrendezés
      A programozásban általában, főként az objektum-orientált programozásban fontos, hogy a függvényeket és azok használatát külön felületre, más fájlokba helyezzük. Az osztályok esetén az osztály deklarálását, definícióját és alkalmazását is elválasztják egymástól. Rendszerint az osztályokat header (fejléc) fájlokban deklarálják (.h), melyet aztán az include segítségével láthatóvá lehet tenni a program számára. A fenti programot a következőképp lehet felbontani:

time.h
#ifndef TIME_H
#define TIME_H
class Time
{
     public:               // bárhol elérhető
           Time();         // konstruktor
           void printShort();
           void printLong ();
           void setTime(int, int, int);
     private:              // csak a tagfüggvények látják
           int hour;
           int minute;
           int second;
          
};
#endif

time.cpp
#include <iostream>
#include "time.h"
using std::cout;

Time::Time()
{
     hour = minute = second = 0; //inicializálás
}

void Time::printShort()
{
     cout << (hour < 10 ? "0" : "") << hour
          << ":"
          << (minute < 10 ? "0" : "") << minute;
}

void Time::printLong()
{
     cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)
          << ":"
          << (minute < 10 ? "0" : "") << minute
          << ":"
          << (second < 10 ? "0" : "") << second
          << " "
          << (hour < 12 ? "AM" : "PM");

void Time::setTime(int h, int m, int s)
{
     hour = (h >= 0 && h < 24) ? h : 0;
     minute = (m >= 0 && m < 60) ? m : 0;
     second = (s >= 0 && s < 60) ? s : 0;
}

test_time.cpp
#include <iostream>
#include "time.h"
using std::cout;

int main()
{           
     Time t;    // a tagváltozók lenullázódnak a konstruktorban
    
     cout << "\nRövid változat: ";
     t.printShort();
     cout << "\nHosszú változat: ";
     t.printLong();
    
     t.setTime(13,27,6);
     cout << "\nRövid változat a setTime után: ";
     t.printShort();
     cout << "\nHosszú változat a setTime után: ";
     t.printLong();
    
     return 0;
}

A főprogram csak a time.h fájlt látja, ami az osztály deklarációját tartalmazza. Az osztálydefiníciót, ami a time.cpp fájlban van, a linker találja meg. Ez a kompilálásnál kezdődik, amikor minden .cpp kiterjesztésű fájlból a kompilátor egy .o kiterjesztésű obejktumfájlt generál. Ezekben külső és belső szimbólumok találhatók. A test_time.o-ban például a printShort() külső szimbólum, mert definíciója nem ott van. A time.o-ban ugyanez belső szimbólumként szerepel. A linker feladata, hogy a szimbólumokat listázza és annak alapján összekapcsolja az objektumfájlokat, létrehozván a futtatható programot. A test_time.o vizsgálata során észreveszi, hogy a  printShort() egy megoldatlan külső szimbólum, így keresgélni kezd az eddig megtalált szimbólumok táblázatában. A hiányzó szimbólumot a time.o objektumfájlban fogja megtalálni, ezért összekapcsolja a test_time.o-val.

Az include tulajdonképpen a "foglald bele" szószoros jelentése, vagyis ugyanaz, mintha az itt megadott fájl teljes tartalma szerepelne ebben a sorban.

A tagfüggvénynek olyan függvényeket írunk, amilyent a feladat megkövetel, azonban az átláthatóság kedvéért ezeket is csoportosítani lehet:
Függvények, melyek lekérdezik a tagváltozók értékeit (getter), pl. void getHour().
Függvények, melyek értéket adnak a tagváltozóknak (setter), pl. void setHour(int).
Függvények, melyek az osztály szolgáltatásait végzik, azaz műveleteket végeznek a tagváltozókkal.
Függvények, melyek alapműveleteket végeznek: inicializálás, osztályok összekapcsolása, típuskonverzió, memóriakezelés, stb.
Segítő függvények, melyeket a többi tagfüggvény használ. Ezek privát függvények, például a lekérdező vagy az értékadó függvények használhatják az isAM()vagy isPM() segítő függvényeket.

A következő program példát ad néhány függvénytípusra a felsoroltak közül:

sales.h
#ifndef SALES_H
#define SALES_H
class SalesPerson
{
     public:
           SalesPerson();              // alapművelet: inicializálás
           void getSalesFromUser();    // lekérdező
           void setSales(int, double); // értékadó
           void printAnnualSales();    // szolgáltatás
     private:
           double totalAnnualSales();  // segítő
           double sales[12];
};
#endif

sales.cpp
#include <iostream>
#include <iomanip>
#include "sales.h"
using namespace std;

SalesPerson::SalesPerson()           // kezdetben a tömb minden eleme nulla
{
     for(int i = 0; i < 12; i++)
           sales[i] = 0.0;
}

void SalesPerson::getSalesFromUser() // beolvassa a havi eladásokat
{
     double salesFigure;
    
     for(int i = 1; i <= 12; i++)
     {
           cout << "Az eladások értéke a(z) " << i << ". hónapban: ";
           cin >> salesFigure;
           setSales(i, salesFigure);
     }
}

void SalesPerson::setSales(int month, double amount) // beteszi a tömbbe a beolvasott értéket
{
     if(month >= 1 && month <= 12 && amount > 0)
           sales[month - 1] = amount;  //a tömb indexe 0-tól indul
     else
           cout << "A hónap vagy az eladások száma hibás!" << endl;
}

void SalesPerson::printAnnualSales() //beállítja a tizedes pontosságot és kiírja az összeget
{
     cout << setprecision(2) << setiosflags(ios::fixed | ios::showpoint) << endl
     << "Az évi eladások száma: " << totalAnnualSales() << " EUR" << endl;
}

double SalesPerson::totalAnnualSales() // összeadja a tömb értékeit
{
     double total = 0.0;
    
     for(int i = 0; i < 12; i++)
           total += sales[i];
     return total;
};

test_sales.cpp
#include "sales.h"
int main()
{
     SalesPerson s;
     s.getSalesFromUser();
     s.printAnnualSales();
    
     return 0;
}

A SalesPerson osztálynak van egy 12 elemű privát tömbje, melyben a havi eladások értékeit tárolja. A konstruktor ezt nullákkal inicializálja és a setSales értékadó függvény a billentyűzetről beolvasott értékekkel tölti fel. A printAnnualSales osztályszolgáltatást végző függvény a totalAnnualSales segítő függvényt használja az összeadás elvégzésére. A program eredménye a következő:

Az eladások értéke a(z) 1. hónapban: 34.25
Az eladások értéke a(z) 2. hónapban: 57.61
Az eladások értéke a(z) 3. hónapban: 14
Az eladások értéke a(z) 4. hónapban: 32.09
Az eladások értéke a(z) 5. hónapban: 55.58
Az eladások értéke a(z) 6. hónapban: 35.77
Az eladások értéke a(z) 7. hónapban: 76
Az eladások értéke a(z) 8. hónapban: 42
Az eladások értéke a(z) 9. hónapban: 35
Az eladások értéke a(z) 10. hónapban: 46
Az eladások értéke a(z) 11. hónapban: 67.15
Az eladások értéke a(z) 12. hónapban: 38

Az évi eladások száma: 533.45 EUR


6. Konstruktorok és destruktorok
      A konstruktor egy olyan tagfüggvény, ami alapműveleteket végez, nem térít vissza semmit és automatikusan meghívódik az objektum létrehozásakor. Ugyanaz a neve mint az osztálynak, átdefiniálható (túlterhelhető) és többnyire a tagváltozók inicializálására használják. A time programban ez a legegyszerűbb módon történt:

time.h
Time();

time.cpp
Time::Time()
{
     hour = minute = second = 0; //inicializálás
}

A kezdeti értékek megadhatók a deklarálásnál is:

time.h
Time(int = 0, int = 0, int = 0);

time.cpp
Time::Time(int hr, int min, int sec)
{
setTime(hr, min, sec);
}

test_time.cpp
Time t1(12, 25, 42)  //hour=12, minute=25, second=42
     t2(21, 34);     //hour=21, minute=34, second=0
     t3;             //hour=0,  minute=0,  second=0

A header fájlban a konstruktor prototípusában már nullázni lehet a kezdeti paramétereket, ezáltal ha ezek hiányoznának az objektum létrehozásakor, akkor nullának lesznek értelmezve és a setTime három nullával hívódik meg. A konstruktor használata nem kötelező, a fordító úgyis létrehoz egyet, ám nem minden fordító inicializálja a tagváltozókat.

A destruktor szintén egy tagfüggvény és szintén az osztály nevével azonos névvel rendelkezik, melyet a ~ jel előz meg. Ez akkor hívódik meg automatikusan, mielőtt az objektum törlődne a memóriából, azaz amikor a program elhagyja azt a tartományt ahol az objektum deklarálva volt. A destruktor tehát nem törli az objektumot, hanem lehetőséget ad néhány művelet elvégzésére az objektum törlődése előtt. Nincs paramétere és nem térít vissza semmit. Ritkán használunk destruktorokat, mert csak olyan esetekben hasznosak amikor az objektumok dinamikusan lefoglalt memóriarészekre utaló referenciákat (is) tartalmaznak.

Ha több objektumot hozunk létre, akkor a konstruktorok a létrehozás sorrendjében hívódnak meg, a destruktorok pedig néhány kivétel esetében fordított sorrendben. A globális szinten deklarált objektumok konstruktorai minden más függvény előtt meghívódnak, ide értve a main-t is. Ezen objektumok destruktorai a main végeztével futnak le, vagy amikor meghívódik az exit függvény. Az abort esetén nem hívódik meg a destruktor. Ha az objektum statikus (static), akkor a konstruktor csak egyetlen egyszer hívódik meg és csakis az exit meghívásakor vagy a main befejezésekor bontódik le az objektum. A következő program különböző kódtartományokban deklarál és bont le objektumokat:

create.h
#ifndef CREATE_H
#define CREATE_H
class CreateAndDestroy
{
     public:
           CreateAndDestroy(int); //konstruktor
           ~CreateAndDestroy();   //destruktor
     private:
           int data;
};
#endif

create.cpp
#include <iostream>
#include "create.h"
using namespace std;

CreateAndDestroy::CreateAndDestroy(int value) // konstruktor
{
     data = value;
     cout << "Konstruktorban: " << data;
}

CreateAndDestroy::~CreateAndDestroy()         // destruktor
{
     cout << "Destruktorban: " << data << endl;
}

test_create.cpp
#include <iostream>
#include "create.h"
using namespace std;

void Create();
CreateAndDestroy first(1);      // legutoljára bontja le, mert globális

int main()
{
     cout << " (Globálisan kreált objektum a main előtt)" << endl;
    
     CreateAndDestroy second(2);
     cout << " (Helyi automatikus objeutm)" << endl;
    
     static CreateAndDestroy third(3);
     cout << " (Helyi statikus objektum)" << endl;
    
     Create(); //egy globális függvény
    
     CreateAndDestroy fourth(4);
     cout << " (Helyi automatikus objektum a Create() után)" << endl;
    
     return 0;
}

void Create()
{
     CreateAndDestroy fifth(5);
     cout << " (Create - helyi automatikus objeutm)" << endl;
    
     static CreateAndDestroy sixth(6);
     cout << " (Create - helyi statikus objektum)" << endl;
    
     CreateAndDestroy seventh(7);
     cout << " (Create - helyi automatikus objektum a függvény végén)" << endl;
}

A protram kimenete a következő:

Konstruktorban: 1 (Globálisan kreált objektum a main előtt)
Konstruktorban: 2 (Helyi automatikus objeutm)
Konstruktorban: 3 (Helyi statikus objektum)
Konstruktorban: 5 (Create - helyi automatikus objeutm)
Konstruktorban: 6 (Create - helyi statikus objektum)
Konstruktorban: 7 (Create - helyi automatikus objektum a függvény végén)
Destruktorban: 7
Destruktorban: 5
Konstruktorban: 4 (Helyi automatikus objektum a Create() után)
Destruktorban: 4
Destruktorban: 2
Destruktorban: 6
Destruktorban: 3
Destruktorban: 1

A first objektum globális szinten van, ezért ez lesz lebontva legutoljára, miután a main is véget ért. A main-ben három objektum van: second, third, fourth. Ezek sorban épülnek fel a konstruktorral és fordított sorrendben bontódnak le. A Create függvényben további három objektum szerepel: fifth, sixth, seventh. Ezek is sorrendbe épülnek fel, azonban a statikus sixth mindvégig a memóriában marad, míg a main be nem zárul. Ugyanez érvényes a szintén statikus third és a globális first objektumra is.


7. Objektumok másolása
      Az „=” hozzárendelő operátor használható az objektumok másolására. Tagot taggal másol, azaz mindenik tag az egyik objektumból átmásolódik a másik objektum azonos nevű tagjaiba. Ugyanez történik akkor is, amikor függvény kap objektum paramétert, vagy mikor objektumot térít vissza. Mindkét művelet során értékátadás történik, vagyis az objektumnak egy másolatát küldjük vagy kapjuk vissza. Vigyázni kell arra, hogy ne manipuláljunk túl nagy terjedelmű objektumot ilyen módon, mert az negatív hatással lesz a program teljesítményére. Ilyen esetben referenciával vagy mutatóval kell az objektumokat átadni, ez viszont biztonsági szempontból előnytelen, mert az adott függvénynek közvetlen hozzáférése lesz az eredeti objektumhoz.

#include <iostream>
using std::cout;
using std::endl;

class Date
{
     public:
           Date(int = 1, int = 1, int = 1990);
           void print();
     private:
           int day;
           int month;
           int year;
};

//hibalellenőrzés nélkül
Date::Date(int d, int m, int y)
{
     day = d;
     month = m;
     year = y;
}


void Date::print()
{
     cout << day << '-' << month << '-' << year;
}

int main()
{
     Date date1(9, 3, 2016), date2;
     cout << "date1 = ";
     date1.print();

     cout << endl << "date2 = ";
     date2.print();

     date2 = date1;  //tagot-taggal való másolás
     cout << endl << endl << "Objektum másolás után, date2 = ";
     date2.print();
     cout << endl;
    
     return 0;
}

A fenti program az „=” hozzárendelő műveleti jellel másolta át a date1 objektumot a date2 objektumba. A kimenet:

date1 = 9-3-2016
date2 = 1-1-1990

Objektum másolás után, date2 = 9-3-2016


8. Konstans objektumok és konstans tagfüggvények
      A program stabilitása és biztonsága érdekében használhatók konstans objektumok és konstans tagfüggvények. Konstans tagfüggvénynek például a get típusú függvényeket (getter) deklarálják, hogy a fordító már előre jelezze, ha a lekérdező függvény módosítani próbál az objektumokon vagy tagváltozón:

int Time::getHour() const { return hour };

A konstruktor és a destruktor kivételes esetek, ők szabad módosítsák az objektumokat, hisz az inicializálás és a törlés fontos művelet. A konstansok használata nem csak programozás szempontjából jó, hanem a program teljesítményén is javít, mert a fordító optimizálja a konstansnak deklarált változókat.

time.h
#ifndef TIME_H
#define TIME_H
class Time
{
     public:
           Time(int = 0, int = 0, int = 0); //konstruktor - kezdő értékek
           // setter függvények
           void setTime(int, int, int);
           void setHour(int);
           void setMinute(int);
           void setSecond(int);
           // getter függvények - konstansok
           int getHour() const;
           int getMinute() const;
           int getSecond() const;
           // kiíró függvények - konstansok
           void printShort() const;
           void printLong ();
     private:
           int hour;   //0-23
           int minute; //0-59
           int second; //0-59
};
#endif

time.cpp
#include <iostream>
using std::cout;
#include "time.h"

Time::Time(int hr, int min, int sec)
{
     setTime(hr, min, sec);
}

void Time::setTime(int h, int m, int s)
{
     setHour(h);
     setMinute(m);
     setSecond(s);
}

void Time::setHour(int h)
{
     hour = (h >= 0 && h < 24) ? h : 0;
}

void Time::setMinute(int m)
{
     minute = (m >= 0 && m < 60) ? m : 0;
}

void Time::setSecond(int s)
{
     second = (s >= 0 && s < 60) ? s : 0;
}

int Time::getHour() const { return hour; }
int Time::getMinute() const { return minute; }
int Time::getSecond() const { return second; }

void Time::printShort() const
{
     cout << (hour < 10 ? "0" : "") << hour << ":" << (minute < 10 ? "0" : "") << minute;
}

void Time::printLong()
{
     cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)
          << ":" << (minute < 10 ? "0" : "") << minute
          << ":" << (second < 10 ? "0" : "") << second
          << (hour < 12 ? " AM" : " PM");
}

test_time.cpp
#include "time.h"
int main()
{
     Time wakeUp(6, 45, 0);     // nem konstans objektum
     const Time noon(12, 0, 0); // konstans objektum

     wakeUp.setHour(18); //nem konstans objektum - nem konstans függvény
     //noon.setHour(12); //konstans objektum - nem konstans függvény ----- HIBA!
     wakeUp.getHour();   //nem konstans objektum - konstans függvény
     noon.getMinute();   //konstans objektum - konstans függvény
     noon.printShort();  //konstans objektum - konstans függvény
     //noon.printLong(); //konstans objektum - nem konstans függvény ----- HIBA!

     return 0;
}

A fordító rögtön hibát ad amint észleli, hogy nem konstans függvénnyel próbálunk műveletet végezni konstans objektumon. Ilyen esetben az objektum inicializálása kötelező a konstruktorban. Ha egy tagváltozó a konstans, akkor a konstruktor definícióját módosítani kell hozzá adván a tagváltozók inicializáló listáját:

#include <iostream>
using std::cout;
using std::endl;

class Increment
{
     public:
           Increment(int c = 0, int i = 1);
           void addIncrement(){ count += increment; }
           void print();
     private:
           int count;
           const int increment; //konstans tagváltozó
};

Increment::Increment(int c, int i) : increment(i) // a konstans tagváltozókat itt inicializáljuk
{
     count = c; // a nem-konstans tagváltozót pedig a megszokott módon
}

void Increment::print()
{
     cout << "count = " << count << ", increment = " << increment << endl;
}

int main()
{
     Increment value(10, 5); //konstans 5-tel inkrementálunk végig.
     cout << "Kezdő értékek: ";
     value.print();
    
     for(int j = 0; j < 3; j++)
     {
           value.addIncrement();
           cout << j+1 << ". inkrementálás után: ";
           value.print();
     }
    
     return 0;
}

A kimenet:
Kezdő értékek: count = 10, increment = 5
1. inkrementálás után: count = 15, increment = 5
2. inkrementálás után: count = 20, increment = 5
3. inkrementálás után: count = 25, increment = 5

A konstruktor paraméterei után lévő : increment(i)  azt jelzi, hogy az increment tagváltozó a paraméterek közül az i-t fogja megkapni értékként. Tulajdonképpen az összes tagváltozót inicializálhatjuk ilyen módon, ám a konstansoknál kötelezően így helyes (ellenkező esetben a fordító hibát jelez).


9. Osztályok illesztése
      Sokszor szükség lehet egy osztály objektumának használatára egy másik osztályon belül. Nem öröklésről van szó, hanem például az Employee osztálynak szüksége van dátum kezelésre és erre a Date osztály éppen megfelelő. Az Employee a következő információkat tartalmazza az alkalmazottakról: namebirthdatehiredate. Ezekből a birthdate és a hiredate a Date osztály konstans objektumai és tartalmazzák a month, day és year tagváltozókat.

date.h
#ifndef DATE_H
#define DATE_H
class Date
{
     public:
           Date(int = 1, int = 1, int = 1990);
           void print() const;
           ~Date();
     private:
           int day; //1-12
           int month;//1-31
           int year;
           int checkDay(int); // segítő függvény az ellenőrzéshez
};
#endif

date.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "date.h"

Date::Date(int d, int m, int y)
{
     if(m > 0 && m <= 12)
           month = m;
     else
     {
           month = 1;
           cout << m << " az hibás hónap. Hónap = 1.\n";  
     }
     year = y;
     day = checkDay(d); //lehet-e ilyen nap a hónapban
     cout << "Date konstruktor: ";
     print();
     cout << endl;
}

void Date::print() const { cout << day << '-' << month << '-' << year; }

Date::~Date()
{
     cout << "Date destruktor: ";
     print();
     cout << endl;
}

int Date::checkDay( int testDay )
{
     static const int daysPerMonth[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

     if( testDay > 0 && testDay <= daysPerMonth[month]) return testDay;
     // szökőév:
     if(month == 2 && testDay == 29 && (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))) return testDay;
     // ha idáig ér a program, akkor a nap hibás volt.
     cout << testDay << " az hibás nap. Nap = 1.\n";
     return 1;
}

employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include"date.h"
class Employee
{
     public:
           Employee(char*, int, int, int, int, int, int);
           void print() const;
           ~Employee();
     private:
           char Name[25];
           const Date birthDate;
           const Date hireDate;
};
#endif

employee.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <cstring>
#include "employee.h"

Employee::Employee(char* name, int bday, int bmonth, int byear, int hday,
     int hmonth, int hyear) : birthDate(bday, bmonth, byear), hireDate(hday, hmonth, hyear)
{
     int length = strlen(name);

     length = (length < 30 ? length : 29); // a név minimum 29 hosszú
     strncpy( Name, name, length);
     Name[length] = '\0'; // az utolsó karakter a befejező karakter

     cout << "Employee kosntruktor: " << Name << endl;
}

void Employee::print() const
{
     cout << Name << "\nAlkalmazás: ";
     hireDate.print();
     cout << " Születés: ";
     birthDate.print();
     cout << endl;
}

Employee::~Employee()
{
     cout << "Employee destruktor: " << Name << endl;
}

test_composition.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "employee.h"
#include"date.h"

int main()
{
     char fullname[12] = "Chloe Price";
    
     Employee e(fullname, 11, 3, 1994, 30, 1, 2015);
     cout << '\n';
     e.print();
     cout << "\nA Date konstruktor tesztelése hibás napra es hónapra:\n";
     Date d(35, 14, 2000);
     cout << endl;

     return 0;
}

A kimenet:
Date konstruktor: 11-3-1994
Date konstruktor: 30-1-2015
Employee kosntruktor: Chloe Price

Chloe Price
Alkalmazás: 30-1-2015 Születés: 11-3-1994

A Date konstruktor tesztelése hibás napra es hónapra:
14 az hibás hónap. Hónap = 1.
35 az hibás nap. Nap = 1.
Date konstruktor: 1-1-2000

Date destruktor: 1-1-2000
Employee destruktor: Chloe Price
Date destruktor: 30-1-2015
Date destruktor: 11-3-1994

Akár a konstans tagváltozókat, az objektumokat is a konstruktor fejlécében kell inicializálni. A paraméterlistát követő kettőspont után a Date objektumokat inicializáltuk a listában szereplő változókkal. Az  Employee osztály látja a Date osztályt , hisz az elején ott van az #include Date.h. Ha nem inicializáljuk az objektumokat a paraméterlistában, akkor azok konstruktora úgyis inicializálja őket az alapértelmezett értékekkel. Ha viszont a konstruktor is hiányzik a Date osztályból, akkor azt már jelzi a fordító. A program kimenetén látható, hogy a konstruktorok közül a Date osztály építi fel hamarább az objektumait, hiszen ezeket hamarább inicializálódnak.


10. Friend függvények és friend osztályok
      A friend, azaz a barát függvény nem része az osztály családjának, az osztály tartományán kívül van definiálva, viszont hozzáfér az osztály private és protected tagjaihoz. Ugyanígy egy egész osztályt is lehet friend-nek deklarálni egy másik osztály számára. A friend függvényeket akkor használják, ha bizonyos műveletek nem valósíthatók meg tagfüggvényként (mint például az operátorok átdefiniálása). A következő programban a setX függvény friend függvénye lesz a Count osztálynak és módosítani fogja annak privát tagváltozóját.

#include <iostream>
using std::cout;
using std::endl;

class Count
{
     friend void setX(Count&, int); // friend függvény
    
     public:
           Count(){ x = 0; }
           void print() const { cout << x << endl; }
     private:
           int x;
};

void setX( Count& c, int val ) { c.x = val; }

int main()
{
     Count counter;
     cout << "x = ";
     counter.print();
     setX(counter, 8);
     cout << "setX után x = ";
     counter.print();
    
     return 0;
}

A kimenet:
x = 0
setX után x = 8

A setX függvény nem tagfüggvény, ezért kell a hívásnál az objektumot is a paraméterek közé tenni. A szintaxis szempontjából nem számít, hogy a friend függvényeket a private, public vagy protected zónába írjuk, de általában rögtön a kapcsos zárójel után teszik őket, így publikusak lesznek. A friend osztályok esetén is ugyanez a szabály:

#include"time.h"
class Count
{
     friend class Time; // friend osztály
    
     public:
           Count(){ x = 0; }
           void print() const { cout << x << endl; }
     private:
           int x;
};


11. A this mutató
      Minden objektum hozzáférhet a saját memóriacíméhez a this mutató segítségével. Ez a mutató nem része az objektumnak, hanem az fordító tudja őt úgy értelmezni mint első számú argumentumot, amikor az objektum tagfüggvényeit vagy tagváltozóit használjuk. A this mutató típusa függ az objektum típusától és hogy az általa használt tagfüggvény konstans-e vagy sem. Ha például az Employee osztály nem konstans tagfüggvényét használjuk, akkor a this típusa Employee* const. Ha konstans tagfüggvényt hívunk meg, akkor a this típusa const Employee* const lesz. A következő program a this mutató explicit használatát mutatja be:

#include <iostream>
using std::cout;

class Test
{
     public:
           Test( int = 0);
           void print() const;
     private:
           int x;
};

Test::Test( int a ) { x = a; }

void Test::print() const
{
     cout << "x = " << x;
     cout << "\nthis->x = " << this->x;
     cout << "\n(*this).x = " << (*this).x;
}

int main()
{
     Test testObject(12);
     testObject.print();
    
     return 0;
}

A kimenet:
x = 12
this->x = 12
(*this).x = 12

Az x tagváltozó háromféleképp van kiíratva: közvetlenül és a this mutatóval nyíl illetve pont segítségével. Az utolsó esetben a zárójel kötelező, különben a fordító *(this.x)-nek értelmezi az utasítást. A this mutató egyik érdekes tulajdonsága, hogy ha *this-t tértít vissza egy függvény és a típusa Osztály&, akkor az a függvény láncba fűzhető a pont operátor segítségével: t.setHour(18).setMinute(30).setSecond(22);. Erre látható egy példa a következő programban:

time.h
#ifndef TIME_H
#define TIME_H
class Time
{
     public:
           Time(int = 0, int = 0, int = 0);

           Time &setTime(int, int, int);
           Time &setHour(int);
           Time &setMinute(int);
           Time &setSecond(int);

           void print() const;
    
     private:
           int hour;   //0-23
           int minute; //0-59
           int second; //0-59
};
#endif

time.cpp
#include <iostream>
using std::cout;
#include "time.h"

Time::Time(int hr, int min, int sec) { setTime(hr, min, sec); }

Time &Time::setTime(int h, int m, int s)
{
     setHour(h);
     setMinute(m);
     setSecond(s);
     return *this; //fűzér mód
}

Time &Time::setHour(int h)
{
     hour = (h >= 0 && h < 24) ? h : 0;
     return *this; //fűzér mód
}

Time &Time::setMinute(int m)
{
     minute = (m >= 0 && m < 60) ? m : 0;
     return *this; //fűzér mód
}

Time &Time::setSecond(int s)
{
     second = (s >= 0 && s < 60) ? s : 0;
     return *this; //fűzér mód
}

void Time::print() const
{
     cout << ((hour == 0 || hour == 12) ? 12 : hour % 12)
          << ":" << (minute < 10 ? "0" : "") << minute
          << ":" << (second < 10 ? "0" : "") << second
          << (hour < 12 ? " AM" : " PM");
}

test_time.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "time.h"

int main()
{
     Time t;
     t.setHour(18).setMinute(30).setSecond(22);
     t.print();
     cout << endl;
     t.setTime(20, 20, 20).print();
    
     return 0;
}

A kimenet:
6:30:22 PM
8:20:20 PM


12. Az osztályok statikus tagjai
      Egy osztály minden objektumának megvan a saját másolata az osztály tagjairól. Egyes esetekben csak egyetlen másolatot szükséges fenntartani minden objektum számára és erre jó a static típusú tag. Ez a tag az információt osztály szinten és nem objektum szinten tárolja, azaz globálisnak tekinthető az osztály objektumai számára. Akkor is létezik, ha nincs egyetlen objektum se deklarálva. A statikus tagváltozókat csak a statikus tagfüggvények érhetik el. A következő program egy statikus tagváltozót használ az osztály objektumainak számolására:

employee2.h
#ifndef EMPLOYEE2_H
#define EMPLOYEE2_H
class Employee
{
     public:
           Employee(const char*, const char*);
           ~Employee();
           const char *getFirstName() const;
           const char *getLastName() const;
           static int getCount(); // statikus tagfüggvény
     private:
           char *firstName;
           char *lastName;
           static int count;      // statikus tagváltozó
};
#endif

employee2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include <cstring>
#include <cassert>
#include "employee2.h"

//statikus tagok inicializálása:
int Employee::count = 0;
int Employee::getCount() { return count; }

Employee::Employee(const char* first, const char* last)
{
     firstName = new char[strlen(first) + 1]; // memória lefoglalás
     assert(firstName != 0);   // hiba ha nem sikerült lefoglalni
     strcpy(firstName, first); // különben bemásolja a tagváltozóba
    
     lastName = new char[strlen(last) + 1];
     assert(lastName != 0);
     strcpy(lastName, last);
    
     ++count; //statikus változó növelése
     cout << "Konstruktor: " << firstName << ' ' << lastName << endl;
}

Employee::~Employee()
{
     cout << "Destruktor: "<< firstName << ' ' << lastName << endl;
     delete [] firstName; // memória felszabadítás
     delete [] lastName;
     --count; //statikus változó csökkentése
}

const char *Employee::getFirstName() const { return firstName;}
const char *Employee::getLastName() const { return lastName; }

test_employee2.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "employee2.h"

int main()
{
     cout << "count = " << Employee::getCount() << endl; // objektum létrehozás előtt
    
     Employee *e1Ptr = new Employee("Mark", "Jefferson");
     Employee *e2Ptr = new Employee("Rachel", "Amber");
    
     cout << "count = " << e1Ptr->getCount(); // objektum létrehozás után
    
     cout << "\n\n1.Alkalmazott: " << e1Ptr->getFirstName() << " " << e1Ptr->getLastName();
     cout << "\n2.Alkalmazott: " << e2Ptr->getFirstName() << " " << e2Ptr->getLastName();
     cout << endl << endl;

     delete e1Ptr; // ettől elindul a destruktor
     e1Ptr = 0;
     delete e2Ptr;
     e2Ptr = 0;

     cout << "count = " << Employee::getCount() << endl; // objektum törlése után

     return 0;
}

A kimenet:
count = 0
Konstruktor: Mark Jefferson
Konstruktor: Rachel Amber
count = 2

1.Alkalmazott: Mark Jefferson
2.Alkalmazott: Rachel Amber

Destruktor: Mark Jefferson
Destruktor: Rachel Amber

count = 0

Amíg egyetlen objektum se volt létrehozva, addig a count statikus tagváltozót csak a getCount statikus tagfüggvény érte el. Az objektumok létrehozása után a getCount az objektumokon keresztül is elérhetővé vált. A mutatók és a delete parancs segítségével törölni lehet az objektumokat még a program végezte előtt, s így kiíratható a count értéke a destruktorok lefutása után is. A nem statikus tagoktól eltérően a statikus tagoknak nincs this mutatójuk, mert függetlenek az osztály objektumaitól. Az objektumok létrehozása a new  operátorral történt, mely dinamikusan foglalta le a memóriát és lehetővé tette a lefoglalt terület dinamikus törlését a delete operátorral. Tulajdonképpen ez a dinamikus memóriakezelés lényege, hogy mutatók segítségével memóriát foglalunk le (ellenőrzéssel) és szabadítunk fel amikor szükséges.


13. Proxy osztályok
      Sok esetben előírás, hogy az osztály forráskódjának részletei rejtve maradjanak a felhasználók elől, hogy azok ne tudjanak hozzáférni a privát adatokhoz és a program logikájához. A proxy osztály lehetővé teszi ezt, mert csupán a publikus interfészét ismeri a titkos osztálynak. A következő programban az Implementation osztály a privát adatokkal rendelkező titkos osztály és az Interface a proxy osztály. Az Interface osztálynak ugyanaz a publikus interfésze mint az Implementation osztálynak és van egy privát tagváltozója, ami az Implementation osztályra mutat. Ennek segítségével a felhasználó hozzáfér az Implementation osztályhoz, de annak forráskódja rejtve marad előtte.


implementation.h
#ifndef IMPLEMENTATION_H
#define IMPLEMENTATION_H
class Implementation
{
     public:
           Implementation(int v) { value = v; }
           void setValue(int v) { value = v; }
           int getValue() const { return value; }
     private:
           int value;
};
#endif

interface.h
#ifndef INTERFACE_H
#define INTERFACE_H
class Implementation; // forward deklarálás
class Interface
{
     public:
           Interface(int);
           void setValue(int);
           int getValue() const;
           ~Interface();
     private:
           Implementation *ptr;
};
#endif

interface.cpp
#include "interface.h"
#include "implementation.h"

Interface::Interface(int v) : ptr( new Implementation(v)){}

void Interface::setValue(int v) {ptr->setValue(v);}

int Interface::getValue() const {return ptr->getValue();}

Interface::~Interface() {delete ptr;}

test_interface.cpp
#include <iostream>
using std::cout;
using std::endl;
#include "interface.h"

int main()
{
     Interface i(5);
     cout << "Az interfész tartalma: " << i.getValue() << " a setValue előtt." << endl;
     i.setValue(10);
     cout << "Az interfész tartalma: " << i.getValue() << " a setValue után." << endl;
    
     return 0;
}

A kimenet:
Az interfész tartalma: 5 a setValue előtt.
Az interfész tartalma: 10 a setValue után.

Az interface.h-ban lévő forward deklarálásra az Implementation típusú mutató miatt volt szükség. Helyette használhattuk voln az #include "implementation.h" direktívát is, ám ebben az esetben praktikusabb a forward deklarálás, mert csupán egyetlen mutató mutat arra az osztályra és a fordítónak is könnyebb dolga lesz. Mivel a felhasználó csak az interface.h-t használhatja, nem láthatja a proxy és az Implementation osztály közreműködését.

Nincsenek megjegyzések:

Megjegyzés küldése