Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Καλησπέρα έχω μια εργάσια στην c++ η οποία θέλει να εφαρμόσουμε ένα απλό προγραμματάκι απλώς θέλει να δείξουμε οτι μπορούμε να χειριστόυμε καλά τις virtual συναρτήσεις! Το πρόβλημα είναι το εξής: έχω κάνει το πρόγραμμα το οποίο είναι να φτιάξεις 3 μπάλες basket,tennis,ping-pong και ο παίχτης να επιλέγει μια μπάλα τυχαία κάθε φόρα και σε κάθε χτύπημα η μπάλα να χάνει αντοχή ή να εξαφανίζεται (μαλλον αυτό συμβαίνει αν αυτός που την χτυπάει είναι άμπαλος! :-D ) οι υπόλυπες να κάνουν rest και αύτο να γίνεται για Κ κύκλους που δίνονται απο την γραμμη εντολής. Το πρόβλημα είναι ότι εγώ είχα σταμάτησει να παρακολόυθω λόγω άλλων μαθημάτων το μάθημα και έτσι έμαθα για τις virtual συναρτήσεις απο ένα tutorial Buckys C++ Programming Tutorials - 56 - virtual Functions - YouTube ...το θέμα είναι οτι έτσι όπως έχω υλοποιήσει το πρόγραμμα δεν μ φαίνετε σωστό γιατί το τι κάνει κάθε συνάρτηση το έχω υλοποιήση σε κάθε κλάση ξεχώριστα και δεν καταλαβαίνω γιατι πρέπει να χρησημοποιήσω virtual...θέλώ να μ πείτε αν στο πρόγραμμα υλοποιώ σωστά τις virtual συνάρτησεις και αν χρειάζομαι να προσθέσω τπτ άλλο ευχαριστώ εκ των προτέρων! :)

 

το πρόγραμμα είναι το εξής:

//class.h

class Ball {

        protected:

                int durability;
                int value; //arxiki timi!
                bool hidden;

        public:

                virtual void hit();
                virtual void Set(int);
                virtual void rest();
        };



class Basket:public Ball {

        public:

                void hit();
                void Set(int);
                void rest();

        };

class Tennis:public Ball {

        public:

                void hit();
                void Set(int);
                void rest();

        };


class Ping_pong:public Ball {

        public:

                void hit();
                void Set(int);
                void rest();

        };

//class.cpp

#include <iostream>
#include <cstdlib>
#include <ctime>
#include "class3.h"

using namespace std;

void Ball::hit() {}

void Ball::Set(int) {}

void Ball::rest() {}

void Basket::Set(int a)
        {
                durability = a;
                value = a;
                hidden = false;
        }

void Tennis::Set(int a)
        {
                durability = a;
                value = a;
                hidden = false;
        }

void Ping_pong::Set(int a)
        {
                durability = a;
                value = a;
                hidden = false;
        }

void Basket::hit()
        {
                cout << "Basket ball is going to be hit!" << endl;

                if( hidden == true )
               {
                        cout << "You cannot hit a hidden ball" << endl;
                        return;
                }

                srand(time(0));
                int i = 1 + rand()%5;

                if( i == 5 )
                {
                        hidden = true;
                        cout << "Ball disappeared!" << endl;
                        return;
                }

                durability--;
                cout << "Tsaf!" << endl;

                if( durability == 0 )
                cout << "The ball is about to break!" << endl;
                else if( durability < 0 )
                cout << "Plof!" << endl;
        }
void Tennis::hit()
        {
                cout << "Tennis ball is going to be hit!" << endl;

                if( hidden == true )
                {
                        cout << "You cannot hit a hidden ball" << endl;
                        return;
                }

                srand(time(0));
                int i = 1 + rand()%5;

                if( i == 5 )
                {
                        hidden = true;
                        cout << "Ball disappeared!" << endl;
                        return;
                }

                durability -= 5;
                cout << "Tsaf!" << endl;

                if( durability == 0 )
                cout << "The ball is about to break!" << endl;
                else if( durability < 0 )
                cout << "Plof!" << endl;
        }

