Fizikus C++ labor

1. hét: make és C ismétlés2. hét: pontosság és pontatlanság3. hét: azonos nevű függvények, referencia
4. hét: egyszerű osztályok5. hét: konstruktor és operator overloading6. hét: osztály dinamikus adattaggal
9. hét: >> és << használata I/O-ra, statikus tagok10. hét / 1: STL vector10. hét / 2: STL string, algorithm
11. hét: generikus algoritmusok és osztályok12. hét: öröklés13. hét: heterogén kollekció
14. hét: C++11 kitekintő15. hét: Nagyházi bemutatás

5. hét: konstruktor és operator overloading

1. feladat

Készíts racionális szám osztályt! Próbáld meg egyedül, s ha nem megy, belenézhetsz az előadáson készült jegyzetedbe, ill. a tárgy oldalán lévő segédletbe is. A megvalósítandó funkciók (ami lehet, legyen konstans):

- A számláló és a nevező egész szám.

- Három konstruktor szükséges: paraméter nélküli (=alapértelmezett=default), ami 0-ra állítja a számot, egyparaméteres, amely egy egész számot kap, ennyi lesz a szám értéke (azaz n/1), valamint kétparaméteres: a két egész szám a számláló és a nevező. Ha ügyes vagy, megvalósíthatod a három konstruktort egy függvénnyel. Hogyan lehetséges ez? A kétparaméteres konstruktor dobjon domain_error kivételt, ha 0 értéket kap nevezőként.

- Bónusz feladat: egy double és egy egész paramétert kapó konstruktor, az egész adja meg, hogy hány tizedes jegyet vegyen figyelembe a double értékből a konstruktor. Használj kerekítést a double=>racionális konverziónál! (Pl. ha 3.1415926 a bemenő érték és 4 tizedesjegyet kérünk, akkor a tárolt racionális érték egyszerűsítés előtt 31416/10000).

- Egyszerűsítő függvény: írj függvényt, amely egyszerűsíti a törtet. Pl. 2/6=>1/3. (Ehhez a számláló és a nevező legnagyobb közös osztóját kell kiszámítani.) Az egyszerűsítés után a nevező nem lehet negatív szám (pl. 2/-5 helyett -2/5). Az egyszerűsítő függvény nem kell, hogy osztályon kívülről elérhető legyen.

- + operátor, amely kiszámítja két racionális szám összegét. Az operátort megvalósíthatod globális függvényként vagy tagfüggvényként. Figyelj arra, hogy a + operátor nem változtatja meg a paraméterei értékét, azaz A = B + C esetben B és C változatlan marad, csak A változik.

- += operátor, amely egy racionális számhoz hozzáad egy másikat. Az a += b jelentése a = a + b.

- / operátort, amely két racionális szám hányadosát adja. 0-val való osztás esetén ez is kivételt dob.

- == és != operátort, amely összehasonlít két racionális számot.

- konverziós operátort, amely double típusú értékké konvertálja a racionális számot.

 

Írj main függvényt, amelyben kipróbálod az osztály minden funkcióját!

 

Tipp: Az összeadáshoz a két törtet közös nevezőre kell hozni. Bár a legegyszerűbb, ha nevezőként a két bemenő tört nevezőjének szorzatát választjuk, ez nagy nevezőknél hibát okozhat az egész típusok véges értékkészlete miatt (pl. ha 1/1000 és 1/10000 összegét kell kiszámítanunk, a két nevező szorzata 10000000 lesz, miközben 10000 is elég lenne), ezért célszerű a két nevező legkisebb közös többszörösével dolgozni. A LKKT kifejezhető a LNKO-val: LKKT = A/LNKO*B. Mi lesz ekkor a két számlálóval?

 

Tipp: Az egyes műveletek után a racionális szám mindig a lehető legegyszerűbb alakú legyen. Az egyszerűsítés számításigényes, csak akkor használd, ha szükséges!

 

Tipp: A LNKO kiszámítására legegyszerűbb brute force módszert használni, legalább ezt ZH-ban is tudni kell. Jóval hatékonyabb azonban az euklideszi algoritmus: Ezt nem kell tudni fejből (de ha tudod, az nem baj:), de könnyen találsz a neten akár C nyelvű megvalósítást is.

2. feladat

A múlt heti 2D vektor osztályt egészítsd ki konstruktorral, + operátorral, += operátorral, valamint skalárral (double) szorzó * operátorral! A vektort skalárral balról és jobbról is lehessen szorozni! Ezek segítségével számítsd ki, hol csapódik a földbe egy ferdén elhalyított tárgy (a súrlódást figyelmen kívül hagyjuk).

- A gyosulás (a), sebesség (v) és hely (p) vektor objektumok.

- A gyorsulás kezdetben (0,-g), a sebesség a felhasználó által megadott r és phi, a hely (0,0).

- A számítás Euler integrálással történhet. A gyorsulás végig változatlan, hely: p += v*dt, a sebesség: v +=a*dt. A dt végig állandó, a felhasználó adja meg. A becsapódás akkor történik meg, amikor p.y<=0 lesz.

Gyakorló feladat

Egészítsd ki a racionális szám osztályt értelmes műveletekkel! Legyen ~ (tilde) operátor, amely a reciprokot adja, - előjel operátor (egyoperandusú), és a kimaradt alapműveletek.

