Indexelő objektum
Írj egy osztályt, amely egy 32 bites, előjel nélküli egészben: uint32_t
(#include <cstdint>
) tud
tárolni 32 igaz/hamis értéket! Az objektumot lehessen indexelni, és indexelésen keresztül valamelyik bit értékét lekérdezni és
beállítani!
BoolArray b; /* 32 db false */
std::cout << b[3]; /* 0 = false */
b[3] = true;
std::cout << b[3]; /* 1 = true */
A nehézséget itt az jelenti, hogy a biteket tároló egész szám egyes bitjeire nem létezik referencia, és emiatt az indexelő
operátor nem térhet vissza egyszerűen egy bool&
típussal. Helyette egy segédobjektummal kell visszatérnie, amely
saját típussal rendelkezik, és amely a lekérdezést és az értékadást megfelelően átdefiniált operátorokkal kezelni tudja.
Ha már működik a fenti, oldd meg, hogy a bitek közötti értékadás is működjön!
b[3] = true;
b[2] = b[3];
std::cout << b[2]; /* 1 = true, mert b[3] = true */
Indexelő objektum – hibás gondolatmenet, de működik?
Adott az előző feladatnak az alábbi megoldása. A kód csak a segédobjektumot mutatja vázlatosan, jelenleg csak ez a lényeges rész.
class BitArray::Proxy {
private:
BitArray &ba;
int idx;
public:
Proxy(BitArray &ba, int idx): ba(ba), idx(idx) {}
operator bool () const {
return ba.get(idx);
}
Proxy& operator=(bool b) {
ba.set(idx, b);
return *this;
}
Proxy& operator=(Proxy &other) = delete;
};
Ez egy hibás elgondolást mutat be. Mi az? És hogyan lehetséges, hogy a Proxy mégis működőképes így megvalósítva?
Megoldás
A hibás elgondolás az, hogy ennek törölve van az értékadó operátora. Ugyanis a két Proxy
közötti értékadás
lehetséges: bitarray[0] = bitarray[1]
, ennek működnie kell. Az első bitet kell másolnia a nulladik bitbe,
még akkor is, ha forráskód szintjén ez a Proxy
objektumok értékadásának látszik.
A problémát azért oldja meg mégis a delete
-es sor (valószínűleg nem szándékosan), mert az nem csak azt fejezi ki a
fordítónak, hogy nem hívható az értékadó operátor, hanem azt is, hogy a paraméterezése a szokásostól eltérő. Észre kell venni, hogy
ebben a sorban nincs const
szó; a paraméter Proxy &other
nem konstans referencia. Vagyis ez így két
dolgot jelent: a) ennek az osztálynak nem konstans objektumot vár az értékadó operátora, b) és mellesleg nem használható.
A bitarray[0] = bitarray[1]
sorban két Proxy
keletkezik, és mindkettő temporális objektum. A
referencia típusa a jobb oldali, bitarray[1]
temporális objektum miatt lényeges; a nem konstans referencia nem vehet
át temporális objektumot paraméterként. Ezért a fordító számításba sem veszi azt az operátort, amikor ezt az értékadást
megpróbálja feloldani. Helyette konverziót keres, és rátalál az operator bool
+ operator=(bool)
párosra,
ami pedig épp azt csinálja, amit kell. A delete
-elésnek nincs is jelentősége, működne anélkül is, egy kósza
deklarációval.
Telítéses aritmetika
Írj osztályt, amely telítéses aritmetikát (saturation arithmetic) valósít meg!
Ebben a tárolt számok értékének van egy minimális és maximális értéke. Túlcsordulás (vagy alulcsordulás)
esetén a határon a kapott érték sosem lép túl. Például ha az alsó határ 0, akkor 10-5 = 5
, de 5-7 = 0
.
Vagy ha a felső határ 255, akkor 250 + 7 = 255
.
A digitális jelfeldolgozásban (pl. hangfeldolgozásban) használt eszközök működnek így.
Számrendszerek
Nézd meg az extrákban az I/O manipulátorokkal kapcsolatos írást! Ez az előadás mértékegységes példájának folytatása. Utána...
a) Írj bináris szám kiíró I/O manipulátort! A használata legyen az alábbi:
std::cout << bin << 0 << std::endl; /* 0 */
std::cout << bin << 240 << std::endl; /* 11110000 */
Vajon mi lehet itt a bin típusa?
b) Írj akárhányas számrendszerben kiíró I/O manipulátort!
std::cout << base(2) << 240 << std::endl; /* 11110000 */
std::cout << base(7) << 240 << std::endl; /* 462 */
std::cout << base(16) << 240 << std::endl; /* F0 */
Melyik az első részkifejezés, amit ki kell értékelnie a fordítónak? Indulj ki ebből!
Ügyelj arra, hogy a megvalósításban ne legyen pow()
(egész számokkal kell dolgozni, a
pow()
pedig lebegőpontos számokkal dolgozik, pontatlanság lehet). Ezen kívül, ne legyen
std::to_string()
se! Legalábbis egyetlen számjegy sztringgé alakítására semmiképp, mivel
akkor minden számjegyhez külön std::string
objektum jönne létre.
Mértékegységek
Folytasd az előadás mértékegységes programját!
Mekkora a Ferrari motorjának átlagos teljesítménye a programban megadott gyorsítás közben? (A mozgási energiát az E=1/2*m*v2 képletből tudod kiszámolni; ennyi energiát adott le a motor 4 másodperc alatt.)
A mértékegység osztály dimenzió nélküli változata (m0*kg0*s0) egy egyszerű valós számot ad meg, amely általában egy arányt jelképez. Például a hossz/hossz egy dimenzió nélküli számot ad. Specializáld úgy az osztályt, hogy a dimenzió nélküli mértékegységek konvertálhatók legyenek valós számmá, illetve automatikusan létrehozhatók legyenek valós számból!
Az előző feladat megvalósítása után, működnek-e automatikusan a
valós*dimenziónélküli
(pl.2.0*(v1/v2)
), és avalós+dimenziónélküli
(pl.1.0+(v1/v2)
) alakú kifejezések? Miért?
approx()
A pytest API tartalmaz egy approx()
nevű függvényt, amelyikkel valós számok „nagyjából” egyenlőségre vizsgálhatóak:
0.1 + 0.2 == approx(0.3)
Oldd meg, hogy C++-ban is lehessen ilyet írni!
constexpr
minősítésű _binary
Meg lehet oldani, hogy a _binary
utótag operátor constexpr
legyen? Ehhez az kell, hogy az utótag operátor függvénye egyetlen return
utasításból
álljon. (Egy ügyesen megírt rekurzív segédfüggvénnyel próbálkozz!)
Megoldás
Az alábbi, binary_val
függvény két részre bontja az átalakítandó
számot. A szám már átalakított, bal oldali része a left
, a maradék,
át még nem alakított része a right
paraméterben van. Pl. az 1101
számnál az első számjegy átalakítása után left=1
, right="101"
paraméterekkel hívódik a függvény. Egy lépésben egy számjegy átalakítása történik meg, ehhez a left
-et
eggyel balra kell léptetni, és beírni a right
következő számjegyét:
left=11
, right="01"
értékekkel hívni
újra a függvényt. Ha right
üres, nincs teendő.
constexpr unsigned long long binary_val(unsigned long long left, char const *right) {
return right[0] == '\0'
? left
: binary_val(left<<1 | (right[0]-'0'), right+1);
}
constexpr unsigned long long operator "" _binary(char const *str) {
return binary_val(0, str);
}
A binary_val()
törzse egy return
utasításból áll, ezért
constexpr
lehet. Az operator "" _binary()
törzse egy
return
utasításból áll, és csak constexpr
függvényt hív,
ezért ez is lehet constexpr
.
Dátum literálisok I.
Date d = "2013.08.11"_date;
std::cout << d << std::endl; /* 2013.08.11. */
std::cout << "'99.08.11"_date << std::endl; /* 1999.08.11. */
std::cout << "'12.08.11"_date << std::endl; /* 2012.08.11. */
std::cout << "08.11.2013"_date << std::endl; /* 2013.08.11. */
Dátum literálisok II.
A feladat ugyanaz, mint az előbb – de fordítási időben kell megoldani, constexpr
függvényekkel.
Ha minden rendben van, akkor egy helyes megoldás esetén lehet
"'99.08.11"_date.getYear()
méretű globális tömböt létrehozni, vagy
ezt a számot (1999) sablonparaméternek használni. Ezek persze értelmetlen példák,
csak ötletként szerepelnek itt az ellenőrzésre, hogy tényleg fordítási időben fut-e
a dátum értelmezése. A lényeg az, hogy mivel literálisról van szó, azt már fordítás
közben is lehet (és érdemes) értelmezni, mert úgysem változik futás közben.