void Ping_pong::hit()
        {
                cout << "Ping-pong ball is going to be hit!" << endl;

                if( hidden == true )
                {
                        cout << "You cannot hit a hidden ball" << endl;
                        return;
                }

                srand(time(0));
                int i = 1 + rand()%5;

                if( i == 5 )
                {
                        hidden = true;
                        cout << "Ball disappeared!" << endl;
                        return;
                }

                durability--;
                cout << "Tsaf!" << endl;

                if( durability == 0 )
                cout << "The ball is about to break!" << endl;
                else if( durability < 0 )
                cout << "Plof!" << endl;
     }


void Basket::rest() { cout << "Basket is " << durability << endl; }

void Tennis::rest()
        {
                durability += 3;

                if( durability > value )
                durability = value;

                cout << "Tennis is " <<  durability << endl;
        }

void Ping_pong::rest()
        {
                durability++;

                if( durability > value )
                durability = value;

                cout << "Ping_pong is " <<  durability << endl;
         }


//main.cpp

#include <iostream>
#include <cstdlib>
#include <ctime>
#include "class3.h"

using namespace std;

int main(int argc, char *argv[])
{
        int L1,L2,L3,i,K,option;

        Basket b;
        Tennis t;
        Ping_pong p;

        Ball *ball1 = &b;
        Ball *ball2 = &t;
        Ball *ball3 = &p;

        L1 = atoi(argv[1]);
        L2 = atoi(argv[2]);
        L3 = atoi(argv[3]);
        K = atoi(argv[4]);

        ball1->Set(L1);
        ball2->Set(L2);
        ball3->Set(L3);

        srand(time(0));

        for( i=0; i < K; i++ )
        {
                option = 1 + rand()%3;

                switch(option)
                {
                        case 1:
                        ball1->hit();
                        ball2->rest();
                        ball3->rest();
                        break;

                        case 2:
                        ball2->hit();
                        ball1->rest();
                        ball3->rest();
                        break;

                        case 3:
                        ball3->hit();
                        ball1->rest();
                        ball2->rest();
                        break;
                }
        }

        return 0;
}

  • Moderators
Δημοσ.

Το νόημα των virtual συναρτήσεων είναι ότι κάνουν κάτι διαφορετικό ανάλογα με την κλάση. Δες το παρακάτω παράδειγμα:

class Shape
{
    protected:
        float area;
    public:
        Shape() {}
        ~Shape() {}
        
        virtual float calculateArea();
};

class Circle : public Shape
{
    private:
        float radius;
    public:
        Circle() {}
        ~Circle() {}
        
        float calculateArea()
        {
            area = PI * radius * radius;
        }
};

class Square : public Shape
{
    private:
        float x;
    public:
        Square() {}
        ~Square() {}
        
        float calculateArea()
        {
            area = x * x;
        }
};

class Rectangle : public Shape
{
    private:
        float x;
        float y;
    public:
        Rectangle() {}
        ~Rectangle() {}
        
        float calculateArea()
        {
            area = x * y;
        }
};

Κάθε calculateArea κάνει κάτι διαφορετικό, σε αντίθεση με το δικό σου κώδικα που κάνουν όλες το ίδιο. Άμα σε μια derived class δεν κάνω υλοποίηση μιας virtual μεθόδου της, τότε παίρνει αυτομάτως την υλοποίηση της base class, που σημαίνει ότι θα μπορούσες να βάλεις την ίδια υλοποίηση στη base class και να αφήσεις τις υλοποιήσεις των άλλων μπαλών.

Δημοσ.

Άμα βάλω τον κώδικα που είναι ίδιος στην virtual συνάρτηση που είναι δηλωμένη στηn κλάση ball και στις άλλες απλώς να βάλω αυτό που αλλάζει τότε όταν καλώ την hit για την basket θα καλείται και η hit της κλάσης ball??


βασικά παρατήρησα οτι μπορώ να κάνω αυτό:

#include <iostream>
#include <cstdlib>

using namespace std;

class Ball {

public:
    virtual void hit()
    {
        cout << "Program ";
    }
        };



class Basket:public Ball {

        public:
        void hit()
        {
            Ball b;
            b.hit();
            cout << "Worked!" << endl;
        }

        };


int main(int argc, char *argv[])
{
        Basket b;
        Tennis t;
        Ping_pong p;

        Ball *ball1 = &b;

        ball1->hit();

        return 0;
}

