Κλάσεις και αντικείμενα
Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr
Ορισμός κλάσεων
- Η δήλωση μιας κλάσης (class)
είναι παρόμοια με αυτή μιας δομής:
class point {
int x, y;
};
- Οι κλάσεις χρησιμοποιούνται για την υλοποίηση αφηρημένων τύπων
δεδομένων.
Για το σκοπό αυτό μπορούμε να ορίσουμε μέλη (members)
της κλάσης:
μεταβλητές και συναρτήσεις
που είναι ορατά μόνο από συναρτήσεις που αναφέρονται στον τύπο αυτό
(private) καθώς και μεταβλητές και συναρτήσεις που είναι καθολικά
ορατά (public).
Οι μεταβλητές ορίζουν ιδιότητες (properties)
και οι συναρτήσεις ορίζουν
μεθόδους πρόσβασης (methods)
των αντικειμένων της κλάσης.
Παράδειγμα:
class point {
private:
int x, y;
public:
int getx();
int gety();
void setpos(int sx, int sy);
void display();
};
Στο παραπάνω παράδειγμα τα μέλη (ιδιότητες) της κλάσης x, y δεν είναι ορατά και προσβάσιμα
παρά μόνο από τις συναρτήσεις (μεθόδους) της κλάσης getx, gety, setpos και display.
- Ο ορισμός των συναρτήσεων της κλάσης γίνεται με τη χρήση της
σύνταξης κλάση::συνάρτηση.
Παράδειγμα:
int
point::getx()
{
return (x);
}
int
point::gety()
{
return (y);
}
void
point::display()
{
cout << "(" << x << "," << y << ")\n";
}
void
point::setpos(int sx, int sy)
{
x = sx;
y = sy;
}
- Αφού δηλωθεί μια κλάση (τυπικά σε ένα αρχείο κλάση.h) και οριστούν
οι συναρτήσεις της (τυπικά σε ένα αρχείο κλάση.cpp) μπορούν να οριστούν
μεταβλητές με τον τύπο της κλάσης.
Πρόσβαση στις συναρτήσεις της κλάσης έχουν οι μεταβλητές αυτές με τη
σύνταξη μεταβλητή.συνάρτηση.
Μπορούμε επίσης να δηλώσουμε μεταβλητές δείκτες μιας κλάσης.
Πρόσβαση στις συναρτήσεις της κλάσης έχουν οι μεταβλητές αυτές με τη
σύνταξη μεταβλητή->συνάρτηση.
Παράδειγμα:
#include <iostream.h>
#include "point.h"
main()
{
point a;
point b, *c;
c = new point;
b.setpos(6, 6);
cout << b.getx();
a.display();
b.display();
c->display();
}
- Οι συναρτήσεις της κλάσης μπορούν να έχουν πρόσβαση στα στοιχεία της
κλάσης με τους εξής τρόπους:
- με το όνομά τους (π.χ. x),
- μέσω της ορισμένης από τη γλώσσα
μεταβλητής this που δείχνει στην κλάση (π.χ. this->x),
- με πρόθεμα το όνομα της κλάσης για να αποφεύγεται η σύγχυση
όταν υπάρχει τοπική μεταβλητή με το ίδιο όνομα (π.χ. point::x).
Παράδειγμα:
void
point::setpos(int sx, int sy)
{
this->x = sx;
point::y = sy;
}
Μέθοδοι κατασκευής και καταστροφής
- Σε κάθε κλάση μπορεί να οριστεί μια
μέθοδος κατασκευής (constructor) με όνομα ίδιο
με αυτό της κλάσης και μια
μέθοδος καταστροφής (destructor) με όνομα το
όνομα της κλάσης με το πρόθεμα ~.
-
Η μέθοδος κατασκευής καλείται κάθε φορά που δημιουργείται
ένα νέο αντικείμενο (σε επίπεδο καθολικό, τοπικό, ή με new).
-
Η μέθοδος καταστροφής καλείται κάθε φορά που παύει να υπάρχει
ένα αντικείμενο δηλαδή αντίστοιχα όταν τελειώνει το πρόγραμμα, όταν
η ροή βγαίνει από το τοπικό τμήμα, ή καλείται η delete.
-
Το όρισμα που δηλώνουμε στη μέθοδο κατασκευής επιτρέπει προσδιορισμό
ιδιοτήτων του αντικειμένου που δημιουργούμε (π.χ. τον αριθμό στοιχείων
σε μια στοίβα) ή αρχικών τιμών.
Για το ίδιο αντικείμενο μπορούμε να ορίσουμε πολλές μεθόδους κατασκευής
με διαφορετικά ορίσματα.
-
Η μέθοδος καταστροφής δε δέχεται κάποιο όρισμα.
-
Οι πίνακες δεν μπορούν να αρχικοποιηθούν με τη χρήση μεθόδων που
απαιτούν κάποιο όρισμα.
Έτσι, για αντικείμενα που απαρτίζουν πίνακες πρέπει να έχει δηλωθεί μια
εξ' ορισμού μέθοδος κατασκευής (default constructor)
η οποία ή δε δέχεται κανένα όρισμα ή όλα της τα ορίσματα έχουν δηλωμένες
εξ' ορισμού αρχικές τιμές.
-
Οι μέθοδοι αυτές μπορούν να χρησιμοποιηθούν για να διαχειριστούν τη
μνήμη αντικειμένων που απαιτούν τη χρήση δυναμικής μνήμης με κατάλληλες
κλήσεις στις new και delete.
Παράδειγμα:
#include <stdlib.h>
#include <iostream.h>
// Points in an N dimensional space
class pointnd {
private:
int *values;
int dimensions;
public:
pointnd(); // Default constructor
pointnd(int dimensions); // Constructor
pointnd(int dimensions, int value); // Constructor with initial value
~pointnd(); // Destructor
int &val(int dimension);
void display();
};
pointnd::pointnd()
{
pointnd::dimensions = 3;
values = new int[dimensions];
}
pointnd::pointnd(int dimensions = 3)
{
pointnd::dimensions = dimensions;
values = new int[dimensions];
}
pointnd::pointnd(int dimensions, int value)
{
pointnd::dimensions = dimensions;
values = new int[dimensions];
for (int i = 0; i < dimensions; i++)
values[i] = value;
}
pointnd::~pointnd()
{
delete[] values;
}
int &
pointnd::val(int dimension)
{
if (dimension > dimensions) {
cerr << "Point has only " << dimensions << " dimensions, not " << dimension << "\n";
exit(1);
}
return (values[dimension]);
}
void
pointnd::display()
{
for (int i = 0; i < dimensions; i++)
cout << values[i] << " ";
}
main()
{
pointnd a(3);
pointnd b(2, 0);
pointnd *c;
c = new pointnd[10];
a.val(0) = 8;
a.val(1) = 3;
a.val(2) = 4;
a.display();
cout << "\n";
b.display();
return (0);
}
- Η κλήση της συνάρτησης κατασκευής μπορεί να γίνει κατά τον
ορισμό μιας μεταβλητής με τη μορφή "κλάση μεταβλητή = κλάση(όρισμα)" ή
συνοπτικότερα με τη μορφή "κλάση μεταβλητή(όρισμα)".
Ιδιότητες και μέθοδοι της κλάσης
- Σε μια κλάση μπορούν να δηλωθούν (με τον προσδιορισμό static)
μεταβλητές οι οποίες υπάρχουν μόνο μια φορά για όλη την κλάση,
καθώς και συναρτήσεις που μπορούν
να κληθούν με τη σύνταξη κλάση::συνάρτηση.
Οι μεταβλητές αυτές χρησιμοποιούνται για την επεξεργασία στοιχείων
που αφορούν ολόκληρη την κλάση και όχι τα αντικείμενά της.
Οι συναρτήσεις που έχουν οριστεί static δεν έχουν πρόσβαση σε μη
static μεταβλητές ούτε στη μεταβλητή this.
Η αρχικοποίηση των μεταβλητών που έχουν δηλωθεί static πρέπει να γίνει
σε καθολικό επίπεδο, μια φορά για κάθε κλάση.
Το παρακάτω παράδειγμα ορίζει έναν μετρητή numpoints που μετρά πόσα
σημεία είναι ενεργά καθώς και την αντίστοιχη συνάρτηση πρόσβασης:
class point {
private:
int x, y;
static int numpoints;
public:
// ...
static int points_used();
};
int point::numpoints = 0;
// Constructors
point::point(int sx, int sy)
{
x = sx;
y = sy;
numpoints++;
}
point::point()
{
x = y = 0;
numpoints++;
}
// Destructor
point::~point()
{
numpoints--;
}
// Access function
int
point::points_used()
{
return (numpoints);
}
Συναρτήσεις friend
Σε μια κλάση μπορούν να οριστούν συναρτήσεις με τον προσδιορισμό
friend.
Οι συναρτήσεις αυτές ορίζονται σε καθολικό επίπεδο και μπορούν να
έχουν πρόσβαση στα στοιχεία private της κλάσης.
Οι συναρτήσεις friend χρησιμοποιούνται όταν η χρήση των συναρτήσεων
μέλους δεν είναι βολική, δηλαδή όταν η συνάρτηση πρέπει να έχει
πρόσβαση στα στοιχεία ενός αντικειμένου αλλά δε θέλουμε να
καλείται με τη σύνταξη της μεθόδου (αντικείμενο.μέθοδος()).
Παράδειγμα:
class point {
private:
int x, y;
static int numpoints;
public:
// ...
friend void display(point& p); // Display friend function
};
// Friend function; used as display(a)
void
display(point& p)
{
cout << "(" << p.x << "," << p.y << ")\n";
}
main()
{
point b = point(1, 2);
display(b); // Friend function
}
Ο προσδιορισμός const
Συναρτήσεις που δε μεταβάλλουν μέλη της κλάσης καλό είναι να δηλώνονται
και να ορίζονται ακολουθούμενες με τον προσδιορισμό const:
class point {
private:
int x, y;
public:
int getx() const; // Access functions
int gety() const;
void display(); // Display member function
// ...
};
int
point::getx() const
{
return (x);
}
Ο προσδιορισμός αυτός αναγκάζει το μεταγλωττιστή να ελέγχει αν η δέσμευση
αυτή τηρείται μέσα στο σώμα της συνάρτησης.
Παράδειγμα
Το παρακάτω παράδειγμα ορίζει και χρησιμοποιεί
την κλάση point που - εξεζητημένα - εκμεταλλεύεται όλα τα στοιχεία που
έχουν αναφερθεί:
point.h
class point {
private:
int x, y;
static int numpoints;
public:
point(); // Default contructor
point(int sx, int sy); // Other constructor
~point(); // Destructor
int getx() const; // Access functions
int gety() const;
void display(); // Display member function
void setpos(int sx, int sy); // Set position
static int points_used(); // Return number of points
friend double distance(point p1, point p2); // Display friend function
friend void display(point &p); // Display friend function
};
point.cpp
#include "point.h"
#include <iostream.h>
#include <math.h>
int point::numpoints = 0;
point::point(int sx, int sy)
{
x = sx;
y = sy;
numpoints++;
}
point::point()
{
x = y = 0;
numpoints++;
}
point::~point()
{
numpoints--;
}
int
point::getx() const
{
return (x);
}
int
point::gety() const
{
return (y);
}
// Member function; used as a.display();
void
point::display()
{
cout << "(" << x << "," << y << ")\n";
}
// Friend function; used as display(a)
void
display(point& p)
{
cout << "(" << p.x << "," << p.y << ")\n";
}
double
sqr(double x)
{
return (x * x);
}
double
distance(point p1, point p2)
{
return (sqrt(sqr(p1.x - p2.x) + sqr(p1.y - p2.y)));
}
void
point::setpos(int sx, int sy)
{
this->x = sx;
point::y = sy;
}
int
point::points_used()
{
return (numpoints);
}
main.cpp
#include <iostream.h>
#include "point.h"
main()
{
point a(1, 2);
point b, *c;
c = new point(5, 5);
b.setpos(6, 6);
a.display(); // Member function
display(b); // Friend function
c->display();
cout << "Distance from a to b = " << distance(a, b) << "\n";
cout << "points used = " << point::points_used() << "\n";
delete(c);
cout << "points used = " << point::points_used() << "\n";
return (0);
}
Φροντιστηριακή άσκηση
Να υλοποιηθεί σε C++ μια κλάση που να παριστάνει αντικείμενα
σε κίνηση με ιδιότητες την αρχική τους θέση και
την ταχύτητά τους στους άξονες x και y και τις παρακάτω μεθόδους:
- void setipos(double x, double y)
- Θέτει την αρχική θέση του αντικειμένου.
- void setvelocity(double x, double y)
- Θέτει την ταχύτητα x, y του αντικειμένου.
- double getxpos(int t)
- Επιστρέφει τη θέση x του αντικειμένου κατά τη χρονική στιγμή t.
- double getypos(int t)
- Επιστρέφει τη θέση y του αντικειμένου κατά τη χρονική στιγμή t.
Με βάση το παραπάνω να υλοποιηθεί πρόγραμμα που
- Ζητάει από το χρήστη τον
αριθμό των αντικειμένων που θέλει να δημιουργήσει.
- Για κάθε ένα από τα αντικείμενα ζητάει από το χρήστη την αρχική του θέση.
- Διαρκώς ζητάει από το χρήστη τον αύξοντα αριθμό ενός αντικειμένου,
την ταχύτητά του και ένα χρόνο t και εμφανίζει στην οθόνη τη θέση του κατά το
χρόνο t.
Σημείωση: η θέση κάθε αντικειμένου υπολογίζεται μόνο με βάση την αρχική του
θέση και την ταχύτητά του.
Παράδειγμα:
n=3
A1x=6
A1y=12
A2x=6
A2y=67
A3x=32
A3y=78
A=2
A2 vx=10
A2 vy=10
t=1
A2x=16 A2y=77
A=1
A1 vx=1
A1 vy=1
t=2
A1x=8 A2y=14
...
Ασκήσεις
Άσκηση 2
- Να υλοποιηθούν σε C++ οι κλάσεις andgate, orgate, notgate.
Οι κλάσεις αυτές θα προσομοιάζουν τις αντίστοιχες λογικές πύλες.
Κάθε κλάση να έχει μεθόδους που να θέτουν τις εισόδους (π.χ. seta, setb)
και μέθοδο που να επιστρέφει την τιμή της εξόδου (π.χ. getoutput).
- Με τη χρήση των παραπάνω κλάσεων (και μόνο) να υλοποιήσετε
μια κλάση που να προσομοιάζει έναν ημιαθροιστή.
Η κλάση αυτή να έχει μεθόδους που να θέτουν τις δύο εισόδους και
μεθόδους που να επιστρέφουν το άθροισμα και το κρατούμενο.
- Να γράψετε ένα πρόγραμμα σε C++ που να τυπώνει τους πίνακες
αλήθειας για τις παραπάνω κλάσεις.
- (Προαιρετικά) Με τη χρήση των παραπάνω κλάσεων να υλοποιήσετε
μια κλάση που να προσομοιάζει έναν πλήρη αθροιστή.
Η κλάση αυτή πρέπει να έχει μεθόδους που να θέτουν τις δύο εισόδους και το
κρατούμενο εισόδου καθώς και μεθόδους που να επιστρέφουν το άθροισμα και
το κρατούμενο εξόδου.
Με τη χρήση του πλήρη αθροιστή και τους τελεστές bit της C
μπορεί να υλοποιηθεί μια κλάση αθροιστή ακεραίων αριθμών (wordadder) με
τον παρακάτω τρόπο:
/*
* wordadder.h
*
* D. Spinellis, February 2000
*/
class wordadder {
private:
unsigned int a, b; // Input values
public:
void seta(unsigned int v); // Set input a
void setb(unsigned int v); // Set input b
unsigned int getsum(); // Return sum
};
/*
* wordadder.cpp
*
* D. Spinellis, February 2000
*/
#include "wordadder.h"
#include "fulladder.h"
void
wordadder::seta(unsigned int v)
{
a = v;
}
void
wordadder::setb(unsigned int v)
{
b = v;
}
unsigned int
wordadder::getsum()
{
fulladder fa[sizeof(unsigned int) * 8];
unsigned int i, bit;
unsigned int result = 0;
fa[0].setcarryin(false);
for (i = 0, bit = 1; bit; bit <<= 1, i++) {
fa[i].seta(a & bit);
fa[i].setb(b & bit);
if (bit << 1) // Do carry-over the last bit
fa[i + 1].setcarryin(fa[i].getcarryout());
if (fa[i].getsum())
result |= bit;
}
return (result);
}
Ο αθροιστής αυτός μπορεί να χρησιμοποιηθεί ως εξής:
wordadder add;
int a, b;
cin >> a;
cin >> b;
add.seta(a);
add.setb(b);
cout << a << "+" << b << "=" << add.getsum();
Δοκιμάστε το!