Programozás alapjai II. (6. ea) C++

Mutatókonverziók, heterogén kollekció

Szeberényi Imre, Somogyi Péter BME IIT szebi@iit.bme.hu

Öröklés (ismétlés)

Analitikus öröklés példa (ism.)

class Pont {
  int x, y;
public:
  Pont(int x1, int y1) : x(x1), y(y1) {}
  void set(int x1, int y1) { x = x1; y = y1; }
};
class Pont3D : public Pont {
  int z; // Bővült
public:
  Pont3D(int x1, int y1, int z1) : Pont(x1, y1), z(z1) {}
  void set(int x1, int y1, int z1) { Pont::set(x1, y1);
    z = z1;
  }
};

Korlátozó öröklés példa (ism.)

class Queue {
  // ...
public:
  void put(int e);
  int get();
};

class Stack {
  //...
public:
  void push(int e);
  int pop();
};

#include <cstddef>
class Stack : private Queue { // privát: eltakar a külvilág felé
  size_t nelem;
public:
  Stack() : nelem(0) {}
  int pop() { nelem--; return (get()); }
  // Továbbhívjuk a get()-et   ^
  void push(int e) { put(e); // betesz
    for (int i = 0; i < nelem; i++) { put(get()); } // átforgat
    // ^ Nem hatékony, csak példa!
    nelem++;
  }
};
int main() { Stack s1; s1.pop(); } // s1.get() helyett

Virtuális tagfüggvény (ism.)

Alakzat::rajzol(); Mit hív?

Fontos C++ sajátságok

Inicializálás (ism.)

class Pont {
protected:
  int x, y; // értékadás...
public:
  Pont(int x1 = 0, int y1 = 0) { x = x1; y = y1; }
  // Legyen default, mert...^
};
class Pont3D : public Pont {
  int z;
public:
  Pont3D(int x1, int y1, int z1) {
    x = x1; y = y1; z = z1; } // Mindig lehet így?
}; // Alaposztály konstruktora mikor hívódik?

Inicializáló lista

class Pont {
protected:
  int x, y;
public:
  Pont(int x1, int y1)  : x(x1), y(y1) { }
  // Itt most nem fontos a paraméter nélküli, mert paraméterest hívunk.
};
class Pont3D : public Pont {
  int z;
public:
  Pont3D(int x1, int y1, int z1) : Pont(x1, y1),  z(z1) { }
  // Alaposztály konstruktora      ^
};

Osztálybeli konstans tag

class Kor : public Pont {
  double &r;
  const double PI;
  int x, y;
public:
  Kor(int x, int y, double &r) : x(x), y(y), r(r), PI(3.14) {}
  // Melyik y? Van már this?           ^
  //                Kötelező inicializálni!  ~~~~  ~~~~~~~
  //  error: field 'y' will be initialized after field 'r'  
  //  error: constructor for 'Kor' must explicitly initialize
  //    the base class 'Pont' which does not have a default constructor
};
class FixKor : public Pont {
  double &r;
  static const double PI;
public:
  FixKor(int x, int y, double &r1) : Pont(x, y), r(r1) {}
};
const double FixKor::PI = 3.14; // statikus tag, létre kell hozni
int main() {
  double r = 5.0;
  FixKor k = FixKor(1, 2, r);
}

Explicit konstruktor

Öröklés és polimorfizmus

struct A {
  void valami() { cout << "A valami" << endl; }
  void semmi() { cout << "A semmi" << endl; }
};
struct B : public A {
  void valami() { cout << "B valami" << endl; }
  void valami(int i) { cout << "B valami int " << i << endl; }
};
int main() {   B b;
  b.valami();    // B valami
  b.valami(1);   // B valami int 1
  b.semmi();     // A semmi
  b.A::valami(); // A valami
  b.A::valami(3); // error: too many arguments to function
  // call, expected 0, have 1
}

Értékadás és kompatibilitás

struct Alap { int a; void f(); };
struct Utod : Alap { double d; int f1(); };

A kompatibilitás miatt az értékadás formálisan rendben, de az új résznek nincs helye a memóriában. Elveszik. Szeletelődés (slicing) történik.

Mutatókonverzió és kompatibilitás

struct Alap { int a; void f(); };
struct Utod : Alap { double d; int f1(); };

Memóriakép rendben van, de mi a helyzet a viselkedéssel?