λειτουργεί αλλά είναι σωστό?? Το έκανα αυτό για να βάλω οτι είναι ίδιο στην συναρτήση στην κλάση ball και στις αλλές συναρτήσεις να έχει μόνο αυτό που αλλάζει.

Δημοσ.

Το νόημα των virtual συναρτήσεων είναι ότι κάνουν κάτι διαφορετικό ανάλογα με την κλάση. Δες το παρακάτω παράδειγμα:

class Shape
{
    protected:
        float area;
    public:
        Shape() {}
        ~Shape() {}
        
        virtual float calculateArea();
};

class Circle : public Shape
{
    private:
        float radius;
    public:
        Circle() {}
        ~Circle() {}
        
        float calculateArea()
        {
            area = PI * radius * radius;
        }
};

class Square : public Shape
{
    private:
        float x;
    public:
        Square() {}
        ~Square() {}
        
        float calculateArea()
        {
            area = x * x;
        }
};

class Rectangle : public Shape
{
    private:
        float x;
        float y;
    public:
        Rectangle() {}
        ~Rectangle() {}
        
        float calculateArea()
        {
            area = x * y;
        }
};

Κάθε calculateArea κάνει κάτι διαφορετικό, σε αντίθεση με το δικό σου κώδικα που κάνουν όλες το ίδιο. Άμα σε μια derived class δεν κάνω υλοποίηση μιας virtual μεθόδου της, τότε παίρνει αυτομάτως την υλοποίηση της base class, που σημαίνει ότι θα μπορούσες να βάλεις την ίδια υλοποίηση στη base class και να αφήσεις τις υλοποιήσεις των άλλων μπαλών.

 

 

Κακό παράδειγμα. Πολύ κακό. 

 

Η virtual float calculateArea είναι ΛΑΘΟΣ. Εάν το "κάνεις compile" χτυπάει. 

 

Πέρα από το λάθος, μπερδεύει τις έννοιες pure virtual και virtual. 

 

 

 

@TS

 

το νόημα των virtual μεθόδων δεν είναι των μεθόδων αλλά της μοντελοποίησης που ΠΡΕΠΕΙ να κάνεις. 

 

Έχοντας OOP, ΠΡΕΠΕΙ να δομήσεις τις οντότητες στο πρόγραμμά σου σε σχέσεις. Να σκεφτείς γενικά. 

 

Στο (λάθος) παράδειγμα του Kercyn, η αρχική calculateArea θα υπολόγιζε το εμβαδόν για ένα γενικό παραλληλόγραμμο (εάν η κλάση δεν ήταν shape αλλά parallelogram). Έπειτα, εάν έκανες ένα ΕΙΔΙΚΟ παραλληλόγραμμο, θα έλεγες ότι:

 

Μάγκες, δεν είναι ανάγκη να κάνουμε την γενική διαδικασία.. ας κάνουμε το ειδικό και πιο εύκολο για το συγκεκριμένο παραλληλόγραμμο που έχουμε τώρα. 

 

Εκεί λοιπόν, την γενική λειτουργία της calculateArea την κάνεις override με μία διαφορετική. Το υπόλοιπο πρόγραμμά σου δεν θα το ένοιαζε τι κάνει η συγκεκριμένη calculateArea του πιο ειδικού παραλληλόγραμμου αλλά θα ήξερε ότι κάνει ό,τι και η "αυθεντική" calculateArea (δηλαδή υπολογίζει εμβαδό). 

 

Αυτό που είδες το παράδειγμα του Kercyn είναι κάτι ανάμεσα σε pure virtual και virtual μεθόδους. 

 

Στις pure virtual φτιάχνεις μία γενική (και abstract κλάση) η οποία χρησιμεύει σαν πρότυπο για άλλες. Με τις pure virtual μεθόδους διασφαλίζεις ότι ΟΛΕΣ όσες κληρονομούν θα έχουν αυτή την μέθοδο (δες τα interfaces στην Java). 

 

 

 

Δημοσ.

λοιπόν έκανα το εξής:

