Típusok használata
Czirkos Zoltán · 2024.08.20.
Típusok, I/O manipulátorok, operátorok átdefiniálása.
Gyakorlófeladatok a típusokról szóló előadáshoz. A lényeg mindenhol, hogy olyan típusokat hozzunk létre, olyan operátorokat adjunk meg, amelyekkel aztán szintaktikailag világos, könnyen érthető programokat tudunk írni.
Feltöltés
A laborhoz kiírt beadandó a szokásos helyen van, az admin portálon. Ide óra végén töltsd fel a forráskódokat (*.cpp, *.h)! A feladatokat ezért külön projektben oldd majd meg, ne írd felül a megoldásokat.
Labor otthoni munkában
A labor teljesítéséhez mind a négy feladatot meg kell oldani.
TimeInterval i1{65};
std::cout << i1 << std::endl; // 1h 5m
std::cout << (5_h + 79_m) << std::endl; // 6h 19m
Megoldás
Egyszerűbb az összeadás, ha belül csak egy int
tárolódik;
az osztály mindig csak percekkel dolgozik.
#include <iostream>
class TimeInterval {
public:
explicit TimeInterval(int interval) : interval_{interval} {}
TimeInterval operator+ (TimeInterval rhs) const {
return TimeInterval(this->interval_ + rhs.interval_);
}
int get_hour() const { return interval_ / 60; }
int get_min() const { return interval_ % 60; }
private:
int interval_;
};
std::ostream & operator<< (std::ostream & os, TimeInterval t) {
os << t.get_hour() << "h " << t.get_min() << "m";
return os;
}
TimeInterval operator"" _h (unsigned long long h) {
return TimeInterval(h * 60);
}
TimeInterval operator"" _m (unsigned long long m) {
return TimeInterval(m);
}
int main() {
TimeInterval i1(65);
std::cout << i1 << std::endl; // 1h 5m
std::cout << (5_h + 79_m) << std::endl; // 6h 19m
}
C++14-ben már létezik std::string
-gé alakító operátor: a "hello"s
kifejezés
típusa std::string
. Írj egy ugyanígy működő _s
operátort! Ha mindent jól csinálsz:
std::cout << "hello"_s + " vilag" << std::endl; /* hello vilag */
std::cout << "hello\0vilag"_s.length() << std::endl; /* 11 */
Vajon lehet ez az _s
operátor constexpr
? Miért?
Megoldás
Olyan operátort kell írni, amely paraméterként átveszi a sztring méretét is. Egyrész különbözteti meg
az "123"_xyz
és az 123_xyz
operátort (overload az _xyz
operátorra),
másrészt pedig így lehet lezáró nulla is a sztringben. Ez kell a második sorhoz.
#include <iostream>
#include <string>
std::string operator"" _s (char const *str, size_t siz) {
return std::string(str, str + siz);
}
int main() {
std::cout << "hello"_s + " vilag" << std::endl; /* hello vilag */
std::cout << "hello\0vilag"_s.length() << std::endl; /* 11 */
}
Nem lehet constexpr
, mert az std::string
dinamikus memóriakezelést használ.
Írj bináris utótag operátort! Milyen formában érdemes átvennie ennek az értéket, nyers (raw), azaz sztring, vagy szám (cooked) formában?
int main() {
std::cout << 1111_binary << std::endl; /* 15 */
std::cout << 10000000_binary << std::endl; /* 128 */
std::cout << 10110111010111110111_binary << std::endl; /* 751095 */
}
Tipp
Az átalakításhoz használj Horner-elrendezést! Akkor nem kell tudni előre, hány számjegy lesz. Tízes számrendszeren bemutatva:
12345 = ((((1)·10 + 2)·10 + 3)·10 + 4)·10 + 5
Megoldás
Sztringként átvéve egyszerű a megvalósítás, akár indexelni is tudjuk a számjegyeket. Nem jó ötlet feldolgozott formában átvenni a karaktereket, mert akkor a fordító tízes számrendszerben értelmezi a számot, és könnyen túlcsordult értéket kaphatunk: binárisan egy szám sok számjegyből áll.
unsigned long long operator"" _binary(char const *str) {
unsigned long long result = 0;
for (size_t i = 0; str[i] != '\0'; ++i)
result = result<<1 | (str[i]=='1' ? 1 : 0);
return result;
}
Egyébként a bináris literális a C++14-be ez bekerült,
a hexa 0x
prefix mintájára 0b
prefixként.
A lebegőpontos (float
, double
) értékeken végzett
számítások nem pontosak. Vannak számok, amelyeket a kettes alapú normálalakban
nem lehet ábrázolni. Emiatt két float
vagy double
egyenlőségét nem szabad vizsgálni, mert váratlan eredményt adhat.
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::setprecision(20) << 9.95; // 9.9499999999999992895
}
A feladatod egy olyan Float
osztályt írni, amely figyelembe veszi
a pontatlanságokat az összehasonlításoknál. Az „egyenlő-e” legyen „ε sugarú körön
belül van”, a „nagyobb-e” legyen „legalább ε-nyival nagyobb” és így tovább.
Minden egyéb viselkedésében egyezzen meg egy Float
objektum a
beépített float
-tal!
Float f1 = 1.0f, f2 = 1.00001f;
std::cout << (f1 == f2); /* igaz */
Rajzolj fel egy számegyenest papíron, és jelölj be rajta egy
f
számot. Rajzold be, hogy mely tartományokat kellf
-nél kisebbnek, egyenlőnek, és nagyobbnak tekinteni. Ebből az ábrából dolgozz!Megoldás
Írd meg a
Float
osztályt! Maga az osztály minél kisebb legyen, törekedj arra, hogy később inkább globális függvényeket használj az operátorok megvalósításához. Milyen konverziókat kell definiálni aFloat
osztályhoz? Melyiknek ajánlatos explicitnek lennie, és miért? Írd meg egyelőre csak a kisebb<
operátort! ε=10-4. Ennek a kódrészletnek kell működnie:Float f1 = 1.0f, f2 = 1.00001f, f3 = 100; std::cout << (f1 < f2) << std::endl; /* hamis */ std::cout << (f1 < f3) << std::endl; /* igaz */
Megoldás
A konstruktor ne legyen explicit, hogy bármilyen beépített valós érték az okos párjává konvertálódhasson. Az
operator float
viszont jobb, ha explicit, hogy külön ki kelljen írni a konverziót, amikor vissza akarjuk butítani az értéket.Írd meg az összeadás, kivonás operátorokat! Írd meg a
+=
és-=
operátorokat úgy, hogy visszavezeted őket az előzőekre! Írd meg a kiírás<<
operátort is! Ha ezek készen vannak, az alábbi kódrészlet is működni fog. Ellenőrizd az eredményt!f1 = f2 + f3; for (Float f = 0.999; f < 1.001; f += 0.0001) { std::cout << f << '\t' << (f < 1.0) << std::endl; }
Az ε legyen egy konstans változóban tárolva. Hol érdemes tárolni ezt a változót? Ha eddig nem változóként adtad meg, akkor add meg úgy, és javítsd ki a
<
operátort!Megoldás
A
Float
osztályban, statikus adattagként. Aconstexpr
fordítási időben kiértékelhető konstanst jelent. Statikus adattag inicializálható osztályon belül is, ha megkapja aconstexpr
jelzőt.class Float { static constexpr float epsilon = 1e-4f; };
Írd meg a többi összehasonlító operátort is! Összesen öt függvény kell, de mindegyik egysoros:
>
,<=
,>=
,==
,!=
. Vezesd vissza mindet a<
operátorra! Teszteld őket a fenti ciklussal!Írd meg az egyoperandusú
+
és-
operátorokat, hogy ilyet lehessen írni:std::cout << -f1;
Van-e futási idejű költsége a
Float
osztálynak afloat
-okhoz képest?Megoldás
Memóriafoglalásban:
sizeof(float)==sizeof(Float)
, tehát nincs.Sebességben: az összehasonlítás legyen fair! Ha egy olyan változathoz hasonlítjuk a programot, ahol kézzel mindig kiírtuk az ε sugarú összehasonlítást, akkor nincs. Használjunk optimalizálást és
inline
függvényeket!
Megoldás
#include <iostream>
#include <cmath>
class Float {
public:
Float() = default;
Float(float value) : value_(value) {}
explicit operator float() const { return value_; }
static constexpr float epsilon = 1e-4f;
private:
float value_;
};
Float operator+(Float f1, Float f2) {
return float(f1) + float(f2);
}
Float & operator+=(Float &f1, Float f2) {
return f1 = f1 + f2;
}
Float operator-(Float f1, Float f2) {
return float(f1) - float(f2);
}
Float & operator-=(Float &f1, Float f2) {
return f1 = f1 - f2;
}
/* egyoperandusú */
Float operator-(Float f) {
return -float(f);
}
/* kisebb */
bool operator<(Float f1, Float f2) {
return float(f1) < float(f2)-Float::epsilon;
}
/* nagyobb: "kisebb" fordítva */
bool operator>(Float f1, Float f2) {
return f2<f1;
}
/* nagyobb vagy egyenlő: nem kisebb */
bool operator>=(Float f1, Float f2) {
return !(f1<f2);
}
/* kisebb vagy egyenlő: nem nagyobb */
bool operator<=(Float f1, Float f2) {
return !(f1>f2);
}
/* nem egyenlő: kisebb vagy nagyobb */
bool operator!=(Float f1, Float f2) {
return f1 > f2 || f1 < f2;
}
/* egyenlő: nem nem egyenlő */
bool operator==(Float f1, Float f2) {
return !(f1 != f2);
}
/* kíirás */
std::ostream & operator<< (std::ostream & os, Float f) {
return os << float(f);
}
/* beolvasás */
std::istream & operator>> (std::istream & is, Float & f) {
float raw_f;
is >> raw_f;
f = raw_f;
return is;
}
int main() {
Float f1 = 1.0f,
f2 = 1.00001f,
f3 = 100;
std::cout << (f1 < f2) << std::endl; /* hamis */
std::cout << (f1 < f3) << std::endl; /* igaz */
f1 = f2+=f3;
for (Float f = 0.999; f < 1.001; f += 0.0001) {
std::cout << f << '\t' << (f < 1.0) << std::endl;
}
std::cout << -f1;
}