Προγραμματισμός Υπολογιστών με C++ ( 2012-13 ) 10η διάλεξη Ίων Ανδρουτσόπουλος http://www.aueb.gr/users/ion/ 1
Τι θα ακούσετε σήμερα Υπερφόρτωση των τελεστών εισόδου και εξόδου. Τάξεις και δυναμική καταχώριση μνήμης. Κατασκευαστές αντιγράφων. 2
Υπερφόρτωση του τελεστή << class Point { float x, y; public: Point(float xx = 0, float yy = 0) { x = xx; y = yy; friend void operator<<(ostream& os, const Point& p); ; // Το ostream είναι ροή εξόδου (π.χ. cout ή αρχείο εξόδου): void operator<<(ostream& os, const Point& p) { os << p.x << ", " << p.y; // Δεν μπορώ να γράψω απλά «x», «y». Point p1(1, 2); cout << p1; // Τυπώνει «1, 2». Το cout ανήκει στην τάξη ostream. // operator<<(cout, p1); // Ισοδύναμη με την προηγούμενη γραμμή. // cout << p1 << "test"; // Δεν δουλεύει, γιατί // το cout << p1 δεν επιστρέφει τίποτα. // Ισοδύναμη μορφή: (cout << p1) << "test"; 3
Βελτιωμένη υπερφόρτωση του << class Point { float x, y; public: Point(float xx = 0, float yy = 0) { x = xx; y = yy; friend ostream& operator<<(ostream& os, const Point& p); ; ostream& operator<<(ostream& os, const Point& p) { os << p.x << ", " << p.y; return os; Point p1(1, 2); cout << p1 << endl << "test" << endl; // OK. // Ισοδύναμη μορφή: (((cout << p1) << endl) << "test") << endl; 4
Υπερφόρτωση του τελεστή << Στην περίπτωση του τελεστή << δεν συνηθίζεται ο πρώτος τρόπος υπερφόρτωσης της προηγούμενης διάλεξης (προσθήκη μεθόδου). Γιατί εκείνος ο τρόπος απαιτεί να προσθέσουμε μια μέθοδο operator<< στην τάξη ostream (στην τάξη όπου ανήκει το αριστερό όρισμα του τελεστή), αλλά δεν θέλουμε να αναμιχθούμε στον κώδικα της τάξης ostream (στις βιβλιοθήκες της C++). Ενώ με τον τρόπο που χρησιμοποιήσαμε, προσθέσαμε μια νέα μορφή της συνάρτησης operator<<, που δεν είναι μέθοδος της ostream. Τη γράφουμε συνήθως στο.cpp της νέας τάξης (Point). Το ίδιο ισχύει και για τον τελεστή >>. 5
Παράδειγμα υπερφόρτωσης του >> class MultiPerson {... // Όπως στις διαφάνειες της 8ης διάλεξης. friend istream& operator>>(istream& is, MultiPerson& p); ; // Διαβάζει τα ονόματα του p από μια ροή εισόδου (π.χ. το cin ή // αρχείο εισόδου η ifstream είναι υπο-τάξη της istream). istream& operator>>(istream& is, MultiPerson& p) { for(unsigned i = 0; i < p.howmanynames; i++) { is >> p.names[i]; return is; MultiPerson p1(2), p2(3); ifstream myfile("file.txt"); myfile >> p1 >> p2; // Δουλεύει επειδή η 1η κλήση του τελεστή >> 6 // επιστρέφει ως αποτέλεσμα τη ροή εισόδου.
Τάξεις με δείκτες το πρόβλημα class MultiPerson { unsigned howmanynames; string* names; public: MultiPerson(unsigned num, string* namesin); friend ostream& operator<<(ostream& os, const MultiPerson& p); ; MultiPerson::MultiPerson(unsigned num, string* namesin) { howmanynames = num; names = namesin; // Η αιτία του προβλήματος. string namesarr[] = {"Γιάννης", "Ιωάννης"; MultiPerson mp(2, namesarr); namesarr[1] = "Γιάννος"; // Θέλουμε να μην επηρεάζεται το mp. cout << mp; // Πρόβλημα: τυπώνει «Γιάννης», «Γιάννος». 7
Τι συμβαίνει; Τι συμβαίνει τώρα: namesin namesarr Γιάννης Ιωάννης mp howmanynames 2 names Τι θέλουμε να συμβαίνει: mp howmanynames 2 names Αλλαγές στο namesarr δεν επηρεάζουν το mp. namesin names = namesin; Αλλαγές στον namesarr επηρεάζουν και το mp. namesarr Γιάννης Ιωάννης αντίγραφο του namesarr Γιάννης Ιωάννης Γιάννος 8
Τάξεις με δείκτες η λύση class MultiPerson { unsigned howmanynames; string* names; public: MultiPerson(unsigned num, const string namesin[]); ~MultiPerson() { delete []names; friend ostream& operator<<(ostream& os, const MultiPerson& p); ; MultiPerson::MultiPerson(unsigned num, const string* namesin) { howmanynames = num; names = new string[num]; for(unsigned i = 0; i < num; i++) { names[i] = namesin[i]; string namesarr[] = {"Γιάννης", "Ιωάννης"; MultiPerson mp(2, namesarr); namesarr[1] = "Γιάννος"; cout << mp; // OK: τυπώνει «Γιάννης», «Ιωάννης». 9
Η ανάγκη για κατ/στές αντιγράφων class MultiPerson { unsigned howmanynames; string* names; public: MultiPerson(unsigned num, const string namesin[]); ~MultiPerson() { delete []names; friend ostream& operator<<(ostream& os, const MultiPerson& p); friend void test(multiperson p) { // Μεταβίβαση κατά τιμή. p.names[0] = "Γιώργος"; // Θα έπρεπε να αλλάζει μόνο // το τοπικό αντίγραφο! ; string namesarr[] = {"Γιάννης", "Ιωάννης"; MultiPerson mp(2, namesarr); test(mp); cout << mp; // Χωρίς καταστροφέα, τυπώνει «Γιώργος», «Ιωάννης»! // Με καταστροφέα, απρόβλεπτη συμπεριφορά! 10
Τι συμβαίνει; Μέσα στη main(): Κατά την κατασκευή του mp, δημιουργείται ένα αντίγραφο του NamesArr. Το mp.names δείχνει στο αντίγραφο. mp howmanynames 2 names namesarr Γιάννης Ιωάννης Γιάννης Ιωάννης Μέσα στην test(): p howmanynames 2 names Κατά την καταστροφή του p στο τέλος της test(), γίνεται delete[] ο πίνακας στον οποίο δείχνουν τα p.names και mp.names. Δημιουργείται ένα τοπικό αντίγραφο p του mp. Οι μεταβλητές του p έχουν τις ίδιες τιμές με του mp. Το p.names δείχνει εκεί που έδειχνε και το mp.names. 11
Κατασκευαστής αντιγράφου class MultiPerson {... public:... MultiPerson(const MultiPerson& original); ; MultiPerson::MultiPerson(const MultiPerson& original) { howmanynames = original.howmanynames; // Δημιουργία αντιγράφου του πίνακα: names = new string[howmanynames]; for(unsigned i = 0; i < howmanynames; i++) { names[i] = original.names[i]; 12
Χρήση κατ/τή αντιγράφου string namesarr[] = {"Γιάννης", "Ιωάννης"; MultiPerson mp(2, namesarr); test(mp); // Μεταβίβαση κατά τιμή. Χρησιμοποιείται ο // κατασκευαστής αντιγράφου. cout << mp; // ΟΚ. Τώρα τυπώνει «Γιάννης», «Ιωάννης». MultiPerson mp2(mp); MultiPerson mp3 = mp; // Χρήση κατασκ/τή αντιγράφου. // Το mp3 δεν υπήρχε. Χρησιμο- // ποιείται ο κατ/τής αντιγράφου, όχι // η μέθοδος operator= (βλ. παρακάτω). Αν δεν ορίσουμε κατ/τή αντιγράφου, δημιουργείται αυτόματα ένας, ο οποίος αντιγράφει μία-μία τις μεταβλητές των αντικειμένων. Ο κατ/τής αντιγράφου καλείται αυτόματα και όπου χρειάζεται δημιουργία αντιγράφου (π.χ. μεταβίβαση ορίσματος κατά τιμή, επιστροφή αντικειμένου κατά τιμή). 13