void Ball::hit()
        {
                if( hidden == true )    //Elenxei an i mpala einai idi eksafanismeni
                {
                        cout << "You cannot hit a hidden ball." << endl;
                        return;
                }

                srand(time(NULL));
                int i = 1 + rand()%5;   //Dialegei tixaia ena ari8mo gia tin pi8anotita i mpala
                                                  //Na eksafanistei.Dialegei apo to 1-5 gia na min simvenei polli sixna

                if( i == 5 ) //an dialextike to 5 tote i mpala eksafanizete allios paremenei faneri!
                {
                        hidden = true;
                        cout << "Ball disappeared!" << endl;
                        return;
                }

                cout << "Tsaf!" << endl; //Xtipima tis mpalas(simvenei gia ka8e mpala pou xtipiete)

                if( durability == 0 )   //An antoxi = 0 tote eina f8armeni
                cout << "The ball is about to break!" << endl;
                else if( durability < 0 )       //An ksanaxtipi8ei enw itan f8armeni(antoxi = 0) tote
                cout << "Plof!" << endl;        //8a paei -1 ara plof!
        }

void Basket::hit()
        {
                cout << "Basket ball is going to be hit!" << endl;

                durability--;
        }

kai twra pia stin main kalo prwta: 

ball1->hit();
ball1->Ball::hit();

kai leitourgei opws to proigoumeno! einai apodekto omws?

  • Moderators
Δημοσ.

Αφού στον Timonkaipumpa δεν άρεσε το παράδειγμά μου, βάζω άλλο ένα τελείως διαφορετικό το οποίο (άκουσον άκουσον!) κάνει compile χωρίς να χρειάζεται να κάνεις όλες αυτές τις περίπλοκες αλλαγές που έκανα πιο πάνω.

class Square
{
    protected:
        float area;
        float perimeter;
        float x;
    public:
        Square() {}
        ~Square() {}
        
        virtual void calculatePerimeter()
        {
            perimeter = 4 * x;
        }

        virtual void calculateArea()
        {
            area = x * x;
        }
};

class Rectangle : public Square
{
    protected:
        float y;
    public:
        Rectangle() {}
        ~Rectangle() {}
        
        virtual void calculatePerimeter()
        {
            perimeter = 2*x + 2*y;
        }

        virtual void calculateArea()
        {
            area = x * y;
        }
};

class Parallelogram : public Rectangle
{
    private:
        float h;
    public:
        Parallelogram() {}
        ~Parallelogram() {}
        
        void calculateArea()
        {
            area = x * h;
        }
};

Όπως βλέπεις, έχουμε μια βασική κλάση Square, μια υποκλάση Rectangle και άλλη μία υποκλάση Parallelogram, οι οποίες έχουν 2 μεθόδους. Σε καθένα απ' αυτά τα σχήματα, το εμβαδόν υπολογίζεται με ξεχωριστό τύπο. Έτσι, πρέπει σε κάθε κλάση να φτιάξουμε μια δική μας calculateArea. Όταν όμως πάμε στην περίμετρο, παρατηρούμε ότι στο παραλληλόγραμο και στο ορθογώνιο η περίμετρος βρίσκεται από ακριβώς τον ίδιο τύπο. Έτσι, δε χρειάζεται να "αντικαταστήσουμε" την calculatePerimeter στην κλάση Parallelogram, γιατί έχει ήδη υλοποιηθεί στη βασική κλάση, την οποία κληρονομεί.

 

Για να πάμε τώρα στην άσκησή σου, οι 3 μπάλες που έχουν τις ίδες μεταβλητές, τις ίδιες μεθόδους και συμπεριφέρονται με ακριβώς τον ίδιο τρόπο. Γιατί, λοιπόν, θες να έχεις 3 διαφορετικές κλάσεις όταν μπορείς να έχεις μόνο μία (και θα μπορούσες να βάλεις ένα string για να δηλώνεται το "όνομα" της κάθε μπάλας).

 

