- Definiálj Sztring struktúrát, amelyik egy dinamikus karaktertömböt tartalmaz (amely nullával lezárt sztringet fog tárolni), és a hozzá tartozó hosszt.
- Írj függvényt, amelyik paraméterként vesz át egy ilyet, illetve egy nullával lezárt char const * tömböt. Inicializálja a függvény az eddig memóriaszemetet tartalmazó struktúrát úgy, hogy ezt a karaktertömböt bemásolja!
- Írj függvényt, amelyik három sztringet vesz át paraméterként; az elsőt és a másodikat összefűzve másolja be a harmadikba. A harmadik sztring már inicializálva volt, és tárol egy sztringet, amelyet felül kell írni!
A dinamikus tömböt és a hosszat mindenhol együtt kezeld! C-s
sztringkezelő függvényeket (strcpy
, strlen
) használni lehet.
Megoldás
struct Sztring {
char *adat;
int hossz;
};
void sztring_init(Sztring *s, char const *kezd) {
s->hossz = strlen(kezd);
s->adat = new char[s->hossz + 1]; /* +1 a lezaro 0-nak */
strcpy(s->adat, kezd);
}
void sztring_osszefuz(Sztring const *egyik, Sztring const *masik, Sztring *ide) {
delete[] ide->adat; /* mert mar tartalmazott adatot! */
ide->hossz = egyik->hossz + masik->hossz;
ide->adat = new char[ide->hossz + 1];
strcpy(ide->adat, egyik->adat); /* masol */
strcat(ide->adat, masik->adat); /* hozzafuz */
}
Egészítsd ki az előző programot egy olyan függvénnyel, amelyik egy megadott sztringből kiszedi a paraméterként kapott karakter összes előfordulását! A dinamikus tömb a szűrés után is pont akkora legyen, amekkora kell!
Megoldás
struct Sztring {
int hossz;
char *szov;
};
/* 1. megszámoljuk, mekkora lesz az új sztring
2. foglalunk neki helyet
3. átmásoljuk a karaktereket, amiket kell
4. felszabadítjuk a régit
5. átállítjuk a pointert */
/* érték szerinti paraméterátadás -> Sztring * kell */
void kiszed(Sztring *sz, char mit) {
/* 1. */
int db = 0;
for (int i = 0; i < sz->hossz; i++) /* összes char */
if (sz->szov[i] != mit) /* ha nem olyan */
db++; /* növel */
/* 2. */
char *uj = new char[db + 1]; /* ekkora az új! */
/* 3. */
int j = 0;
for (int i = 0; i < sz->hossz; i++) /* összes eredeti */
if (sz->szov[i] != mit) { /* ha nem olyan */
uj[j] = sz->szov[i]; /* másol */
j++; /* másik index! */
}
uj[j] = '\0'; /* lezáró 0 */
/* 4. */
delete[] sz->szov;
/* 5. */
sz->szov = uj;
}
Alább egy C-ben megírt verem adatszerkezet. A verem_init()
függvény inicializálja az addig memóriaszemetet tartalmazó struktúrát.
A verem_berak()
függvény betesz egy számot a verembe. Ha kell,
átméretezi a dinamikus memóriaterületet. A verem_free()
egy
feleslegessé vált verem dinamikus területét szabadítja fel.
A program memóriakezelési hibát tartalmaz. Hol? Hogyan lehet javítani?
struct Verem {
double *adat; /* dinamikus */
int db; /* értékes adatok száma */
int kapacitas; /* foglalt terület mérete (db <= kapacitas) */
};
int main() {
Verem v1, v2;
verem_init(&v1);
verem_init(&v2);
verem_berak(&v2, 5.1);
v1 = v2;
verem_free(&v1);
verem_free(&v2);
return 0;
}
Megoldás
A fenti kódban az elvi hiba a v1=v2
értékadásnál van.
Itt ugyanis minden egyes adattag átmásolódik v1
-be v2
-ből; ez a
darabnál például nem lenne gond, a pointernél viszont nagyon is az.
Ugyanis innentől kezdve v1
és v2
pointere ugyanoda mutat, és a vermek
nem működnek majd helyesen. Például az egyik verembe berakott adat
a másikban is lehet, hogy meg fog jelenni. A v1.adat
pointer ilyen
módon történő felülírása memóriaszivárgást is okoz.
A struktúrák közötti értékadás maga egyébként működik, lefordul, már a sima C is ismeri! Nem az a hiba, hogy ilyen nincs, hanem amit csinál, az nem jó nekünk itt. (Gyakorlatilag a struktúra értékadásával megsértjük azt a szabályt, hogy nem nyúlunk a struktúra belsejébe - ugyanis kívülről nem tudhatjuk, hogy le szabad-e egyesével másolni az adattagjait. Ezt csak az tudja, aki a struktúrát kezelő függvényeket megírja.) Írni egy másolós függvényt, amelyik egyesével átmásolja a struktúra adattagjait, az ugyanúgy rossz, mintha egy sima értékadással lerendezzük, mert akkor a memóriaszivárgás és a rossz pointerek ugyanúgy meglesznek. Ezen kívül, az egymásra mutató pointerek mindenképpen rosszak, nem csak akkor, ha a két verem darabszáma vagy kapacitása épp eltérő.
A memória felszabadítása lesz az, ahol a program lefagy; egészen pontosan
v2
felszabadítása.
v1.adat
és v2.adat
ugyanoda mutat, ezért
a felszabadító függvény kétszer próbálja meg majd azt a memóriaterületet
felszabadítani (free()
vagy delete[]
, attól függ,
hogyan vannak megírva).
Javítani úgy lehet, ha az értékadás helyett másoló függvényt írunk, és a v1=v2
értékadást egy verem_masol(&v1, &v2);
sorra cseréljük.
void verem_masol(Verem *cel, const Verem *forras) {
/* A cél veremből természetesen minden adat eltűnik, felülírjuk. */
delete[] cel->adat;
/* ez egyértelmű */
cel->db = forras->db;
/* igazából ez is; legegyszerűbb leutánozni a másik vermet, és
akkor itt nem kell azzal foglalkozni, hogy mi a foglalási
stratégia. a másik úgyis aszerint jött létre. */
cel->kapacitas = forras->kapacitas;
/* forrástól független memória foglalása */
cel->adat = new double[cel->kapacitas];
/* másolás */
for (int i=0; i<cel->db; ++i)
cel->adat[i]=forras->adat[i];
}
Rajzold le az alábbi program változóinak elhelyezkedését a memóriában, amikor a végrehajtás a *-gal jelölt résznél tart! Melyik változó hova kerül (globális, verem, dinamikus memóriaterületre)? Az egyes pointereket nyíllal jelöld, hova mutatnak. Van egy hiba a programban. Hol? Hogyan lehet javítani?
#include <cstdlib>
/* n*n-es matrixot foglal */
double **nxn_matrix(int const n) {
double **matr;
matr = (double **) malloc(n * sizeof(double*));
matr[0] = (double *) malloc(n * n * sizeof(double));
for (int y = 1; y < n; y++)
matr[y] = matr[0] + y * n;
return matr;
}
int s = 3;
int main() {
double **m;
m = nxn_matrix(s);
m[2][1] = 5.1; // *
free(m);
return 0;
}
Megoldás
A program változói a lenti ábrán láthatóak. Az s
globális területre kerül, mivel függvényen kívül van. Az
m
a verembe, mivel az a main()
függvény globális változója. Ezeken kívül pedig a heapre kerül a két
malloc()
-olt tömb.
Az első foglalt tömb pointereket tartalmaz, mégpedig hármat. Ennek a tömbnek az első elemébe egy pointert teszünk, amelyet a
malloc()
függvény ad; ez kilenc darab double
változónak hely. A többi pointert úgy inicializáljuk, hogy
ennek a tömbnek a belsejébe mutat, 3 és 6 elemmel arrébb.
A kódban a hiba az, hogy a main()
függvényben csak az első, pointereket tartalmazó tömb kerül felszabadításra. A
második, double
elemeket tartalmazó nem. (Egy malloc → egy free!) Javítani legegyszerűbben úgy lehet, hogy a
free(m)
sor elé egy free(m[0])
hívást írunk.
Félreértések:
- Egy adott memóriaterülethez tartozó
malloc()
ésfree()
hívásnak NEM kell egy függvényben lennie! Ha így lenne, akkor hogy lenne az lehetséges, hogy amalloc()
működik?! Az is lefoglal valamit, de nem szabadítja fel! - A
matr[]
tömb minden eleme inicializálva van. Az első,matr[0]
külön, mert oda egymalloc()
-tól kapott pointert teszünk be. Az összes többi, 1-től kezdődő indexűek pedig afor()
ciklusban lesznek inicializálva. - Éppen ezért helyes az is, hogy a
for()
ciklusy=1
-től indul. - A dinamikus memóriát felszabadítani itt pontosan két
free()
hívással lehet. Az egyik adouble
számok tömbjét szabadítja fel, a másik pedig a pointerekét. Ha a foglalásnál nemfor()
ciklus foglalta a sorokat külön, akkor felszabadítani sem úgy kell!
Csinálj fullos tört (racionális szám) osztályt! Legyen benne elrejtve a számláló és a nevező! Legyen konstruktora (amely ellenőrzi a nevező nulla értékét), legyenek getterek, amelyek lekérdezik a számlálót, a nevezőt; és legyenek mindenféle operátorok az aritmetikai műveletekhez! A függvények dobjanak kivételt, ha egy művelet nem végezhető el!
A Tort osztály racionális számokat tárol, vagyis egész számok hányadosát. Két adattagja van, a számláló és a nevező. Ezeket elrejtjük a külvilág elől, nehogy pl. valaki jókedvében nullára állítsa a nevezőt. A következő programrésznek kéne helyesen működnie, vagyis kell tudni létrehozni, összehasonlítani, szorozni és összeadni:
Tort a(3, 5), b(1, 2), c(3), d;
/* a=3/5, b=1/2, c=3, d=0 */
if (c != a) std::cout << "c nem ugyanannyi, mint a.";
if (c == a) std::cout << "c ugyanannyi, mint a.";
c = a+b*c;
c.beolvas(std::cin);
Definiáld az osztályt! A szorzás műveletet definiáld is (írd meg), a többit elég csak deklarálni. (Két fő kérdés: 1. az egyes függvények neve, és a paramétereik típusa, 2. hány konstruktor kell?)
Írj egy osztályt, amelyik egy időpontot tárol (IdoPont)! Legyen egy
konstruktora, amelynek óra (0..2), perc (0..59) formátumban lehet
megadni az időpontot. Legyen alapértelmezett konstruktora, amely
éjfelet állít be. Legyen ==
operátora, amellyel össze lehet
hasonlítani két ilyet (bool igazzal tér vissza, ha egyenlőek)! Legyen -
operátor függvénye, amellyel ki lehet vonni egymásból két időpontot,
és egy egész számot ad (pl. 10:40 és 09:55 között 45 perc telt el –
érdemes belül mindent percre átszámolni). Ki lehessen írni egy
ostreamre: pl. IdoPont i(5, 35); std::cout<<i;
hatására
5:35 jelenjen meg. Legyen inline és nem inline megvalósított tagfüggvény
is!
Megoldás
#include <iostream>
class IdoPont {
private:
int ora, perc;
public:
IdoPont() { ora=perc=0; }
IdoPont(int ora_, int perc_) { ora=ora_; perc=perc_; }
friend bool operator==(const IdoPont &a, const IdoPont &b);
int operator-(const IdoPont &b) const;
friend std::ostream& operator<<(std::ostream &os, const IdoPont &i);
};
int IdoPont::operator-(const IdoPont &b) const {
return (ora*60+perc) - (b.ora*60+b.perc);
}
bool operator==(const IdoPont &a, const IdoPont &b) {
return a.ora==b.ora && a.perc==b.perc;
}
std::ostream& operator<<(std::ostream &os, const IdoPont &i) {
os << i.ora << ':' << i.perc;
}
int main() {
IdoPont i;
IdoPont i1(10, 40);
IdoPont i2(9, 55);
if (i1 == i2)
std::cout << i1 << " és " << i2 << " egyenlőek." << std::endl;
std::cout << i1 << " és " << i2 << " között "
<< i1-i2 << " perc telik el." << std::endl;
}
Az InfoC oldal adminisztrációs része háromértékű logikát használ a tárgykövetelmények teljesülésének jellemzéséhez. A szokásos IGEN és NEM mellett egy harmadik érték is lehetséges: LEHET. Így a félév közben sem mutat fals eredményt az eredmények táblázata.
A három érték jelentését könnyű megfogni a laborjelenlétek példáján. 14 hét van, 4-et lehet hiányozni. Ha valakinek 4-nél több hiányzása van, már nem kaphat jegyet: NEM. Ha 10-nél több jelenléte összegyűlik, akkor a hátralévő időben akármennyit hiányzik, már biztosan teljesül a követelmény: IGEN. Amúgy még nem lehet tudni, ha a félév közepén vagyunk: LEHET. A félév végén, a csoport lezárásakor az összes LEHET átváltozik NEM értékűre.
A háromértékű logikához logikai függvényeket is kellett definiálni. A logikai ÉS kapcsolat működése: ha mindkét érték IGEN, akkor az eredmény is IGEN; ha bármelyik érték NEM, akkor az eredmény NEM, amúgy pedig LEHET. Az igazságtáblát felrajzolva látszik, hogy ha nincs LEHET érték, akkor ez egyenértékű a Boole-féle kétértékű logikai ÉS függvénnyel.
A feladatok a következők. 1) Definiálj felsorolt típust, amely IGEN, NEM, LEHET értékű lehet. 2) Definiáld a logikai VAGY és TAGADÁS műveleteket is a háromértékű logikára. 3) Valósítsd meg C++-ban az &&
, ||
, !
operátorokat a háromértékű logikára! 4) Működnek a &&
és ||
operátorok, ha valamelyik operandusuk bool? 5) Mi a helyzet a háromértékű logikai típushoz definiált &&
és ||
operátorok rövidzár tulajdonságával?
Írd meg a fenti „háromértékű logika” feladatot C++-ban úgy, hogy egy egyszerű enum
helyett osztályt használsz!
Legyen ennek konstruktora (amelyik mindig inicializálja valamilyen értékre a változót), legyenek a fentihez hasonló módon
operátorai! Oldd meg, hogy kompatibilis tudjon ez lenni a beépített bool
-okkal! Pl. a HaromErteku x = true;
definíciónak működnie kell. Vajon van értelme annak, hogy a bool → HaromErteku
mellett HaromErteku → bool
konverzió is legyen?
Írj egy háromdimenziós vektor osztályt! Legyen 3 paraméterű konstruktora (x,
y, z komponensek), lehessen összeadni két vektort +
és +=
operátorral,
lehessen lekérdezni a hosszát egy adott vektornak. Lehessen kiírni egy
std::ostream
-re a <<
operátorral. Legyen minden konstans és/vagy referencia,
ami csak lehet!
Magyarázd el, miért úgy kell megvalósítani a <<
operátort, ahogyan – miért
az az egyetlen lehetséges módja. Melyik osztálynak miért/miért nem lehetne
tagfüggvénye az az operátor?
Megoldás
#include <cmath>
#include <iostream>
class Vektor {
private:
double x, y, z;
public:
Vektor(double x, double y, double z)
: x(x), y(y), z(z) {
}
double hossz() const {
return sqrt(x*x+y*y+z*z);
}
// ettől változik az, ami a bal oldalán van, a *this
// ezt tagként csináltam, de lehetne globális is
Vektor& operator+=(Vektor const& rhs) {
this->x += rhs.x;
this->y += rhs.y;
this->z += rhs.z;
return *this; // emiatt lehet ref
}
friend std::ostream& operator<<(std::ostream& os, Vektor const& rhs);
};
// a két paraméter NEM változik, hanem egy újjal tér vissza!
// globálisként csináltam, de lehetne tag is
Vektor operator+(Vektor const& lhs, Vektor const& rhs) {
Vektor uj(lhs);
uj += rhs; // visszavezettem erre
return uj; // emiatt NEM lehet ref
}
// ez csak globális lehet
std::ostream& operator<<(std::ostream& os, Vektor const& rhs) {
os << '(' << rhs.x << ',' << rhs.y << ',' << rhs.z << ')';
return os;
}
int main() {
Vektor a(0,0,0), b(2,2,2), c(3,3,3);
a = b+c;
std::cout << b << c;
b += c;
std::cout << b << c;
}
A lényeg, hogy a +=
operátor megváltoztatja a bal oldalán
álló vektort, a +
operátor pedig nem. Jelen esetben a +
operátort egyszerűen visszavezettem a már megírt +=
-re. Így még
barátnak sem kell lenni, mert csak a publikus interfészét használja: a másoló
konstruktorát és a +=
operátorát.
A kiíró operátor azért lehet csak globális, mert a bal oldalán egy
std::ostream
típusú objektum van. Ha tagfüggvény lenne, akkor
annak kellene a tagfüggvénye, ami viszont nem lehet, hiszen az az osztály
már definiálva van, nem tehetünk hozzá új tagot.
Nem feltétlenül kell barátnak is lennie; itt azért lett az, mert a Vektor publikus
interfészén keresztül nem látja azokat a tagokat, amelyeket kiír (a vektor
komponenseit.) A vektornak azért nem lehet tagja, mert akkor a vektor kellene
az operátor bal oldalán álljon.
A Komplex osztály belül re+im*j alakban tárol számokat. Készítsd el az osztályt, amelyet használva működőképessé
válik a lenti program! A kiemelt részek utalnak a megvalósítandó függvényekre. Próbáld minél kevesebb függvénnyel
megoldani a dolgot (bőven elfér a lapon), illetve az operátorokat lehetőség szerint visszavezetni egymásra.
Átgondonandó: re és im publikusak vagy privátak? Miért? Hány konstruktor kell? Melyik operátor vezethető vissza
melyikre? Referenciákkal kapcsolatban: a függvények a paraméterüket akkor vehetik át referenciaként, ha nem
akarják lemásolni (mert az lassú lenne), vagy egyáltalán nem is másolható (pl. std::cout
). Visszatérési érték is lehet
referencia, ugyanezekben az esetekben. Ha a függvényen belül jön létre a visszaadott objektum, akkor viszont tuti
nem használhatunk referenciát, mert visszatéréskor megszűnik a lokális változó. Használj a kódban mindenhol
referenciát, ahol lehet, és objektumot, ahol muszáj!
Komplex a(3.4, 5), b, c(1);
if (c!=a) std::cout << "c nem ugyanannyi, mint a.";
if (c==a) std::cout << "c ugyanannyi, mint a.";
c = a+b*c;
c += a;
b *= a;
std::cout << c;
- Írj függvényt, amely paraméterként egy egész számot kap, visszatérési értékként pedig a szám prímtényezőit adja vissza növekvő sorrendben!
- Írj függvényt, amely átvesz két prímtényezős felbontást, ahogyan azt az előző függvény adta, és visszaadja a legkisebb közös többszörös prímtényezős felbontását!
- Írj függvényt, amely egy prímtényezős felbontást megkapva előállítja a számot!
- Írj programot, amely kér két számot a felhasználótól, és a fenti függvények használatával kiírja a legkisebb közös többszörösüket!
Használd a beépített tárolókat, ne írj sajátot!
- Írj függvényt (split), amelyik egy sztringet felbont sztringek tömbjére egy adott karakter mentén. Például
"alma körte barack"
és' '
→{"alma", "körte", "barack"}
. Figyelj arra, hogy több elválasztó karakter is lehet a szavak között, például több szóköz. - Írj függvényt (join), amelyik sztringek tömbjét fűzi össze, az adott sztringet közéjük téve! pl.
{"alma", "körte", "barack"}
és", "
→"alma, körte, barack"
. - Írj függvényt, amelyik szétbontja a beírt, szóközökkel elválasztott szavakat egy tömbre, majd egyesíti azokat vesszőkkel – pont, mint a fenti példákban!
Használd a beépített tárolókat, ne írj sajátot!
Hova kell const-ot rakni?
class Verem {
private:
double *szamok;
int meret;
public:
Verem() _____ { szamok = nullptr; meret = 0; }
~Verem() _____ { delete[] szamok; }
Verem(_____ Verem& masik) _____;
_____ Verem& operator=(_____ Verem& masik) _____;
_____ double legutobbi() _____ { return szamok[meret-1]; }
_____ double kivesz() _____;
bool ures_e() _____;
void berak(_____ double mit) _____;
};
Hova kell const-ot rakni?
struct Komplex {
double re, im;
};
bool operator==(_____ Komplex& lhs, _____ Komplex& rhs) {
return lhs.re==rhs.re && lhs.im==rhs.im;
}
bool operator!=(_____ Komplex& lhs, _____ Komplex& rhs) {
return !(lhs==rhs);
}
_____ Komplex operator*(_____ Komplex& lhs, _____ Komplex& rhs) {
return Komplex(lhs.re*rhs.re-lhs.im*rhs.im, lhs.re*rhs.im+lhs.im*rhs.re);
}
_____ Komplex& operator*=(_____ Komplex& lhs, _____ Komplex& rhs) {
lhs=lhs*rhs;
return lhs;
}
_____ std::ostream& operator<<(_____ std::ostream& os, _____ Komplex& k) {
os << k.re << '+' << k.im << 'j';
return os;
}
Egydimenziós tömb osztályt kell írni, amelyik sokféle konstruktorral rendelkezik. Adattagjai int méret és int *számok. Van egy paraméter nélküli konstruktora, amely üres tömböt kell létrehozzon (méret=0). A második konstruktora egy méretet kap paraméterként; létrehoz annyi számnak helyet, de nem ad nekik értéket. A harmadik konstruktor egy kezdőértéket is átvesz, amelyet minden létrehozott elembe bemásol. A negyedik egy tömböt vesz át kezdőcímével és méretével; úgy hozza létre a tömb objektumot, hogy a tömbből átmásolja a számokat. Definiáld az osztályt és írd meg a konstruktorokat! Esetleg vannak olyanok, amelyeket össze lehet vonni default paraméterrel?
int t[6]={ 1, 2, 5, 9, 11, 7 };
Tomb v1; /* üres, mérete sincs */
Tomb v2(10); /* 10 elemű, inicializálatlan */
Tomb v3(20, 1); /* 20 elemű, létrejöttekor mind 1-es */
Tomb v4(t, 6); /* 6 elemű, és t-ből átmásolja a számokat */
Tomb v5(v2); /* másoló ctor. */
Írj egy Fifo osztályt, amelyik int
számok várakozási sorát valósítja meg. A sor végére
rakhatunk új számokat, illetve ha elveszünk egyet, azt mindig a sor elejéről kapjuk (vagyis amelyiket legkorábban
raktuk be – first in, first out). Pl. belenyomunk 1, 2, 3-at (ilyen sorrendben), akkor utána először az 1-et tudjuk
kivenni, és marad benne 2, 3.
- Az osztálynak legyen paraméter nélküli (default) konstruktora.
- Legyen destruktora.
- Legyen helyesen működő értékadó operátora.
- Helyesen működő másoló konstruktora.
- Legyen
berak(int)
tagfüggvénye. - Legyen
int kivesz()
tagfüggvénye. - Legyen
ures_e()
tagfüggvénye, amely igazzal tér vissza, ha a fifo üres.
Minden függvény legyen const
tag, ha lehet. Belülről a fifo tartalmazhat tetszőlegesen tömböt
vagy láncolt listát. Írj egy main()
függvényt, amely:
- Létrehoz egy üres fifót (f1).
- Belerak három számot.
- Kivesz egyet, és kiírja.
- Létrehoz egy másik fifót (f2), amely az előbbinek (f1) a másolata.
- Ciklussal kivesz belőle mindent, amíg ki nem ürül, és kiírja a számokat.
- Létrehoz egy harmadik fifót (f3) üresen.
- Értékadással bemásol mindent f1-ből.
- Ebből is ciklussal kivesz belőle mindent, amíg ki nem ürül, és kiírja a számokat.
Mi a különbség az operator= és a másoló konstruktor között? Mire kell figyelni a megvalósításuknál?
Adott a következő, komplex számokat kezelő C++ osztály, és hozzá tartozó függvény. Alakítsd sablonná! Hozz létre a sablonból int-es és double-s komplex osztályt létrehozni, és pár sorban mutasd meg a használatukat!
class Komplex {
private:
int re;
int im;
public:
Komplex(int re_=0, int im_=0) {
re=re_;
im=im_;
}
bool operator==(Komplex m);
friend Komplex operator+(Komplex k1, Komplex k2);
};
bool Komplex::operator==(Komplex m) {
return re==m.re && im==m.im;
}
Komplex operator+(Komplex k1, Komplex k2) {
return Komplex(k1.re+k2.re, k1.im+k2.im);
}
Megoldás
Sablonná alakítás: az osztályból sablon lesz, a tagfüggvényeiből sablon lesz.
Az osztályon belül nem kell kiírni sehova, hogy
A globális operator+
kicsit problémás; annak is sablonnak kell
lennie. A friend
-nél meg kell adni, hogy a T
-vel példányosított
operator+
a barát (mivel a friend
osztályon kívüli dologra
hivatkozik, rá nem érvényes, hogy template
-lt kódon belül van). Ehhez
viszont elődeklarálni kell az osztály előtt az operator+
sablonfüggvényt,
ami miatt pedig elődeklarálni kell magát az osztályt.
template <typename T>
class Komplex;
template <typename T>
Komplex<T> operator+(Komplex<T> k1, Komplex<T> k2);
template <typename T>
class Komplex {
private:
T re;
T im;
public:
Komplex(T re_=0, T im_=0) {
re=re_;
im=im_;
}
bool operator==(Komplex m);
friend Komplex<T> operator+ <T>(Komplex<T> k1, Komplex<T> k2);
};
template <typename T>
bool Komplex<T>::operator==(Komplex<T> m) {
return re==m.re && im==m.im;
}
template <typename T>
Komplex<T> operator+(Komplex<T> k1, Komplex<T> k2) {
return Komplex<T>(k1.re+k2.re, k1.im+k2.im);
}
int main() {
Komplex<int> a;
Komplex<double> b, c;
b = b+c;
}
Írj egy tömb osztálysablont. Legyen megírva a konstruktora (méret integer paraméterrel), destruktora, másoló konstruktora (ez osztályon kívül definiálva), két indexelő operátora, darabszám lekérdező függvénye. Az értékadó operátorát csak deklaráld. Add meg, a tartalmazott objektumokról milyen műveleteket vársz el, és hol használod ezeket.
Megoldás
template <class T>
class Tomb {
int meret;
T *adat;
public:
/* konstruktor, méret int paraméterrel; kell neki T::T() */
explicit Tomb(int m): meret(m) { adat=new T[meret]; }
/* destruktor, kell neki T::~T() */
~Tomb() { delete[] adat; }
/* másoló konstruktor */
Tomb(const Tomb&);
/* értékadó operátor */
Tomb& operator=(const Tomb&);
/* méret lekérdező */
int get_meret() const { return meret; }
/* két indexelő: konstans és nem konstans */
T& operator[](int i) { return adat[i]; }
const T& operator[](int i) const { return adat[i]; }
};
/* másoló konstruktor, mivel Tomb sablon,
a függvény is sablon.
- T-ket tartalmazó Tomb (Tomb<T>)
- másoló konstruktora (::Tomb)
- ami paraméterben egy konstans
T-ket tartalmazó Tomb ref-t vesz át
(const Tomb<T>&)
kell neki T::T() és T::operator=(T&) */
template <class T>
Tomb<T>::Tomb(const Tomb<T>& eredeti)
: meret(eredeti.meret)
{
adat=new T[meret];
for (int i=0; i<meret; ++i)
adat[i]=eredeti.adat[i];
}
Írj egy for_each_kiir nevű függvénysablont, amelyik paraméterként vesz át egy ilyen tömböt. Írja ki a tömb minden elemét a képernyőre. Barátja kell legyen ez a függvény az osztálynak? (Másik: írj egy for_each nevű függvénysablont, amelyik paraméterként vesz át egy ilyen tömböt, és egy műveletet. Hívja meg a tömb összes elemére a megadott műveletet.)
Megoldás
/* Nem kell barát legyen, mert csak a publikus tagjait
használja (get_meret és operator[]). T szerint sablon,
T-ket tartalmazó Tomb-öt veszünk át. */
template <class T>
void for_each_kiir(const Tomb<T>& t) {
for (int i=0; i<t.get_meret(); ++i)
std::cout << t[i] << ' ';
std::cout << std::endl;
}
/* Olyan for_each, ami végigmegy az egész tömbön, és
mindegyikre meghívja m-et. A MIT típus lehet egy függvény,
aminek T a paramétere, de lehet egy objektum is, aminek
van T paraméterű fv. hívó operátora. */
template <class T, class MIT>
void for_each(const Tomb<T>& t, MIT& m) {
for (int i=0; i<t.get_meret(); ++i)
m(t[i]);
}```
```c++
/* A kipróbáláshoz, nem része a feladatnak */
class IntOsszegzo {
int osszeg;
public:
IntOsszegzo(): osszeg(0) {}
void operator()(int i) { osszeg+=i; }
int get_osszeg() const { return osszeg; }
};
int main() {
Tomb<int> t(3);
t[0]=2; t[1]=9; t[2]=5;
for_each_kiir(t);
IntOsszegzo o;
for_each(t, o);
std::cout<<o.get_osszeg();
return 0;
}
Írj egy Verem
osztálysablont! A veremnek a következő műveletei legyenek:
betesz(x)
– beteszi x-et a verembe.kivesz()
– visszatér a legutóbb betett értékkel. Ha üres a verem, akkor dobjon valamilyen kivételt!ures()
– igazzal tér vissza, ha a verem üres, amúgy hamissal.
Példányosítsd a vermet double
és char
típusokkal! Írj rövid programot, amely a billentyűről
számokat olvas (a 0 érték beolvasásáig), utána pedig kiírja fordított sorrendben a beírt számokat – mindezt a verem segítségével.
Írj egy olyan programrészt is, amely szöveget olvas be sor végéig (\n karakterig), és kiírja azt fordítva!
Megoldás
Ezt nagyon egyszerűen le lehet kódolni láncolt listával. Ugyanis ha mindig a lista elejére fűzünk be elemet (az a legegyszerűbb művelet), és mindig a lista elejéről veszünk ki (az is a legegyszerűbb), akkor pont egy vermet kapunk – hiszen azt vesszük ki először, amit legutoljára beraktunk! Emiatt egyébként a lista egyszeresen láncolt is lehet, és strázsa nélkül is könnyen megvalósítható.
A lent látható megvalósítás egyébként csak egy másoló konstruktort vár el T-től (meg persze destruktort).
#include <iostream>
template <class T>
class Verem {
class Elem {
public:
T adat;
Elem *kov;
Elem(T const& adat): adat(adat) {}
};
Elem *elso;
public:
/* üres verem */
Verem(): elso(NULL) {}
~Verem() { /* kéne neki */ }
Verem(Verem const&) { /* kéne neki */ }
Verem& operator=(Verem const&) { /* kéne neki */ }
/* lista elejére fűz. T const& célszerű. */
void berak(T const& mit) {
Elem *uj = new Elem(mit);
uj->kov = elso;
elso = uj;
}
/* lista elejéről vesz el. muszáj T, T& nem lehet. */
T kivesz() {
if (elso == NULL)
throw "kifogytam";
T kivett = elso->adat;
Elem *masodik = elso->kov;
delete elso;
elso = masodik;
return kivett;
}
};
- Származtass az
std::exception
osztályból egyIndexHiba
osztályt! A konstruktora vegyen át egy egész számot, amelyik a hibás indexet fogja tartalmazni. Dobjon ilyen típusú hibát a fentebb megírt tömböd túlindexelés esetén! (Mindkét indexelő operátorból - const és nem const!) - Írj programrészt, amelyben kipróbálod ezt a hiba dobást. Kapd el a hibát, és írasd
ki a kivételben tárolt üzenetet (amely a
what()
függvénnyel kérdezhető legyen lekérdezhető). - Származtass
NincsTobbElem
hibát az std::exception osztályból. Dobjon ilyet a Verem osztályod, ha a kivesz() függvényt üres veremre hívod. Természetesen ennek is legyen működő, és a hibaüzenetet visszaadówhat()
függvénye.
Írj egy valós számokat tartalmazó halmaz osztályt, amellyel a következők lehetségesek:
- Üres halmaz létrehozása (default konstruktorral).
- Valós szám betétele a halmazba (ha már benne van, ne történjen semmi).
- Szám kivétele a halmazból (ha nincs benne, ne történjen semmi).
- Adott elem vizsgálata, szerepel-e a halmazban (igaz/hamis →
bool
). - Elemszám lekérdezése.
- Üres voltának (igaz/hamis →
bool
) lekérdezése. - Elemek kiírása a << operátorral.
- Halmaz másolása (másoló konstruktorral).
- Halmazok közötti értékadás.
- (És persze destruktor.)
Az előző feladatot folytatva:
- Legyen uniót képző globális függvénye:
h1 = unio(h2, h3);
. (Ehhez az unió függvénynek egy halmazzal kell visszatérnie; ah1
-be történő értékadás már azoperator=
dolga.) - Legyen ugyanígy működő metszet függvénye:
h1 = metszet(h2, h3);
. - Legyen kivonás függvény: h1 = h2\h3 a kódban
h1 = kivon(h2, h3);
formában jelenjen meg. - Írj főprogramot, amely teszteli ezeket a függvényeket. Figyelj a tesztadatokra, hogy ténylegesen a megfelelő programrészeket mozgasd meg: unió képzésénél legyen olyan elem a két forrás halmazban, amely közös (hogy a cél halmazban az ne szerepeljen kétszer.) Metszet képzésénél legyenek olyanok a forrásokban, amelyek csak az egyikben, illetve csak a másikban szerepelnek stb.
- Írj egy
operator==
-t a halmaz számára. Vigyázz, ez is trükkös: nem lehet páronként összehasonlítani a két halmaz elemeit, mert lehet, hogy nem ugyanabban a sorrendben vannak. Ami belül {5,6} és {6,5}, azok egyforma halmazok! (Egyáltalán mi van belül?)
Az első három függvény legyen globális. Ha kell, friend
-dé is teheted őket.
Az operator==
lehet tagfüggvény vagy globális is, ez rád van bízva.
Figyelj arra, hogy a programban sehol ne legyen memóriaszivárgás! Ahol new[]
van,
ott (vagy máshol) kell legyen delete[]
is.
Van egy Auto osztályunk, amely az autó márkáját (elég a fix méretű sztring, nem kell dinamikus most) és a gyártási évét (int ev) tárolja (pl. Honda, 2004). Szeretnénk, ha bármikor meg lehetne tudni, hogy éppen hány autó objektumot tárolunk összesen a memóriában. Aki használja az autó osztályt, annak természetesen ne kelljen foglalkoznia ezzel. (Ötlet: amikor létrejön egy autó objektum, növeljük a darabot. Ha megszűnik, csökkentjük.) Definiáld az osztályt, és írd is meg csak az ehhez szükséges részeit. Hozz létre néhány autót, és ezután írd ki a darabszámot! Adott a lenti három függvény is; írj példát, melyiket hogyan kell meghívni egy bizonyos autóra, és hogy amikor az egyes függvények belsejében van a végrehajtás, mennyi épp az autók darabszáma! Ha ilyen programot írunk, fogjuk-e szeretni az első függvényt?
void auto_fv_o(Auto a);
void auto_fv_r(Auto& a);
void auto_fv_p(Auto* a);
A fájlrendszereinkben mappákat és fájlokat tárolunk. Minden mappának és fájlnak van neve. A fájloknak mérete is van, a mappák pedig fájlokat és további mappákat tartalmaznak. Készíts objektumorientált modellt a fájlrendszerhez! Írj rekurzív függvényt, amely kiszámítja egy mappában (és annak almappáiban) tárolt fájlok összesített méretét! Hozz létre szimbolikus link típust, amely segítségével egy fájlnak több név adható! A szimbolikus linknek saját neve kell legyen, és egy másik, meglévő fájlra vagy mappára kell mutasson, amelyik akár másik mappában is lehet, mint a link maga. Figyelj az öröklés miatt szükséges indirekcióra, és a helyes memóriakezelésre!
Egy étteremnek írunk programot. Azt szeretnénk, ha a programba a pincérek be tudnák vinni az egyes rendeléseket, amelyeket az asztaloknál (20db) adnak fel a vendégek. Összesen húsz asztal van a teremben. A rendeléseknél megkülönböztetünk étel- és italrendeléseket. Az ételek egységáron számolódnak, de lehet fél adagot rendelni, amely esetben az ár 70%-át kérik el. Az italokat mennyiség (dl) és Ft/dl árral visszük be. Természetesen minden rendelésnél a megnevezést is felírjuk, hogy részletes számlát tudjunk adni.
- Hogyan viszonyulnak egymáshoz az említett objektumok? Rajzold le az objektummodellt!
- Definiáld az összes osztályt! A tagfüggvényeket részletesen megírni nem kell. STL bátran használható.
- Írd meg azokat a metódusokat, amelyek egy számla kiállításához szükségesek. (Egyáltalán melyik objektum állítja ki a számlát?)
Megoldás
#include <iostream>
#include <vector>
#include <string>
class Rendeles {
std::string megnevezes;
public:
Rendeles(std::string nev) : megnevezes(nev) { }
virtual int ar() const = 0;
void kiir() { std::cout << megnevezes << ' ' << ar() << std::endl; }
};
class Ital: public Rendeles {
int forint_per_deci;
int deci;
public:
Ital(std::string nev, int egyseg, int dl)
: Rendeles(nev), forint_per_deci(egyseg), deci(dl) { }
int ar() const { return forint_per_deci*deci; }
};
class Etel: public Rendeles {
int etlap_ar;
bool fel_adag;
public:
Etel(std::string nev, int ar, bool fel=false)
: Rendeles(nev), etlap_ar(ar), fel_adag(fel) { }
int ar() const { return fel_adag?etlap_ar*0.7:etlap_ar; }
};
class Asztal {
std::vector<Rendeles*> rendelesek;
public:
void szamla() {
int vegosszeg=0;
for (int i=0; i < rendelesek.size(); i++) {
rendelesek[i]->kiir();
vegosszeg+=rendelesek[i]->ar();
}
std::cout << "fizetendo: " << vegosszeg << std::endl;
}
void rendel(Rendeles *r) { rendelesek.push_back(r); }
};
class Etterem {
public:
Asztal asztalok[20];
};
int main() {
Etterem e;
/* A NEW MIATT AZ ASZTALOKNAK KELLENE DESTRUKTOR */
e.asztalok[0].rendel(new Ital("mango lassi", 150, 3));
e.asztalok[0].rendel(new Etel("penne all'arrabbiata", 1200));
e.asztalok[0].szamla();
}
Az objektumok közötti kapcsolathoz... Egy egyszerűbb feladatnál a szövegben lévő főnevek utalnak az osztályokra, és az igék utalnak az osztályok tagfüggvényeire. Ezek alapján:
- Az étel- és az italrendeléseket tároljuk.
- Kétféle rendelés létezik, étel- és italrendelés, ebből adódik egy öröklődés.
- Az asztalhoz tartoznak a rendelések. Ez nem öröklés, hanem tartalmazás, hiszen az asztal nem rendelés.
- Az étterem tartalmaz asztalokat.
- A számlát egy adott asztalnál ülő ember kéri, és a számla az annál az asztalnál ülő emberek fogyasztásának összege. Vagyis azoké a rendeléseké, amelyeket annál az asztalnál adtak fel. A számla ezért nem a rendelés osztály feladata, hanem az asztal osztályé!
Strandcikket kölcsönző kisvállalkozás részére szeretnénk nyilvántartó programot készíteni. Minden cikkről szeretnénk egy alapinformációt tárolni: vonalkód (31 karakter); és tudni szeretnénk, hogy ki van-e kölcsönözve. A kölcsönző jelenleg csak két fajta cikk kölcsönzésével foglalkozik, de remélhetőleg az üzlet fellendül, és lesz forrás a bővítésre. Az biztos, hogy 50 cikk kölcsönzésénél többel nem akar foglalkozni a cégtulajdonos, mert a raktárhelyiség kicsi. Jelenleg a következőket lehet kölcsönözni: vízibicikli (személyek száma, vonalkód), kenu (tömeg, fajta(verseny/túra), vonalkód).
- Hogyan és miből öröklődnek ezek az osztályok? Rajzold le az öröklés ábráját!
- Definiáld az egyes osztályokat! A tagfüggvényeket részletesen megírni nem kell, csak deklarálni.
- Hozd létre az 50 elemű tömböt, és rakj bele néhány tetszőleges vízibicikli és kenu példányt.
- Listázd ki az eszközöket, amelyek ki vannak kölcsönözve. Az ehhez szükséges tagfüggvényeket írd meg!
Megoldás
Ez itt a megoldás. Nagyon rövid így, hogy csak azok a függvények vannak definiálva, amiket kért a feladat! Ez persze önmagában még nem fog futni.
- A vonalkód típusa
char[]
. A feladat szövege: 31 karakter. - A kikölcsönözve adattag típusa
bool
, nemint
. Ki van kölcsönözve? Igen, nem. - A kenu fajtája enum! Nem int (milyen fajta kenu? kettő?!), nem bool (milyen fajta kenu? igen?!).
- Get/set tagfüggvények feleslegesek.
- Az adatok kiírása nem működhet az
operator<<
-vel, mert az nem tagja a strandcikk osztálynak. Így azon nem működhet a virtuális fv. mechanizmus sem.
class StrandCikk {
protected:
char vonalkod[32];
public:
bool kint;
StrandCikk(char const *vk);
virtual void adatok() = 0;
};
class ViziBicikli: public StrandCikk {
int szemelyek;
public:
ViziBicikli(char const *vk, int szem);
void adatok() {
cout << "ViziBicikli, "<< vonalkod << ", " << szemelyek << " szemelyes.\n";
}
};
class Kenu: public StrandCikk {
public:
enum Fajta { Verseny, Tura };
private:
int kilo;
Fajta tip;
public:
Kenu(char const *vk, int kg, Fajta f);
void adatok() {
cout << "Kenu, " << vonalkod << ", " << kilo << " kilos, ";
switch(tip) {
case Verseny: cout << "verseny"; break;
case Tura: cout << "tura"; break;
}
cout << ".\n";
}
};
StrandCikk *cuccok[50];
cuccok[0] = new ViziBicikli("129943", 4);
cuccok[1] = new Kenu("996532", 10, Kenu::Verseny);
for (int i = 0; i < 2; ++i)
if (cuccok[i]->kint)
cuccok[i]->adatok();
Ez itt pedig a futtatható megoldás. Copypastelhető fordítóba.
// Ez jóval több kód, mint amennyit a feladat kért! A rajz is több.
#include <cstring>
#include <iostream>
class StrandCikk {
protected:
char vonalkod[32];
bool kint;
public:
StrandCikk(char const *vk): kint(false) { strcpy(vonalkod, vk); }
virtual ~StrandCikk() {} // virt bármi → virt destr ökölszabály
virtual void adatok()=0;
bool kint_van_e() const { return kint; }
void kivisz() { kint=true; }
void visszahoz() { kint=false; }
};
class ViziBicikli: public StrandCikk {
int szemelyek;
public:
ViziBicikli(char const *vk, int szem)
: StrandCikk(vk), szemelyek(szem) {}
void adatok() {
std::cout<< "ViziBicikli, " << vonalkod << ", " << szemelyek << " szemelyes.\n";
}
};
class Kenu: public StrandCikk {
public:
enum Fajta { Verseny, Tura };
private:
int kilo;
Fajta tip;
public:
Kenu(char const *vk, int kg, Fajta f)
: StrandCikk(vk), kilo(kg), tip(f) {}
void adatok() {
std::cout << "Kenu, " << vonalkod << ", " << kilo << " kilos, ";
switch(tip) {
case Verseny: std::cout << "verseny"; break;
case Tura: std::cout << "tura"; break;
}
std::cout<<".\n";
}
};
int main()
{
StrandCikk *cuccok[50];
for (int i=0; i<50; ++i) /* üres raktár */
cuccok[i]=0;
cuccok[0] = new ViziBicikli("129943", 4);
cuccok[0]->kivisz(); /* kikölcsönzik */
cuccok[1] = new Kenu("996532", 10, Kenu::Verseny);
for (int i=0; i<2; ++i) /* csak 2-t raktunk bele */
if (cuccok[i]->kint_van_e())
cuccok[i]->adatok();
for (int i=0; i<50; ++i)
delete cuccok[i];
}
(Régebbi NZH feladat copy-paste.) A Húsvéti Nyúl háromfajta ajándékot (Present) oszt: tojást (Egg), csokit (Chocolate) és cukrot (Candy). Mindegyik különböző értékű, az alapegység egy statikus változója az alaposztálynak (basePrice=50), a tojás darabja ennek konstansszorosa (eggFactor=0.4), míg a csoki esetében ez a tényező chocolateFactor=2.6, a cukornál candyFactor=1.5. A Nyúlnak írt programunk egy tömbben tartja nyilván a kiosztott ajándékokat. Az egyes ajándékok darabszámot is tartalmaznak. A célunk kiszámolni és kiírni az egyes ajándékok értékét (getPrice()).
- Tervezzük meg és vázoljuk fel az osztályok öröklési hierarchiáját! Használjuk fel a fenti osztály-, függvény- és változóneveket! Az osztályok téglalapjaiban tüntessük fel az összeget lekérdező függvény (getPrice) deklarációját és láthatóságát! Ügyeljünk az elegáns OO megoldásokra!
- Implementáljuk az osztályokat és konstansokat figyelve arra, hogy esetlegesen egyes konstansokat is tagként vagy statikus tagként érdemes implementálni. Ne legyen egy függvénytörzsben sem felesleges, nem használt kód! Egy új ajándéktípus esetleges felvételéhez ne kelljen a már meglévő osztályokat módosítani!
- Írjunk egy egyszerű programrészletet nem dinamikus tömbbel, ami megmutatja három különböző típusú ajándék felvételét, valamint kiírja a nevüket és árukat.
Az SVG (scalable vector graphics) egy szöveges fájlformátum, amellyel vektorgrafikus rajzok adhatók meg. Elterjedten használják weboldalakon. Egy SVG fájl a következőképpen néz ki:
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="20" stroke="black" fill="yellow" />
<line x1="100" y1="70" x2="100" y2="90" stroke="black" />
<circle cx="90" cy="45" r="3" stroke="black" fill="blue" />
<circle cx="110" cy="45" r="3" stroke="black" fill="blue" />
<rect x="80" y="80" width="40" height="70" stroke="black" fill="blue" />
<line x1="90" y1="150" x2="60" y2="190" stroke="black" />
<line x1="110" y1="150" x2="140" y2="190" stroke="black" />
<line x1="80" y1="90" x2="40" y2="70" stroke="black" />
<line x1="120" y1="90" x2="160" y2="70" stroke="black" />
</svg>
Vagyis a kezdő svg és lezáró /svg között különféle formákat lehet megadni:
circle
= kör, amelynek cx,cy a középpontja, r a sugara (valós számok), fill pedig a kitöltési színe (sztring).rect
= téglalap, amelynek x,y a bal felső sarka, és width,height a méretei, fill a kitöltési színe.line
= szakasz, x1,y1 ponttól x2,y2 pontig.- és még egy csomó egyéb dolgot.
Láthatóan minden alakzat rendelkezik egy, a körvonal színét meghatározó tulajdonsággal (ez sztring),
továbbá egy koordinátával (középpont, bal felső sarok, kezdőpont.) Az előbbit a fájlban mindig
stroke
-nak (ecsetvonás) hívják, az utóbbi viszont más-más neveken szerepel (cx,cy; x,y; x1,y1).
A feladat: írj heterogén kollekciót használó programot, amely eltárolja egy ilyen kép adatait! Valósítsd meg a kör, téglalap és szakasz osztályokat! A heterogén kollekció legyen kiírható egy SVG fájlba a következő kódrészlettel:
#include <fstream> /* std::ofstream-hez kell */
std::ofstream os("misi.svg"); /* fájl nyitása írásra */
os << "\n";
for (i = 0; i < 9; ++i)
alakzat[i]->kiir(os); /* os típusa is std::ostream */
os << "\n";
A keletkező SVG fájlt bármelyik modern böngészővel (Firefox, Chrome) meg tudod nyitni.
(Ha szintaktikai hibás, és nem jeleníti meg, akkor pedig érdemes Notepad-del megvizsgálni.)
Figyelj a kiíráskor az idézőjelekre; az idézőjel karaktert a sztring belsejében
escapelni kell: \"
, ahogy a fenti kódban is látható.
Egy másik rajz:
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
<ellipse cx="100" cy="120" rx="30" ry="40" stroke="black" fill="white" />
<ellipse cx="70" cy="90" rx="10" ry="10" stroke="black" fill="white" />
<ellipse cx="130" cy="90" rx="10" ry="10" stroke="black" fill="white" />
<ellipse cx="130" cy="150" rx="10" ry="12" stroke="black" fill="white" />
<ellipse cx="70" cy="150" rx="10" ry="12" stroke="black" fill="white" />
<ellipse cx="88" cy="30" rx="8" ry="25" stroke="black" fill="white" />
<ellipse cx="112" cy="30" rx="8" ry="25" stroke="black" fill="white" />
<ellipse cx="88" cy="30" rx="2" ry="14" fill="pink" stroke="red" />
<ellipse cx="112" cy="30" rx="2" ry="14" fill="pink" stroke="red" />
<circle cx="100" cy="60" r="23" stroke="black" fill="white" />
<circle cx="92" cy="50" r="3" fill="red" stroke="red" />
<circle cx="108" cy="50" r="3" fill="red" stroke="red" />
<ellipse cx="100" cy="60" rx="5" ry="3" fill="pink" stroke="red" />
<line x1="105" y1="58" x2="130" y2="50" stroke="gray" />
<line x1="105" y1="60" x2="130" y2="60" stroke="gray" />
<line x1="105" y1="62" x2="130" y2="70" stroke="gray" />
<line x1="95" y1="58" x2="70" y2="50" stroke="gray" />
<line x1="95" y1="60" x2="70" y2="60" stroke="gray" />
<line x1="95" y1="62" x2="70" y2="70" stroke="gray" />
</svg>
A programunk titkosító objektumokkal dolgozik. Egy ilyen objektum arra képes, hogy egy neki adott karaktert titkosít. Például így lehet használni:
void kiir(char const *szoveg, Titkosito& t) {
for (int i = 0; szoveg[i] != '\0'; ++i)
std::cout << t.titkosit(szoveg[i]);
}
-
Definiáld a Titkosito absztrakt alaposztályt a fenti használat alapján!
-
Származtass belőle egy ABCaesar osztályt, amelyik a Caesar-féle titkosítást használja a=b kulccsal, vagyis a→b, b→c, … z→a a titkosítás menete. Írj egy egy-két soros programot, amelyik a fenti függvénnyel, és egy ABCaesar objektummal bekódol egy általad megadott sztringet.
-
Származtass egy Caesar osztályt is, amelynek a kulcsa egy egész számmal adható meg. Pl. ha a kulcs 3, akkor a→d, mert abcd, b→e, mert bcde. (Hol tárolódik a titkosítás kulcsa? Az objektumban!) Titkosíts egy szöveget ezzel is!
-
Szorgalmi feladat: írj egy SzoCaesar osztályt, amelynek egy szót lehet megadni, ami a titkosítás kulcsa. Ennek a szónak a betűi mutassák azt, hogy az egymás utáni karaktereket milyen kulccsal kell titkosítani. Pl. ha a kulcs "bcd", akkor az első karakternél a→b a kulcs, a másodiknál a→c, a harmadiknál a→d, a negyediknél újra a→b stb. (A működését könnyű tesztelni az "aaaaaaaa" sztringen.)
Megoldás
#include <iostream>
class Titkosito { /* az absztrakt alaposztály */
public:
virtual char titkosit(char) const = 0;
};
void kiir(char const *szoveg, Titkosito const& t) {
for (int i = 0; szoveg[i] != 0; ++i)
std::cout << t.titkosit(szoveg[i]);
}
class ABCaesar: public Titkosito {
public:
virtual char titkosit(char c) const {
if (c < 'a' || c > 'z') /* többit nem */
return c;
/* csak a kisbetűket */
if (c == 'z')
return 'a';
return c+1;
}
};
class Caesar: public Titkosito {
int kulcs;
public:
Caesar(int kulcs_): kulcs(kulcs_) {}
virtual char titkosit(char c) const {
if (c < 'a' || c > 'z') /* többit nem */
return c;
/* csak a kisbetűket */
return (c- 'a' + kulcs) % ('z' - 'a' + 1) + 'a';
}
};
int main() {
ABCaesar abc;
kiir("hello z!\n", abc);
/* vagy: kiir("hello z!", ABCaesar()); */
Caesar harommal_tol(3);
kiir("hello z!\n", harommal_tol);
/* vagy: kiir("hello z!", Caesar(3)); */
}
Adott az alábbi láncolt lista kezdemény:
template <typename T>
class MyList {
struct ListElement {
T data;
ListElement* next;
};
ListElement* head;
};
A lista egyik végén sem strázsás. Írj meg néhány függvényt, hogy listát tudj építeni, majd írj iterátort a lista osztályhoz! Mutass kódrészleteket, amelyekkel teszteled az iterátort!
Gyakran szoktak csinálni tárolókhoz ún. reverse iterator-t is. Ez mindent fordítva csinál:
a tároló .rbegin()
függvénye nem az elejét, hanem a végét adja, az .rend()
viszont pont az elejét. Az iterátor ++
operátora pedig a végétől az eleje felé lépked.
Csinálj ilyen iterátort az előző láncolt listához! Ha mindent jól csinálsz, az alábbi kódrészlet
kiírja a numbers
listát hátrafelé:
MyList<int>::ReverseIterator it;
for (it = numbers.rbegin(); it != numbers.rend(); ++it)
std::cout << *it;
Adott az alábbi kód, amelyet egy bináris fa építésére szánunk:
template <typename T>
class MyTree {
struct Node {
T data;
Node *left, *right, *parent;
};
Node *root;
};
Írj beszúró függvényt a fához! (Milyen operátorral kell ehhez rendelkezzen a T
típus?) A parent
pointer minden csomópontban a szülőre kell mutasson, vagy NULL
értékű kell legyen (a legfölső csomópontban, azaz a gyökérben).
Írj iterátort a fához! A szülő csomópontok ismeretében ez nem olyan nehéz, csak ki kell találni azt, hogy egy adott csomópont ismeretében melyik a következő csomópont, amelyet az inorder bejárás adná.
(Ötlet: Egy adott elemet megelőző és az azt követő elem könnyen megtalálható iteratív algoritmussal is. Az őt megelőző elem a nála kisebbek közül a legnagyobb, tehát a bal oldali részfájának jobb szélső eleme. Az őt követő elem pedig szimmetrikusan a jobb oldali részfájának bal szélső eleme.)
Az alábbi kódrészlet kiírja a számokat 1-től 10-ig:
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
std::cout << *it;
Egy olyan osztályt kell csinálnod, amellyel tetszőleges tartományban írathatók ki a számok:
Range r(1, 10);
for (Range::Iterator it = r.begin(); it != r.end(); ++it)
std::cout << *it;
Ahhoz, hogy kiírjuk őket, igazából eltárolni nem kell a számokat. Így oldd meg a feladatot!
(Az órai plágiumkereső programmal kapcsolatban.) A szövegek közös szópárjainak keresésekor igazából nem vagyunk kíváncsiak
arra, hogy pontosan mik azok a szópárok, amik közösek, csak
a számukra. Ezért a metszet halmazt tulajdonképpen felesleges előállítani.
Jobb lenne írni egy olyan speciális iterátort, amely úgy viselkedik,
mintha rendes iterátor lenne, de nem ír sehova, hanem csak számolja,
hányszor hívták az operator++
-át. (Ehhez érdemes jól
megnézni a set_intersection()
dokumentációját, hogy pontosan
mit csinál a neki adott iterátorral!)
(Az órai plágiumkereső programmal kapcsolatban.) És még egyszer: igazából nincsen szükség a konkrét szópárokra, elég
a számukat tudni. Ezért szópárok helyett bármi más típust is használhatnánk.
Például ha minden különböző szópárhoz hozzárendelünk egy egész számot,
akkor a halmazokba ezeket az egész számokat lehet tenni – a metszet
halmazok mérete pedig ugyanakkora lesz. Ezzel a program gyorsul (mert
a metszéskor csak egész számokat kell összehasonlítania, O(1)), és
a memóriaigénye is csökken (sztringek helyett intek vannak csak).
A sztring→egész hozzárendelést beolvasás közben kell elvégezni,
és erre egy std::map<std::string,int>
kiválóan alkalmas.
Írj egy függvénysablont, amelyik átvesz paraméterként egy C tömböt. Az elemei tetszőleges típusúak lehetnek. A függvény csináljon valamit sorban az összes elemmel. Ez a „valami” is legyen sablonparaméter.
Hozz létre egy öt elemből álló int tömböt, és a megírt sablon használatával írd ki minden elemét az std::cout
-ra. (Ha a fenti függvénysablonban szerepel bármi a kiírással kapcsolatban, akkor természetesen az hibás.)
Megoldás
#include <iostream>
template <typename T, typename FUNC>
void mindegyikkel_csinal(T *tomb, int meret, FUNC csinal) {
for (int i = 0; i < meret; ++i)
csinal(tomb[i]); /* kell legyen fv.hívó operátora */
}
/* ez a fv. még nem is létezett, amikor a
* mindegyikkel_csinal sablont "írták" */
void kiir(int i) {
std::cout << i << '\n';
}
int main() {
int t[5] = {1, 2, 3, 4, 5};
mindegyikkel_csinal(t, 5, kiir);
}
Definiálj egy függvény objektumot sablonként, amelynek overloadolt függvényhívó operátora kap egy objektumot,
és kiírja azt a képernyőre annak <<
operátorával. Utána definiálj egy függvényt sablonként, amely az első
paraméterében egy std::vector
-t vesz át, második paramétereként egy ilyen függvény objektumot.
Mutass rá példát, hogyan kell ezek segítségével kiírni a vektor összes elemét!
Sta::Sta (const Sta& a) {... *this = a;}
Sta& Sta::operator=(Sta a) {...}
class A {
public:
A() { f(); }
virtual int f() = 0;
};
class B: public A {
public:
int a;
int f() { a++; }
};
class A {
private:
int a;
public:
void f() const { a++; }
};
void operator<<(ostream& os, B& e) { ... }
B b12;
cout << b12 << endl;
class Madar {
double suly;
virtual void jar() { cout<<"lepked"; }
};
class Vereb : public Madar {
double suly;
void jar() { cout<<"ugral"; }
};
double& fuggveny() {
double d;
...;
return d;
}
class Asztal {
int labak_szama;
bool fiokos;
};
class Butor : public Asztal {
enum Anyag { fem, fa, muanyag } anyag;
int szinkod;
};
class Krumpli {
double kilo;
int egysegar;
public:
static double ar();
};
double Krumpli::ar()
{
return kilo*egysegar;
}
void fv(int &i);
int a, b;
fv(a+b);
class Alakzat {
virtual void rajzol()=0;
};
class Teglalap: public Alakzat {
void rajzol() { ... }
};
void fv(Alakzat k);
Teglalap t;
fv(t);