Προγραμματισμός Υπολογιστών με C++ ( 2012-13 ) 8η διάλεξη Ίων Ανδρουτσόπουλος http://www.aueb.gr/users/ion/ 1
Τι θα ακούσετε σήμερα Προεπιλεγμένοι κατασκευαστές. Αντικείμενα και δυναμική καταχώριση μνήμης. Πίνακες αντικειμένων. Μέθοδοι και const. Καταστροφείς. 2
Κατασκευαστές class Person { string name; unsigned short year; public: void setname(const string& n) { name = n; void setyear(unsigned short y) { year = y; void print() { cout << name << ": " << year << endl; Person(const string& n, unsigned short y); // Κατασκευαστής ; Person::Person(const string& n, unsigned short y) { name = n; year = y; int main() { // Person p1; // Λάθος. Δεν επιτρέπεται πια! Person p2 = Person("Γιώργος", 1975); p2.print(); // Τυπώνει «Γιώργος: 1975». 3
Προεπιλεγμένος κατασκευαστής class Person { string name; unsigned short year; public: void setname(const string& n) { name = n; void setyear(unsigned short y) { year = y; void print() { cout << name << ": " << year << endl; Person(const string& n, unsigned short y) { name = n; year = y; Person() { name = "?"; year = 1980; // Προεπιλεγμένος κατ/τής ; int main() { Person p1; // OK. Χρησιμοποιεί τον προεπιλεγμένο κατασκευαστή. // Person p1 = Person(); // Ισοδύναμο με το προηγούμενο. Person p2 = Person("Γιώργος", 1975); p1.print(); // Τυπώνει «?: 1980». p2.print(); // Τυπώνει «Γιώργος: 1975». 4
Κατασκευαστές με προεπιλεγμένες τιμές class Person { string name; unsigned short year; public: void setname(const string& n) { name = n; void setyear(unsigned short y) { year = y; void print() { cout << name << ": " << year << endl; // Παίζει και το ρόλο προεπιλεγμένου κατασκευαστή: Person(const string& n = "?", unsigned short y = 1980) { name = n; year = y; ; int main() { Person p1; // OK. Χρησιμοποιεί το μοναδικό κατασκευαστή. // Person p1 = Person(); // Ισοδύναμο με το προηγούμενο. Person p2 = Person("Γιώργος", 1975); // ΟΚ. p1.print(); p2.print(); // Τυπώνει "?: 1980", "Γιώργος: 1975". 5
Τρόποι κλήσης κατασκευαστών Στατική καταχώριση μνήμης: Person p1; // Χρήση προεπιλεγμένου κατασκευαστή. // Πρέπει να υπάρχει! (Ή κανένας κατ/τής.) Person p2 = Person(); // Εναλλακτική χρήση προεπ/νου κατ/τή. X Person p3 = Person; // Δεν επιτρέπεται. Person p4 = Person("Γιώργος", 1975); // Χρήση άλλου κατ/τή. Person p5("γιώργος", 1975); // Συντόμευση του προηγουμένου. X Person p6(); // Δεν επιτρέπεται. Δυναμική καταχώριση μνήμης (χρειάζεται delete): Person* q1 = new Person(); // Χρήση προεπιλεγμένου κατ/τή. Person* q2 = new Person; // Συντόμευση του προηγουμένου. Person* q3 = new Person("Γιώργος", 1975); 6
Αντικείμενα και δυναμική καταχώριση μνήμης void somefunction() { Person p1, p2("γιάννης", 1975); Person* q1 = new Person; Person* q2 = new Person("Μαρία", 1984); p1.print(); p2.print(); // Τυπώνει «?: 1980», «Γιάννης: 1975». (*q1).print(); // Τυπώνει: «?: 1980». q1->print(); // Ισοδύναμο με το προηγούμενο. (*q2).print(); // Τυπώνει: «Μαρία: 1984». q2->print(); // Ισοδύναμο με το προηγούμενο. delete q1; delete q2; // Τα αντικείμενα που έχουν // δημιουργηθεί με new δεν καταστρέφονται // αυτόματα στο τέλος της συνάρτησης. 7
Πίνακες αντικειμένων Person p1[3]; // Χρήση προεπιλεγμένου κατασκευαστή. Person p2[] = { // Αυτόματος υπολογισμός αριθμού θέσεων. Person("Γιώργος", 1975), Person("Μαρία", 1980) ; Person p3[3] = { // Χρήση προεπιλεγμένου Person("Γιώργος", 1975), // κατασκευαστή για την 3η θέση. Person("Μαρία", 1980) ; Person* q = new Person[2]; // Δυναμική καταχώριση με χρήση // του προεπιλεγμένου κατασκευαστή. q[0].setname("γιώργος"); q[0].setyear(1975); 8
Μέθοδοι και const const Person p1; // Το p1 δεν μπορεί να αλλάξει. () print X p1.print(); // Ο μεταγλωττιστής δεν ξέρει ότι η // δεν αλλάζει το p1. class Person { // Βελτίωση για να αντιμετωπιστεί το πρόβλημα.... public:... void print() const { cout << name << ": " << year << endl;... ; p1.print(); // ΟΚ τώρα ξέρουμε ότι η print() δεν αλλάζει το p1. Μια const μέθοδος δεν επιτρέπεται να αλλάζει τις μεταβλητέςμέλη ή να καλεί μη-const μεθόδους του ίδιου αντικειμένου. 9
Η αναγκαιότητα των καταστροφέων class MultiPerson { unsigned howmanynames; // Άλλος αριθμός σε κάθε αντικείμενο. string* names; // Θα δείχνει στην 1η θέση δυναμικού πίνακα. public: MultiPerson(unsigned num = 1); void setname(unsigned i, const string& n) { names[i] = n; string getname(unsigned i) const { return names[i]; ; MultiPerson::MultiPerson(unsigned num) { howmanynames = num; names = new string[num]; // Δυναμική καταχώριση μνήμης. void f() { MultiPerson p(2); // Δύο θέσεις ονομάτων. p.setname(0, "Γιάννης"); p.setname(1, "Ιωάννης"); // Καταστρέφεται το p (μαζί και o δείκτης p.names), αλλά όχι ο // χώρος στον οποίο δείχνει το p.names... 10
Καταστροφείς class MultiPerson { unsigned howmanynames; string* names; public: MultiPerson(unsigned num = 1); ~MultiPerson(); // Καταστροφέας. void setname(unsigned i, const string& n) { names[i] = n; string getname(unsigned i) const { return names[i]; ; MultiPerson::~MultiPerson() { cout << "deleting" << endl; delete []names; void f() { MultiPerson p(2); p.setname(0, "Γιάννης"); p.setname(1, "Ιωάννης"); // ΟΚ, τώρα όταν τελειώνει η f καλείται ο καταστροφέας του p. 11
Περισσότερα για τους καταστροφείς Δεν τους καλούμε απευθείας στο πρόγραμμά μας. Για αντικείμενα που έχουν δημιουργηθεί με στατική καταχώριση μνήμης, ο καταστροφέας καλείται αυτόματα όταν το αντικείμενο παύει να υπάρχει (π.χ. στο τέλος της f στο προηγούμενο παράδειγμα). Για αντικείμενα που έχουν δημιουργηθεί με δυναμική καταχώριση μνήμης (με new), ο καταστροφέας καλείται όταν εκτελείται η εντολή delete (βλ. επόμενο παράδειγμα). 12
Καταστροφείς και delete void f() { MultiPerson* q = new MultiPerson(2); q->setname(0, "Γιάννης"); q->setname(1, "Ιωάννης");... delete q; // Καλείται ο καταστροφέας της MultiPerson για το // αντικείμενο στο οποίο δείχνει ο q. Ελευθερώνεται έτσι // ο χώρος του δυναμικού πίνακα ονομάτων του // αντικειμένου. Στη συνέχεια ελευθερώνεται και ο // χώρος που καταλάμβανε το ίδιο το αντικείμενο // (το αντικείμενο είχε δημιουργηθεί με new). 13