Gyakorló feladatok (5. hét)

Fontosabb kulcsszavak, fogalmak

  • OO dekompozíció
  • objektum modell
  • kapcsolatok (relációk és linkek)
  • öröklés
  • virtuális függvények
  • absztrakt osztályok
Kapcsolatok, jelölések áttekintése
Osztályok közötti kapcsolatok jelölése
Asszociáció


(folytonos vonal)
Aggregáció
(B tart.A-t)

(folytonos vonal üres rombusszal)
Kompozíció
(spec.aggr.)

(B tart.A-t)

(folytonos vonal tele rombusszal)
Öröklés
(B A-ból származik)

(folytonos vonal üres nyíllal)
Kapcsolat minősítése
(C a kapcs. eredménye)

(szaggatott vonal)

Az OO tervezési folyamat során több szempontból vizsgáljuk és írjuk le a modellünket. Az objektummodell az objektumok és példányok statikus leírását adja. A programozás alapjai 2. című tárgy keretében csak felszínesen érintjük az objektummodell leírását, melynek mélyebb megismeréséhez pl. az Objektum orientált szoftverfejlesztés c. könyv példáinak áttekintését javasoljuk.

Az osztályokat, azok fontosabb attribútumait valamint az osztályok közötti statikus kapcsolat jelölésére osztálydiagramot használunk, amit UML (Unified Modeling Language) jelölésrendszerével adunk meg.

Az osztálydiagramon gyakran megadjuk a tagfüggvények és adattagok láthatóságát is a nevük előtt álló +, -, vagy # jellel. A - jel a privát, + jel a publikus, a # pedig a védett (protected) láthatóságot jelöli. Az adattagok/tagfüggvények típusát a név után írt kettőspont (:) mögé kell írni.

C++ esetében fontos külön jelölnünk a virtuális tagfüggvényeket. Erre az UML sajnos nem ad előírást, így mi ezt a típus mögé, vagy elé írt virtual kulcsszóval jelöljük.

Az UML részletesebb bemutatásával, további modellezési eszközökkel a következő félévben a szoftver technológia c. tárgy foglalkozik. A programozás alapjai 2. tárgyban csak az alapvető jelöléseket használjuk a statikus modell leírására. (Az osztálydiagram UML jelöléseiről rövid összefoglaló itt található)

Feladatok megoldásokkal

  1. Készítsen objektummodellt öröklés felhasználásával állatok (először csak majom és tigris, de bővíthető legyen) modellezésére! Az állatoknak van egy egyedi neve. Műveletek:
    • hangot ad — minden állat másképp
    • játszik — egy másik állat társaságában, minden állat kicsit másképp
  2. Implementálja az osztályokat C++ nyelven!
    • Hangkeltéskor a majom makogjon, a tigris üvöltsön a szabvány kimenetre.
    • A majom makogjon kétszer, amikor megszűnik.
    • Ha a tigris éhes, úgy játsszon, hogy megeszi (megszünteti) a játszópajtását. A majom ennél ártalmatlanabbul játsszon: hangot ad ő is és a játszópajtás is.
    • Az alábbi kódrésztettel szeretnénk kipróbálni az elkészült osztályokat:
        Allat *sirkan = new Tigris("Sir Kan");
        Allat *csita = new Majom("Csita");
        Allat *kingkong = new Majom("King Kong");
        cout << "-= Csita jatszik =-" << endl;
        csita->jatszik(kingkong);
        csita->jatszik(sirkan);
        cout << "-= Sir Kan jatszik =-" << endl;
        sirkan->jatszik(csita);
        sirkan->jatszik(kingkong);
      

  3. Kérdések, további feladatok:
    • Neve minden állatnak van, ezért ezt az alaposztályba célszerű tenni. Mi az előnye annak, ha a név private adattag az alaposztályban? Mi az előnye, ha protected?
    • Mi történik, ha az alaposztályban a jatszik(), a hangot_ad() vagy a destruktorfüggvényt nem virtuálisként deklaráljuk?
    • Hogy néz ki a feladatban szereplő különböző típusú objektumok memóriaképe?
    • Miért nem kell explicit típuskonverzió, amikor a pointereknek értéket adunk, pl. Allat *sirkan =(Allat*)new Majom("Csita");?
    • Korlátozó örökléssel a tigris felhasználásával hozzon létre egy cirkuszi tigrist, amelyik nem játszik!
  4. Mi íródik ki az alábbi kódok főprogramjainak egyes sorai hatására?
    #include <iostream>
    using namespace std;
    class A { 
    public :
     virtual void f1() { cout << "A"; }
     void f2() { cout << "A";} 
     void f3() { f1(); } 
     void f4() { f2(); }
    };
    class B : public A { 
    public :
     void f1() { cout << "B"; } 
     void f2() { cout << "B"; }
    };
    class C : public B {};
    
    int main() {
     A* a = new A;
     A* b = new B;
     A* c = new C;
    
     a->f1();
     a->f2();
     a->f3();
     a->f4();
     b->f1();
     b->f2();
     b->f3();
     b->f4();
     c->f1();
     c->f2();
     c->f3();
     c->f4();
    }
    #include <iostream>
    using namespace std;
    class A {
    public:
    //void Kiir() { cout << "A kiir" << endl; }
      virtual void Kiir() { cout << "A kiir" << endl; }
    };
    
    class B : public A{
    public:
      void Kiir() { cout << "B kiir" << endl; }
    };
    void foo( A& x ) { cout << "foo: "; x.Kiir(); }
    int main() {
      A a;
      B b;
      a.Kiir();
      b.Kiir();
      A* p1 = &a;
      p1->Kiir();
      A* p2 = &b;
      p2->Kiir();
      foo(a);
      foo(b);
      return 0;
    }
  5. Input/output stream-ekhez visszatérünk. Az istream és ostream osztállyal már találkoztunk: az előbbi példánya a cin, az utóbbié a cout, a cerr és a clog. Ezeken kívül fájlokon, sőt, stringeken is végezhetünk stream-es műveleteket az fstream, ill. stringstream segítségével. Mindezek az osztályok számos továbbival együtt egy osztályhierarchiába rendeződnek, melynek leegyszerűsített változata itt látható:

    Részletesebben itt: http://www.cplusplus.com/ref/ios (Figyelem! A linken található ábrán a nyilak nem az UML szabályoknak megfelelő irányba mutatnak! Az ábra egyes osztályaira kattintva már jó jelölésekkel találkozunk.)

    A korábbi Komplex osztályunk számára megírt operator<< csak ostream típusú objektumra használható?

Utolsó frissítés: 2018-03-08 21.03