3. előadás

Tipp: a diák között lépkedni a J és K billentyúkkel lehet. Szöveges kereséshez ctrl-F használható.

Letöltés

1.

Programozás alapjai II. (3. ea) C++ OO paradigmák, osztály, operátorok átdefiniálása Szeberényi Imre, Somogyi Péter BME IIT <szebi@iit.bme.hu> M Ű E GY E T E M 1 7 82 C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 1-

2.

Programfejlesztés • Feladatanalízis – világ = dolgok + tevékenységek • Modellezés • Tervezés – absztrakció (elvonatkoztatás a részletektől) – dekompozíció (részfeladatra bontás) • Implementáció (programozás) – program = adatstruktúrák + algoritmusok C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 2-

3.

Néhány programozási módszer • • • • • • • • • Korai szoftverkészítés Strukturált Moduláris Objektum-orientált Funkcionális Deklaratív Adatfolyam-orientált Aspektus-orientált ... C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 3-

4.

Korai szoftverkészítés jellemzői • • • • többnyire gépi nyelvek nehezen követhető nehezen módosítható nincsenek letisztult vezérlési szerkezetek – ciklusba nem illik beugrani • zsenigyanús programozók • pótolhatatlan emberek, nem dokumentált • szoftverkrízis kezdete (1968) http://homepages.cs.ncl.ac.uk/brian.randell/NATO/NATOReports C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 4-

5.

Gépi nyelv ? // Kiírunk 00401350 00401351 00401353 00401356 00401357 00401358 00401359 0040135C 00401361 00401366 00401368 0040136B 0040136C 00401371 00401376 00401379 0040137A 0040137B 0040137C 0040137F 00401381 00401386 00401388 00401389 egy stringet void print(String str) push ebp 00401350 55 mov ebp,esp 00401358 57 sub esp,40h 00401360 00 push ebx 00401368 8B push esi 00401370 00 push edi 00401378 08 lea edi,[ebp-40h] 00401380 EC mov ecx,10h 00401388 5D mov eax,0CCCCCCCCh rep stos dword ptr [edi] mov eax,dword ptr [ebp+8] push eax push offset string "%s" (0042201c) call printf (004037d0) add esp,8 pop edi pop esi pop ebx add esp,40h cmp ebp,esp call __chkesp (00403620) mov esp,ebp pop ebp ret C++ programozási nyelv © BME-IIT Sz.I. 8B 8D B8 45 E8 5F E8 C3 EC 7D CC 08 5A 5E 9A CC 83 C0 CC 50 24 5B 22 CC EC B9 CC 68 00 83 00 CC 40 10 CC 1C 00 C4 00 CC 53 00 F3 20 83 40 8B CC 56 00 AB 42 C4 3B E5 CC U‹ě.ě@SV WŤ}Ŕą... .¸ĚĚĚĚó« ‹E.Ph. B .čZ$...Ä ._^[.Ä@; ěčš"..‹ĺ ]ĂĚĚĚĚĚĚ 2021.02.22. - 5-

6.

Strukturált tervezés • "oldd meg a feladatot" -> "gépen futó pr." (E.W.Dijkstra, C.A.Hoare) • fokozatos finomítás • absztrakt gépek rétegei "A" gép "B" gép "C" gép – absztrakció: • részletektől való elvonatkoztatás, hasonlóságok felismerése, ábrázolás, műveletvégzés, axiómák felállítása – dekompozíció: • részekre bontás, egymástól függetlenül kezelhető kisebb feladatok elhatárolása, határfelületen "látható" viselkedések meghatározása C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 6-

7.

Strukturált tervezés /2 • strukturált adatok, tipizálás • strukturált műveletek, tipizálás • előnyök: – áttekinthetőbb, minden réteghez önálló döntések, – hordozhatóság • hátrányok: – adatstruktúrákat nagyon pontosan kell definiálni a magasabb absztrakciós szinteken is, – hatékonysági problémák • PASCAL nyelv (blokkok fa struktúrája) C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 7-

8.

Moduláris tervezés • modul: önálló egység meghatározott kapcsolódási felülettel (interface) • cserélhető • önállóan fordítható • önállóan tesztelhető • információ elrejtése • funkcionális megközelítés • modulban a belső kötés erős • modulok között a kötés gyenge C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 8-

9.

Moduláris tervezés /2 • egy adatszerkezeten egy funkció • előnyök: – funkcionális bontás magától értetődő – interfészek jól kézben tarthatók • hátrányok: – esetenként több példány az elrejtés miatt – az adatok megjelennek az interfészeken, így azok "kőbe" lettek vésve • FORTRAN, MODULA-2, ADA C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 9-