Konverzió alaposztály mutatóra

struct Alap { void f(); };
struct Pub : public Alap { void f(); };
struct Priv : private Alap { void f(); };
int main() {
  Alap *p1B, *p2B;
  Pub pub;             // pub kaphat Alap-nak szóló üzeneteket.
  p1B = &pub;          // nem kell explicit típuskonverzió
  p1B->f();            // alap o. f() elérhető
  Priv priv;           // priv nem érti Alap üzeneteit pl: priv.Alap::f()
  p2B = (Alap *)&priv; // explicit konverzió
  p2B->f();            // így már érti
}

Viselkedés és a memóriakép is kompatibilis.

Konverzió származtatott o. mutatóra

struct Alap { void f(); };
struct Pub : public Alap { void f(); };
struct Priv : private Alap { int a; };
Alap alap;
Pub *p1D;
Priv *p2D;
int main() {
  p1D = (Pub *)&alap;
  p1D->f(); // ?????
  p2D = (Priv *)&alap;
  p2D->a = 0; // ?????
}

Típuskonverzió és az öröklés

Függv. elérése alap. o. mutatóval

class Alakzat { /*...*/ virtual void rajz() = 0; void k(); };
class Szakasz : public Alakzat { void rajz(); void k(); };
class Kor : public Alakzat { void rajz(); void k(); /*...*/ };
int main() {
  Alakzat* tar[100];
  tar[0] = new Szakasz(/*...*/); // konverzió, (kompatibilis)
  tar[1] = new Kor(/*...*/);     // konverzió, (kompatibilis)
  // ...
  for (int i = 0; i < 100; i++ ) {
    tar[i]->rajz(); 
    //      ^ Származtatott o. fv.
    tar[i]->k();
    //      ^ Alap oszt. függvénye
  }
}

Heterogén gyűjtemények

Heterogén kollekció példa

Eseménynaplózó osztályai

Esemény és leszármazottai

class Event {
  bool checked;
public:
  Event() : checked(false) {}
  virtual void show() { cout << " Checked: " << checked << endl;
    checked = true; } // Hurok?
  virtual ~Event() {};
};
class Event1 : public Event {  // ...
public:
  Event1();
  void show() { cout << /*...*/; Event::show(); } // Hurok?
};

Eseménylista: pointerek tárolója

class EventList {
  size_t nevent; Event *events[100];
public:
  EventList() : nevent(0) {}
  void add(Event *e) { // Alaposztály pointereket tárolunk
    if (nevent < 100) { events[nevent++] = e; }
  }
  void list() {
    for (size_t i = 0; i < nevent; i++) { events[i]->show(); }
    // Származtatott osztály függvénye            ^
  }
  ~EventList() {
    for (size_t i = 0; i < nevent; i++) { delete events[i]; }
    // Megszűnik az esemény is (komponens reláció) ^
  }
};

Eseménynapló használata

class Event { /*...*/ };
class Event1 : public Event { /*...*/ }; // Új esemény:
class Event2 : public Event { /*...*/ }; // csupán definiálni
class Event9 : public Event { /*...*/ }; // kell az új osztályt
class EventList {
public:
  void add(Event * e) { } // "Rábiztuk" a list-re
  void list() { /*...*/ }
};
int main() {
  EventList list;
  list.add(new Event1(/*...*/)); list.add(new Event2(/*...*/));
  /*...*/  list.add(new Event9(/*...*/)); list.list();
  // 
}

Ki szabadít fel?

#include <cstddef>
class EventList {
  size_t nevent;  Event *events[100];
public: // ...
  void add(Event *e) {
    if (nevent < 100) { events[nevent++] = e; } else { delete e; throw "Megtelt!"; } }
  void list() {
    for (size_t i = 0; i < nevent; i++) { events[i]->show(); }
  }
  ~EventList() {
    for (size_t i = 0; i < nevent; i++) { delete events[i]; } }
    // list.add(new Event1(....));     ^
    // -> ~Event(); Virtuális kell!    ^
}; //...

Virtuális destruktor újból

class Event {
public:
  virtual void show() {}
  virtual ~Event() {} // 2.
};
class Event1 : public Event { int *p;
public:
  Event1(int s) { p = new int[s]; }
  void show() {}
  ~Event1() { delete[] p; } // 1.
  // Event::~Event() (2.) ^ hívódik utána
};
int main() { Event *ep = new Event1(120);
  ep->show(); /*Event1::show()*/ delete ep; /* Event1::~Event1() */
}

