====== Κατασκευαστές και καταστροφείς της κλάσης ====== Στο προηγούμενο παράδειγμα της κλάσης //Rectangle// δεν δηλώθηκε κάποιος κατασκευαστής. Ο κατασκευαστής της κλάσης είναι μέθοδος που έχει το όνομα της κλάσης και δεν έχει επιστρεφόμενο τύπο. Ο ρόλος του κατασκευαστή της κλάσης είναι να αρχικοποιήσει τις τιμές των πεδίων της κλάσης. Σημειώστε, ότι ο κατασκευαστής της κλάσης στη C++ δεν δεσμεύει χώρο στη μνήμη, όπως συμβαίνει στη Java. Για την κλάση //Rectangle//, παρακάτω δηλώνεται ο κατασκευαστής ''Rectangle(int w, int h)''. #include using namespace std; class Rectangle { private: int width, height; public: Rectangle(int w, int h); void setWidth(int w); void setHeight(int h); int getWidth() const; int getHeight() const; }; Rectangle::Rectangle(int w, int h) { width = w; height = h; } void Rectangle::setWidth(int w) { width = w; } void Rectangle::setHeight(int h) { height = h; } int Rectangle::getWidth() const { return width; } int Rectangle::getHeight() const { return height; } #include "Rectangle.hpp" int main () { Rectangle rect(5,6); cout << "area: " << rect.getWidth() * rect.getHeight() << endl; return 0; } Σε αναλογία με τις μεθόδους ο κατασκευαστής μπορεί να οριστεί εντός της κλάσης, όπως παρακάτω. Η δήλωση εντός της κλάσης συνεπάγεται τη δήλωση από τον προγραμματιστή του κατασκευαστή ως //inline//. class Rectangle { private: int width, height; public: Rectangle(int w, int h){ width = w; height = h; } }; ===== Default κατασκευαστής ===== Εάν δεν οριστεί κανένας κατασκευαστής σε μία κλάση τότε για την κλάση ορίζεται από τον μεταγλωττιστή ο //default// κατασκευαστής. Ο κατασκευαστής αυτός δεν έχει ορίσματα. Τα πεδία της κλάσης αρχικοποιούνται ως εξής: - για πεδία που έχουν βασικού τύπους (int, double, char κλπ) οι τιμές είναι τυχαίες. - για πεδία που περιγράφονται από κλάσεις καλείται ο //default// κατασκευαστής της εκάστοτε κλάσης. Εάν έχετε δηλώσει τουλάχιστον ένα κατασκευαστή που δεν είναι ο //default// κατασκευαστής χωρίς παραμέτρους τότε ο //compiler// δεν κατασκευάζει αυτόματα τον //default// κατασκευαστή. Σε περίπτωση που ο //default// κατασκευαστής δεν δημιουργηθεί αυτόματα από τον //compiler//, λαμβάνετε μήνυμα λάθους κατά τη μεταγλώττιση. ===== Υπερφόρτωση κατασκευαστών ===== Σε αναλογία με την [[cpp:functions#υπερφόρτωση_συναρτήσεων|υπερφόρτωση συναρτήσεων]] η C++ υποστηρίζει και υπερφόρτωση κατασκευαστών. Συγκεκριμένα, μπορείτε να έχετε περισσότερους από έναν κατασκευαστές αρκεί αυτοί να έχουν διαφορετικό αριθμό ή τύπο ορισμάτων. Δείτε το παρακάτω παράδειγμα της κλάση //Rectangle//, όπου δίνονται δύο επιπλέον κατασκευαστές, ένα κατασκευαστή που λαμβάνει ένα κοινό όρισμα και για τις δύο διαστάσεις του παραλληλογράμμου και ένα κατασκευαστή χωρίς ορίσματα που αναθέτει τυχαίες τιμές στις διαστάσεις του παραλληλογράμμου. #include #include #include using namespace std; class Rectangle { private: int width, height; public: Rectangle(int w, int h); Rectangle(int s); Rectangle(); void setWidth(int w); void setHeight(int h); int getWidth() const; int getHeight() const; }; Rectangle::Rectangle(int w, int h) { width = w; height = h; } Rectangle::Rectangle(int s) { width = s; height = s; } Rectangle::Rectangle() { srand(time(NULL)); width = rand() % 10 + 1; height = rand() % 10 + 1; } void Rectangle::setWidth(int w) { width = w; } void Rectangle::setHeight(int h) { height = h; } int Rectangle::getWidth() const { return width; } int Rectangle::getHeight() const { return height; } #include "Rectangle.hpp" int main () { Rectangle rect(5,6); cout << "area: " << rect.getWidth() * rect.getHeight() << endl; return 0; } ===== Χρήση member initialization list για την αρχικοποίηση των μελών της κλάσης ===== Παραπάνω συναντήσαμε τον κατασκευαστή με δύο ορίσματα Rectangle::Rectangle(int w, int h) { width = w; height = h; } Αντί για την παραπάνω δήλωση θα μπορούσατε να γράψετε ισοδύναμα: Rectangle::Rectangle(int w, int h) : width(w) { height = h; } ή Rectangle::Rectangle(int w, int h) : width(w), height(h) {} Τελικά η κλάση Rectangle μπορεί να γραφεί ως εξής: #include #include #include using namespace std; class Rectangle { private: int width, height; public: Rectangle(int w, int h); Rectangle(int s); Rectangle(); void setWidth(int w); void setHeight(int h); int getWidth() const; int getHeight() const; }; Rectangle::Rectangle(int w, int h) : width(w), height(h) { } Rectangle::Rectangle(int s) : width(s), height(s) { } Rectangle::Rectangle() { srand(time(NULL)); width = rand() % 10 + 1; height = rand() % 10 + 1; } void Rectangle::setWidth(int w) { width = w; } void Rectangle::setHeight(int h) { height = h; } int Rectangle::getWidth() const { return width; } int Rectangle::getHeight() const { return height; } ===== Κλήση ενός κατασκευαστή μέσα από άλλο κατασκευαστή ===== Συχνά μπορεί να θέλουμε να καλέσουμε μέσα από ένα κατασκευαστή έναν άλλο κατασκευαστή. Μπορείτε να το κάνετε αυτό χρησιμοποιώντας ως οποιαδήποτε άλλη μέθοδο. Στο προηγούμενο παράδειγμα, ο κατασκευαστής με τη μία παράμετρο καλεί εσωτερικά τον κατασκευαστή με τις δύο παραμέτρους ως εξής: Rectangle::Rectangle(int s) { Rectangle(s,s); } O ίδιος κώδικας με χρήση //initialization list// μπορεί να γραφεί ως εξής: Rectangle::Rectangle(int s) : Rectangle(s,s) { } ===== Αρχικοποίηση αντικειμένων που περιέχουν πεδία που περιγράφονται από κλάσεις ===== Στην προηγούμενη ενότητα είδαμε την κλάση Cuboid που περιγράφει την κλάση του κυβοειδούς και περιέχει ένα πεδίο τύπου Rectangle. Εδώ θα δούμε πως μπορούμε να ορίσουμε ένα ή περισσότερους κατασκευαστές για τη συγκεκριμένη κλάση. #include using namespace std; #include "Rectangle.hpp" class Cuboid { private: int length; Rectangle rect; public: Cuboid(int w, int h, int l); Cuboid(Rectangle r, int l); void setRectangle(Rectangle r); Rectangle getRectangle() const; void setLength(int l); int getLength() const; int volume(); }; #include "Cuboid.hpp" Cuboid::Cuboid(int w, int h, int l) { rect.setWidth(w); rect.setHeight(h); length = l; } Cuboid::Cuboid(Rectangle r, int l) { rect = r; length = l; } void Cuboid::setRectangle(Rectangle r) {rect = r;} Rectangle Cuboid::getRectangle() const {return rect;} void Cuboid::setLength(int l) { length = l; } int Cuboid::getLength() const { return length; } int Cuboid::volume() { return length * rect.getWidth() * rect.getHeight(); } Οι παραπάνω δύο κατασκευαστές με χρήση member initialιzation list μπορούν να γραφούν ως εξής: Cuboid::Cuboid(Rectangle r, int l) : rect(r), length(l) { } Cuboid::Cuboid(int w, int h, int l) : rect(w,h), length(l) { } Στην 1η περίπτωση γίνεται ανάθεση του αντικειμένου //r// στο πεδίο //rect//. Στην 2η περίπτωση καλείται ο κατασκευαστής της κλάσης //Rectangle// με ορίσματα //(w,h)//. Η ανάθεση ''rect = r;'' δεν είναι προφανής. Έχοντας δύο αντικείμενα τύπου Rectangle το ''rect'' και το ''r'', η εντολή ανάθεσης αντιγράφει τα περιεχόμενα του ''r'' στο ''rect'' πεδίο προς πεδίο. ===== Καταστροφέας της κλάσης ===== Σε αναλογία με τον κατασκευαστή της κλάσης η C++ δηλώνει και τον καταστροφέα της κλάσης. Ο καταστροφέας της κλάσης επιτρέπει να γίνουν οι απαραίτητες εργασίες καθαρισμού για το αντικείμενο που καταστρέφεται. Για παράδειγμα είναι πιθανόν κατά την δημιουργία του αντικειμένου να έχει δεσμευθεί μνήμη την οποία πρέπει να ελευθερώσουμε ή να έχουν ανοιχθεί αρχεία τα οποία κατά την καταστροφή του αντικειμένου πρέπει να τα κλείσουμε. Στο παρακάτω παράδειγμα βλέπετε την κλάση //Rectangle// όπου τα πεδία width και height είναι δείκτες. Σε αυτή την περίπτωση θα πρέπει να δεσμεύουμε την απαραίτητη μνήμη με χρήση του τελεστή new. Στον καταστροφέα την κλάσης απελευθερώνουμε τη μνήμη που δεσμεύσαμε προηγούμενα. #include #include using namespace std; class Rectangle { private: int *width, *height; public: Rectangle(int w, int h); ~Rectangle(); void setWidth(int w); void setHeight(int h); int getWidth(); int getHeight(); }; Rectangle::Rectangle(int w, int h) { width = new (nothrow) int; height = new (nothrow) int; if(width == NULL || height == NULL) { cerr << "Memory allocation failure!\n"; exit(-1); } *width = w; *height = h; cout << "Constructing rectangle (w:"<< *width <<", h:"<<*height<<")\n"; } Rectangle::~Rectangle() { cout << "Destructing rectangle (w:"<< *width <<", h:"<<*height<<")\n"; delete width; delete height; } void Rectangle::setWidth(int w) { *width = w; } void Rectangle::setHeight(int h) { *height = h; } int Rectangle::getWidth() { return *width; } int Rectangle::getHeight() { return *height; } int main () { Rectangle rect(5,6); cout << "area: " << rect.getWidth() * rect.getHeight() << endl; return 0; }