10.

Dekompozíció • Felbontás egyszerűbb részfeladatokra • A felbontás absztrakt, ha – a felbontás anélkül történik, hogy a részeket pontosan meg kellene oldani, vagy meg kellene érteni; – csak a felület megadására szorítkozik (a kapcsolódáshoz); – a részletek megadását elodázza C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 10 -

11.

Funkcionális dekompozíció • Mit csinál a rendszer? – Strukturáló szempont: tevékenység • Tevékenység: résztevékenységekre bontunk – absztrakt: mit csinál a résztevékenység anélkül, hogy kellene tudni, hogy hogyan csinálja • Adatok: résztevékenységek ki-bemenete – nem absztrakt, mert tudnunk kell a pontos adatszerkezetet C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 11 -

12.

Feladat: komplex számok • Olvassunk be 10 komplex számot és írjuk ki a számokat és abszolút értéküket fordított sorrendben! • Funkcionális dekompozíciónál az adatokon végzett tevékenységekre koncentrálunk: Tevékenység beolvasás() és tárolás kiírás() abs() C++ programozási nyelv © BME-IIT Sz.I. Adat Komplex, KomplexTömb Komplex, KomplexTömb Komplex 2021.02.22. - 12 -

13.

Funkcionális dekompozícióval struct Komplex { double re, im; }; int main() { Komplex t[10]; // adatok beolvasas(t); // művelet kiiras(t); // művelet return 0; } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 13 -

14.

Funkcionális dekompozícióval/2 double abs(Komplex k){//adatot ismerni kell return sqrt(k.re*k.re + k.im*k.im); } void beolvasas(Komplex t[]){//ismerni kell for (int i=0; i<10; i++) cin >> t[i].re >> t[i].im; } void kiiras(Komplex t[]) {//ismerni kell for (int i=9; i>=0; i--) cout << t[i].re << '+' << t[i].im << 'j' << abs(t[i]) << endl; } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 14 -

15.

Kőbe vésett adatszerkezet • Ahhoz, hogy dekompozíció során nyert funkciók megvalósíthatók legyenek, rögzíteni kell a funkciók által kezelt adatok formátumát, struktúráját. – pl. el kell dönteni, hogy tömböt használunk, melynek a szerkezetét pontosan meg kell adni. • Nehezen módosítható (pl. átállás polár koordinátákra) • Nehezen használható fel újra. • Az adat nem absztrakt C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 15 -

16.

Absztrakt adattípus Az adat matematikai modellje – – – – – – – – viselkedésre koncentrálunk (viselkedési osztály) értékkészlet és az azon értelmezett a művelet halmaz a lényeges művelet: leképezés az értelmezési tartomány és az értékkészlet között a művelek algebrai leírással megadhatók nem kell ismerni a megvalósítást, azt sem, hogy mi a konkrét adat, csak a műveleteket egy adaton több funkció pl: komplex, verem, sor, tömb, lista, fa, stb. C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 16 -

17.

Objektum • Az OBJEKTUM testesíti meg a konkrét adatot és a rajta végezhető műveleteket • • • • • egyedileg azonosítható viselkedéssel és állapottal jellemezhető felelőssége és jogköre van képes kommunikálni más objektumokkal a belső adatszerkezet, és a műveleteket megvalósító algoritmus rejtve marad • könnyen módosítható • újrafelhasználható • általánosítható C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 17 -

18.

Objektum orientált dekompozíció • Kik a probléma szereplői? – Strukturáló szempont: dolgok (alany, adatok) • Dekompozíció: szereplőkre (objektumokra) bontunk • Adat: – absztrakt: a belső szerkezetet eltakarjuk • Tevékenységek: műveletek a szereplőkön (ige) – absztrakt: nem kell tudni, hogy hogyan működik. C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 18 -

19.

A feladat OO dekompozícióval • Olvassunk be 10 komplex számot és írjuk ki a számokat és abszolút értéküket fordított sorrendben! • Objektum orientált dekompozíció használatakor az absztrakt adatra koncentrálunk: Szereplő (objektum) Komplex KomplexTar C++ programozási nyelv © BME-IIT Sz.I. Művelet (üzenet) beolvas(), kiir() abs() tarol() elovesz() 2021.02.22. - 19 -

20.

A feladat OO dekompozícióval/2 Komplex k; // beolvas, kiir, abs KomplexTar t;// tarol, elovesz for (int i = 0; i < 10; i++) { k.beolvas(); a k objektum beolvas műveletét aktivizáljuk t.tarol(i, k); } for (int i = 9; i >= 0; i--) { k = t.elovesz(i); k.kiir(); cout << ' ' << k.abs() << endl; } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 20 -

21.

Objektum orientált modell • az objektumok jelentik a valóság és a modell kapcsolatát • együttműködő objektumok • megvalósítás: objektumokat „szimuláló” programegységekkel C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 21 -

22.

Komplex obj. megvalósítása C-ben struct Komplex { double re, im; }; Az összetartozásra csak a név utal void beolvasKomplex(Komplex *kp); double absKomplex(Komplex *kp); void setKomplex(Komplex *kp, double r, double i); struct Komplex k1, k2; // deklaráció és definíció setKomplex(&k1, 1.2, 0); // inicializálás f = absKomplex(&k1); Névtér hiánya f = absKomplex(&k2); C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 22 -

23.

Interfész függvények paraméterei setKomplex(Komplex *kp, double r, double i); funkció + obj. típusa melyik konkrét adat művelet operandusa i void beolvasKomplex(Komplex *kp); double absKomplex(Komplex *kp); Ilyen paraméterezést használtunk a laborban a String esetében is. C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 23 -

24.

OO paradigmák • egységbezárás (encapsulation) – osztályok (adatszerkezet, műveletek egységbezárása) • többarcúság (polymorphism) – műveletek paraméter függőek, tárgy függőek (kötés) • példányosítás (instantiation) • öröklés (inheritance) • generikus adatszerkezetek és algoritmusok C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 24 -

25.

Egységbezárás C++-ban struct Komplex { adatok double re, im; void set(double r, double i); double abs(); }; Komplex k1, k2; k1.re = 1.2; k1.im = 0; k1.set(1.2, 0); f = k1.abs(); tagfüggvények A fv. névben elég a funkciót jelölni. A saját adatot sem kell átadni. setKomplex(&k1, 1.2, 0); k1, k2 objektum: adatok és a rajta végezhető műveletek C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 25 -

26.

Adattakarás C++-ban struct Komplex { privát adatok private: double re, im; nyilvános tagfüggvények public: void set(double r, double i); double abs(); Közvetlen hozzáférés }; a priváthoz TILOS Komplex k1; k1.re = 1.2; k1.im = 0; k1.set(1.2, 0); f = k1.abs(); CSAK ÍGY C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 26 -

27.

Osztály • Objektum osztály ≡ objektum fajta, típus (viselkedési osztály) • Osztály ≠ Objektum • Objektum ≡ Egy viselkedési osztály egy konkrét példánya. osztály Komplex k1, k2, k3; C++-ban a struct egy osztály ! C++ programozási nyelv © BME-IIT Sz.I. objektumok 2021.02.22. - 27 -

28.

Adatelérés megvalósítása class Komplex { double re, im; public: void set(double r, double i) { re = r; im = i; } }; Komplex k1; C++ k1.set(1.2, 3.4); struct Komplex { double re, im; }; C void setKomplex(struct Komplex *this, double r, double i) { this -> re = r; a konkrét objektumra mutat this -> im = i; } struct Komplex k1; set(&k1, 1.2, 3.4); C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 28 -

29.

this pointer ≡ példányra mutató ptr. class Komplex { k2.set(2,1, -4); double re, im; public: void set(double re, double im) { this->re = re; this->im = im; } *this azt az objektumot jelenti, ...... amelyre a tagfüggvényt meghívták. }; double Komplex::abs() { return sqrt(this->re * this->re + this->im * this->im); } Komplex k1, k2; double f = k1.abs(); C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 29 -

30.

Kívül és belül definiált tagfvüggvény class Komplex { adatok privátak double re, im; public: void set(double r, double i) { re = r; im = i; } double abs(); inline-nak megfelelő }; double Komplex::abs() { return sqrt(re*re+im*im); } int main() { scope operátor Komplex k1; k1.set(1.2, 3.4); cout << k1.abs(); } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 30 -

31.

Tagfüggvények szerepe • Privát adatok lekérdezése (getter fv.) • Privát adatok beállítása (setter fv.) • Objektum állapotának (adatainak) változtatása • Műveletek az adatokkal • Adatok létrehozása • Adatok megszüntetése C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 31 -

32.

Konstans tagfüggvények class Komplex { double re, im; public: void set(double r, double i) { re = r; im = i; } double getRe() const { return re; } double getIm() const { return im; } double abs() const; }; Nem változtat(hat)ja double Komplex::abs() const { meg az állapotot (adatokat) return sqrt(re*re + im*im); } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 32 -

33.

Alapértelmezett tagfüggvények Automatikusan keletkező (implicit deklarált): • Konstruktor – Létrehozza az objektumot • Destruktor – Megszünteti az objektumot • Másoló konstruktor – Másolás útján hoz létre új objektumot • Értékadás (értékadó operátor) – Új érteket ad egy létező objektumnak • Címképző és dereferáló operátorok C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 33 -

34.

Konstruktor KONSTRUKTOR: Az objektum létrejöttekor hívódik. Feladata, hogy alapállapotba hozza az objektumot. Ha nem deklarálunk egyet sem, akkor implicit jön létre. Ilyen üres programozott törzs class Komplex { keletkezik implicit módon double re, im; public: Komplex() { } // konstruktornak nincs típusa Komplex(double r, double i) { re = r; im = i; } }; Komplex k1; // paraméter nélkül hívható (default) Komplex k2 = k1; // másoló (copy) ctr. (ez most implicit) Komplex k3 = Komplex(1.2, 3.4); ideiglenes objektum C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 34 -

35.

Destruktor DESTRUKTOR: Az objektum megszüntetésekor hívódik. Alapvető feladata, hogy megszüntesse az obj. által din. mem. területen létrehozott objektumokat/adatokat. class Komplex { Ez keletkezne implicit módon double re, im; public: ~Komplex() {} // destruktornak paramétere sincs }; { Komplex k1; // paraméter nélkül hívható (default) Komplex k2 = k1; // másoló (copy) konstruktor } destruktorok hívódnak C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 35 -

36.

Komplex példa újból class Komplex { double re, im; public: Komplex(double r) { re = r; } Komplex(double r, double i) { re = r; im = i; } double getRe() const { return re; } double getIm() const { return im; } ~Komplex() { cout << "Nincs mit megszüntetni"; } }; { Komplex k1(1.3, 0); // definíció és inic. Komplex k2(3), k3; Nincs ilyen konstr. } destruktorok meghívódnak C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 36 -

37.

Default argumentummal class Komplex { double re, im; public: Komplex(double r = 0, double i = 0) { re = r; im = i; } double getRe() const { return re; } double getIm() const { return im; } void kiir(ostream& os = cout) const; ~Komplex() { cout << "Nincs mit megszüntetni"; } }; void Komplex::kiir(ostream& os) const { os << re << '+' << im << 'j'; } Csak az egyik helyen, tipikusan a deklarációnál jelöljük a default-ot! C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 37 -

38.

Paraméter nélküli (default) konstr Objektum létrehozása alapállapottal. Automatikusan hívódik minden olyan esetben, amikor az objektumnak alapállapotban kell létrejönnie. Pl: – nem paraméteres ctor-t hívtunk ( Komplex k1; ) – tömbelemek létrehozásánál ( Komplex kt[10]; ) – tartalmazott objektumoknál struct Fraktal { Komplex c; int i; .... }; – származtatásnál (ld. később) • Nem keletkezik implicit, ha van legalább 1 explicit C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 38 -

39.

Most itt tartunk class Komplex { double re, im; public: Komplex(double r = 0, double i = 0) { re = r; im = i; } double getRe() const { return re; } // hasonlóan getIm() is void kiir(ostream& os = cout) const; void setRe(double r) { re = r; } // hasonlóan setIm(double) is void beolvas(isteram& is = cin); }; int main() { Komplex k1, k2(1, 1), kt[10], k3; kt[2].kiir(); // itt mit ír ki? Komplex *kp = new Komplex[100]; // mi történik itt? delete[] kp; } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 39 -

40.

Mit tud az osztályunk? • Azokat a műveleteket (metódusokat), amit implementáltunk (set/get, kiír, beolvas, ...) • + néhány alapértelmezett dolgot, amit az ajándékba kapott implicit deklarált tagfüggvények valósítanak meg pl: Komplex k4 = Komplex(1,8) k1 = k2; Komplex *p = &k1; k2 = *p; // inicializálás // értékadás // címképzés // dereferálás • Összeadni nem tud? C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 40 -

41.

Tud összeadni, ha megtanítjuk • Az operátorokat függvények valósítják meg. • A függvények túlterhelhetők. • Majdnem minden operátor túlterhelhető, ha legalább az egyik operandus objektum. Komplex összeadás: • Globális operátorral (eddig ilyenek voltak) • Tagfüggvénnyel (miért ne lehetne op. tagf.) C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 41 -

42.

k1 = k2 + k3 • először a + -t kell kiértékelni: – ha a bal oldal objektum, akkor van-e megfelelő, azaz k2.operator+(k3) formára illeszkedő tagfüggvénye ha nincs, vagy beépített típus és a jobb old. obj., akkor – van-e megfelelő globális függvény, azaz operator+(k2, k3) formára illeszkedő függvény. • Ugyanez történik az = -vel is, de ehhez van implicit deklarált függvény abban az esetben, ha mindkét oldal azonos típusú, aminek a hatása az, amit várunk: értékadás. C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 42 -

43.

Műveletekkel bővített Komplex class Komplex { double re, im; public: .... Komplex operator+(const Komplex& k) const { Komplex sum(k.re + re, k.im + im); return sum; } Komplex operator+(const double r) const Alapér{ return operator+(Komplex(r)); } }; .... telmezett Komplex k1, k2, k3; k1 + k2; 3.14 + k1; k1 + 3.14; k1 = k2; // bal oldal nem objektum ! // Ezért globális függvény kell ! C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 43 -

44.

double + Komplex class Komplex { ..... }; Globális fv., nem tagfüggvény: Komplex operator+(const double r, const Komplex& k) { return Komplex(k.re + r, k.im); } Baj van! Nem férünk hozzá, mivel privát! 1. megoldás: privát adat elérése pub. fv. használatával: Komplex operator+(const double r, const Komplex& k) { return Komplex(k.getRe() + r, k.getIm()); } Publikus lekérdező fv. C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 44 -

45.

Kiírás: cout << k1 A bal oldal objektum ugyan, de nincs a kezünkben. Ezért csak egy operator<<(cout, k1) hívásra illeszthető globális függvénnyel lehet megoldani: ostream& operator<<(ostream& os, const Komplex& k) { k.kiir(os); return os; } Így láncolható cout << k1 << k2; C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 45 -

46.

Beolvasás: cin >> k1 A bal oldal objektum ugyan, de nincs a kezünkben. Ezért csak egy operator>>(cin, k1) hívásra illeszthető globális függvénnyel lehet megoldani: istream& operator>>(istream& is, Komplex& k) { k.beolvas(is); return is; } A kiir() és a beolvas() tagfüggvény akár el is hagyható: ostream& operator<<(ostream& os, const Komplex& k) { return os << k.getRe() << '+' << k.getIm() << 'j'; } C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 46 -

47.

Op. túlterhelés szabályai • Minden túlterhelhető kivéve: . :: ?: sizeof • A szintaxis nem változtatható meg • Az egyop./kétop. tulajdonság nem változtatható meg • Precedecia nem változtatható meg • operator++() -- pre (++i) • operator++(int) -- post (i++) • operator double() -- cast (double) • operator[ ](typ i) -- index (typ tetszőleges) • operator()() -- függvényhívás C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 47 -

48.

Op. túlterhelés előnye/hátránya Előnyök • Szokásos aritmetikai, logikai funkciók – Teljes aritmetika (pl: komplex) – Összegzés növelés (pl. dátum) – Összehasonlítás Hátrányok • Szokásostól eltérő funkciók esetén zavaró lehet – (double)Komplex(3, 5) – mit jelent? – „almás” + „rétes” =?= „rétes” + „almás” • A kommutativitás sérül. Lehet, hogy zavaró. – cout << 1; C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 48 -

49.

Egy furcsa példa Komplex k1, k2; double d = (double)k1; // mit jelent? valós rész? abs? Jelentse a valós részt: Komplex { … operator double() { return re; } // formálisan nincs típusa !!! }; Veszély! A típuskonverzió automatikus is lehet! Pl: k1 + 3.14 (double)k1 + 3.14 lesz, ha nincs operator+(Komplex, double) C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 49 -

50.

Demo #include <iostream> using std::cout; using std::endl; struct Valami { Valami() { cout << "HAHO!" << endl; } ~Valami() { cout << "Jaj!" << endl; } }; int main() { cout << "1." << endl; Valami o1; cout << "2." << endl; Valami o2; Valami *o3 = new Valami; return 0; } https://git.ik.bme.hu/Prog2/eloadas_peldak/ea_03 C++ programozási nyelv © BME-IIT Sz.I. 2021.02.22. - 50 -

Utolsó frissítés: 2021-02-28 13.27