Virt. destr. más, mint a többi virt. fv., mert az ős destruktora mindig meghívódik! https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_06

Összetettebb példa: CppBolt

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_06 CppBolt

Heterogén kollekció összefoglalás

Tipikus halálfejes hiba

class EventList {
  int ne;            // negatív?
  Event events[100]; // nem pointert tárol!
public:
  EventList() { ne = 0; }
  void add(Event &e) { if (ne < 100) { events[ne++] = e; } }
  // Adatvesztés!                                    ^
  // Szeletelés (slicing) A származtatott rész elveszik!
  void list() { for (int i = 0; i < ne; i++) { events[i].show(); } }
  // Event::show()                                       ^
};

Digitális áramkör modellezése

Modell

A változásokat üzenetek továbbítják. Ha nincs változás, nem küldünk újabb üzenetet.

Csak véges számú iterációt engedünk meg.

Áramköri elemek felelőssége

Osztályhierarchia

Obj: alaposztály

Obj: absztrakt alaposztály

class Obj {
  String nev;                  // objektum neve
  Pin nk;                      // kapcsolódási pontok száma
  Conn *konn;                  // kapcsolatok leírása
  Obj(const Obj &);            // hogy ne lehessen használni
  Obj &operator=(const Obj &); // hogy ne lehessen haszn.
public:
  Obj(const char *n, Pin k) : nev(n) { konn = new Conn[nk = k]; }
  virtual ~Obj() { delete[] konn; }               // tömb felszab.
  void setNev(const char *n) { nev = String(n); } // név beáll.
  void setConn(Pin k, Obj &o, Pin on);            // összekapcs.
  void uzen(Pin k, Message msg);                  // üzen
  virtual void set(Pin n, Message msg) = 0;       // működtet
};

Conn: kapcsolatok tárolása

class Conn {
  Obj *obj; // ezen objektumhoz kapcsolódik
  //  ^ Miért nem referencia ?
  Pin n; // erre a pontra
public:
  Conn() : obj(NULL) {}
  void setConn(Pin k, Obj &o) { n = k; obj = &o; } // beállít
  Obj *getConn(Pin &k) { k = n; return (obj); } // lekérdez
};

Message: jel mint üzenet

struct Message {
  enum msgt { undef, jel } typ; bool J; int c;
  Message(msgt t = undef, bool j = false, int n = MaxIter)
      : typ(t), J(j), c(n) {
  } // két üzenet egyenlő, ha az típusuk és jelszintjük is azonos
  bool operator==(const Message &m) const { return (typ == m.typ && J == m.J); }
  bool operator!=(const Message &m) const { return (!operator==(m)); }
  Message operator+(const Message &m) const {
    return Message(std::max(typ, m.typ),
                   (static_cast<int>(J) + static_cast<int>(m.J)) != 0,
                   std::max(c, m.c)); }
  Message &operator--() { // pre-dekremens op.
    if (--c <= 0) { throw "Sok Iteracio!"; } // iterációs számláló csökkentése
    return (*this);
  }
};

Üzenet továbbítása

/**
 * Üzenet (msg) küldése a k. pontra kapcsolódó obj. felé
 */
void Obj::uzen(Pin k, Message msg) {
  Pin n; // kapcsolodó objektum kapcs. pontja
  if (k >= nk) {
    throw "Uzenet hiba";
  } // hiba, nincs ilyen végpont
  if (Obj *o = konn[k].GetConn(n)) {
    o->set(n, --msg); // szomszéd működtető függvénye
  }
}

Drót obj. modellje

Drót kicsit precízebben

