Kihagyás

3. gyakorlat

Ismétlés

  • osztály
  • konstruktor
  • inicializáló lista
  • const method

Objektum orientált programozás II

Az előző anyagrészben megismert osztályokhoz kapcsolódó kibővített anyagrész következik. Ezek olyan elemek, melyek a kényelmesebb és kifejezőbb kódolást teszik lehetővé.

Default adattag értékek

C++11 óta itt is lehetőségünk van alapértelmezett értéket adni az adattagjainknak deklarációkor. Így megússzuk azt, hogy minden konstruktorban be kelljen állítani az adattagoknak értéket (ez főleg akkor jöhet jól, ha ugyanazt az értéket szeretnénk mindenhol beállítani, így ha mégis más alapértelmezett értéket szeretnénk beállítani, nem kell az összes konstruktort módosítani). Persze ettől még megtehetjük, ilyenkor a konstruktorban lévő érték felülírja az alapértelmezett értéket. Ez is ismerős lehet Javaból.

1
2
3
4
class Kurzus {
  string nev, kod;
  unsigned max = 25;
}

Default paraméter értékek

Az úgynevezett "boilerplate" (felesleges) kódsorok elkerülése érdekében, lehetőségünk van default paraméter értékeket is megadni a függvényeinknek. Ez akkor tud jól jönni, amikor nagyon hasonló függvényeket szeretnénk írni, amik csak a paraméterlistájukban különböznek (pl. ha nem adjuk meg a max. létszámot, akkor azt 25-nek veszi). Azért, hogy ne kelljen egy adott függvényt (a benne lévő potenciális hibával) annyiszor lemásolni, lehetőségünk van megadni a paramétereknek alapértelmezett értéket, amit a függvény fejlécében a paraméter neve után tehetjük meg egyenlőség jellel.

Ezzel kapcsolatban a legfontosabb szabály, hogy mindig csak és kizárólag az utolsó valamennyi paraméternek lehet alapértelmezett értéke. Ez persze nem zárja ki, hogy az utolsó összesnek legyen, de olyat nem lehet, hogy csak a 2. és 5. paraméternek adott alapértelmezett értéket. A default paraméterekkel kapcsolatos összes szabály itt érthető el.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Kurzus {
//...
public:
  Kurzus(string nev, string kod, int max = 25) {
    this->nev = nev;
    this->kod = kod;
    this->max = max;
  }
// ...
};

int main() {
  Kurzus k1("Programozas II.", "IB302G-1", 23);
  Kurzus k2("Programozas II.", "IB302G-1"); // itt is ugyanaz a konstruktor hivodik
}

Delegating konstruktor

Megeshet, hogy két konstruktor működése elég hasonló, egyik kicsit több/másabb mint egy másik. Szerencsére itt is lehetőségünk van a kódmásolás elkerülésére, és meghívhatjuk az egyik konstruktorból a másikat. Ez hasonló lesz az inicializáló listához, annyi különbséggel, hogy az adattagok inicializálása helyett egy másik konstruktort is meghívunk (de ilyenkor már nem inicializálhatunk adattagot a konstruktor inicializáló listában). Ehhez annyit kell tennünk, hogy kiírjuk az osztály nevét, majd zárójelek között a paramétereket (mintha egy egyszerű metódushívás lenne). Megjegyzés: az itt látott megoldással lehet majd az örökölt osztály konstruktorát is meghívni.

1
2
3
4
5
public:
  Kurzus(string nev, string kod, int max) : nev(nev), kod(kod), max(max) {
  }
  Kurzus(string nev, string kod) : Kurzus(nev, kod, 25) { // a masik konstruktor hivasa
  }

Default konstruktor szerepe

A konstruktor feladata az objektum inicializálása, általában a paraméterben kapott értékeket felhasználásával. Azonban a default konstruktor úgy inicializálja, hogy ,,nem kap'' paramétereket, amivel inicializálhatná az objektumot. Így kérdéses, hogy miért akarunk olyan lehetőséget biztosítani, hogy a konstruktor valamilyen ,,alapértelmezett'' értékkel inicializálja az objektumot (pl. a Hallgato-t üres névvel és kóddal).

A Hallgato osztályt pl. névvel és kóddal látjuk el, de készítünk egy konstruktort, mely egyiket sem állítja be.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Hallgato {
  string nev, kod;
public:
  Hallgato() {
  }

  Hallgato(const string& nev, const string& kod) {
      this->nev = nev;
      this->kod = kod;
  }

  const string& get_nev() const {
    return nev;
  }

  const string& get_kod() const {
    return kod;
  }
};

Ez azért fontos, mert ha kiakarjuk bővíteni a Kurzus osztályt úgy, hogy a feljelentkezett Hallgato-kat egy tömbben tárolja, fordítási hibát kapnánk. Ha nem lenne default konstrukora a Hallgato osztálynak, akkor a C-ben megszokott, egyszerű módon nem tudnánk belőle tömböt létrehozni (csak ha expliciten inicializálunk minden elemet)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const unsigned KURZUS_LIMIT = 10;

class Kurzus {
  string nev, kod;
  unsigned felvette = 0;
  Hallgato hallgatok[KURZUS_LIMIT]; //inicializált elemek, de hogyan?
public:
  Kurzus(const string& nev, const string& kod) {
    this->nev = nev;
    this->kod = kod;
  }
  ...
};