Órai feladatok mintamegoldása

1. feladat
#include <cstdio>
#include <stdexcept>

class Rac{
    int sz, n;
    int lnko(int a, int b)const;
    void egyszerusit();
public:
    Rac(int sz = 0, int n = 1){
        this->sz = sz; this->n = n;
        if (n == 0)
            throw std::domain_error("Rac::Rac: 0 a nevezoben");
        egyszerusit();
    }

    Rac(double szam, int tizedes){
        bool neg = false;
        if (szam < 0)
            szam = -szam, neg = true;
        if (tizedes < 0)
            throw std::domain_error("Rac::Rac: tizedes jegyek szama<0");
        n = 1;
        for (; tizedes > 0; tizedes--)
            n *= 10;
        sz = (int)(szam*n + 0.5);
        sz = neg ? -sz : sz;
        egyszerusit();
    }
    
    void print()const{
        printf("(%d/%d)\n", sz, n);
    }
    
    Rac operator+(const Rac &b)const;

    Rac& operator+=(const Rac&);

    Rac operator/(const Rac& b);

    bool operator==(const Rac& b){ return sz == b.sz && n == b.n; }
    bool operator!=(const Rac&b){ return !(*this == b); }

    operator double()const{ return (double)sz / n; }
    
    void set(int sz, int n = 1){
        this->sz = sz, this->n = n;
        if (n == 0)
            throw std::domain_error("Rac::set: 0 a nevezoben");
        egyszerusit();
    }
};

int Rac::lnko(int a, int b)const{
    a = a < 0 ? -a : a;
    b = b < 0 ? -b : b;
    while (b>0){
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

void Rac::egyszerusit(){
    int neg = sz < 0 ? -1 : 1;
    neg *= n < 0 ? -1 : 1;
    sz = sz < 0 ? -sz : sz;
    n = n < 0 ? -n : n;
    int oszto = lnko(sz, n);
    if (oszto != 0)
        sz /= oszto, n /= oszto;
    sz *= neg;
}

inline Rac Rac::operator+(const Rac &b)const{
    int oszto = lnko(n, b.n);
    return Rac(b.n/oszto*sz + n/oszto*b.sz, n/oszto*b.n); // konstruktor: egyszerűsít is
}

inline Rac& Rac::operator+=(const Rac& b){
    *this = *this + b; //op+ majd egyszerűsít
    return *this;
}

inline Rac Rac::operator/(const Rac& b){
    if (b.sz == 0)
        throw std::domain_error("Rac::operator/: osztas 0-val");
    int oszto1 = lnko(sz, b.sz);
    int oszto2 = lnko(n, b.n);
    return Rac((sz / oszto1)*(b.n / oszto2), (n / oszto2)*(b.sz / oszto1));
}

int main(){
    try{
        Rac a(2, 6);
        a.print();
        Rac b(-3.1415926, 3);
        b.print();
        printf("%g\n", (double)b); // expicit meg kell adni a konverziót, mert a printf nem ismeri fel
        double x;
        x = b; // automatikusan létrejön a konverzió
        printf("%g\n", x);
        Rac c(2, 3);
        Rac d = a + c;
        d.print();
        a.set(1, 1000);
        b.set(1, 10000);
        d = a + b;
        d.print();
        d += Rac(1, 10000); // ideiglenes objektum a referencia erre mutat, e sor után megszűnik
        d.print();
        a.set(-21, 10);
        b.set(9, 25);
        d = a / b;
        d.print();
        if (a == b)
            printf("a==b\n");
        else 
            printf("a!=b\n");
        a = b;
        if (a == b)
            printf("a==b\n");
        else
            printf("a!=b\n");
        Rac(1, 0);
    }
    catch (const std::domain_error &d){
        printf("Domain error:\n\t%s\n", d.what());
    }
    return 0;
}

 

2. feladat
#include <cstdio>
#include <cmath>

class vec2D{
    double x, y;
public:
    vec2D(double x = 0, double y = 0) :x(x), y(y){}
    double getX()const{ return x; }
    double getY()const{ return y; }
    double getR()const{ return sqrt(x*x + y*y); }
    double getPhi()const{ return atan2(y, x); }
    void setXY(double x, double y){ this->x = x; this->y = y; }
    void setRPhi(double r, double phi){ x = r*cos(phi); y = r*sin(phi); }
    vec2D operator+(const vec2D &v)const{ return vec2D(getX() + v.getX(), getY() + v.getY()); }
    const vec2D& operator+=(const vec2D &v){ setXY(getX() + v.getX(), getY() + v.getY()); return *this; }
    vec2D operator*(double d)const{ return vec2D(getX()*d, getY()*d); }
};

inline vec2D operator*(double d, const vec2D &v){
    return v*d;
}

int main(){
    vec2D a(0, -9.81), v, p;
    double r, phi, dt;
    const double pi = acos(0)*2;
    printf("v0= "); scanf("%lg", &r);
    printf("szog= "); scanf("%lg", &phi);
    phi *= pi / 180;
    v.setRPhi(r, phi);
    printf("dt= "); scanf("%lg", &dt);
    do{
        p += v*dt;
        v += a*dt;
    } while (p.getY() > 0);
    printf("Becsapodas: %g\n", p.getX());
    return 0;
}