A diagram szerkesztéséhez az UMLet (http://www.umlet.com/) programot használtam.

Drót

class Drot : public Obj {
protected:        // megengedjük a származtatottnak
  Message veg[2]; // két vége van, itt tároljuk az üzeneteket
public:
  Drot(const char *n = "") : Obj(n, 2) {}         // 2 végű obj. Létrehozása
  Message get() const { return veg[0] + veg[1]; } // bármelyik vég
  void set(Pin n, Message msg);                   // működtet
};
void Drot::set(Pin n, Message msg) {
  if (veg[n] != msg) { // ha változott
    veg[n] = msg;      // megjegyezzük és
    uzen(n ^ 1, msg);  // elküldjük a másik végére (vezet)
    //     ^ Gonosz trükk!
  }
}

Csomópont

class Csp : public Obj {
protected:        // megengedjük a származtatottnak
  Message veg[3]; // három vége van, itt tároljuk az üzeneteket
public:
  Csp(const char *n = "") : Obj(n, 3) {} // 3 végű objektum
  void set(Pin n, Message msg);          // működtet
};

void Csp::set(Pin n, Message msg) {
  if (veg[n] != msg) {      // ha változott
    veg[n] = msg;           // megjegyezzük és
    uzen((n + 1) % 3, msg); // elküldjük a másik 2 végére
    uzen((n + 2) % 3, msg);
  }
}

Kapcsoló

class Kapcsolo : public Drot { // Drótból
  bool be;                     // állapot
public:
  Kapcsolo(const char *n = "") : Drot(n), be(false) {}
  void set(Pin n, Message msg);
  // jel, false, lehetne undef
  void kikap() { be = false; uzen(0, Message(Message::jel));
    uzen(1, Message(Message::jel)); }
  void bekap() { be = true; uzen(0, veg[1]); uzen(1, veg[0]); }
};
void Kapcsolo::set(Pin n, Message msg) {
  if (be) { Drot::set(n, msg); } // be van kapcsolva, drótként viselk.
  else { veg[n] = msg; } // ki van kapcsolva, csak megjegyezzük
}

NAND kapu

class NAND : public Obj {
  Message veg[3]; // három "vége" van
public:
  NAND(const char *n = "") : Obj(n, 3) {} // 3 végű obj. létreh.
  void set(Pin n, Message msg);           // működtet
  Message get() { return (veg[2]); }      // kim. lekérdezése
};
void NAND::set(Pin n, Message msg) {
  if (n != 2 && veg[n] != msg) { // ha változott bemenet
    veg[n] = msg;                // megjegyezzük
    uzen(2, veg[2] = Message(Message::jel, !(veg[0].J * veg[1].J),
                             // kimenet előállítása   ^
                             msg.c)); // üzenünk a kimeneten
    // ciklusszám marad          ^
  }
}

R_S_ tároló

Class R_S_FF : public Obj {
protected:
  Message veg[4]; // négy "vége" van
  NAND N[2];      // két db NAND kapu, komponens
public:
  R_S_FF(const char *n) : Obj(n, 4) {
    N[0].setConn(2, N[1], 0); // összekötések létrehozása
    N[1].setConn(2, N[0], 0);
  }
  void set(Pin n, Message msg); // működtet
  Message get(int i) {          // kimenet lekérdezése
    if (i >= 2) { i = 0; }
    return (veg[i + 2]);
  }
};
Void R_S_FF::set(Pin n, Message msg) {
  if (n < 2 && veg[n] != msg) {   // ha input és változott,
    veg[n] = msg;                 // letárolja
    N[n].set(1, msg);             // megfelelő bemenetre küldi
    uzen(2, veg[2] = N[0].get()); // üzen a kimeneten
    uzen(3, veg[3] = N[1].get()); // üzen a kimeneten
    // ^ Kimenetek előállítása, mert bent nincs csomópont.
  }
}

Szimulátorunk próbája

Kapcsolo K1("K1"), K2("K2"); Forras F1("F1"), F2("F2");
R_S_FF FF("FF");
try { F1.setConn(0, K1, 0);  FF.setConn(0, K1, 1);
  F2.setConn(0, K2, 0);  FF.setConn(1, K2, 1);
  F1.init();  F2.init();  K1.bekap();  K2.bekap();
  cerr << FF.get(0).J << FF.get(1).J << FF.get(2).J << FF.get(3).J << endl;
  K1.kikap();
  cerr << FF.get(0).J << FF.get(1).J << FF.get(2).J << FF.get(3).J << endl;
  K1.bekap();
  cerr << FF.get(0).J << FF.get(1).J << FF.get(2).J << FF.get(3).J << endl;
  K2.kikap();
  cerr << FF.get(0).J << FF.get(1).J << FF.get(2).J << FF.get(3).J << endl;
} catch (const char *s) { cerr << s << endl; }

https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_06 digit

Összefoglalás

Öröklés