Mikor egy tömböt létrehozunk pl. a Hallgato osztályból, akkor a tömb elemek inicializálódnak. Nézzük meg, hogyan is iniciálizálunk egy objektumot! Természetesen a konstruktor által, azonban kérdéses, hogy milyen értékeket kellene megadnia a rendszernek. Természetesen nem tudja, hogy minek kellene ott szerepelnie, ezért hibát jelez. Azonban ha van default konstruktor, akkor tud olyan konstruktort hívni, melynek nem kell paramétert átadnia, így minden elem inicializálása a megadott módon lezajlik. Nulla paraméteres konstruktort akkor is hívhatunk, ha egy paraméteres konstruktor minden paraméterének van default értéke. Ez könnyedén ambiguous hívást okozhat!

A probléma abból származott, hogy a tömb elemeinek inicializálásakor nem tudta a rendszer kitalálni, hogy milyen értékekkel akartunk inicializálni. Ha ezt a problémát megszüntetjük, vagyis megadjuk a várt értékeket, akkor default konstruktor nélkül is létrehozhatunk tömböt. Ekkor azonban minden elemet egyesével inicializálnunk kell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Hallgato {
  string nev, kod;
public:
  Hallgato(const string& nev, const string& kod) {
    this->nev = nev;
    this->kod = kod;
  }

  const string& get_nev() const {
    return nev;
  }

  const string& get_kod() const {
    return kod;
  }
};

class Kurzus {
  string nev, kod;
  unsigned felvette = 0;
  Hallgato hallgatok[2] = {{"Kreatív Név", "KNAA.SZE"}, {"IV Béla", "IBAA.SZE"}};
public:
  Kurzus(const string& nev, const string& kod) : nev(nev), kod(kod) {
  }
  // ...
};

Mivel tömböt a {} jelek között inicializálhatunk s egy-egy objektumot is inicializálhatunk a {} jelek között a fönti példában egyesével megadtuk, hogy mik legyenek a 0. és az 1. indexű elemek konstruktor hívásakor átadott paraméterek. Ez nem tanácsos, főleg ha több elemről beszélünk.

default metódus

Már volt szó a default konstruktorról. Ha nem írjuk ki, van default konstruktor, azonban ha már bármilyen másik konstruktort írunk, alapból nem lesz generált default konstruktor, ekkor meg kell írnunk magunknak. Ha már egy üres konstruktort írunk, kérdéses lehet, hogy az default akar lenni, vagy elfelejtettük megírni. Ha a default kulcsszót használjuk, rövidebb és olvashatóbb kódot kapunk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Kurzus {
  unsigned max_letszam;

 public:
  Kurzus(unsigned max_letszam) : max_letszam(max_letszam) {}    // emiatt nincsen default konstruktor
  /*
  Kurzus(){}    // nem tudni, mit is akartunk
  */
  Kurzus() = default;   // Ez a kifejezes sokkal beszedesebb es egy default konstruktor szerepet teljesiti
//....
};

deleted metódus

Sokszor kell bizonyos dolgokat megtiltanunk a felhasználónak. C++ esetében ezt egy osztályra a deleted metódussal tehetjük meg.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Kurzus {
  unsigned max_letszam;
 public:
  Kurzus() = default;
  Kurzus(unsigned max_letszam) : max_letszam(max_letszam){}
  // Kurzus(int max_letszam) : max_letszam(max_letszam){}  
  // Ha az int parameters konstruktor letezne, akkor akar egy negativ szam is atadhato lenne,
  // es mivel van konverzio unsigned <--> int, igy a rendszer ertelmezni fogja azt anelkul,
  // hogy megirnank az int verziot.
  // Ha ezt le akarjuk tiltani, ki kell torolnunk a metódust
  Kurzus(int max_letszam) = delete; // Igy mar ha intet adnak at, akkor nem az unsignedre konvertalodik,
  // hanem erzekeli ezt a konstruktort s latja, hogy le van tiltva, forditasi hibat okoz.
};

int main() {
    int i = -5;
    unsigned u = 3;

    Kurzus k(u);
    Kurzus k2(i); // forditasi hiba?
}

Otthoni gyakorló feladatok

  1. Írjunk egy osztályt, amely egy Kurzust reprezentál.
    • A Kurzusnak van neve (string típusú), kódja (string) és maximális létszám, hogy hány hallgató veheti fel.
    • Ne legyenek publikusak az adattagok, de legyen hozzájuk getter metódus.
    • Az osztálynak legyen egy konstruktora, ahol megadhatjuk a jellemzőket és a konstruktor inicializálja ezeket.
    • Legyen egy main függvény is, ami bekéri a kurzus adatait, majd létrehozza azt, és kiírja a kurzus jellemzőit.
    • Megoldás
  2. A konstruktor paramétereinek a neve egyezzen meg az adattagok neveivel, és a this segítségével inicializáljuk az adattagokat.
    • Default paraméter segítségével valósítsuk meg, hogy ha nem adják meg a max. létszámot, akkor az legyen 25.
    • Megoldás
  3. Konstruktor inicializáló lista segítségével inicializáljuk az osztály adattagjait. Legyen 2 konstruktora az osztálynak (function overload), ahol a második esetében nem kell megadni a max. létszámot, és azt 25-re állítja. Megoldás.
  4. Inicializáljuk a maximális létszámot default inicializáció segítségével. Megoldás.
  5. A két konstruktor "működése" azonos, azért a 2 paraméteres konstruktor "hívja meg" a másik konstruktort (delegating konstruktor). Megoldás.
  6. Írjunk egy függvényt (ebben a példában ne a Kurzus osztály metódusa legyen), amelyik paraméterként (pointer) megkapja a kurzust, majd beolvas egy egész számot, és ennyivel megváltoztatja a kurzus max. létszámát. Ehhez a Kurzus osztályt is egészítsük ki a szükséges metódussal. Megoldás.
  7. Valósítsuk meg a 6. feladatban szereplő függvényt referencia segítségével. Megoldás.

Utolsó frissítés: 2022-09-15 09:40:31