Επίσης, στο τελευταίο σου post, άμα καλέσεις την Basket::hit το μόνο που θα κάνει είναι να εκτυπώσει ένα μήνυμα και να μειώσει το durability (δηλαδή δε θα κάνει τίποτα απ' αυτά που κάνεις στην Ball::hit). Αντί να κάνεις ball1->hit() ΚΑΙ ball1->Ball::hit() μπορείς να καλείς την Ball::hit μέσα στην Basket::hit.

Δημοσ.

Οταν γραφεις μια base class πρεπει να κανεις τον destructor virtual, πχ:

//class.h

class Ball {

        protected:

                int durability;
                int value; //arxiki timi!
                bool hidden;

        public:
                virtual ~Ball();
                virtual void hit();
                virtual void Set(int);
                virtual void rest();
        };

Επισης πρεπει να σκεφτεις το ενδεχομενο μιας abstract class με μονο pure virtual functions πχ

//class.h

class IBall {
        public:
                //βλεπεις γιατι δεν χρειαζεται να κανεις τον desctructor virtual ??
                virtual void hit() = 0;
                virtual void Set(int) = 0;
                virtual void rest() = 0;
};



Δημοσ.

Αφού στον Timonkaipumpa δεν άρεσε το παράδειγμά μου, βάζω άλλο ένα τελείως διαφορετικό το οποίο (άκουσον άκουσον!) κάνει compile χωρίς να χρειάζεται να κάνεις όλες αυτές τις περίπλοκες αλλαγές που έκανα πιο πάνω.

class Square
{
    protected:
        float area;
        float perimeter;
        float x;
    public:
        Square() {}
        ~Square() {}
        
        virtual void calculatePerimeter()
        {
            perimeter = 4 * x;
        }

        virtual void calculateArea()
        {
            area = x * x;
        }
};

class Rectangle : public Square
{
    protected:
        float y;
    public:
        Rectangle() {}
        ~Rectangle() {}
        
        virtual void calculatePerimeter()
        {
            perimeter = 2*x + 2*y;
        }

        virtual void calculateArea()
        {
            area = x * y;
        }
};

class Parallelogram : public Rectangle
{
    private:
        float h;
    public:
        Parallelogram() {}
        ~Parallelogram() {}
        
        void calculateArea()
        {
            area = x * h;
        }
};

Για να πάμε τώρα στην άσκησή σου, οι 3 μπάλες που έχουν τις ίδες μεταβλητές, τις ίδιες μεθόδους και συμπεριφέρονται με ακριβώς τον ίδιο τρόπο. Γιατί, λοιπόν, θες να έχεις 3 διαφορετικές κλάσεις όταν μπορείς να έχεις μόνο μία (και θα μπορούσες να βάλεις ένα string για να δηλώνεται το "όνομα" της κάθε μπάλας).

 

 

Σοβαρά τώρα?

Δημοσ.

?

 

Απλά μου φάνηκε εντυπωσιακό το πώς μια stringly typed λύση προτάθηκε ως καλή εναλλακτική, πόσο μάλλον για μια γλώσσα προγραμματισμού όπως η C++.

  • Like 1
  • Moderators
Δημοσ.

Απλά μου φάνηκε εντυπωσιακό το πώς μια stringly typed λύση προτάθηκε ως καλή εναλλακτική, πόσο μάλλον για μια γλώσσα προγραμματισμού όπως η C++.

 

Το μόνο που αλλάζει στην hit είναι το όνομα της μπάλας στο cout, γι' αυτό το είπα. Πώς θα το έκανες εσύ;

Δημοσ.

Οταν γραφεις μια base class πρεπει να κανεις τον destructor virtual

 

Γιατί;

 

Επισης πρεπει να σκεφτεις το ενδεχομενο μιας abstract class με μονο pure virtual functions

Γιατί;

 

...
          //βλεπεις γιατι δεν χρειαζεται να κανεις τον desctructor virtual ??

 

Με μπέρδεψες τώρα... :blink:

 

Το μόνο που αλλάζει στην hit είναι το όνομα της μπάλας στο cout, γι' αυτό το είπα. Πώς θα το έκανες εσύ;

Όχι, αλλάζει και η rest().

Δημοσ.

Γιατί;

Γιατί διαφορετικά αν κάνεις αυτό είναι UB:

 

Base* p = new Derived();

delete p;

 

Επομένως classes που είναι για polymorphic use πάντα πρέπει να έχουν virtual destructor (τουλάχιστον αν αυτός είναι public).

 

Γιατί;

Γιατί αυτό που έκανε εκεί είναι το "interface" στη C++ (κάνεις multiple inheritance αντί για "implements", αλλά μ' αυτό όπως είναι γλυτώνεις όλα τα προβλήματα του multiple inheritance). Interface = good.

 

Βέβαια θα έπρεπε ας πούμε να είναι protected ο constructor αλλά λεπτομέρειες.

 

Με μπέρδεψες τώρα... :blink:

Και μένα με μπέρδεψε.

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...