Professional Documents
Culture Documents
Αντικειμενοστραφής Προγραμματισμός Με Χρήση Της c++ Γκόγκος Χρήστος Πανεπιστήμιο Ιωαννίνων
Αντικειμενοστραφής Προγραμματισμός Με Χρήση Της c++ Γκόγκος Χρήστος Πανεπιστήμιο Ιωαννίνων
Αντικειμενοστραφή
Προγραμματισμό με C++
#1
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
2
Βασικές έννοιες Αντικειμενοστραφούς
Προγραμματισμού
• Μια βασική έννοια των αντικειμενοστραφών γλωσσών είναι η
ενθυλάκωση δεδομένων και συναρτήσεων μαζί σε μονάδες που
ονομάζονται αντικείμενα.
• Ένα αντικείμενο αποτελείται από:
• Όνομα: αποτελεί τον τρόπο με τον οποίο γίνεται η αναφορά στα αντικείμενα μέσα
στο πρόγραμμα.
• Μέλη δεδομένα (member data): τα δεδομένα που περιέχονται σε ένα αντικείμενο.
• Μέλη συναρτήσεις (member functions): συναρτήσεις που επιδρούν στα δεδομένα
του αντικειμένου.
• Διεπαφή (interface): Καθορίζει τους τρόπους με τους οποίους ο προγραμματιστής
μπορεί απευθείας να προσπελαύνει μέλη δεδομένα και μέλη συναρτήσεις ενός
αντικειμένου.
Κλάσεις
• Η κλάση είναι μια ακόμα θεμελιώδης έννοια στον
αντικειμενοστραφή προγραμματισμό και μπορεί να περιγραφεί ως
το «αρχιτεκτονικό σχέδιο» που προσδιορίζει ένα νέο τύπο
αντικειμένων.
• Η κλάση καθορίζει:
• τα δεδομένα, τις συναρτήσεις και τη διεπαφή των αντικειμένων της κλάσης.
• το πως τα αντικείμενα μιας κλάσης συμπεριφέρονται παρέχοντας κώδικα
που υλοποιεί τις συναρτήσεις που σχετίζονται με την κλάση.
• Ο προγραμματιστής μπορεί να δημιουργεί ένα ή περισσότερα
αντικείμενα μιας κλάσης
4
Πως ορίζεται και χρησιμοποιείται μια κλάση
σε ένα πρόγραμμα;
• Δήλωση της κλάσης: επιλογή του τι θα αποθηκεύουν (μέλη
δεδομένων) και πως θα συμπεριφέρονται (μέλη συναρτήσεων) τα
αντικείμενα της κλάσης.
• Ορισμός των μελών συναρτήσεων: παρέχεται υλοποίηση για κάθε
μέλος συνάρτηση της κλάσης.
• Χρήση της κλάσης για τη δημιουργία αντικειμένων: δήλωση νέων
στιγμιοτύπων αντικειμένων της κλάσης όπως δηλώνονται και οι
απλές μεταβλητές.
class Circle {
public: // διεπαφή (interface)
void SetRadius(double r); // θέτει το μέλος δεδομένων radius στην τιμή r
double AreaOf(); // επιστρέφει το εμβαδόν του κύκλου
private:
double radius; // ακτίνα του κύκλου
};
6
Ορισμός των συναρτήσεων μελών
• Υπάρχουν 2 τρόποι με τους οποίους μπορεί να καθοριστεί ο κώδικας που
περιέχουν οι συναρτήσεις μέλη μιας κλάσης:
• Μέσα στη δήλωση της κλάσης
• Μετά τη δήλωση της κλάσης
• Η αναφορά σε μια συνάρτηση μέλος γίνεται ως εξής:
όνομαΚλάσης::όνομαΣυνάρτησηςΜέλους
• Το αναγνωριστικό όνομαΚλάσης::όνομαΣυνάρτησηςΜέλους αναφέρεται
στη συνάρτηση μέλος όνομαΣυνάρτησηςΜέλους της κλάσης
όνομαΚλάσης
• Ο τελεστής :: ονομάζεται τελεστής προσδιορισμού εμβέλειας (scope
resolution operator)
• Μετά τη δήλωση της κλάσης, οι συναρτήσεις μέλη ορίζονται όπως
οποιαδήποτε άλλη συνάρτηση.
7
Αντικείμενα
• Από τη στιγμή που μια κλάση έχει δηλωθεί και οριστεί, μπορούν να δηλώνονται
και να χρησιμοποιούνται αντικείμενα της κλάσης όπως οποιοσδήποτε άλλος
τύπος δεδομένων
• Ο προγραμματιστής μπορεί να δηλώνει ένα αντικείμενο με τον ακόλουθο τρόπο:
• ΌνομαΚλάσης όνομαΑντικειμένου;
• Η παραπάνω δήλωση δημιουργεί ένα αντικείμενο βάσει των «οδηγιών» που
περιέχονται στην κλάση ΌνομαΚλάσης και το αντικείμενο μπορεί να αναφερθεί
με το αναγνωριστικό όνομαΑντικειμένου
• Ο τελεστής . (dot operator) μπορεί να χρησιμοποιηθεί για να προσπελαστούν τα
δημόσια μέλη ενός αντικειμένου.
• Η μορφή με την οποία γίνεται η αναφορά στα μέλη ενός αντικειμένου είναι:
• όνομαΑντικειμένου.ΜέλοςΣυνάρτηση()
• όνομαΑντικειμένου.ΜέλοςΔεδομένων
10
Αντικείμενα
int main() {
Circle C1;
Circle C2;
C1.SetRadius(2);
C2.SetRadius(3);
cout<<"The area of C1 is "<<C1.AreaOf()<<endl;
cout<<"The area of C2 is "<<C2.AreaOf()<<endl;
return 0;
}
11
Ο κώδικας συνολικά
• Ο κώδικας του sample1.cpp
• Δηλώνει την κλάση Circle και ορίζει τα μέλη της και τη διεπαφή της.
• Ορίζει την υλοποίηση των συναρτήσεων μελών της κλάσης Circle.
• Δηλώνει 2 αντικείμενα της κλάσης Circle με ονόματα C1 και C2.
• Χρησιμοποιεί τις διεπαφές των C1 και C2 για να αποθηκεύσει τις ακτίνες των
2 κύκλων και στη συνέχεια για να υπολογίσει το εμβαδό τους.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect1/sample1.cpp
12
Σύνοψη
• Ένα αντικείμενο είναι μια μονάδα που ενθυλακώνει δεδομένα και
συναρτήσεις. Έχει 4 στοιχεία: όνομα, μέλη δεδομένα, μέλη συναρτήσεις
και διεπαφή.
• Μια κλάση καθορίζει την ορισμένη από το χρήστη μορφή των
αντικειμένων.
• Η χρήση των αντικειμένων σε ένα C++ πρόγραμμα ακολουθεί τη σειρά:
δήλωση, ορισμός και χρήση.
• Ο τελεστής :: χρησιμοποιείται έτσι ώστε να οριστούν οι συναρτήσεις μιας
κλάσης εκτός της κλάσης.
• Ο τελεστής . χρησιμοποιείται για να κληθεί μια συνάρτηση μέλος ή να
προσπελαστούν τα μέλη δεδομένων ενός αντικειμένου.
13
V 1.01
Επίπεδα προστασίας
• Όταν σχεδιάζουμε μια κλάση μπορούμε να ελέγξουμε το πως θα προσπελαύνονται τα μέλη δεδομένα και
τα μέλη συναρτήσεις της κλάσης.
• Σε κάθε μέλος της κλάσης ανατίθεται ένα επίπεδο προστασίας:
• Δημόσια μέλη: Δεδομένα και συναρτήσεις των αντικειμένων που μπορούν να προσπελαστούν τόσο εκτός όσο και εντός
των μελών συναρτήσεων της κλάσης.
• Ιδιωτικά μέλη: Δεδομένα και συναρτήσεις των αντικειμένων που μπορούν να προσπελαστούν μόνο εντός των μελών
συναρτήσεων της κλάσης.
• Δηλαδή, ο κώδικας που υλοποιεί την κλάση έχει πρόσβαση στα ιδιωτικά δεδομένα και συναρτήσεις, ενώ ο κώδικας που χρησιμοποιεί
την κλάση δεν έχει πρόσβαση στα ιδιωτικά δεδομένα και συναρτήσεις
• Ο κύριος στόχος των επιπέδων προστασίας είναι να επιτρέπει στους προγραμματιστές να παρέχουν μια
δημόσια διεπαφή για την κλάση, ενώ παράλληλα να είναι σε θέση να αλλάζουν την υλοποίηση της κλάσης
χωρίς να δημιουργούνται προβλήματα σε κώδικα που χρησιμοποιεί την κλάση.
• Πρόκειται για έναν μηχανισμό μέσω του οποίου υλοποιείται η απόκρυψη πληροφορίας.
#include <iostream>
sample1.cpp
class Fraction {
public:
void SetNumerator(int n);
void SetDenominator(int d);
double ToDecimal();
• Έξοδος προγράμματος:
private:
int numer;
Decimal: 0.5
• Τι θα συμβεί αν στη main
int denom;
};
void Fraction::SetNumerator(int n) {
numer = n; // παρατηρήστε ότι προσπελαύνουμε το ιδιωτικό μέλος numer εδώ αφαιρέσουμε τα σχόλια από
}
void Fraction::SetDenominator(int d) {
την εντολή:
if (d != 0) F.numer = 1;
denom = d;
else $ g++ sample1.cpp
denom = 1;
}
sample1.cpp: In function 'int main()':
double Fraction::ToDecimal() { sample1.cpp:37:7: error: 'int Fraction::numer' is private within this context
return (double)numer / denom; F.numer = 1;
} ^~~~~
int main() { sample1.cpp:11:9: note: declared private here
Fraction F; int numer;
// F.numer = 1;
F.SetNumerator(1);
^~~~~
F.SetDenominator(2);
std::cout << "Decimal: " << F.ToDecimal() << std::endl;
return 0;
} 3
https://github.com/chgogos/oop/blob/master/various/COP3330/lect2/sample1.cpp
#include <iostream>
5
Κατασκευαστές (1/2)
• Ο κατασκευαστής είναι μια ειδική συνάρτηση μέλος που συνήθως
έχει ως σκοπό την αρχικοποίηση των μελών ενός αντικειμένου.
• Ο κατασκευαστής είναι εύκολο να αναγνωριστεί διότι:
• Έχει το ίδιο όνομα με την κλάση. class Circle {
https://github.com/chgogos/oop/blob/master/various/COP3330/lect2/circle.cpp 6
Κατασκευαστές (2/2)
• Οι κατασκευαστές είναι συναρτήσεις, συνεπώς μπορούν να περιέχουν
οποιοδήποτε κώδικα όπως και οι άλλες συναρτήσεις.
• Τι είναι ιδιαίτερο για τους κατασκευαστές;
• Καλούνται αυτόματα όταν δημιουργείται ένα στιγμιότυπο ενός αντικειμένου (π.χ.
Circle C1;)
• Η συνάρτηση κατασκευαστής δεν μπορεί να κληθεί όπως οι άλλες συναρτήσεις
χρησιμοποιώντας τον τελεστή . (π.χ. C1.Circle();)
• Ο όρος προκαθορισμένος κατασκευαστής (default constructor)
αναφέρεται σε έναν κατασκευαστή χωρίς παραμέτρους.
• Κάθε κλάση θα πρέπει να έχει έναν κατασκευαστή. Αν δεν γραφεί από τον
προγραμματιστή τότε ένας κενός προκαθορισμένος κατασκευαστής
δημιουργείται από τη γλώσσα.
7
Πέρασμα παραμέτρων σε έναν
κατασκευαστή
• Γίνεται απλά προσθέτοντας (…) ως τμήμα της δημιουργίας του
στιγμιοτύπου του αντικειμένου
Circle C1(5); /* δηλώνει ένα νέο αντικείμενο Circle με το όρισμα 5 να περνά στο
δεύτερο κατασκευαστή της Circle στη παράμετρο r */
class Circle {
public:
Circle(); // αυτός είναι ένας κατασκευαστής
Circle(double r); // και αυτός είναι ένας κατασκευαστής
void setCenter(double x, double y);
void SetRadius(double r);
void Draw();
double AreaOf();
private:
double radius;
double center_x;
double center_y;
};
Σύνοψη (ερωτήσεις)
• Για ποια επίπεδα προστασίας μιλήσαμε;
• Γιατί χρησιμοποιούμε επίπεδα προστασίας;
• Ποιο τμήμα του προγράμματος μπορεί να προσπελάσει τα ιδιωτικά μέλη
(δεδομένα ή συναρτήσεις);
• Ποιο τμήμα του προγράμματος δεν μπορεί να προσπελάσει τα ιδιωτικά μέλη;
• Τι συμβαίνει όταν ένα πρόγραμμα προσπαθεί να προσπελάσει με μη έγκυρο
τρόπο ιδιωτικά μέλη της κλάσης;
• Τι είναι ένας κατασκευαστής;
• Πως καταλαβαίνουμε ότι μια συνάρτηση είναι κατασκευαστής μιας κλάσης;
• Τι είναι ο προκαθορισμένος κατασκευαστής;
• Πως περνάμε ορίσματα σε έναν κατασκευαστή;
9
Περιβάλλον Unix και
μεταγλώττιση
#3
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
2
Βασικές εντολές στο Linux
ls Λίστα με όλα τα αρχεία και τους καταλόγους στον τρέχοντα κατάλογο
cd <dir> Αλλαγή του τρέχοντος καταλόγου σε <dir>
cd .. Αλλαγή του τρέχοντος καταλόγου ένα επίπεδο πάνω στην ιεραρχία καταλόγων
cd ~ Αλλαγή του τρέχοντος καταλόγου στο home κατάλογο
mkdir <dir> Δημιουργία του υποκαταλόγου <dir> στον τρέχοντα κατάλογο
rm <filename> Διαγραφή του αρχείου <filename>
rm -r <dir> Διαγραφή του καταλόγου <dir> των περιεχομένων του και όλων των υποκαταλόγων του
cat <filename> Έξοδος του κειμένου του αρχείου <filename> στην οθόνη
chmod +x <filename> Αλλαγή των ιδιοτήτων του αρχείου <filename> έτσι ώστε να επιτρέπεται η εκτέλεσή του.
more <filename> Παρόμοιο με το cat, επιτρέπει σκρολάρισμα της οθόνης
man <keyword> Εμφάνιση της σελίδας του εγχειριδίου (manual) για διάφορες προγραμματιστικές έννοιες
και έννοιες του unix
4
Projects πολλαπλών αρχείων (1/2)
• Αν και είναι δυνατόν οποιοδήποτε πρόγραμμα να γραφεί σε ένα
μόνο αρχείο, συχνά τα προγράμματα διαμερίζονται σε πολλά αρχεία:
• Αρχείο επικεφαλίδας (header): περιέχει τη δήλωση της κλάσης, συνήθως
έχει ως επέκταση .hpp (π.χ. circle.hpp).
• Αρχείο υλοποίησης: περιέχει τον ορισμό της κλάσης, δηλαδή της υλοποίησης
των συναρτήσεων μελών της κλάσης, συνήθως έχει ως επέκταση .cpp (π.χ.
circle.cpp).
• Αρχείο οδηγός: περιέχει την main() και πιθανώς άλλες συναρτήσεις που
χρησιμοποιούνται στο πρόγραμμα, συνήθως έχει ως επέκταση .cpp (π.χ.
main.cpp).
6
Μεταγλώττιση
• Μεταγλώττιση είναι η διαδικασία μετατροπής ενός αρχείου από τη
C++ γλώσσα σε γλώσσα που μπορεί να «καταλάβει» ο υπολογιστής,
δηλαδή σε γλώσσα μηχανής.
• Η μεταγλώττιση εμπεριέχει δύο κύριες φάσεις:
• φάση μεταγλώττισης που είναι υπεύθυνη για τη μετάφραση της γλώσσας.
• φάση σύνδεσης που είναι υπεύθυνη για την επίλυση των αναφορών μεταξύ
επιμέρους αρχείων.
8
Φάση σύνδεσης (link phase)
• Συνδέει τα αρχεία αντικείμενα σε ένα εκτελέσιμο πρόγραμμα.
• Η φάση σύνδεσης είναι η φάση κατά την οποία οι κλήσεις
συναρτήσεων συνδέονται με τους ορισμούς (υλοποιήσεις) των
συναρτήσεων, και ο μεταγλωττιστής ελέγχει ότι υπάρχει ένας και
μόνο ένας ορισμός για κάθε συνάρτηση που καλείται.
• Όλες οι συναρτήσεις μέλη θα πρέπει να έχουν οριστεί κατά τη
διάρκεια αυτής της φάσης.
• Θα πρέπει να υπάρχει μια main() συνάρτηση έτσι ώστε το εκτελέσιμο
να γνωρίζει από που να ξεκινήσει.
https://github.com/chgogos/oop/tree/master/various/COP3330/lect4/sample1
• Μια λύση στη C++ είναι η χρήση της δεσμευμένης λέξης friend.
4
Η δεσμευμένη λέξη friend
• Η δεσμευμένη λέξη friend επιτρέπει σε μια κλάση να χορηγήσει πλήρη
πρόσβαση σε μια εξωτερική οντότητα.
• Πλήρης πρόσβαση σημαίνει δυνατότητα προσπέλασης και των ιδιωτικών μελών της
κλάσης.
• Εξωτερική οντότητα μπορεί να είναι μια συνάρτηση, ή ακόμα και μια άλλη κλάση.
• Για να αποδοθεί η ιδιότητα friend, θα πρέπει να προστεθεί η δήλωση
friend μέσα στη δήλωση της κλάσης, και να ακολουθήσει η αντίστοιχη
οντότητα.
• Μια friend οντότητα δεν είναι ούτε δημόσια ούτε ιδιωτική, καθώς δεν πρόκειται για
μέλος της κλάσης, οπότε δεν έχει σημασία αν θα τοποθετηθεί σε τμήμα public ή
private της κλάσης.
• Μια friend συνάρτηση σε μια κλάση έχει πλήρη πρόσβαση στα ιδιωτικά μέλη της
κλάσης.
https://github.com/chgogos/oop/tree/master/various/COP3330/lect4/sample2_friend
6
Παράδειγμα με friend συναρτήσεις (2/2)
class Fraction #include <iostream>
{ #include "Fraction.hpp"
... using namespace std;
friend bool Equals(Fraction x, Fraction y); int main() {
friend Fraction Add(Fraction x, Fraction y); Fraction f1(1, 2);
}; Fraction f2(2, 4);
if (Equals(f1, f2))
Fraction.hpp cout << "The fractions are equal" << endl;
Fraction f3 = Add(f1, f2);
... f3.Show();
bool Equals(Fraction x, Fraction y) { return 0;
if (x.numer * y.denom == y.numer * x.denom) }
return true;
else main.cpp
return false;
}
$ g++ Fraction.cpp main.cpp -o main
Fraction Add(Fraction x, Fraction y) { $ ./main
int num = x.numer * y.denom + y.numer * x.denom; The fractions are equal
int denom = x.denom * y.denom;
8/8
Fraction answer(num, denom);
return answer;
}
7
Fraction.cpp
8
Παράδειγμα με συναρτήσεις μέλη αντί για
friend συναρτήσεις
bool Fraction::Equals(Fraction other) {
class Fraction { Fraction.hpp return numer * other.denom == other.numer * denom;
public: }
...
bool Equals(Fraction other); Fraction Fraction::Add(Fraction other) {
Fraction Add(Fraction other); int n = numer * other.denom + other.numer * denom;
... int d = denom * other.denom;
}; return Fraction(n, d);
main.cpp }
#include <iostream>
#include "Fraction.hpp"
Fraction.cpp
using namespace std;
int main() {
Fraction f1(1, 2); The fractions are equal
Fraction f2(2, 4);
if (f1.Equals(f2))
11/14
cout << "The fractions are equal" << endl; Decimal value: 0.785714
f2.SetValue(2, 7);
Fraction f3 = f1.Add(f2);
f3.Show(); https://github.com/chgogos/oop/tree/master/
cout << "Decimal value: " << f3.Evaluate() << endl;
various/COP3330/lect4/sample3_member
return 0;
} 9
11
Κατασκευαστές μετατροπής
• Ένας κατασκευαστής μετατροπής είναι ένας κατασκευαστής με μια παράμετρο.
• Καθώς ο κατασκευστής δημιουργεί/αρχικοποιεί ένα νέο αντικείμενο, μπορούμε να
χρησιμοποιήσουμε έναν κατασκευαστή μετατροπής για να μετατρέψουμε μια μεταβλητή με
τύπο τον τύπο της παραμέτρου σε ένα νέο αντικείμενο.
• Παράδειγμα κατασκευαστή μετατροπής:
Fraction(int n); /* μπορεί να χρησιμοποιηθεί για να μετατρέψει έναν ακέραιο σε
Fraction, αρχικοποιεί το κλάσμα σε n/1 */
12
Κατασκευαστής μετατροπής
• Ένας κατασκευαστής με πολλές παραμέτρους μπορεί να είναι
κατασκευαστής μετατροπής αν όλες, πλην μιας, από τις
παραμέτρους είναι προαιρετικές (optional):
Fraction(int n, int d=1);
• Η αυτόματη μετατροπή τύπων από κατασκευαστές μπορεί να
αποτραπεί χρησιμοποιώντας τη δεσμευμένη λέξη explicit στην αρχή
της δήλωσης:
explicit Fraction(int n);
• Ο ανωτέρω κατασκευαστής δεν θα πραγματοποιεί πλέον αυτόματη
μετατροπή ακεραίων σε Fraction.
13
https://github.com/chgogos/oop/tree/master/various/COP3330/lect4/sample4_conversion 14
Καταστροφείς (destructors)
• Eπιπρόσθετα στις ειδικές συναρτήσεις των κατασκευαστών, κάθε κλάση διαθέτει και
μια ειδική συνάρτηση που ονομάζεται καταστροφέας (destructor).
• Ο καταστροφέας μοιάζει με τον προκαθορισμένο κατασκευαστή (δηλαδή τον
κατασκευαστή χωρίς παραμέτρους) αλλά έχει το σύμβολο ~ πριν το όνομά του.
• Οι καταστροφείς δεν μπορούν να έχουν παραμέτρους, άρα υπάρχει ένας μόνο
καταστροφέας ανά κλάση.
• Για παράδειγμα ο καταστροφέας της κλάσης Fraction θα είναι: ~Fraction();
• Όπως και με τους κατασκευαστές, οι καταστροφείς καλούνται αυτόματα (όχι ρητά).
• Οι καταστροφείς καλούνται αυτόματα ακριβώς πριν το αντικείμενο αποδεσμευτεί από
το σύστημα, συνήθως όταν βγαίνει εκτός εμβέλειας (δηλαδή, όταν δεν είναι πλέον
προσπελάσιμο από τον προγραμματιστή).
• Η τυπική εργασία ενός καταστροφέα είναι να πραγματοποιεί όποιες εργασίες
αποδέσμευσης πόρων (συνήθως μνήμης) απαιτούνται, πριν το αντικείμενο
αποδεσμευτεί.
15
Παράδειγμα καταστροφέα
#include <iostream>
#include <string>
using std::string;
class Thing {
public:
Thing(const char *n);
~Thing();
private:
string name;
Constructor running for T1
}; Hello
Thing::Thing(const char *n) {
name = n;
Constructor running for Tfoo
std::cout << "Constructor running for " << name << std::endl; Destructor running for Tfoo
}
Thing::~Thing() {
Constructor running for T2
std::cout << "Destructor running for " << name << std::endl; Destructor running for T2
}
void foo() {
Destructor running for T1
Thing TFoo("Tfoo");
}
int main() {
Thing T1("T1");
std::cout << "Hello" << std::endl;
foo();
Thing T2("T2");
return 0;
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect4/sample5_destructor/destructor.cpp 16
Ερωτήσεις σύνοψης
• Ποιος είναι ο ρόλος της δεσμευμένης λέξη friend σε μια κλάση;
• Μια συνάρτηση που έχει γίνει friend σε μια κλάση είναι public ή
private;
• Ποια είναι η διαφορά ανάμεσα στις δύο ακόλουθες κλήσεις:
• f3 = f1.Add(f2);
• f3 = Add(f1,f2);
• Τι είναι ο κατασκευαστής μετατροπής;
• Πώς ακυρώνουμε τις αυτόματες μετατροπές που γίνονται από έναν
κατασκευαστή μετατροπής;
17
https://github.com/chgogos/oop/blob/master/various/COP3330/lect5/t1.cpp 2
const
• Σε ένα άλλο παράδειγμα (t2.cpp):
int main()
g++ t2.cpp
{
t2.cpp:6:11: error: assigning to 'int *' from incompatible type 'const int *'
const int x = 5;
x_ptr = & x;
int *x_ptr;
^~~
x_ptr = & x;
1 error generated.
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect5/t2.cpp 3
Επισκόπηση: const παράμετροι
• Στη C++ υπάρχουν δύο τρόποι με τους οποίους μπορούν να περάσουν
ορίσματα στις συναρτήσεις:
• Πέρασμα με τιμή (call by value): Ένα αντίγραφο του ορίσματος δημιουργείται και η
συνάρτηση επενεργεί σε αυτό το αντίγραφο.
• Πέρασμα με αναφορά (call by reference): Δεν δημιουργείται αντίγραφο, η
παράμετρος της συνάρτησης είναι ένα αναγνωριστικό που αναφέρεται στα ίδια
δεδομένα με το όρισμα (η παράμετρος λειτουργεί ως ένα ψευδώνυμο για το
όρισμα).
• Η μέθοδος που χρησιμοποιείται για το πέρασμα των ορισμάτων
υποδηλώνεται από τις παραμέτρους της συνάρτησης:
void foo(int x); // το x περνά με τιμή
void foo(int &x); // το x περνά με αναφορά
• Ποια είναι η κύρια διαφορά ανάμεσα στους δύο αυτούς μηχανισμούς
περάσματος παραμέτρων (δείτε το t3.cpp);
https://github.com/chgogos/oop/blob/master/various/COP3330/lect5/t3.cpp 4
https://github.com/chgogos/oop/blob/master/various/COP3330/lect5/t4.cpp 5
Πέρασμα αντικειμένων με const αναφορά
• Τα αντικείμενα μπορούν επίσης να περάσουν με const αναφορά
έτσι ώστε να αποφευχθεί η επιβάρυνση αντιγραφής:
friend Fraction Add(const Fraction& f1, const Fraction& f2);
https://github.com/chgogos/oop/tree/master/various/COP3330/lect5/const_fraction 8
{
// κώδικας
}
• Η παραπάνω λίστα αρχικοποίησης θα θέσει το const_member_var1 και
το member_var2 στις τιμές που περνάνε στον κατασκευαστή ως p1 και p2
αντίστοιχα.
10
Ερωτήσεις σύνοψης
• Περιγράψτε το πέρασμα με τιμή και το πέρασμα με αναφορά.
• Γιατί χρησιμοποιούμε το πέρασμα με αναφορά;
• Σε τι αναφέρεται ο όρος «καλών αντικείμενο»;
• Τι είναι μια const συνάρτηση μέλος;
• Τι είναι ένα const αντικείμενο;
• Πώς το αρχικοποιούμε;
• Τι είναι const μέλος δεδομένων;
• Πώς το αρχικοπoιούμε;
• Μέσω ποιου μηχανισμού το const επιβάλλεται;
11
Υπερφόρτωση τελεστών
#6
Τμήμα Πληροφορικής και Τηλεπικοινωνιών
Πανεπιστήμιο Ιωαννίνων (Άρτα)
Γκόγκος Χρήστος
Τελεστές
• Υπάρχουν πολλοί διαθέσιμοι τελεστές (+, -, *, /, ==, !=, <<, >>) που
λειτουργούν με τους ενσωματωμένους τύπους (π.χ. int, double).
• Μπορούμε να φανταστούμε ότι οι τελεστές αυτοί υλοποιούνται με
κλήσεις συναρτήσεων που γίνονται αυτόματα από τη C++:
• Για παράδειγμα, η εντολή:
int x = 1 + 2;
• Μπορεί να αντικατασταθεί αυτόματα από μια κλήση συνάρτησης όπως η
ακόλουθη:
int x = operator+(1,2);
• Με το ακόλουθο πρωτότυπο:
int operator+(int p1, int p2); /* επιστρέφει το άθροισμα των
p1 και p2 */
2
Τελεστές
• Ένα ακόμα παράδειγμα:
double var = 1.1;
double x = 4.3 * 2.1 - var;
• Θα μπορούσε να αντικατασταθεί αυτόματα από τις εξής κλήσεις
συναρτήσεων:
double x = operator-(operator*(4.3, 2.1), var);
• Με τα ακόλουθα πρωτότυπα:
double operator*(double p1, double p2);
double operator-(double p1, double p2);
• H C++ υποστηρίζει την υπερφόρτωση τελεστών που επιτρέπει σε
γνώριμους τελεστές, όπως το +, να χρησιμοποιούνται με τύπους
ορισμένους από τον χρήστη (όπως είναι τα αντικείμενα).
Παράδειγμα κίνητρο
• Παράδειγμα 1
• Παρατηρήστε τη χρήση των αριθμητικών τελεστών στο ακόλουθο παράδειγμα:
int x = 3, y = 6, z;
float a=3.4, b = 2.1, c;
z = x + y;
c = a / b;
• Έστω ότι επιθυμούμε να πραγματοποιούμε πράξεις με αντικείμενα Fraction με τον ίδιο
γνώριμο τρόπο:
1/2 + 3/4;
2/3 - 1/3;
• Θα μπορούσαμε να το πετύχουμε αυτό χρησιμοποιώντας την κλάση Fraction ως εξής;
Fraction f1(1,2), f2(3,4), f3;
f3 = f1 + f2; /* θα το δεχθεί ο μεταγλωττιστής; */
• Θα πρέπει να είναι σαφές ότι το παραπάνω δεν έχει νόημα για τον μεταγλωττιστή απευθείας. Το
Fraction είναι ένας τύπος που έχει οριστεί από τον χρήστη. Πώς θα μπορούσε να γνωρίζει ο
υπολογιστής περί κοινών παρονομαστών κ.λπ.;
• Ωστόσο, θα ήταν καλό να μπορούσαμε να χρησιμοποιούμε τα αντικείμενα Fraction με τον ίδιο τρόπο με
τον οποίο χρησιμοποιούμε και τους άλλους αριθμητικούς τύπους (όπως το int και το double).
4
Παράδειγμα κίνητρο
• Παράδειγμα 2:
• Θεωρείστε την έξοδο του ακόλουθου κώδικα που γνωρίζουμε ότι είναι
έγκυρη:
int x = 5;
cout << x;
• Τι θα συμβεί όμως στην ακόλουθη περίπτωση;
Fraction f(3,4);
cout << “The fraction f = ” << f << endl;
• Πάλι, θα πρέπει να είναι σαφές ότι ο μεταγλωττιστής δεν θα μπορούσε να γνωρίζει πως
το αντικείμενο Fraction θα έπρεπε να εμφανίζεται στην οθόνη.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect6/example1.cpp 6
Λεπτομέρειες σχετικά με την υπερφόρτωση
τελεστών (1/2)
• Η υπερφόρτωση τελεστών έχει τους ακόλουθους περιορισμούς:
• Η υπερφόρτωση ενός τελεστή δεν αλλάζει την προτεραιότητά του.
• a + b * c θα είναι πάντα ισοδύναμο με a + (b * c)
• Η υπερφόρτωση ενός τελεστή δεν αλλάζει την προσεταιριστικότητά του.
• a + b + c θα είναι πάντα ισοδύναμο με (a + b) + c
• Η υπερφόρτωση ενός τελεστή δεν μπορεί να αλλάξει την πληθικότητα του
(δηλαδή, τον αριθμό των τελεστέων του)
• Η συνάρτηση που πραγματοποιεί το a+b θα έχει πάντα 2 παραμέτρους, δεν γίνεται να
γίνει η πράξη a+b+c στη συνάρτηση.
• Δεν είναι δυνατόν να κατασκευαστούν νέοι τελεστές, μόνο νέες εκδόσεις των
ήδη υπαρχόντων.
• Η λειτουργία των τελεστών για τους ενσωματωμένους τύπους δεν αλλάζει.
8
Υπερφόρτωση αριθμητικών τελεστών (1/3)
• Επίδειξη της πρόσθεσης κλασμάτων υπεφορτώνοντας τον τελεστή +.
• Επιθυμούμε να επιτύχουμε το:
Fraction f1, f2(1,2), f3(3,4);
f1 = f2 + f3;
• H δεύτερη σειρά του παραπάνω κώδικα μεταφράζεται σε:
f1 = f2.operator+(f3); // υπερφόρτωση συνάρτησης μέλους
https://github.com/chgogos/oop/tree/master/various/COP3330/lect6/fraction_alone_overload
11
• Στη συνέχεια παρουσιάζονται οι κλήσεις των συναρτήσεων για κάθε μια από τις παραπάνω
περιπτώσεις:
Fraction f1, f2;
if (Equals(f1,f2)) cout << “Equals”;
vs.
12
Υπερφόρτωση συγκριτικών τελεστών
• Για να μπορούν να χρησιμοποιηθούν όλοι οι συγκριτικοί τελεστές, θα
χρειαστεί να υπερφορτώσουμε και τους 6:
• operator==
• operator!=
• operator<
• operator>
• operator<=
• operator>=
• Εναλλακτικά μπορούν να υπερφορτωθούν μόνο οι operator== και
operator< και να συμπληρωθεί:
#include <utility>
using namespace std::rel_ops;
13
14
Υπερφόρτωση τελεστών stream (2/4)
• Γιατί η πρώτη παράμετρος είναι ostream&;
• Το cout είναι ένα αντικείμενο τύπου ostream. Καθώς η κλήση έχει τη μορφή cout <<
str, η πρώτη παράμετρος στην υπερφόρτωση είναι η cout και η δεύτερη η str.
• Γιατί η πρώτη παράμετρος δεν είναι const;
• To cout αναπαριστά την έξοδο στην οθόνη του Η/Υ. Αλλάζοντας την έξοδο στην
οθόνη σημαίνει ότι η cout θα πρέπει να μπορεί να αλλάξει, άρα δεν μπορεί να είναι
const.
• Γιατί η συνάρτηση επιστρέφει ένα ostream&, δεν θα έπρεπε να είναι void;
• Αυτό συμβαίνει έτσι ώστε να μπορούν να υποστηριχθούν διαδοχικές κλήσεις
(cascading calls). Για παράδειγμα:
• cout << s1 << s2 << s3; ➔ operator<<(cout, s1) << s2 << s3; ➔
cout << s2 << s3; ➔ operator<<(cout, c2) << s3; ➔ cout << s3; ➔
operator<<(cout, s3) ➔ cout; ➔ ;
15
16
Υπερφόρτωση τελεστών stream (4/4)
• Στη συνέχεια παρουσιάζονται τα prototypes για την υπερφόρτωση
των τελεστών εισαγωγής και εξαγωγής στην κλάση Fraction.
friend ostream& operator<<(ostream& os, const Fraction& f);
friend istream& operator>>(istream& is, Fraction& f);
https://github.com/chgogos/oop/tree/master/various/COP3330/lect6/fraction_alone_overload 17
Ερωτήσεις σύνοψης
• Τι είναι η υπερφόρτωση τελεστών;
• Πως μπορεί να γίνει η υπερφόρτωση του τελεστή << σε μια κλάση
που αναπτύσσουμε;
• Αναφέρατε τρεις περιορισμούς της υπερφόρτωσης τελεστών.
• Αναφέρατε δύο πιθανούς τρόπους με τους οποίους μπορεί να γίνει η
υπερφόρτωση του τελεστή +.
18
Σύνθεση (composition)
#7
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
https://github.com/chgogos/oop/blob/master/various/COP3330/lect7/poker_hand.cpp
3
#include <iostream>
using namespace std;
καλείται ο προκαθορισμένος
small_class sc;
};
κατασκευαστής για το μέλος large_class::large_class() {
δεδομένων.
cout << "large_class constructor called " << endl;
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect7/sample1.cpp
4
#include <iostream>
using namespace std;
class small_class {
Κατασκευαστές
public:
small_class();
small_class(int);
small_class parameter constructor called
private: large_class parameter constructor called
int data;
};
• Τι θα πρέπει να γίνει έτσι ώστε να cout << "small_class parameter constructor called " << endl;
}
#include <iostream>
using namespace std;
τότε μπορούμε να τα
return (length);
}
Πίνακες αντικειμένων
#8
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Πίνακες: επισκόπηση
• Ένας πίνακας είναι μια δεικτοδοτημένη συλλογή από στοιχεία
δεδομένων του ίδιου τύπου:
int aaa[10];
• 10 στοιχεία τύπου int. Κάθε θέση του πίνακα είναι ένα αντικείμενο.
• Ποιο είναι το εύρος αποδεκτών τιμών για το δείκτη;
• Τι αναπαριστούν τα 10 στοιχεία του πίνακα;
3
Αρχικοποίηση πινάκων αντικειμένων
• Παρόμοια με τη δήλωση ενός πίνακα ακεραίων.
• Δεν χρειάζεται να κάνουμε τίποτα για να χρησιμοποιηθεί ο προκαθορισμένος
κατασκευαστής.
int x;
Fraction num;
Fraction nums[4];
• Για να αρχικοποιήσουμε με συγκεκριμένο τρόπο, καλούμε ρητά τον κατάλληλο
κατασκευαστή.
int x(10);
Fraction f(10,20);
• Πώς γίνεται να αρχικοποιήσουμε έναν πίνακα αντικειμένων; Χρειαζόμαστε έναν τρόπο έτσι
ώστε να καθοριστούν διαφορετικοί κατασκευαστές για διαφορετικά αντικείμενα.
Fraction numlist[3]={Fraction(2,4), Fraction(5), Fraction()};
5
Δείκτες
#9
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Μνήμη
Memory
• Η κύρια μνήμη του υπολογιστή
χρησιμοποιείται για να 0x00000000
αποθηκεύει κώδικα (το ίδιο το 0x00000001
πρόγραμμα) και μεταβλητές του 0x00000002
προγράμματος. …
2
Μνήμη
• Κάθε μεταβλητή αποθηκεύεται
κάπου στη μνήμη, συσχετίζεται δε #include <iostream>
με μια διεύθυνση μνήμης που using namespace std;
επιτρέπει την πρόσβαση στη
μεταβλητή. int main() {
int i;
• Διάφοροι τύποι μεταβλητών char c;
καταλαμβάνουν διαφορετικό χώρο char *ptr = &c;
στη μνήμη: cout << "size of int variable = " << sizeof(i) << endl;
• char (1 byte) cout << "size of char variable = " << sizeof(c) << endl;
cout << "size of char* variable = " << sizeof(ptr) << endl;
• int (4 bytes) return 0;
• float (4 bytes) }
• double (8 bytes)
• Η συνάρτηση sizeof() size of int variable = 4
Μνήμη
Memory
• Τα μέλη δεδομένων ενός αντικειμένου
αποθηκεύονται και αυτά στη μνήμη. 0x00000000
int main() 0x00000001
{ y 0x00000002
int y; y
…
Fraction f; y
… y
} f.numer
f.numer
• Η μνήμη χωρίζεται σε διαφορετικά τμήματα: f.numer
• Η κάθε μεταβλητή αποθηκεύεται f.numer
καταλαμβάνοντας διαφορετική περιοχή. f.denom
• στατική μνήμη (static memory): καθολικές f.denom
μεταβλητές f.denom
• μνήμη στοίβας (stack memory): τοπικές
μεταβλητές f.denom
• μνήμη σωρού (heap memory): δυναμικά
δεσμευμένη μνήμη 0xffffffff
4
Δείκτες
• Κάθε μεταβλητή (αντικείμενο, μέλος δεδομένων, κ.λπ.)
αποθηκεύεται σε μια θέση στη μνήμη.
• Κάθε θέση μνήμης αντιστοιχείται σε έναν μοναδικό αριθμό που
ονομάζεται διεύθυνση της θέσης μνήμης.
• Ένας δείκτης είναι μια μεταβλητή που περιέχει μια θέση μνήμης.
• Ένας δείκτης μπορεί να χρησιμοποιηθεί για να αποθηκεύσει τη θέση
ενός αντικειμένου ή μιας μεταβλητής στη μνήμη.
• Στη συνέχεια μπορούμε να κάνουμε «αποαναφορά» (dereference)
ενός δείκτη έτσι ώστε να αποκτήσουμε απευθείας πρόσβαση στο
αντικείμενο ή στην μεταβλητή που δείχνει ο δείκτης.
Δείκτες Memory
100
• Κάθε διεύθυνση μνήμης
1 byte
101
Δείκτες Memory
• Όταν ο μεταγλωττιστής
100
val 101
16
πραγματοποιεί μια ανάθεση 102
103
void foo() {
τιμής, μεταβαίνει στη 104
int val;
int *valptr;
διεύθυνση μιας μεταβλητής και
105
106 int **valptrptr;
ενημερώνει την τιμή που είναι valptr 107
108
αποθηκευμένη σε αυτή. 109 val = 16;
110 valptr = &val;
• Γενικότερα, όταν ο 111
112
*valptr = 5;
προγραμματιστής γράφει το 113
Δείκτες Memory
100
• Τοποθετώντας τον τελεστή & val 16 101
εμπρός από το όνομα μιας 102
103
void foo() {
int val;
μεταβλητής, μπορούμε να 104
105 int *valptr;
αναφερθούμε στη διεύθυνση μιας 106 int **valptrptr;
μεταβλητής αντί για την τιμή της. valptr 107
100 108
109 val = 16;
• Συνεπώς &x σημαίνει «η 110 valptr = &val;
διεύθυνση της θέσης μνήμης που
111 *valptr = 5;
112
Δείκτες Memory
100
• Καθώς και ένας δείκτη σε δείκτη val 5 101
100
• Η ειδική θέση μνήμης NULL val 5 101
• Προτείνεται οι δείκτες να
108
109 val = 16;
Τοπικές μεταβλητές
• Οι τοπικές μεταβλητές καταλαμβάνουν χώρο στη στοίβα.
• Η διάρκεια ζωής μιας τοπικής μεταβλητής καθορίζεται από την
συνάρτηση στην οποία δηλώνεται.
• Η μεταβλητή δημιουργείται όταν καλείται η συνάρτηση
• Όταν η συνάρτηση επιστρέφει, όλες οι τοπικές μεταβλητές παύουν να
υπάρχουν (η δε μνήμη που καταλάμβαναν επιστρέφει στο σύστημα).
• Προσοχή στη χρήση δεικτών που δείχνουν σε τοπικές μεταβλητές
συναρτήσεων
• Δεν θα πρέπει ποτέ μια συνάρτηση να επιστρέφει έναν δείκτη σε τοπική μεταβλητή.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect9/sample2.cpp
14
Πίνακες και αριθμητική δεικτών
Memory
‘a’
• Όταν ένας πίνακας δηλώνεται, ο
100
my_array ‘b’ 101
μεταγλωττιστής δεσμεύει στη ‘c’ 102
103
μνήμη το χώρο μνήμης που 104
αποθηκευμένος πραγματικά
117
118
ένας δείκτης στη μνήμη. 119
120
121
15
18
Αναφορές (references)
• Οι αναφορές είναι μια υποκατηγορία δεικτών που χρησιμοποιείται στη C++ και όχι στη C.
• Πρόκειται για ένα περιορισμένων δυνατοτήτων δείκτη που μπορεί να δείχνει προς ένα μόνο
αντικείμενο ή μια μόνο μεταβλητή.
• Δηλώνεται χρησιμοποιώντας τον τελεστή & και όχι το * (int &ref).
• Δεν μπορεί να υπάρχει αναφορά σε αναφορά.
• Μια αναφορά πρέπει να αρχικοποιείται κατά τη δήλωση της.
• Από τη στιγμή που αρχικοποιείται μια αναφορά δεν μπορεί να αλλάξει τιμή προς ένα άλλο
αντικείμενο ή μια άλλη μεταβλητή.
• Δεν μπορεί να εφαρμοστεί αριθμητική δεικτών σε αναφορές.
• Οι αναφορές πραγματοποιούν αυτόματη αποαναφορά και συνεπώς μόνο η τιμή της μεταβλητής
που αναφέρεται είναι προσβάσιμη.
• Μια αναφορά λειτουργεί ως δεύτερο όνομα ή ως ψευδώνυμο για μια μεταβλητή ή ένα
αντικείμενο.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect9/sample6.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect9/sample7.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect9/sample8.cpp
19
https://github.com/chgogos/oop/blob/master/various/COP3330/lect9/sample9.cpp
http://pythontutor.com/cpp.html
20
Πειραματισμός με δείκτες: skorbut
https://github.com/fredoverflow/skorbut-release 21
Ερωτήσεις σύνοψης
• Τι αποθηκεύεται στο a όταν υπάρχει η δήλωση int* a;
• Τι αποθηκεύεται στο a όταν υπάρχει η δήλωση int** a;
• Γιατί δεν θα πρέπει να επιστρέφουμε έναν δείκτη προς μια τοπική μεταβλητή;
• Ποια είναι η μορφή με την οποία με δείκτη μπορούμε να αναφερθούμε στο στοιχείο a[100];
• Τι σημαίνει ο δείκτης NULL;
• Ποια είναι η σχέση ανάμεσα σε δείκτες και αναφορές;
• Ποια είναι τα μεγέθη των p1, p2 και p3 στον ακόλουθο κώδικα;
int *p1;
char *p2;
Fraction *p3;
• Ποια είναι η έξοδος του ακόλουθου κώδικα;
int a = 5;
int *ptr = &a;
cout << ptr;
cout << *ptr;
22
Δυναμική δέσμευση μνήμης
#10
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Δέσμευση μνήμης
• Υπάρχουν δύο τύποι δέσμευσης μνήμης
• Στατική δέσμευση μνήμης - πραγματοποιείται αυτόματα από το
μεταγλωττιστή (υπονοούμενη=implicitly).
• καθολικές μεταβλητές: η μνήμη δεσμεύεται στην αρχή του προγράμματος και
απελευθερώνεται όταν το πρόγραμμα τερματίζει, συνεπώς οι καθολικές μεταβλητές
είναι διαθέσιμες σε όλη τη διάρκεια εκτέλεσης του προγράμματος.
• Μπορούν να προσπελαστούν από οποιοδήποτε σημείο του προγράμματος.
• τοπικές μεταβλητές (δηλώνονται σε μια συνάρτηση): η μνήμη δεσμεύεται όταν η
συνάρτηση ξεκινά και ελευθερώνεται όταν η συνάρτηση επιστρέφει.
• Μια τοπική μεταβλητή δεν μπορεί να προσπελαστεί από μια άλλη συνάρτηση.
• Η δέσμευση και η αποδέσμευση μνήμης γίνεται αυτόματα (υπονοούμενα).
• Η μη αναγκαιότητα διαχείρισης της μνήμη είναι βολική αλλά έχει περιορισμούς:
• Για παράδειγμα στη στατική δέσμευση μνήμης το μέγεθος των πινάκων πρέπει να είναι
καθορισμένο κατά τη δήλωση τους.
2
Δέσμευση μνήμης
• Ο δεύτερος τύπος δέσμευσης μνήμης είναι η δυναμική δέσμευση.
• Δυναμική δέσμευση μνήμης - πραγματοποιείται από τον προγραμματιστή με
σαφή (explicitly) τρόπο.
• Ο προγραμματιστής με σαφείς εντολές ζητά από το σύστημα να δεσμεύσει μνήμη και
να επιστρέψει την αρχική διεύθυνση από τη μνήμη που έχει δεσμευθεί. Η διεύθυνση
αυτή μπορεί να χρησιμοποιηθεί από τον προγραμματιστή για να προσπελάσει τη μνήμη
που έχει δεσμευθεί.
• Όταν πλέον δεν χρειαζόμαστε τη μνήμη που έχει δεσμευθεί, η μνήμη θα πρέπει να
ελευθερωθεί με σαφείς εντολές που θα δώσει ο προγραμματιστής.
4
Απελευθέρωση μνήμης: ο τελεστής delete
• O τελεστής delete χρησιμοποιείται για να ελευθερώσει μνήμη που έχει
δεσμευθεί με τον τελεστή new.
• O τελεστής delete θα πρέπει να καλείται σε έναν δείκτη προς μια δεσμευμένη
μνήμη όταν η μνήμη αυτή πλέον δεν χρειάζεται.
• O τελεστής delete μπορεί να διαγράψει μια απλή μεταβλητή ή έναν πίνακα:
delete pointerName;
delete [] arrayName;
• Από τη στιγμή που έχει κληθεί η delete για μια περιοχή μνήμης, δεν θα πρέπει
να γίνονται πλέον προσπελάσεις σε αυτή τη περιοχή μνήμης.
• Η σύμβαση είναι να θέτουμε το δείκτη προς μια μνήμη που έχει διαγραφεί σε
NULL.
• Κάθε new θα πρέπει να έχει το αντίστοιχο delete (αλλιώς το πρόγραμμα έχει διαρροή
μνήμης).
• Το new και το delete μπορεί να μην βρίσκονται στην ίδια συνάρτηση.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect10/sample3.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect10/sample4.cpp
5
Ο σωρός (heap)
• O σωρός είναι μια μεγάλη περιοχή μνήμης ελεγχόμενη από το σύστημα
χρόνου εκτέλεσης που είναι υπεύθυνο να εξυπηρετεί αιτήματα
δέσμευσης και αποδέσμευσης δυναμικής μνήμης.
• Είναι δυνατόν να δεσμευθεί δυναμικά μνήμη και στη συνέχει να «χαθεί» ο
δείκτης προς τη μνήμη αυτή. Σε αυτή την περίπτωση έχουμε διαρροή
μνήμης.
• Οι διαρροές μνήμες μπορεί να προκαλέσουν την εξάντληση του χώρου
του σωρού.
• Αν γίνει απόπειρα να δεσμευθεί μνήμη από το σωρό και δεν υπάρχει
αρκετή μνήμη, τότε παράγεται εξαίρεση (exception) που υποδηλώνει το
λάθος.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect10/sample5.cpp
6
Γιατί να χρησιμοποιηθεί δυναμική δέσμευση
μνήμης;
• Επιτρέπει στα δεδομένα (ειδικά στους πίνακες) να λάβουν
μεταβλητά μεγέθη (π.χ. να γίνεται ερώτηση προς το χρήστη για τον
αριθμό των ακεραίων που επιθυμεί να αποθηκεύσει, και στη
συνέχεια να δημιουργεί έναν πίνακα ακεραίων με αυτό ακριβώς το
μέγεθος).
• Επιτρέπει σε μεταβλητές που έχουν δημιουργηθεί τοπικά να
εξακολουθούν να υπάρχουν και μετά την ολοκλήρωση της
συνάρτησης.
• Επιτρέπει τη δημιουργία πολλών δομών δεδομένων που
χρησιμοποιούνται στις Δομές Δεδομένων και στους Αλγορίθμους.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect10/sample6.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect10/sample7.cpp
7
8
Δέσμευση και αποδέσμευση μνήμης στη C
(λειτουργεί επίσης και στη C++)
• Οι συναρτήσεις malloc και free
• Οι συναρτήσεις malloc και free ορίζονται στο <stdlib.h>
• H malloc είναι παρόμοια με την new, αλλά θα πρέπει να καθοριστεί το
ακριβές μέγεθος μνήμης.
• Επιστρέφει void* (πρέπει να μετατραπεί στον κατάλληλο τύπο δείκτη)
• Η free είναι ισοδύναμη με την delete (ωστόσο, δεν υπάρχει
διαφοροποίηση ανάμεσα στην αποδέσμευση μνήμης για απλή μεταβλητή
και για πίνακα)
Ερωτήσεις σύνοψης
• Σε ποια περιοχή της μνήμης επενεργεί η στατική δέσμευση και σε ποια η
δυναμική δέσμευση μνήμης;
• Πότε θα πρέπει να χρησιμοποιούμε δυναμική δέσμευση μνήμης;
• Πώς γίνεται η δέσμευση και η αποδέσμευση μνήμης για ένα απλό στοιχείο
δεδομένων και πως για έναν πίνακα;
• Πότε παρατηρείται διαρροή μνήμης;
• Ποια είναι η αντίστοιχη εντολή της new με την οποία στη C γίνεται
δέσμευση μνήμης;
• Ποια είναι η αντίστοιχη εντολή της delete με την οποία στη C γίνεται
αποδέσμευση μνήμης;
• Με τι μπορεί να αντικατασταθεί ο τελεστής -> ;
10
Αναδρομή
#11
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
2
Διάσπαση προβλήματος
• Παράδειγμα 2:
• Πρόβλημα (μέγεθος = Ν): Υπολογισμός του σ𝑁
𝑖=1 𝑖
3
• Διάσπαση σε:
• Υποπρόβλημα (μεγέθος = Ν-1): Υπολογισμός του Χ = σ𝑁−1
𝑖=1 𝑖
3
• Η λύση είναι Χ + Ν * Ν * Ν
• Παράδειγμα 3:
• Πρόβλημα: Εύρεση του αθροίσματος Α[1..Ν]
• Διάσπαση σε:
• Χ = άθροισμα των Α[2..Ν]
• Η λύση είναι Χ + Α[1]
4
Συγγραφή αναδρομικών συναρτήσεων (1/4)
• Βήμα 1: κατανόηση του πως ένα πρόβλημα μπορεί να διασπαστεί σε
ένα σύνολο από μικρότερα προβλήματα της ίδιας μορφής καθώς και
πως οι λύσεις των μικρότερων προβλημάτων μπορούν να
συνδυαστούν για να σχηματίσουν τη λύση του μεγαλύτερου
προβλήματος.
• Παράδειγμα:
• Πρόβλημα: Εύρεση του αθροίσματος Α[1..Ν]
• Γενίκευση του προβλήματος στην εύρεση του αθροίσματος του
Α[beg..end]
• Διάσπαση σε:
• Χ = άθροισμα των Α[beg+1..end]
• Η λύση είναι Χ + Α[beg]
6
Συγγραφή αναδρομικών συναρτήσεων (3/4)
• Βήμα 3: Ορίζεται η βασική περίπτωση (base case) της αναδρομής.
Συνήθως πρόκειται για τις εύκολες περιπτώσεις στις οποίες το
μέγεθος του προβλήματος είναι 0 ή 1.
• int sum(int A[], int beg, int end);
• Η βασική περίπτωση μπορεί να είναι το άθροισμα όταν δεν υπάρχει κάποιο
στοιχείο στον πίνακα που να περιέχεται ανάμεσα στο δείκτη beg και στο
δείκτη end.
if (beg>end) return 0;
ή απλούστερα:
8
Τελική μορφή αναδρομικής συνάρτησης
• Η αναδρομική συνάρτηση στο σύνολό της είναι η ακόλουθη:
https://github.com/chgogos/oop/blob/master/various/COP3330/lect11/sample1.cpp
11
Παράδειγμα αναδρομής 2
• Βασική περίπτωση:
• if (beg>=end) return;
• Αναδρομική περίπτωση, η ακολουθία έχει περισσότερα από ένα στοιχεία
(beg<end):
• Υποπρόβλημα 1: Ταξινόμησε την ακολουθία A[beg+1..end]
sort(A, beg + 1, end);
• Υποπρόβλημα 2: Εισήγαγε το A[beg] στην ταξινομημένη ακολουθία A[beg+1..end]
tmp = A[beg];
for (i = beg + 1; i <= end; i++) {
if (tmp > A[i])
A[i - 1] = A[i];
else
break;
}
A[i - 1] = tmp; 12
Τελική μορφή αναδρομικής συνάρτησης
• Η αναδρομική συνάρτηση στο σύνολό της είναι η ακόλουθη:
void sort(int A[], int beg, int end)
{
if (beg >= end)
return;
sort(A, beg + 1, end);
int tmp, i;
tmp = A[beg];
for (i = beg + 1; i <= end; i++)
{
if (tmp > A[i])
A[i - 1] = A[i];
else
break;
}
A[i - 1] = tmp;
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect11/sample2.cpp 13
Αναδρομική σκέψη
• Εντοπισμός της βασικής περίπτωσης (base case) για την οποία
συνήθως είναι προφανής η αντιμετώπισή της.
• Ερώτηση: Αν μπορούμε να επιλύσουμε ένα πρόβλημα μεγέθους Ν-1,
πως μπορούμε να χρησιμοποιήσουμε αυτή τη λύση για να λύσουμε
ένα πρόβλημα μεγέθους Ν;
• Αυτή είναι η αναδρομική περίπτωση (recursive case)
14
Αναδρομή και μαθηματική επαγωγή
• Η μαθηματική επαγωγή είναι χρήσιμο εργαλείο για την απόδειξη θεωρημάτων
• Πρώτα αποδεικνύεται η βασική περίπτωση (Ν=1)
• Επίδειξη ότι το θεώρημα ισχύει για κάποιες μικρές τιμές.
• Στη συνέχεια, διατύπωση της επαγωγικής υπόθεσης
• Υπόθεση ότι το θεώρημα είναι αληθές για όλες τις περιπτώσεις μέχρι κάποιο όριο (N=k)
• Απόδειξη ότι το θεώρημα ισχύει για την επόμενη τιμή (N=k+1)
N N+1
• Παράδειγμα: σ𝑁 𝑖=1 𝑖 = 2
• Αναδρομή
• Βασική περίπτωση: γνωρίζουμε πως να επιλύσουμε το πρόβλημα για τη βασική περίπτωση
(Ν=0 ή 1).
• Αναδρομική περίπτωση: Υποθέτοντας ότι μπορούμε να λύσουμε το πρόβλημα για N=k-1,
προσπαθούμε να λύσουμε το πρόβλημα για Ν=k.
• Η αναδρομή είναι βασικά η εφαρμογή της επαγωγής στην επίλυση
προβλημάτων.
15
16
Αντιγραφή λεκτικών αναδρομικά
void mystrcpy(char *dst, const char *src)
{
if (*src == '\0')
{
*dst = *src;
return;
}
*dst = *src;
mystrcpy(dst + 1, src + 1);
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect11/sample3.cpp
17
19
Κατασκευαστής αντιγραφής
και αντιγραφή ανάθεσης
#12
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Συναρτήσεις που δημιουργούνται αυτόματα
• Οι ακόλουθες δύο συναρτήσεις μέλη που έχουμε ήδη συναντήσει,
κάτω από ορισμένες περιστάσεις δημιουργούνται αυτόματα από τον
μεταγλωττιστή
• Κατασκευαστής: Ένας κενός προκαθορισμένος κατασκευαστής (δηλαδή ένας
κατασκευαστής χωρίς παραμέτρους και με κενό σώμα) δημιουργείται
αυτόματα αν δεν οριστεί κάποιος άλλος κατασκευαστής.
• Καταστροφέας: Ένας κενός καταστροφέας δημιουργείται αν δεν οριστεί
καταστροφέας (επίσης με κενό σώμα).
• Δύο άλλες συναρτήσεις μέλη που επίσης, κάτω από ορισμένες
περιστάσεις δημιουργούνται αυτόματα είναι o κατασκευαστής
αντιγραφής (copy constructor) και o τελεστής ανάθεσης (assignment
operator)
2
Κατασκευαστής αντιγραφής
• Τι θα πρέπει να κάνει ένας copy constructor;
• Να αντιγράφει τα μέλη δεδομένων από την παράμετρο στο νέο αντικείμενο.
• Παράδειγμα:
Fraction(const Fraction &f) Fraction(const Fraction &f)
{ {
numer = f.numer; ή this->numer = f.numer;
denom = f.denom; this->denom = f.denom;
} }
5
«Προβληματικός» προκαθορισμένος
κατασκευαστής αντιγραφής: ρηχή αντιγραφή
• Έστω ότι θέλουμε να αντιγράψουμε ένα αντικείμενο playlist
-ΑΡΧΙΚΟ PLAYLIST-
(Συναρτήσεις)
…
S S E E E
Song *plist: 0xFFA08
1 2
int array_size: 5
int num_songs: 2
-ΑΝΤΙΓΡΑΦΟ-
(Συναρτήσεις)
…
Song *plist: -
int array_size: 5
int num_songs: 2 8
-ΑΡΧΙΚΟ PLAYLIST-
(Συναρτήσεις)
…
S S E E E
Song *plist: 0xFFA08
1 2
int array_size: 5
int num_songs: 2
-ΑΝΤΙΓΡΑΦΟ-
(Συναρτήσεις)
…
Song *plist: 0xFFB74
int array_size: 5
int num_songs: 2 9
Βαθιά αντιγραφή (deep copy)
• Αντιγράφονται τα δεδομένα από την παλιά δυναμική μνήμη στη νέα.
-ΑΡΧΙΚΟ PLAYLIST-
(Συναρτήσεις)
…
S S E E E
Song *plist: 0xFFA08
1 2
int array_size: 5
int num_songs: 2
-ΑΝΤΙΓΡΑΦΟ-
(Συναρτήσεις)
…
S S E E E
Song *plist: 0xFFB74
1 2
int array_size: 5
int num_songs: 2 10
11
Τελεστής ανάθεσης
• Μορφή:
• classname& operator=(const classname&)
• Παράδειγμα:
• Fraction lhs(1,2), rhs(2,5);
lhs = rhs;
• lhs είναι το καλών αντικείμενο, ενώ rhs είναι η παράμετρος, η συνάρτηση
του τελεστή ανάθεσης αλλάζει το lhs έτσι ώστε να είναι ένα αντίγραφο του
rhs και επιστρέφει μια αναφορά στο lhs μέσω του δείκτη this.
12
Ο δείκτης this
• Σε κάθε αντικείμενο υπάρχει ένας δείκτης που ονομάζεται this
• Είναι σαν να υπάρχει η ακόλουθη δήλωση στα μέλη δεδομένων του
αντικειμένου:
• classname *this;
• Ο δείκτης this δείχνει προς το ίδιο το αντικείμενο.
• Μπορούμε να καλούμε άλλες συναρτήσεις μέλη με την εντολή:
• this->memberFunction();
• Χρησιμοποιώντας το δείκτη this επιστρέφουμε μια αναφορά προς
το ίδιο το αντικείμενο στον τελεστή ανάθεσης.
13
Ρηχή αντιγραφή με ανάθεση
• Έστω ότι αναθέτουμε το αντικείμενο playlist RHS στο LHS.
• LHS = RHS;
-RHS-
(Συναρτήσεις)
…
S S E E E
Song *plist: 0xFFB74
1 2
int array_size: 5
int num_songs: 2
-LHS-
(Συναρτήσεις)
…
S S S S E E E
Song *plist: 0xFFA08
1 2 3 4
int array_size: 7
int num_songs: 4
14
18
https://github.com/chgogos/oop/blob/master/various/COP3330/lect12/playlist_shallow_copy.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect12/playlist_deep_copy.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect12/playlist_deleted.cpp
19
Ερωτήσεις σύνοψης
• Ποιο είναι το πρωτότυπο του κατασκευαστή αντιγραφής (copy
constructor);
• Ποια είναι η διαφορά ανάμεσα στη ρηχή αντιγραφή (shallow copy) και στη
βαθιά αντιγραφή (deep copy); Ποια είναι η προκαθορισμένη;
• Πότε καλείται ο κατασκευαστής αντιγραφής για ένα αντικείμενο;
• Γιατί η παράμετρος στον κατασκευαστή αντιγραφής περνά με const
αναφορά; Τι θα συνέβαινε αν περνούσε με τιμή;
• Ποιο είναι το πρωτότυπο για τον τελεστή ανάθεσης (assignment operator);
• Ποιες είναι οι διαφορές ανάμεσα στον κατασκευαστή αντιγραφής και στην
ανάθεση αντιγραφής;
• Τι επιστρέφει o operator=;
20
C-Strings, std::string,
std::string_view και οι τελεστές
[] και &
#13
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
C-strings
• Τα C-strings υλοποιούνται ως πίνακες χαρακτήρων που τερματίζονται με το
χαρακτήρα ‘\0’ (NULL).
buffer
char buffer[5];
strcpy(buffer, “hi!\n”); ‘h’ ‘i' ‘!’ ‘\n’ ‘\0’
cout << buffer;
3
Μειονεκτήματα των C-strings
• Σταθερό μέγεθος (ορίζεται όταν δηλώνεται το C-string ως στατικός
πίνακας).
• Το όνομα του C-string λειτουργεί ως δείκτης.
• Τα όρια του πίνακα δεν επιβάλλονται με κάποιο τρόπο.
• Πρέπει να χρησιμοποιούν «άβολες» συναρτήσεις αντί για
διαισθητικά εύκολα κατανοητούς τελεστές.
• strcpy(str1, str2) αντί για str1=str2
• strcmp(str1, str2) αντί για str1==str2
• strcat(str1, str2) αντί για str1+=str2
• Η χρήση του NULL χαρακτήρα μπορεί να δημιουργήσει προβλήματα.
5
std::string
• Πλεονεκτήματα std::string έναντι C-strings
• Μεταβλητό μέγεθος
• Εύρεση μήκους σε σταθερό χρόνο (και όχι σε γραμμικό)
• Δεν απαιτούν εντολές διαχείρισης μνήμης
• Αυτόματος χειρισμός των ορίων των λεκτικών
• Διαισθητικά εύκολη ανάθεση τιμής με το = αντί για το strcpy
• Διαισθητικά εύκολη σύγκριση με το == αντί για το strcmp
• Διαισθητικά εύκολη συνένωση λεκτικών με το + αντί για το strcat
• Μετατροπή σε C-string με τη συνάρτηση μέλος c_str()
• Δείτε το string1.cpp
https://github.com/chgogos/oop/blob/master/cpp_playground/ex085/string1.cpp
https://github.com/chgogos/oop/blob/master/cpp_playground/ex085/string2.cpp 6
std::string_view (C++17)
• Στη C++17, με τη χρήση της
std::string_view μπορούν να
αποφευχθούν περιττές const char *s = "ABCDEF";
https://github.com/chgogos/oop/blob/master/various/COP3330/lect13/sample6.cpp
9
Ερωτήσεις σύνοψης
• Πως υλοποιείται στη C ένα λεκτικό;
• Τι είναι η συνάρτηση strcpy και ποια επικεφαλίδα πρέπει να γίνει
include έτσι ώστε να μπορεί να χρησιμοποιηθεί;
• Τι επιτυγχάνουμε με τη συνάρτηση getline() του αντικειμένου
cin;
• Γιατί υπάρχουν δύο υπερφορτώσεις συναρτήσεων για το
operator[];
10
Κληρονομικότητα
#14
Τμήμα Πληροφορικής και Τηλεπικοινωνιών
Πανεπιστήμιο Ιωαννίνων (Άρτα)
Γκόγκος Χρήστος
Σχέσεις μεταξύ αντικειμένων
• Δύο βασικές σχέσεις μεταξύ αντικειμένων είναι η σχέση HAS-A και η
σχέση IS-A.
• Παραδείγματα σχέσης HAS-A
• PlayList has-a Song
• PokerHand has-a Card
• Σχέση IS-A
• Ένας ComputerScienceStudent είναι ένας (IS-A) UniversityStudent.
• Ένας UniversityStudent είναι ένας (IS-A) Student.
Εισαγωγή
• Η «κληρονομικότητα» (inheritance) στη C++ επιτρέπει στους
προγραμματιστές να ορίσουν IS-A σχέσεις:
• Μια κλάση μπορεί να οριστεί ως παραγόμενη από μια άλλη κλάση (που
ονομάζεται βασική κλάση).
• Η παραγόμενη κλάση γίνεται ένα είδος/εξειδίκευση της βασικής κλάσης.
• Ένα αντικείμενο της παραγόμενης κλάσης είναι (IS-A) αντικείμενο της βασικής κλάσης.
• Εναλλακτικά, μπορεί να θεωρηθεί ότι η παραγόμενη κλάση είναι ότι είναι η
βασική κλάση και (πιθανά) περισσότερα.
• Αυτό επιτρέπει, σε ορισμένες περιπτώσεις και εφόσον απαιτείται, τη χρήση
της παραγόμενης κλάσης σαν να ήταν η βασική κλάση.
3
Κληρονομικότητα, βασική και παραγόμενη
κλάση
• Η σχέση IS-A υλοποιείται μέσω της κληρονομικότητας χρησιμοποιώντας τον
ακόλουθο τρόπο δήλωσης, που σημαίνει ότι τα αντικείμενα της παραγόμενης
κλάσης είναι αντικείμενα της βασικής κλάσης (μπορούν να χρησιμοποιούν όλες
τις συναρτήσεις της διεπαφής της βασικής κλάσης)
• class derivedClassName: public baseClassName
class Mammal {
public:
void PrintInfo() const;
};
class Cow : public Mammal {
public:
void Sound() const;
};
...
int main() {
Cow C;
C.PrintInfo();
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect14/sample1.cpp 4
Επίπεδα προστασίας
• Μια παραγόμενη κλάση μπορεί να προσπελάσει όλα τα δημόσια μέλη της
βασικής κλάσης, αλλά δεν μπορεί να προσπελάσει τα ιδιωτικά μέλη της βασικής
κλάσης.
• Αν επιθυμούμε να επιτρέψουμε σε μερικά μέλη της βασικής κλάσης να προσπελαύνονται
από την παραγόμενη κλάση, αλλά να μην προσπελαύνονται από άλλες κλάσεις τότε
χρησιμοποιούμε το επίπεδο προστασίας protected.
• Σύνοψη επιπέδων προστασίας:
• public: μέλη που μπορούν να προσπελαστούν με το όνομά τους στο εσωτερικό
οποιασδήποτε συνάρτησης.
• private: Μέλη που μπορούν να προσπελαστούν στο εσωτερικό συναρτήσεων μελών και
από συναρτήσεις που έχουν δηλωθεί ως φίλες συναρτήσεις (friend) της κλάσης.
• protected: Μέλη που μπορούν να προσπελαστούν στο εσωτερικό συναρτήσεων μελών και
από συναρτήσεις που έχουν δηλωθεί ως φίλες συναρτήσεις (friend) της κλάσης και επίσης
μπορούν να προσπελαστούν στο εσωτερικό των συναρτήσεων μελών της παραγόμενης
κλάσης και στις friend συναρτήσεις της παραγόμενης κλάσης.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect14/sample2.cpp
5
Κατασκευαστές/καταστροφείς
• Καθώς ένα στιγμιότυπο μιας παραγόμενης κλάσης είναι επίσης και
στιγμιότυπο της βασικής κλάσης, τόσο ο κατασκευαστής της βασικής
κλάσης όσο και ο κατασκευαστής της παραγόμενης κλάσης θα
πρέπει να εκτελείται όταν ένα αντικείμενο της παραγόμενης κλάσης
δημιουργείται.
• Οι κατασκευαστές εκτελούνται στη σειρά από τον πλέον γενικό προς
τον πλέον ειδικό.
• Οι καταστροφείς εκτελούνται στη σειρά από τον πλέον ειδικό προς
τον πλέον γενικό.
• Αν οι κατασκευαστές έχουν παραμέτρους, τότε η κλήση τους γίνεται
μέσω της λίστας αρχικοποίησης.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect14/sample3.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect14/sample4.cpp 6
V 1.0
Ανάγκη ύπαρξης ιδεατών συναρτήσεων
• Στο παράδειγμα με τους σπουδαστές (lect14/sample5.cpp) για κάθε τύπο σπουδαστή (Student,
Grad και Undergrad) εκτυπώνεται διαφορετική έκθεση προόδου (GradeReport).
• Student s;
s.GradeReport();
Grad g;
g.GradeReport();
Undergrad u;
u.GradeReport();
• Έστω ότι έχουμε μια λίστα με 3000 σπουδαστές και προσπαθούμε με τον ακόλουθο κώδικα να
εμφανίσουμε τις εκθέσεις προόδου για όλους τους σπουδαστές:
• Student list[3000];
for(i=0;i<3000;i++)
list[i].GradeReport();
• Υπάρχουν δύο προβλήματα με τον παραπάνω κώδικα:
• Η λίστα είναι ένας ομογενής πίνακας, πρέπει να χρησιμοποιήσουμε τον πλέον κοινό τύπο (το βασικό τύπο).
Ο βασικός τύπος δεν έχει όλες τις πληροφορίες για τις παραγόμενες κλάσεις Grad και Undergrad
προκειμένου να παράγει την έκθεση προόδου που χρειάζεται.
• Η συνάρτηση GradeReport που καλείται είναι η έκδοση που υπάρχει στην κλάση Student (η γενική
έκθεση προόδου), και όχι η εξειδικευμένη συνάρτηση που υπάρχει σε κάθε παραγόμενη κλάση.
3
Ανάγκη ύπαρξης ιδεατών συναρτήσεων
• Διάσχιση της λίστας όλων των σπουδαστών και εκτύπωση της
έκθεσης προόδου για κάθε σπουδαστή.
• Μια πιθανή λύση:
• Student *list[3000];
…
for (int i=0;i<size;i++)
list[i]->GradeReport();
5
Ιδεατές συναρτήσεις
• Όταν μια συνάρτηση δηλωθεί ως virtual, αυτό σημαίνει ότι η συνάρτηση μπορεί
να γίνει bound σε πολλές διαφορετικές συναρτήσεις δυναμικά κατά το χρόνο
εκτέλεσης.
• Ο πολυμορφισμός αναφέρεται στη δυνατότητα να συσχετίσουμε πολλά νοήματα με μια
(ιδεατή) συνάρτηση.
• Παράδειγμα:
• class Student {public: virtual void GradeReport();...};
• Η συνάρτηση GradeReport() μπορεί να αντιστοιχηθεί σε διαφορετικές συναρτήσεις στις
παραγόμενες κλάσεις (το πρόγραμμα θα κοιτά στο αντικείμενο στο οποίο θα δείχνει ο
δείκτης για να αποφασίσει ποια συνάρτηση θα κληθεί) .
Student *sp1, *sp2, *sp3;
Student s; Grad g; Undergrad u;
sp1 = &s; sp2 = &g; sp3 = &u;
sp1->GradeReport(); // εκτελείται η έκδοση του Student
sp2->GradeReport(); // εκτελείται η έκδοση του Grad
sp3->GradeReport(); // εκτελείται η έκδοση του Undergrad
https://github.com/chgogos/oop/blob/master/various/COP3330/lect15/sample1.cpp
6
7
Καθαρές ιδεατές συναρτήσεις (pure virtual
functions)
• Οι κανονικές συναρτήσεις μέλη, συμπεριλαμβανομένων και των ιδεατών
συναρτήσεων θα πρέπει να έχουν κάποια υλοποίηση.
• Συνεπώς η ιδεατή συνάρτηση GradeReport() θα πρέπει κανονικά να έχει κάποια
υλοποίηση στη βασική κλάση.
• Ωστόσο, αυτό δεν είναι πάντα απαραίτητο (ή εφικτό). Mπορεί να μην υπάρχουν
πολλά που μπορούν να γίνουν στις ιδεατές συναρτήσεις των βασικών κλάσεων
καθώς η όποια λειτουργικότητα θα πρέπει να υλοποιηθεί στις παραγόμενες κλάσεις
όπου θα υπάρχουν και περισσότερες πληροφορίες.
• Η C++ επιτρέπει σε μια ιδεατή συνάρτηση να μην έχει υλοποίηση και σε αυτή τη
περίπτωση η συνάρτηση λέγεται ότι είναι pure virtual, η δε υλοποίηση γίνεται στην
παραγόμενη κλάση.
• Μια ιδεατή συνάρτηση δηλώνεται ως pure τοποθετώντας το =0 στη δήλωσή της.
• virtual void GradeReport() = 0;
9
Αφηρημένες κλάσεις
• Μια αφηρημένη κλάση είναι μια κλάση με τουλάχιστον μια καθαρή
ιδεατή συνάρτηση.
• Μια αφηρημένη κλάση δεν μπορεί να δημιουργεί στιγμιότυπα.
• Student st; // error, Student is an abstract class, the object
cannot be created
• Μια αφηρημένη κλάση μπορεί να χρησιμοποιηθεί για να δηλωθούν δείκτες
• Ένας δείκτης δεν απαιτεί τη δημιουργία ενός αντικειμένου
• Ένας δείκτης σε μια βασική κλάση μπορεί να δείχνει προς ένα αντικείμενο της
παραγόμενης κλάσης.
• Student *st; // έγκυρο, μπορεί να δείχνει προς αντικείμενο της
παραγόμενης κλάσης
https://github.com/chgogos/oop/blob/master/various/COP3330/lect15/sample2.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect15/sample3.cpp
10
Παράδειγμα
• Αφηρημένη κλάση Employee
• Κλάση Temporary, παραγόμενη κλάση από την Employee
• Κλάση Permanent, παραγόμενη κλάση από την Employee
(αφηρημένη)
• Κλάση Hourly, παραγόμενη κλάση από την Permanent
• Κλάση Salaried, παραγόμενη κλάση από την Permanent
https://github.com/chgogos/oop/tree/master/various/COP3330/lect15/employee
11
Ερωτήσεις σύνοψης
• Τι είναι μια ιδεατή συνάρτηση;
• Τι μπορούμε να πετύχουμε με τις ιδεατές συναρτήσεις;
• Πως ορίζεται μια καθαρή ιδεατή συνάρτηση;
• Τι είναι μια αφηρημένη κλάση;
• Μπορεί μια μεταβλητή να δηλωθεί ότι έχει ως τύπο, τον τύπο μιας
αφηρημένης κλάσης;
• Μπορεί ένας δείκτης να δηλωθεί ότι έχει ως τύπο, τον τύπο μιας
αφηρημένης κλάσης;
12
Templates (πρότυπα)
#16
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Πρότυπα
• Τα πρότυπα επιτρέπουν την περιγραφή λειτουργιών που μπορούν να
εφαρμοστούν σε πολλαπλούς τύπους δεδομένων.
• Γενερικός προγραμματισμός (generic programming)
• Επαναχρησιμοποίηση κώδικα
• Πρότυπα συναρτήσεων (function templates)
• Επιτρέπουν τον ορισμό λογικής με τέτοιο τρόπο έτσι ώστε η ίδια αλγοριθμική
λογική να μπορεί να εφαρμοστεί σε πολλαπλούς τύπους δεδομένων.
• Πρότυπα κλάσεων (class templates)
• Επιτρέπουν τον ορισμό generic προτύπων κλάσεων που δίνουν τη
δυνατότητα προσάρτησης συγκεκριμένων τύπων δεδομένων έτσι ώστε να
προκύπτουν νέες κλάσεις.
Πρότυπα συναρτήσεων
• Οι συναρτήσεις της C++ λειτουργούν με συγκεκριμένους τύπους δεδομένων.
Συχνά προκύπτει η ανάγκη του να γράψουμε διαφορετικές συναρτήσεις για να
πραγματοποιηθεί η ίδια λειτουργία με διαφορετικούς τύπους δεδομένων.
int maximum(int a, int b, int c) float maximum(float a, float b, float c)
{ {
int max = a; float max = a;
if (b>max) max = b; if (b>max) max = b;
if (c>max) max = c; if (c>max) max = c;
return max; return max;
} }
3
Πρότυπα συναρτήσεων
• Generic συνάρτηση εύρεσης μεγαλύτερης τιμής από τρεις τιμές.
template <class T> template <typename T>
T maximum(T a, T b, T c) T maximum(T a, T b, T c)
{ {
T max = a;
if (b>max) max = b;
ή T max = a;
if (b>max) max = b;
if (c>max) max = c; if (c>max) max = c;
return max; return max;
} }
• Ένα πρότυπο συνάρτησης δεν αποτελεί μια πλήρως ορισμένη συνάρτηση για το μεταγλωττιστή,
καθώς ο μεταγλωττιστής χρειάζεται να γνωρίζει τον πραγματικό τύπο δεδομένων για να
δημιουργήσει τον αντίστοιχο κώδικα. Συχνά, πρότυπα συναρτήσεων τοποθετούνται σε αρχεία
επικεφαλίδων (.h ή .hpp) για να συμπεριληφθούν σε προγράμματα που χρησιμοποιούν τη
συνάρτηση. Ο μεταγλωττιστής δημιουργεί τον κώδικα της συνάρτησης με βάση την πραγματική
χρήση του προτύπου συνάρτησης.
7
Πρότυπα κλάσεων (class templates)
• Μερικές φορές είναι χρήσιμο να επιτρέπεται η αποθήκευση τιμών
διαφορετικών τύπων σε μια κλάση.
• Δείτε τα παραδείγματα simplelist1 και simplelist2
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist1.h
Λίστα 10
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist1.cpp θέσεων με
ακεραίους
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/main_simplelist1.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist2.cpp Λίστα 10
θέσεων με
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist2.cpp ακεραίους ή
πραγματικούς
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/main_simplelist2.cpp
Πρότυπα κλάσεων
• Η ίδια ιδέα με τα πρότυπα συναρτήσεων εφαρμόζεται και στα
πρότυπα κλάσεων.
• Δείτε το παράδειγμα simplelist3
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist3.h
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/simplelist3.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect16/main_simplelist3.cpp
9
Πρότυπα κλάσεων
• Για να γίνει μια κλάση, πρότυπο κλάσης χρειάζεται να τοποθετηθεί στην
αρχή της δήλωσης της κλάσης ο ακόλουθος κώδικας:
• template<class T> ή template<typename T>
• Εδώ το T είναι απλά μια παράμετρος που υποδηλώνει έναν τύπο.
• Όταν θα δημιουργηθούν αντικείμενα της κλάσης, το T αντικαθίσταται με έναν
πραγματικό τύπο.
• Για να οριστεί μια συνάρτηση μέλος της κλάσης θα πρέπει να
χρησιμοποιηθεί η ακόλουθη σύνταξη:
• className<T>::memberName(…){…}
• Αντίστοιχα, για να δηλωθεί μια μεταβλητή του προτύπου κλάσης θα
πρέπει να χρησιμοποιηθεί η ακόλουθη σύνταξη:
• className<υπαρκτός τύπος δεδομένων> variable;
10
Ερωτήσεις σύνοψης
• Τι είναι τα πρότυπα συναρτήσεων (function templates);
• Τι είναι τα πρότυπα κλάσεων (class templates);
• Τι είναι ο γενερικός προγραμματισμός (generic programming);
13
Συνδεδεμένες λίστες
#17
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
2
Δομές δεδομένων για την αποθήκευση μιας
συλλογής αντικειμένων
• Τι επιλογές έχουμε;
• Στατικοί πίνακες
• Περιορισμοί:
• Προκαθορισμένη χωρητικότητα, άρα μπορεί να μη γίνεται καλή χρήση του διαθέσιμου χώρου που έχει
δεσμευθεί στη μνήμη.
• Οι λειτουργίες εισαγωγής και διαγραφής μπορεί να έχουν υψηλό κόστος (καθώς γίνονται πολλές αντιγραφές)
αν δεν θέλουμε να αφήνουμε κενά ενδιάμεσα στον πίνακα.
• Πλεονεκτήματα:
• Ευκολία προγραμματισμού.
• Συνεχόμενες θέσεις μνήμης για εύκολη δεικτοδότηση.
• Δυναμικοί πίνακες
• Περιορισμοί:
• Η χωρητικότητα είναι δυναμική, η μνήμη πάλι μπορεί να μη χρησιμοποιείται με τον καλύτερο τρόπο, αλλά σε
ορισμένες περιπτώσεις αποτελεί καλύτερη λύση σε σχέση με τους στατικούς πίνακες.
• Οι λειτουργίες εισαγωγής και διαγραφής μπορεί να έχουν υψηλό κόστος, ειδικά όταν η χωρητικότητα αλλάζει.
• Πλεονεκτήματα:
• Ευκολία προγραμματισμού (ο προγραμματιστής ωστόσο πρέπει να φροντίζει για τη δέσμευση και την
αποδέσμευση μνήμης).
• Συνεχόμενες θέσεις μνήμης για εύκολη δεικτοδότηση.
4
Συνδεδεμένες λίστες και πίνακες
s
Συνδεδεμένες λίστες
• Μειώνεται η σπατάλη μνήμης (απαίτηση επιπλέον χώρου μόνο για
τους δείκτες).
• Κάθε κόμβος της λίστας δεσμεύεται δυναμικά με τον τελεστή new.
• Υπάρχουν πολλές παραλλαγές συνδεδεμένων λιστών:
• Απλά συνδεδεμένη λίστα head
• Διπλά συνδεδεμένη λίστα
•… “abc” “white” “black” NULL
head
tail 6
Μια διπλά συνδεδεμένη λίστα
• Ας υποθέσουμε ότι αποθηκεύουμε δύο πεδία δεδομένων σε κάθε
κόμβο (ένα λεκτικό και έναν ακέραιο). Τότε η δομή δεδομένων
listnode θα είναι η ακόλουθη:
s
“abc”, 0
class listnode
{
previous next
public:
count
string s;
int count;
listnode *next;
listnode *prev;
listnode(): s(“”), count(0), next(NULL), prev(NULL) {};
listnode(const string &ss, const int &c): s(ss), count(c), next(NULL), prev(NULL) {};
};
8
Η δημόσια διεπαφή (public interface) της
mylist (1/2)
• mylist();
• mylist(const mylist &l);
• ~mylist();
• mylist &operator=(const mylist &l);
• void print() const;
• void insertfront(const string &s, const int &c);
• void insertback(const string &s, const int &c);
• void insertbefore(listnode *ptr, const string &s, const int &c);
• void insertafter(listnode *ptr, const string &s, const int &c);
• void insertpos(const int &pos, const string &s, const int &c);
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.h
9
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 11
Εισαγωγή (insert)
• insertback
• Δύο περιπτώσεις:
• Εισαγωγή σε άδεια λίστα.
• Εισαγωγή σε λίστα με στοιχεία.
• Εισαγωγή σε άδεια λίστα
• Δημιουργία ενός νέου κόμβου (prev=NULL, next=NULL), και τόσο ο
δείκτης head όσο και δείκτης tail πρέπει να δείχνουν στο νέο κόμβο.
listnode *t = new listnode(s, c);
if (head == NULL) {
head = t;
tail = t;
size++;
}
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 12
insertback (1/4)
• insertback σε μια λίστα με στοιχεία
• Βήμα 1: Δημιουργία του νέου κόμβου
listnode *t = new listnode(s,c);
head
t
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 13
insertback (2/4)
• insertback σε μια λίστα με στοιχεία
• Βήμα 2: σύνδεση του νέου κόμβου με την ουρά της λίστας (δείκτης next)
tail->next = t;
head
t
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 14
insertback (3/4)
• insertback σε μια λίστα με στοιχεία
• Βήμα 3: σύνδεση του νέου κόμβου με την λίστα (δείκτης prev)
t->prev = tail;
head
t
tail
“xxx”, 0 NULL
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 15
insertback (4/4)
• insertback σε μια λίστα με στοιχεία
• Βήμα 4: αλλαγή του δείκτη tail έτσι ώστε να δείχνει στο νέο κόμβο
tail = t;
head
t
“xxx”, 0 NULL
tail
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 16
insertbefore (1/5)
• Η εισαγωγή στην αρχή (insertfront) είναι παρόμοια με την
insertback.
• Η εισαγωγή πριν τη κεφαλή είναι ισοδύναμη με την insertfront.
• H εισαγωγή στο ενδιάμεσο της λίστας πριν το δείκτη ptr είναι
διαφορετική: ένας νέος κόμβος πρόκειται να προστεθεί μεταξύ του
ptr->prev και του ptr.
head
tail
ptr 17
insertbefore (2/5)
• H εισαγωγή στο ενδιάμεσο της λίστας πριν το δείκτη ptr.
• Ένας νέος κόμβος πρόκειται να προστεθεί μεταξύ του ptr->prev και του ptr.
• Βήμα 1: δημιουργία ενός νέου κόμβου:
listnode *t; t
t = new listnode(s,c);
NULL “yyy”, 0 NULL
head
tail
ptr 18
insertbefore (3/5)
• H εισαγωγή στο ενδιάμεσο της λίστας πριν το δείκτη ptr.
• Ένας νέος κόμβος πρόκειται να προστεθεί μεταξύ του ptr->prev και του ptr.
• Βήμα 2: σύνδεση του νέου κόμβου με τη λίστα:
t->next = ptr; t
t->prev = ptr->prev;
“yyy”, 0
head
tail
ptr 19
insertbefore (4/5)
• H εισαγωγή στο ενδιάμεσο της λίστας πριν το δείκτη ptr.
• Ένας νέος κόμβος πρόκειται να προστεθεί μεταξύ του ptr->prev και του ptr.
• Βήμα 3: αλλαγή του next για το ptr->prev:
ptr->prev->next = t; t
“yyy”, 0
head
tail
ptr 20
insertbefore (5/5)
• H εισαγωγή στο ενδιάμεσο της λίστας πριν το δείκτη ptr.
• Ένας νέος κόμβος πρόκειται να προστεθεί μεταξύ του ptr->prev και του ptr.
• Βήμα 4: αλλαγή του ptr->prev:
ptr->prev = t; t
“yyy”, 0
head
tail
ptr 21
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 22
removefront (1/4)
• removefront
• Η λίστα έχει περισσότερα από ένα στοιχεία
• Βήμα 1: listnode *t = head;
head
tail
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 23
removefront (2/4)
• removefront
• Η λίστα έχει περισσότερα από ένα στοιχεία
• Βήμα 2: ο δείκτης head προχωρά έτσι ώστε να δείξει το αμέσως επόμενο στοιχείο της
λίστας σε σχέση με αυτό που ήδη έδειχνε: head = head->next;
head
tail
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 24
removefront (3/4)
• removefront
• Η λίστα έχει περισσότερα από ένα στοιχεία
• Βήμα 3: αποσύνδεση του δείκτη prev του head: head->prev = NULL;
head
tail
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 25
removefront (4/4)
• removefront
• Η λίστα έχει περισσότερα από ένα στοιχεία
• Βήμα 4: delete t;
head
tail
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 26
remove (1/3)
• Διαγραφή του στοιχείου στο οποίο δείχνει ο δείκτης ptr.
• Βήμα 1: αλλαγή του next για το δείκτη ptr->prev
ptr->prev->next = ptr->next;
head
tail
ptr
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 27
remove (2/3)
• Διαγραφή του στοιχείου στο οποίο δείχνει ο δείκτης ptr.
• Βήμα 2: αλλαγή του prev για το δείκτη ptr->prev
ptr->next->prev = ptr->prev;
head
tail
ptr
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 28
remove (3/3)
• Διαγραφή του στοιχείου στο οποίο δείχνει ο δείκτης ptr.
• Βήμα 3: delete ptr;
head
tail
ptr
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 29
Αναζήτηση (search)
• Χρησιμοποιώντας μια επανάληψη με while επιτυγχάνεται διάσχιση
όλων των κόμβων της λίστας.
listnode *mylist::search(const string &s)
{
listnode *t = head;
https://github.com/chgogos/oop/blob/master/various/COP3330/lect17/mylist.cpp 30
Ερωτήσεις σύνοψης
• Γιατί οι λειτουργίες εισαγωγής και διαγραφής έχουν αυξημένο
υπολογιστικό κόστος στους στατικούς πίνακες και στους δυναμικούς
πίνακες;
• Ποια είναι τα πλεονεκτήματα και ποια τα μειονεκτήματα των
συνδεδεμένων λιστών;
• Τι πλεονεκτήματα και τι μειονεκτήματα έχουν οι διπλά συνδεδεμένες
λίστες έναντι των απλά συνδεδεμένων λιστών;
31
Εξαιρέσεις
#18
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Χειρισμός εξαιρέσεων στη C++
• Εξαίρεση (exception): ένα λάθος ή μια προβληματική κατάσταση
• π.χ. διαίρεση με το μηδέν, πρόσβαση σε NULL δείκτη, …
• Χειρισμός εξαιρέσεων (exception handling): αφορά την αντιμετώπιση
του λάθους ή της προβληματικής κατάστασης.
• H C++ έχει ενσωματωμένους μηχανισμούς για χειρισμό εξαιρέσεων.
• Αν δεν χρησιμοποιηθεί ο ενσωματωμένος στη γλώσσα μηχανισμός χειρισμού
εξαιρέσεων, τα λάθη και οι προβληματικές καταστάσεις μπορούν να αντιμετωπιστούν
με προσθήκη ελέγχων (ifs) μέσα στον κώδικα.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect18/sample1.cpp
https://github.com/chgogos/oop/blob/master/various/COP3330/lect18/sample2.cpp
3
Πότε πρέπει να χρησιμοποιείται ο χειρισμός
εξαιρέσεων;
• Ο χειρισμός εξαιρέσεων δεν αποτελεί πάντα τον πλέον κατάλληλο
τρόπο για το χειρισμό λαθών.
• Π.χ. ο παραδοσιακός τρόπος (με χρήση ifs) είναι καλύτερος για έλεγχο
εισόδου.
try
{
… κώδικας που πρέπει να ελεγχθεί για εξαιρέσεις
… πιθανές προκλήσεις εξαιρέσεων (throw exceptions)
}
catch (type1 catch_parameter_1)
{… κώδικας χειρισμού εξαίρεσης τύπου type1}
catch (type2 catch_parameter_2)
{… κώδικας χειρισμού εξαίρεσης τύπου type2}
5
Το μπλοκ try
• Συντακτικό:
try
{
… η κύρια λογική, πιθανά προκαλεί εξαιρέσεις (throw exceptions)
}
• Περιέχει τον κώδικα που πρόκειται να εκτελεστεί όταν η εκτέλεση θα
πραγματοποιηθεί χωρίς προβλήματα
• Ωστόσο, ο κώδικας μπορεί να προκαλέσει εξαιρέσεις, άρα θα πρέπει να
πραγματοποιηθεί «δοκιμαστική» λειτουργία (try).
• Αν κάτι μη συνηθισμένο συμβεί, αυτό υποδηλώνεται με την πρόκληση μιας
εξαίρεσης (throw exception).
Η εντολή throw
• Σύνταξη εντολής throw:
• throw exception_for_value_to_be_thrown;
• Σημασία:
• Υποδηλώνει ότι συνέβη μια εξαίρεση.
• Αν μια εντολή throw εκτελεστεί, το μπλοκ try αυτόματα τερματίζει.
• Το πρόγραμμα προσπαθεί να ταιριάξει την εξαίρεση με κάποιο από τα catch μπλοκς που
ακολουθούν το try μπλοκ (που περιέχουν τον κώδικα χειρισμού της κάθε εξαίρεσης).
• Το ταίριασμα γίνεται με βάση τον τύπο της τιμής που γίνεται throw.
• Αν βρεθεί κάποιο ταίριασμα, εκτελείται ο κώδικας στο catch μπλοκ του.
• Μόνο ένα catch μπλοκ μπορεί να ταιριάξει το πολύ.
• Το πρόγραμμα συνεχίζει την εκτέλεσή του με τον κώδικα μετά το τελευταίο catch μπλοκ.
• Τόσο η τιμή της εξαίρεσης όσο και η ροή ελέγχου μεταβιβάζονται (γίνονται
throw) στο catch μπλοκ που αποτελεί το χειριστή της εξαίρεσης.
7
To catch μπλοκς
• Ένα ή περισσότερα catch μπλοκς ακολουθούν το try μπλοκ.
• Κάθε catch μπλοκ έχει έναν τύπο παραμέτρου.
• Κάθε catch μπλοκ είναι ένας χειριστής εξαιρέσεων (που χειρίζεται
εξαιρέσεις ενός τύπου).
• catch (type catch_block_parameter)
{ … κώδικας χειρισμού εξαίρεσης }
• Η catch_block_parameter συλλαμβάνει (catches) την τιμή της εξαίρεσης
που προκαλείται και η οποία μπορεί να χρησιμοποιηθεί στον κώδικα
χειρισμού της εξαίρεσης.
• Η εξαίρεση που προκαλείται ταιριάζει με μια από τις παραμέτρους των
catch μπλοκς.
• Αν αυτό δεν συμβεί τότε έχουμε την κατάσταση στην οποία δεν γίνεται catch μια
εξαίρεση (un-caught exception).
Κλάσεις εξαιρέσεων
• Συχνά ορίζονται κλάσεις με σκοπό το χειρισμό εξαιρέσεων.
• Μπορούν να χρησιμοποιηθούν έτσι ώστε για κάθε κλάση εξαίρεσης
να υπάρχει διαφορετική κλάση.
• Οι κλάσεις εξαιρέσεων είναι κανονικές κλάσεις που απλά
χρησιμοποιούνται με σκοπό το χειρισμό εξαιρέσεων.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect18/sample6.cpp
11
Οι ενσωματωμένες κλάσεις εξαιρέσεων του
προτύπου της C++
• Στην C++ υπάρχει η standard βιβλιοθήκη που περιλαμβάνει
ορισμένες προκαθορισμένες κλάσεις εξαιρέσεων.
• Η βασική κλάση είναι η exception και βρίσκεται στο αρχείο
επικεφαλίδας exception:
• #include <exception>
using std::exception;
• Ο προγραμματιστής μπορεί να ορίσει δικές του κλάσεις εξαιρέσεων
που να κληρονομούν από την κλάση std::exception.
https://github.com/chgogos/oop/blob/master/various/COP3330/lect18/sample7.cpp
12
https://github.com/chgogos/oop/blob/master/various/COP3330/lect18/sample8.cpp
14
16
Ερωτήσεις σύνοψης
• Τι είναι και πως λειτουργούν τα try-throw-catch μπλοκς;
• Ποια είναι τα πλεονεκτήματα και ποια τα μειονεκτήματα των εξαιρέσεων;
• Τι μπορεί να γίνει throw σε έναν κώδικα;
• Ποιος είναι ο ρόλος του catch(…), σε ποια θέση τοποθετείται και γιατί;
• Τι είναι η κλάση std::exception;
• Τι συμβαίνει αν μια εξαίρεση δεν συλλαμβάνεται (γίνεται catch);
• Ποιος είναι ο ρόλος της δεσμευμένης λέξης noexcept;
• Ποιος είναι ο ρόλος της εξαίρεσης logic_error ποιος της εξαίρεσης
runtime_error;
17
Χρήσιμες έννοιες στη C++ και
μερικές δομές δεδομένων
#19
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
https://github.com/chgogos/oop/blob/master/various/COP3330/lect19/sample1.cpp
2
Αρχεία κεφαλίδων (header files) C++
• Τα αρχεία πηγαίου κώδικα $ g++ sample2.cpp
In file included from myheader2.h:1,
χρησιμοποιούν την οδηγία from sample2.cpp:5:
#include προκειμένου να myheader1.h:1:7: error: redefinition of 'class
συμπεριλάβουν τα αρχεία abc'
κεφαλίδων. class abc {
^~~
• Μερικές φορές η χρήση αρχείων In file included from sample2.cpp:4:
κεφαλίδων μπορεί να myheader1.h:1:7: note: previous definition of
δημιουργήσει προβλήματα. 'class abc'
• Κάνοντας include το ίδιο header file class abc {
δύο ή περισσότερες φορές μπορεί να ^~~
δημιουργηθούν λάθη «διπλής
δήλωσης». https://github.com/chgogos/oop/blob/master/various/COP333
0/lect19/sample2.cpp
• Κάνοντας include δύο φορές το
stdio.h ωστόσο δεν δημιουργείται https://github.com/chgogos/oop/blob/master/various/COP333
αντίστοιχο πρόβλημα. Γιατί; 0/lect19/myheader1.h
https://github.com/chgogos/oop/blob/master/various/COP333
0/lect19/myheader2.h 3
#ifndef MYHEADER
#define MYHEADER
…
/* το σώμα του header ή #pragma once
αρχείου */
#endif
https://github.com/chgogos/oop/blob/master/various/COP3330/lect19/sample3.cpp
4
Μακροεντολές με παραμέτρους
• Οι μακροεντολές με παραμέτρους μοιάζουν και λειτουργούν
παρόμοια με τις συναρτήσεις
• #define max(a,b) (a>b)?a:b
6
Bitwise λειτουργίες
• unsigned char x = 8 (00001000)
• unsigned char x = 192 (11000000)
• x = 00001000 = 00001000
• y = 11000000 = 11000000
• x&y = 00000000 x|y = 11001000
• Έλεγχος αν το bit στη θέση 2 (τρίτο από δεξιά προς αριστερά) του x είναι 1:
• if (x & 0x04 !=0)
• Με αυτό τον τρόπο επιτυγχάνεται σύνταξη λογικών εκφράσεων με βάση τα bits
μιας μεταβλητής
7
Δομές δεδομένων
• Οι δομές δεδομένων επιτρέπουν την ευκολότερη συγγραφή
προγραμμάτων.
• Οι δομές δεδομένων εστιάζουν στην οργάνωση των δεδομένων με
κατάλληλο τρόπο έτσι ώστε να πραγματοποιούνται αποδοτικότερα
λειτουργίες που απαιτούνται.
• Η επιλογή των κατάλληλων δομών δεδομένων είναι εξαιρετικά
σημαντική για τη δημιουργία αποδοτικών προγραμμάτων.
• Πολλές κοινές δομές δεδομένων έχουν υλοποιηθεί στην STL
(Standard Template Library) της C++.
8
Δομές Δεδομένων – ένα παράδειγμα
• Έστω ένα πρόγραμμα στο οποίο ένα μεγάλο σύνολο λέξεων αποθηκεύεται με
κάποιο τρόπο.
• Δεδομένης μιας λέξης, θέλουμε να γνωρίζουμε το που έχει αποθηκευτεί.
• Αν οι λέξεις είναι αποθηκευμένες σε έναν πίνακα ή σε μια συνδεδεμένη λίστα ή
σε ένα διάνυσμα τότε θα πρέπει να περάσουμε από όλα τα δεδομένα (στη
χειρότερη περίπτωση) έτσι ώστε να βρεθεί η θέση του στοιχείου που ψάχνουμε,
δηλαδή η λειτουργία αυτή θα έχει πολυπλοκότητα χρόνου Ο(Ν).
• Μια καλύτερη δομή δεδομένων για το συγκεκριμένο πρόβλημα είναι ένας
πίνακας κατακερματισμού που εντοπίζει το στοιχείο προς αναζήτηση σε χρόνο
Ο(1).
• Σε μεγάλα σύνολα δεδομένων η χρήση πίνακα κατακερματισμού στη θέση
δομών δεδομένων όπως οι πίνακες, οι συνδεδεμένες λίστες και τα διανύσματα
επιτυγχάνουν επιτάχυνση του χρόνου εκτέλεσης της τάξης του 100.
9
10
Αφηρημένοι τύποι δεδομένων (ADT=Abstract
Data Types)
• Ένα αφηρημένος τύπος δεδομένων (ADT=Abstract Data Type) ορίζεται από τις
λειτουργίες τις οποίες πρέπει να υποστηρίζει και δεν περιέχει λεπτομέρειες
υλοποίησης
• Οι δομές δεδομένων αποτελούν συνήθως υλοποιήσεις αφηρημένων τύπων
δεδομένων: στοίβες, ουρές, διανύσματα, συνδεδεμένες λίστες, δένδρα
• Στοίβες (stacks)
• LIFO (Last In First Out). Οι εισαγωγές και οι διαγραφές επιτρέπονται μόνο από την κορυφή
της στοίβας.
• Μια στοίβα έχει δύο βασικές λειτουργίες:
• push: προσθέτει ένα στοιχείο στην κορυφή της στοίβας
• pop: αφαιρεί ένα στοιχείο από την κορυφή της στοίβας
• Τυπικές περιοχές εφαρμογών των στοιβών είναι οι μεταγλωττιστές, τα λειτουργικά
συστήματα, η διαχείριση της μνήμης του προγράμματος κατά την κλήση συναρτήσεων κ.α.
11
Ουρές (queues)
• FIFO (First In First Out): Οι εισαγωγές γίνονται στο πίσω άκρο της
ουράς, και οι διαγραφές γίνονται από το εμπρός άκρο της ουράς.
• Μια ουρά έχει δύο βασικές λειτουργίες:
• enqueue: προσθήκη ενός στοιχείου στην ουρά
• dequeue: αφαίρεση ενός στοιχείου από την ουρά
• Τυπικές περιοχές εφαρμογών στις οποίες χρησιμοποιούνται ουρές
είναι η ουρά αναμονής του εκτυπωτή, ο προγραμματισμός εργασιών
στα λειτουργικά συστήματα κ.α.
12
Διανύσματα (vectors)
• Τα διανύσματα είναι δομές δεδομένων που αποθηκεύουν δεδομένα του
ίδιου τύπου και βασίζονται στην αποθήκευσή τους σε κάποιο πίνακα.
• Ενθυλακώνοντας έναν πίνακα σε μια κλάση, μπορούμε:
• να χρησιμοποιήσουμε δυναμική δέσμευση μνήμης έτσι ώστε να αυξομειώνουμε το
μέγεθος του πίνακα εφόσον χρειάζεται.
• να χειριζόμαστε απόπειρες πρόσβασης εκτός των ορίων του πίνακα.
• Πλεονέκτημα: τυχαία πρόσβαση (πρόσβαση απευθείας σε κάποιο στοιχείο
με βάση το δείκτη).
• Μειονεκτήματα: Οι εισαγωγές και οι διαγραφές ενδιάμεσα στη δομή είναι
αργές καθώς μπορεί να απαιτούν την ολίσθηση πολλών στοιχείων που
καταλαμβάνουν διαδοχικές θέσεις στον πίνακα.
13
15
Ερωτήσεις σύνοψης
• Ποιος είναι ο ρόλος των argc και argv μεταβλητών στην main;
• Τι εξυπηρετεί η δήλωση #pragma once σε ένα αρχείο header;
• Πως αλλιώς μπορεί να γραφεί ισοδύναμος κώδικας με τη δήλωση
#pragma once ;
• Ποιο είναι το αποτέλεσμα της εντολής 56 << 1;
• Πότε η συνδεδεμένη λίστα αποτελεί καλύτερη λύση σε σχέση με το
διάνυσμα;
• Μπορεί να κατασκευαστεί τελεστής τυχαίας προσπέλασης [] για μια
συνδεδεμένη λίστα;
16
Pointers in C and C++
Βασικές γνώσεις για τους δείκτες στη C++
Νοέμβριος 2019
Τμήμα Πληροφορικής και Τηλεπικοινωνιών
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
https://github.com/chgogos/ceteiep_dsa/tree/master/appendix_pointers
#include <cstdio>
using namespace std;
int main()
{
int x = 5; x=7 &x=61fe1c
int *px; // δήλωση χωρίς αρχικοποίηση δείκτη
px=61fe1c *px=7, &px=61fe10
px = &x; // αρχικοποίηση δείκτη
*px = 7; // αποαναφορά (dereference) δείκτη
printf("x=%i &x=%x\n", x, &x);
printf("px=%x *px=%i, &px=%x\n", px, *px, &px);
}
2
simple_pointer_example.cpp
Συνηθισμένο λάθος με δείκτη
#include <cstdio>
using namespace std;
int main()
{
int *p1;
int *p2 = NULL;
int *p3 = nullptr; // C++ guidelines
printf("p1=%x, p2=%x, p3=%x\n", p1, p2, p3); p1=6, p2=0, p3=0
3
uninitialized_pointer.cpp
Δείκτης σε struct
#include <iostream>
using namespace std;
struct point {
int x;
int y;
};
int main() {
point a = {3, 2};
point *p = &a;
p->x = 4; // ή (*p).x = 4;
p->y = 5; // ή (*p).y = 5;
}
4
pointer_to_struct.cpp
Δείκτης σε union
#include <cstdio>
using namespace std;
union my_union {
int i;
double d;
char c;
}; 1 0.00
int main() {
1374389535 3.14
my_union mu; 1374389601 3.14 a
my_union *p = μ
printf("sizeof union is %d\n", sizeof(mu));
p->i = 1;
printf("%d %.2f %c\n", p->i, p->d, p->c);
p->d = 3.14;
printf("%d %.2f %c\n", p->i, p->d, p->c);
p->c = 'a';
printf("%d %.2f %c\n", p->i, p->d, p->c);
}
5
pointer_to_union.cpp
int main() {
int a=5,b=5;
fun1(a,&b);
cout << a << " " << b << endl;
fun2(a,b);
cout << a << " " << b << endl;
}
6
call_by_reference.cpp
Δείκτες και πίνακες (αριθμητική δεικτών)
#include <iostream>
using namespace std;
int main() {
int a[] = {1, 2, 3, 4, 5};
int *p;
p = a; // ή p=&a[0];
*p = 7; // {7, 2, 3, 4, 5};
4
p++;
*p = 7; // {7, 7, 3, 4, 5};
p += 2;
*p = 7; // {7, 7, 3, 7, 5};
int *p1 = &a[0];
int *p2 = &a[4];
cout << p2 - p1 << endl; // εμφανίζει 4
}
7
pointer_arithmetic.cpp
8
pointer_vs_array.cpp
Δυναμική δέσμευση μνήμης (C)
#include <cstdio>
#include <cstdlib>
int main() {
int *p = (int *)malloc(sizeof(int));
*p = 5;
printf("p=%x *p=%i\n", p, *p);
free(p);
p=cd1360 *p=5
p = (int *)calloc(sizeof(int), 1); p=cd1360 *p=0
printf("p=%x *p=%i\n", p, *p); p=cd1360 p[0]=1 p[1]=2
free(p); p=cd1360 p[0]=1 p[1]=2, p[2]=3
p = (int *)malloc(sizeof(int)*2);
p[0]=1;
p[1]=2;
printf("p=%x p[0]=%i p[1]=%i\n", p, p[0], p[1]);
p = (int*)realloc(p,3);
p[2]=3;
printf("p=%x p[0]=%i p[1]=%i, p[2]=%i\n", p, p[0], p[1], p[2]);
free (p);
}
9
dynamic_in_c.cpp
struct point {
int x;
int y;
}; 11
int main() {
point *p = (point *)malloc(sizeof(point));
p->x = 1;
p->y = 1;
printf("%i %i\n", p->x, p->y);
free(p);
}
10
dynamic_memory_allocation_struct1.cpp
Δυναμική δέσμευση μνήμης (C++)
#include <iostream>
#include <cstdio>
p=6d1690 *p=5
int main() { p=6d1690 *p[0]=7154192 *p[1]=0 *p[2]=7143760
int *p = new int;
p=6d1690 *p[0]=0 *p[1]=0 *p[2]=0
*p = 5;
printf("p=%x *p=%i\n", p, *p);
delete p;
p = new int[3];
printf("p=%x *p[0]=%i *p[1]=%i *p[2]=%i\n", p, p[0], p[1], p[2]);
delete[] p;
11
dynamic_in_cpp.cpp
struct point {
int x;
int y;
}; 11
int main() {
point *p = new point;
p->x = 1;
p->y = 1;
cout << p->x << " " << p->y << endl;
delete p;
}
12
dynamic_memory_allocation_struct2.cpp
Η περίπτωση του *p++
#include <iostream>
using namespace std;
int main() {
int a[] = {10, 20, 30, 40, 50};
int *p = a; 10
int sum = 0;
20
for (int i = 0; i < 5; i++) {
cout << *p << endl; 30
sum += *p++; 40
} 50
cout << "sum=" << sum << endl; sum=150
}
13
pointer_plus_plus.cpp
struct point {
int x;
int y;
POINTER TO DYNAMIC ARRAY: 1 1
};
int main() {
point *p1 = new point[5];
p1[0].x = 1;
p1[0].y = 1;
cout << "POINTER TO DYNAMIC ARRAY: " << p1[0].x << " " << p1[0].y << endl;
delete[] p1;
}
14
pointer_to_dynamic_array.cpp
Αυτόματος πίνακας δεικτών προς εγγραφές
#include <iostream>
using namespace std;
struct point {
int x;
int y;
};
vp = ip;
*(int *)vp = 1;
printf("2. ip=%x dp=%x vp=%x *ip=%i *dp=%.2f\n", ip, dp, vp, *ip, *dp);
fun1((int *)vp);
vp = dp;
*(double *)vp = 1.5;
printf("3. ip=%x dp=%x vp=%x *ip=%i *dp=%.2f\n", ip, dp, vp, *ip, *dp);
fun2((double *)vp);
} 17
void_pointer.cpp
int main(){
cout << fun(fun1, 4,5) << endl;
cout << fun(fun2, 4,5) << endl;
}
18
function_pointers1.cpp
f(-3.1415)=-9.26536e-05
f(-2.5132)=-0.587845
Δείκτες συναρτήσεων
#include <iostream>
#include <vector> aristea chris maria nikos
#include <algorithm>
using namespace std;
chris maria nikos aristea
int main() {
vector<int> v = {1, 2, 3, 4, 5};
24
vector_iterator_is_a_pointer.cpp
Οι iterators των maps είναι δείκτες
#include <iostream>
#include <map>
using namespace std;
int main() {
std::map<std::string, int> months = {
{"Jan", 31}, {"Feb", 28}, {"Mar", 31}, {"Apr", 30}
};
map<string, int>::iterator itr = months.begin(); Apr 30
while (itr != months.end()) { Feb 28
if (itr->second == 31)
itr = months.erase(itr);
else
++itr;
}
for (auto itr = months.cbegin(); itr != months.cend(); ++itr)
cout << itr->first << " " << itr->second << endl;
}
25
map_iterator_is_a_pointer.cpp
int main() {
int a = 5, b = 10;
const int c = 20; pointer to int: a=6 b=11 c=20 *p1=11
int *p1; // pointer to int
p1 = &a;
(*p1)++;
p1 = &b;
(*p1)++;
// p1 = &c; // error: assigning to 'int *' from incompatible type 'const int *'
printf("pointer to int: a=%i b=%i c=%i *p1=%i\n", a, b, c, *p1);
26
const_and_pointers.cpp
Δείκτης σε const int
#include <cstdio>
using namespace std;
int main()
{
int a = 5, b = 10;
const int c = 20;
int const *p2; // pointer to const int pointer to const int: a=5 b=10 c=20 *p2=20
p2 = &a;
p2 = &b;
p2 = &c;
printf("pointer to const int: a=%i b=%i c=%i *p2=%i\n", a, b, c, *p2);
// p2 = &a;
// (*p2)++; // error: read-only variable is not assignable
}
27
const_and_pointers.cpp
int main()
{
int a = 5, b = 10;
const int c = 20; const pointer to int: a=6 b=10 c=20 *p3=6
int *const p3 = &a; // const pointer to int
(*p3)++;
// p3 = &b; // error: cannot assign to variable 'p3' with const-qualified type 'int *const'
printf("const pointer to int: a=%i b=%i c=%i *p3=%i\n", a, b, c, *p3);
}
28
const_and_pointers.cpp
const δείκτης σε const int
#include <cstdio>
using namespace std;
int main()
{
int a = 5, b = 10; const pointer to const int: a=5 b=10 c=20 *p4=5
const int c = 20;
int const *const p4 = &a; // const pointer to const int
29
const_and_pointers.cpp
Ο δείκτης this
#include <iostream>
using namespace std;
class point {
private:
int x, y;
public:
point() {}
void set_xy(int x, int y) {
this->x = x; 0x62fe18(x=1, y=2)
this->y = y;
}
void display() {
cout << this << "(x=" << x << ", y=" << y << ")" << endl;
}
};
int main() {
point a_point;
a_point.set_xy(1, 2);
a_point.display();
}
30
this_pointer.cpp
Δυναμικός δισδιάστατος πίνακας (δείκτης σε
δείκτη)
#include <iostream>
using namespace std;
void print(int **a, int m, int n) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++)
cout << a[i][j] << " ";
cout << endl;
}
} 12
int main() { 34
int m = 3, n = 2, c = 0;
56
int **mat = new int *[m];
for (int i = 0; i < m; i++) {
mat[i] = new int[n];
for (int j = 0; j < n; j++)
mat[i][j] = ++c;
}
print(mat, m, n);
for (int i = 0; i < m; i++)
delete[] mat[i];
delete[] mat;
}
31
2d_array.cpp
STL
Standard Template Library
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Standard Template Library
• Η STL είναι μια βιβλιοθήκη επαναχρησιμοποιήσιμων στοιχείων που
βασίζεται στο γενερικό προγραμματισμό (προγραμματισμό με templates).
• Η STL αποτελείται από
• Containers (περιέκτες): Επιτρέπουν την οργάνωση μιας συλλογής αντικειμένων στη
μνήμη του Η/Υ. Πρόκειται για templated κλάσεις (π.χ. vector<int>, list<double>, …).
• Algorithms (αλγόριθμοι): Αλγόριθμοι που εφαρμόζονται σε containers (π.χ. sort,
find, …). Είναι γενικοί και μπορούν να χρησιμοποιηθούν σε διάφορους τύπους
containers.
• Iterators (επαναλήπτες): «Δείκτες» που επιτρέπουν τη διάσχιση και σε ορισμένες
περιπτώσεις την αλλαγή ενός container. Ένας iterator δείχνει σε κάποιο στοιχείο
ενός container.
Containers
• Σε ένα container μπορούν να αποθηκευτούν τιμές βασικών τύπων καθώς και αντικείμενα.
• Τα containers χωρίζονται σε δύο βασικές κατηγορίες:
• Ακολουθιακά containers (sequence containers) – κάθε αντικείμενο ακολουθείται από κάποιο άλλο
αντικείμενο, έχουν δηλαδή γραμμική διευθέτηση.
• vector (διάνυσμα)
• array (διάνυσμα σταθερού μεγέθους)
• list (διπλά συνδεδεμένη λίστα)
• forward_list (απλά συνδεδεμένη λίστα)
• deque (ουρά με δύο άκρα)
• container adaptors
• stack (στοίβα)
• queue (ουρά)
• priority_queue (ουρά προτεραιότητας)
• Containers αντιστοίχισης (associative containers) – γίνεται χρήση κλειδιών για την πρόσβαση στα στοιχεία
του container, επιτρέπουν τη γρήγορη πρόσβαση βάσει κλειδιών.
• set (σύνολο)
• multiset
• map (πίνακας αντιστοίχισης)
• multimap
3
std::vector
• Διάνυσμα με δυνατότητα δυναμικής αύξησης του μεγέθους του έτσι ώστε
να δεχθεί νέα στοιχεία.
• Constructors
• vector<T> v; // άδειο vector
• vector<T> v(n); // vector με n αντίγραφα της προκαθορισμένης τιμής του Τ
• vector<T> v(n, value); // vector με n αντίγραφα της τιμής value
• Προσθήκη νέων στοιχείων std::vector<int> v1{1, 2, 3, 4};
• push_back()
• Μέγεθος – χωρητικότητα 1 2 3 4
• size()
• capacity() v.begin() v.end()
Δεικτοδότηση σε vector
• Δεικτοδότηση με []
• Υψηλή απόδοση, η πρόσβαση γίνεται χωρίς έλεγχο ορίων του vector.
• Δεικτοδότηση με at()
• Πραγματοποιεί έλεγχο ορίων, προκαλεί την εξαίρεση out_of_range exception
αν επιχειρηθεί πρόσβαση εκτός των ορίων του vector.
https://chgogos.github.io/oop/cpp_playground/ex046/stl_vector1.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_vector2.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_vector3.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_vector4.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_vector5.cpp
5
std::array
• Διάνυσμα με προκαθορισμένο μέγεθος
• Βασικές λειτουργίες:
• size()
• τελεστής []
std::array<int, 5> a{1, 2, 3, 4, 5};
https://chgogos.github.io/oop/cpp_playground/ex046/stl_array.cpp
std::deque
• Διάνυσμα με δύο άκρα.
• Βασικές συναρτήσεις μέλη:
• operator[]
• at()
• front() https://chgogos.github.io/oop/cpp_playground/ex046/stl_deque.cpp
• push_front()
• back()
• push_back()
• …
7
std::forward_list
• Απλά συνδεδεμένη λίστα
• Βασικές συναρτήσεις μέλη:
• front()
• πρόσβαση στο πρώτο στοιχείο της λίστας
• push_front() https://chgogos.github.io/oop/cpp_playground/ex046/stl_forward_list.cpp
std::list
• Διπλά συνδεδεμένη λίστα
• Βασικές συναρτήσεις μέλη:
• front()
• πρόσβαση στο πρώτο στοιχείο της λίστας
• push_front()
• εισαγωγή στοιχείου στην αρχή της λίστας https://chgogos.github.io/oop/cpp_playground/ex046/stl_list.cpp
• back()
• πρόσβαση στο πρώτο στοιχείο της λίστας
• push_back()
• εισαγωγή στοιχείου στο τέλος της λίστας
• sort()
• ταξινόμηση
• reverse()
• Αντιστροφή
• …
9
std::set
• Σε ένα set αποθηκεύονται αντικείμενα που καθένα από αυτά
αποτελεί το ίδιο ή περιέχει ένα κλειδί.
• Κάθε κλειδί μπορεί να υπάρχει μόνο μια φορά το πολύ.
• Τα κλειδιά είναι ταξινομημένα.
• To std::set συνήθως υλοποιείται ως ισορροπημένο δυαδικό δένδρο
αναζήτησης.
• Παραλλαγές του set
• multiset: επιτρέπει πολλές εμφανίσεις του ίδιου κλειδιού.
• unordered_set: δεν υπάρχει διάταξη με βάση τα κλειδιά, υλοποιείται ως
πίνακας κατακερματισμού.
https://chgogos.github.io/oop/cpp_playground/ex046/stl_set.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_set2.cpp 10
std::map
• Σε ένα map αποθηκεύονται ζεύγη (key, value), δηλαδή για κάθε
κλειδί υπάρχει μια σχετιζόμενη τιμή.
• Κάθε κλειδί μπορεί να υπάρχει μόνο μια φορά το πολύ.
• Τα ζεύγη είναι ταξινομημένα με βάση τα κλειδιά.
• Παραλλαγές του map:
• multimap: επιτρέπει πολλές εμφανίσεις του ίδιου κλειδιού.
• unordered_map: δεν υπάρχει διάταξη με βάση τα κλειδιά, υλοποιείται ως
πίνακας κατακερματισμού.
https://chgogos.github.io/oop/cpp_playground/ex046/stl_map.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_map2.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_multimap.cpp
11
Container Adaptor κλάσεις
• stack (στοίβα)
• queue (ουρά)
• priority_queue (ουρά προτεραιότητας)
https://chgogos.github.io/oop/cpp_playground/ex046/stl_stack.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_queue.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_priority_queue.cpp
12
Iterators
• Οι iterators είναι αντικείμενα που μοιάζουν με δείκτες και χρησιμοποιούνται για
την πρόσβαση στα περιεχόμενα ενός container.
• Επιτρέπουν τη «διάσχιση» ενός container από το ένα στοιχείο του στο επόμενο.
• Οι συναρτήσεις begin() και end() επιτρέπουν την αναφορά στο πρώτο
στοιχείο ενός container και στη θέση αμέσως μετά από το τελευταίο στοιχείο
ενός container, αντίστοιχα. Εναλλακτικά μπορούν να χρησιμοποιηθούν οι
συναρτήσεις μέλη begin() και end() για containers όπως το vector.
vector<int> v;
…
vector<int>::iterator iter=begin(v);
ή
auto iter = begin(v);
ή
auto iter = v.begin(); // συνάρτηση μέλος του vector
13
Iterators
• Οι iterators αποτελούν ένα μηχανισμό για τον καθορισμό μιας θέσης
μέσα σε ένα container.
• Υπάρχουν διάφοροι τύποι iterators, αλλά όλοι χρησιμοποιούν τον
ίδιο βασικό τρόπο διάσχισης και πρόσβασης στα στοιχεία ενός
container, δηλαδή:
• Τον τελεστή ++ για τη μετακίνηση στο επόμενο στοιχείο.
• Τον τελεστή * για την πρόσβαση στο τρέχον στοιχείο.
• Σύγκριση ενός iterator με έναν άλλο iterator.
14
Τύποι iterators
• Υπάρχουν συγκεκριμένοι random
access
iterators που μπορούν να
χρησιμοποιηθούν με κάθε
bidirectional
container.
• Υπάρχουν 5 βασικοί τύποι
forward
iterators: random access,
bidirectional, forward, input,
output input
output
15
Τύπος iterator ανά container
Container Iterator • O vector<int>::iterator είναι random
vector random access access iterator για πρόσβαση σε ακεραίους.
deque random access • O list<float>::iterator είναι
bidirectional iterator για πρόσβαση σε
list bidirectional πραγματικούς.
set bidirectional
• O forward_list<student>::iterator
multiset bidirectional είναι forward iterator για πρόσβαση σε
map bidirectional αντικείμενα student.
multimap bidirectional • Οι container adaptors (stack, queue,
forward_list forward
priority_queue) δεν έχουν iterators.
unordered_set forward
https://chgogos.github.io/oop/cpp_playground/ex046/stl_random_iterator.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_bidirectional_iterator.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/stl_forward_iterator.cpp
16
17
stream iterator
• vector<int> v{1,2,3,4,5};
copy(v.begin(), v.end(), ostream_iterator<int>(cout, “ “));
https://github.com/chgogos/oop/blob/master/cpp_playground/ex046/stream_iterator1.cpp
• vector<int> v;
copy(istream_iterator<int>(cin), istream_iterator<int>(),
back_inserter(v))
https://github.com/chgogos/oop/blob/master/cpp_playground/ex046/stream_iterator2.cpp
18
https://chgogos.github.io/oop/cpp_playground/ex046/advance.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/distance.cpp
19
Δείκτες συναρτήσεων (function pointers)
• Οι δείκτες συναρτήσεων είναι μεταβλητές που κρατούν τη διεύθυνση
μιας συνάρτησης.
• «Περίεργη» σύνταξη
• int (*fp)(int, int); // δήλωση function pointer με όνομα fp προς μια
συνάρτηση που δέχεται 2 ακεραίους και επιστρέφει έναν ακέραιο.
• Αν υπάρχει μια συνάρτηση της μορφής int fun(int, int) τότε μπορεί ο
function pointer να δείχνει σε αυτή τη συνάρτηση:
• fp = fun;
• Η δε κλήση της συνάρτησης μέσω του function pointer γίνεται ως εξής:
• x = fp(2,3); ή ως x = (*fp)(2,3);
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers1.cpp
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers2.cpp
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers3.cpp 20
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers4.cpp
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers5.cpp
https://chgogos.github.io/oop/cpp_playground/ex001/function_pointers6.cpp
21
Αντικείμενα συναρτήσεων (functors =
function objects)
• Functor είναι οποιοδήποτε αντικείμενο στο οποίο έχει
υπερφορτωθεί ο τελεστής () και συνεπώς μπορεί να
χρησιμοποιηθεί με παρενθέσεις σαν να είναι συνάρτηση.
• Ένα functor μπορεί να διατηρεί κατάσταση καθώς ως αντικείμενο
μπορεί να διαθέτει μέλη δεδομένων.
• Συνήθως ένα functor είναι ταχύτερο από ένα function pointer.
22
https://chgogos.github.io/oop/cpp_playground/ex046/functor1.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/functor2.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/functor3.cpp
23
Τύποι functors – η περίπτωση των predicates
• Στην STL υπάρχουν τα ακόλουθα functors:
• generators, πρόκειται για functors που καλούνται χωρίς παραμέτρους.
• unary functions, πρόκειται για functors που καλούνται με μια παράμετρο.
• binary functions , πρόκειται για functors που καλούνται με δύο
παραμέτρους.
• Τα κατηγορήματα (predicates) είναι functors που επιστρέφουν μια
λογική τιμή.
• Μια unary function που επιστρέφει μια λογική τιμή είναι ένα predicate.
• Μια binary function που επιστρέφει μια λογική τιμή είναι ένα binary
predicate.
• Παράδειγμα χρήσης functor με τη remove_if
https://chgogos.github.io/oop/cpp_playground/ex046/remove_if_functor.cpp
24
25
Functors της STL (2/2)
• Λογικά δυαδικά κατηγορήματα • Αριθμητικά μοναδιαία functors
• logical_and<T> f; • negate<T> f;
• f(x,y) επιστρέφει x && y. • f(x) επιστρέφει -x.
• logical_or<T> f; • logical_or<T> f;
• f(x,y) επιστρέφει x || y. • f(x) επιστρέφει !x.
https://chgogos.github.io/oop/cpp_playground/ex046/functor4.cpp
https://chgogos.github.io/oop/cpp_playground/ex046/functor5.cpp
26
Λάμδας (lambdas)
• Λάμδα (lambda ή closure) είναι μια ανώνυμη συνάρτηση που μπορεί
να γραφεί απευθείας ως παράμετρος ή γενικότερα να
χρησιμοποιηθεί σε μια έκφραση.
auto my_lambda = [](int x)->int {return x*2;};
cout << my_lambda(5); // εμφανίζει την τιμή 10
27
Σύνταξη λάμδα συναρτήσεων: [](){}
• [captures](parameters)->return_type
{statements;}
• [captures]
• Ποιες μεταβλητές έξω από τη λάμδα θα είναι
διαθέσιμες στις εντολές της λάμδα και αν αυτές
οι μεταβλητές θα περνούν με τιμή ή με
αναφορά.
• (parameters)
https://chgogos.github.io/oop/cpp_playground/ex071/lambda1.cpp
28
https://chgogos.github.io/oop/cpp_playground/ex087/fp_functor_lambda.cpp
29
Αλγόριθμοι: sort(), merge()
• Η sort() ταξινομεί μια περιοχή τιμών.
• H stable_sort() ταξινομεί διατηρώντας τη μεταξύ τους σειρά για
στοιχεία που έχουν το ίδιο κλειδί.
• Η merge() συγχωνεύει δύο ταξινομημένες περιοχές τιμών σε μια.
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_sort.cpp
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_stable_sort.cpp
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_merge.cpp
30
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_set_find.cpp
31
Αλγόριθμοι: count(), count_if()
• H count() καταμετρά τις εμφανίσεις μιας τιμής σε μια περιοχή τιμών.
• Η count_if() καταμετρά τα στοιχεία σε μια περιοχή τιμών για τα
οποία το predicate που δέχεται ως παράμετρο είναι αληθές.
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_count.cpp
32
Αλγόριθμοι: for_each()
• Εφαρμόζει ένα lambda σε κάθε στοιχείο μιας περιοχής.
https://github.com/chgogos/oop/blob/master/cpp_playground/ex086/stl_for_each.cpp
33
erase remove idiom
• Η διαγραφή ενός συνόλου τιμών από ένα vector με βάση κάποιο
predicate γίνεται ως εξής:
vector<int> v{7,13,2,8,9,1,8,4,5};
// διαγραφή περιττών τιμών από το v
v.erase(remove_if(v.begin(), v.end(), [](int x){return x%2==1;}), v.end());
2884
34
36
C++ vs Java
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Δημοφιλείς γλώσσες προγραμματισμού
(2020)
https://www.tiobe.com/tiobe-index/
https://redmonk.com/sogrady/2020/07/27/language-rankings-6-20/
https://pypl.github.io/PYPL.html 2
Αντικειμενοστρέφεια
• Τόσο η C++ όσο και η Java υποστηρίζουν τον αντικειμενοστραφή
προγραμματισμό
• Στην Java τα πάντα(;) είναι αντικείμενα και δεν επιτρέπει σε δεδομένα ή
συναρτήσεις να υπάρχουν εκτός κλάσεων
• Η C++ υποστηρίζει και το μοντέλο διαδικασιακού προγραμματισμού (procedural
programming)
• Η Java αντιμετωπίζει διαφορετικά τους πρωτογενείς τύπους (π.χ. int,
double, char κλπ) από τα αντικείμενα
• H Java είναι απλούστερη από τη C++ και περισσότερο ασφαλής
• H Java υποστηρίζει πολυμορφισμό αυτόματα ενώ στη C++ χρειάζεται να
δηλώνονται οι συναρτήσεις μέλη ως virtual
• Η Java υποστηρίζει διεπαφές (interfaces) έναν τρόπο επιβολής
υλοποίησης συγκεκριμένων συναρτήσεων από κλάσεις
3
Σύγκριση χαρακτηριστικών C++ και Java (1/2)
C++ Java
• Υποστηρίζει δείκτες, πολλαπλή • Δεν υποστηρίζει δείκτες, πολλαπλή
κληρονομικότητα, υπερφόρτωση κληρονομικότητα και υπερφόρτωση
τελεστών τελεστών
• O προγραμματιστής είναι υπεύθυνος • Εμπεριέχει ελέγχους για όρια πινάκων
έτσι ώστε να μην βγαίνει εκτός ορίων • Διαθέτει αυτόματο garbage collection
πινάκων
• Η διαχείριση μνήμης είναι ευθύνη του • Είναι αρκετά γρήγορη
προγραμματιστή • Είναι cross-platform (o κώδικας αρχικά
• Είναι γρήγορη μεταγλωττίζεται σε bytecode και στη
συνέχεια διερμηνεύεται από το JVM) -
• Ο κώδικας μεταγλωττίζεται (το WORA(Write Once and Run Everywhere)
εκτελέσιμο δεν εκτελείται σε άλλη
πλατφόρμα)
C++ Java
• Υποστηρίζει πέρασμα με τιμή (call • Υποστηρίζει μόνο πέρασμα με τιμή
by value) και πέρασμα με (call by value)
αναφορά (call by reference) • Χρησιμοποιεί generics
• Χρησιμοποιεί templates • Διαθέτει βιβλιοθήκες για μεγάλο
• Στην ίδια τη γλώσσα υπάρχει εύρος λειτουργιών (π.χ. GUIs,
μικρότερος αριθμός βιβλιοθηκών Network programming, JDBC κ.α.)
• Είναι system level • Βρίσκεται υψηλότερα από το
• Χρησιμοποιείται όπου απαιτείται system level
ταχύτατη εκτέλεση κώδικα (π.χ. • Χρησιμοποιείται για ανάπτυξη
επιστημονικές εφαρμογές, εφαρμογών σε κινητές συσκευές
ανάπτυξη παιχνιδιών) (Android)
5
JVM (Java Virtual Machine) - μεταφερσιμότητα κώδικα
JVM Windows
Java bytecode
.java JVM Linux
compiler .class
JVM OSX
bytecode: ενδιάμεση μορφή κώδικα που μπορεί
να εκτελεστεί διερμηνευόμενος από το JVM
.java
πηγαίος .class εκτελέσιμος
java compiler JIT compiler
κώδικας σε bytecode κώδικας
java
Φάση μεταγλώττισης
7
Java: Garbage collection (1/2)
• Η συλλογή απορριμμάτων στη Java είναι η διαδικασία της αυτόματης
διαχείρισης μνήμης (δέσμευσης/αποδέσμευσης μνήμης)
• Ο GC (Garbage Collector) εντοπίζει τα αντικείμενα που δεν
χρησιμοποιούνται, τα διαγράφει και ελευθερώνει τη μνήμη που
κατελάμβαναν
• Η δυναμική δέσμευση μνήμης γίνεται (στο heap) με τον τελεστή new
και η μνήμη παραμένει δεσμευμένη έως ότου να πάψουν να
υπάρχουν αναφορές προς τη μνήμη αυτή
• Η συλλογή απορριμμάτων γίνεται αυτόματα καθώς εκτελείται το
πρόγραμμα
8
9
Παραδείγματα κώδικα
Υλοποιήσεις σε C++ και σε Java
10
C++ Java
hello.cpp Hello.java
#include <iostream> public class Hello {
using namespace std; public static void main(String[] args) {
int main() System.out.println("Hello World");
{ }
cout << "Hello World" << endl;
} }
$ g++ hello.cpp
$ ./a.out
$ javac Hello.java
Hello World
$ java Hello
https://github.com/chgogos/oop/blob/master/various/CPP_VS_JAVA/helloworld_cpp/hello.cpp
Hello World
https://github.com/chgogos/oop/blob/master/various/CPP_VS_JAVA/helloworld_java/Hello.java
11
Έλεγχος ορίων (bounds check)
https://github.com/chgogos/oop/blob/master/various/CPP_VS_JAVA/bounds_check_cpp/bounds.cpp
12
C++ Java
dogs.cpp public class Dog {
Dog.java
#include <iostream>
using namespace std; private String name;
class dog private String species;
{ public Dog(String n, String s) {
private: name = n;
string name; species = s;
string species; }
public: public void makeSound() {
dog(string n, string s) : name(n), species(s) {} System.out.println("Dog " + name + " of species "
void make_sound() + species + " barks");
{ }
cout << "Dog " << name << " of species " << species public static void main(String[] args) {
<< " barks" << endl; Dog obj = new Dog("Adelle", "Maltese");
} obj.makeSound();
}; }
int main() }
{
dog obj("Adelle", "Maltese"); $ javac Dog.java
obj.make_sound(); $ java Dog
}
Dog Adelle of species Maltese barks
$ g++ dogs.cpp
https://github.com/chgogos/oop/blob/master/various/CPP_VS_JAVA/simple_java/Dog.java
$ ./a.out
Dog Adelle of species Maltese barks
13
https://github.com/chgogos/oop/blob/master/various/CPP_VS_JAVA/simple_cpp/dog.cpp
Παραδείγματα κώδικα σε C++ και Java
Εκφωνήσεις: https://chgogos.github.io/oop/preparation/proodos.pdf
14
UML
διαγράμματα κλάσεων
Τμήμα Πληροφορικής και Τηλεπικοινωνιών (Άρτα)
Πανεπιστήμιο Ιωαννίνων
Γκόγκος Χρήστος
V 1.0
Τι είναι η UML;
• H UML (Unified Modeling Language) είναι μια γλώσσα
μοντελοποίησης, βασισμένη σε διαγράμματα, που υποστηρίζει την
ανάπτυξη λογισμικού σε όλα τα στάδιά του. Ορίζει ένα κοινό
λεξιλόγιο και κοινά αποδεκτούς τύπους διαγραμμάτων για τη
περιγραφή του λογισμικού καθώς αναπτύσσεται εξετάζοντάς το υπό
διάφορες οπτικές γωνίες
• Η πρώτη έκδοση της UML ανακοινώθηκε το 1997 και η τρέχουσα
έκδοσή της είναι η UML 2.5.1 (Δεκέμβριος 2017)
• https://www.omg.org/spec/UML/About-UML/
3
Διαγράμματα κλάσεων
• Τα διαγράμματα κλάσεων είναι τα πλέον κοινά διαγράμματα UML
• Τα διαγράμματα κλάσεων περιγράφουν:
• τους τύπους των αντικειμένων
• τις στατικές σχέσεις μεταξύ των αντικειμένων
• τις ιδιότητες των κλάσεων
• τις λειτουργίες των κλάσεων
• τους περιορισμούς στον τρόπο με τον οποίο τα αντικείμενα συνδέονται
μεταξύ τους
• Στην UML οι ιδιότητες (μέλη δεδομένων στη C++) και οι λειτουργίες
(συναρτήσεις μέλη στη C++) μιας κλάσης αναφέρονται από κοινού
ως χαρακτηριστικά (features)
protected:
int y;
int bar(int y, int z){…}
public:
Example(){…}
https://github.com/chgogos/oop/blob/master/uml/example1.cpp
int z;
};
Σχέσεις (relationships)
• Τύποι σχέσεων
• Εξάρτηση (dependency)
• Συσχέτιση (association)
• Συνάθροιση (aggregation)
• Σύνθεση (composition)
• Γενίκευση (generalization)
• Υλοποίηση (realization)
• Για κάθε τύπο σχέσης
χρησιμοποιείται
διαφορετικός οπτικά τύπος
σύνδεσης
7
Σχέση εξάρτησης (dependency)
• Το A έχει εξάρτηση από το Β
• Αν ένα αντικείμενο της κλάσης Β
περνά ως παράμετρος σε
συνάρτηση της κλάσης Α
ή
• Αν ένα αντικείμενο της κλάσης Β
εκχωρείται σε τοπική μεταβλητή
μιας συνάρτησης της κλάσης A
• Η σχέση εξάρτησης είναι γνωστή
και ως σχέση “uses a”
(χρησιμοποιεί ένα)
class A
{
public:
void bar(B b){b.foo();}
};
10
14
16
int main()
{ https://github.com/chgogos/oop/blob/master/uml/generalization1.cpp
Derived obj;
obj.foo();
} 17
Γενίκευση με αφηρημένη κλάση (κώδικας)
class Shape Τα italics
υποδηλώνουν ότι η
{
κλάση Shape είναι
public: αφηρημένη (abstract)
virtual ~Shape() {}
virtual void draw() = 0; // pure virtual function
};
class Rectangle : public Shape
{
public:
void draw(){…}
};
class Triangle : public Shape
{…};
class Circle : public Shape
{…}; https://github.com/chgogos/oop/blob/master/cpp_playground/ex022/shapes.cpp
int main()
{
Shape *a[] = {new Rectangle, new Triangle, new Circle};
for (int i = 0; i < 3; i++) a[i]->draw();
for (int i = 0; i < 3; i++) delete a[i];
} 18
19
Παράδειγμα με σχέση υλοποίησης (κώδικας)
class Person
{
protected:
https://github.com/chgogos/oop/blob/master/uml/realization1.cpp
string name;
public:
Person(string n) : name(n) {}
};
class Musician
{
public:
virtual void play() = 0;
};
class Pianist : public Person, public Musician
{
private:
string piano;
public:
Pianist(string name, string piano) : Person(name), piano(piano) {}
void play()
{
cout << "Musician: " << name << " Instrument: " << piano << endl;
}
}; Στη C++ μια διεπαφή ορίζεται ως αφηρημένη
κλάση (στο παράδειγμα η κλάση Musician)
int main(){
Pianist p("Nikolaos", "Steinway & Sons");
p.play();
} 20
21
Πολλαπλότητα συσχετίσεων (multiplicity)
• * (οποιοσδήποτε αριθμός) • Κάθε αντικείμενο Α σχετίζεται
• 1 (ακριβώς ένα) με οποιοδήποτε αριθμό
αντικειμένων Β
• n (ακριβώς n)
• Κάθε αντικείμενο B σχετίζεται
• 0..1 (μηδέν ή ένα) με ένα αντικείμενο A
• 1..* (ένα ή περισσότερα)
• n..m (από n μέχρι m)
22
23