Πρότυπα στη C++
Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr
Παραμετροποίηση προγραμμάτων με πρότυπα
Η C++ μας επιτρέπει να δηλώσουμε και να ορίσουμε
συναρτήσεις και κλάσεις παραμετροποιημένες
ως προς τους τύπους ή τις σταθερές που χρησιμοποιούν.
Βάση για τους ορισμούς αυτούς είναι ένα πρότυπο (template).
Έχοντας ορίσει μια συνάρτηση ή μια κλάση με μορφή προτύπου μπορούμε στη
συνέχεια να τη χρησιμοποιήσουμε για κάποιο συγκεκριμένο τύπο ή σταθερά.
Η χρήση αυτή γίνεται αυτόματα μέσα στον πηγαίο κώδικα.
Το παρακάτω παράδειγμα ορίζει και
χρησιμοποιεί μια συνάρτηση max παραμετροποιημένη
ως προς τον τύπο των παραμέτρων και του αποτελέσματός της:
#include <iostream.h>
/*
* Return the maximum of a, b.
* A, b and the return type of max are of type T.
*/
template <class T>
T
max(T a, T b)
{
if (a > b)
return (a);
else
return (b);
}
main()
{
cout << max("Samos", "Zambia") << "\n";
cout << max(3, 42) << "\n";
cout << max(3.1415, 1.4142) << "\n";
}
Χρησιμοποιούμε πρότυπα:
- για να ορίσουμε μια δομή δεδομένων η οποία να δουλεύει για
οποιοδήποτε τύπο θέλουμε,
- για να ορίσουμε πολυμορφικές συναρτήσεις που να δουλεύουν
σε οποιοδήποτε τύπο τις χρησιμοποιήσουμε,
- για να επιτύχουμε έλεγχο τύπων σε συναρτήσεις των οποίων
οι παράμετροι θα ήταν τύπου void * (π.χ. qsort, bsearch).
Σε αντίθεση με πολυμορφικές συναρτήσεις που ορίζονται με υπερφόρτωση,
συναρτήσεις που ορίζονται με πρότυπα απαιτούν έναν μόνο ορισμό για
όλους τους πιθανούς τύπους.
Ο μεταγλωττιστής αυτόματα υλοποιεί την κάθε συνάρτηση για το συγκεκριμένο
τύπο που χρησιμοποιείται.
Με τη χρήση των προτύπων αυξάνουμε την ευελιξία του κώδικα που γράφουμε και
μειώνουμε το μέγεθος του κώδικα που απαιτείται για μια συγκεκριμένη υλοποίηση.
Ο ορισμός του προτύπου περιέχει μετά τη δεσμευμένη λέξη
template μια σειρά τύπων ή παραμέτρων μέσα σε < >
που χρησιμοποιούμε για να ορίσουμε τη συγκεκριμένη συνάρτηση ή κλάση.
Οι τύποι και οι παράμετροι χωρίζονται μεταξύ τους με κόμμα.
Οι τύπου μπορούν να οριστούν ως class όνομα_κλάσης
(π.χ. class T) ή ως typename όνομα_τύπου (π.χ. typename T).
Οι παράμετροι ορίζονται όπως και στις συναρτήσεις (π.χ. int stacksize).
Μέσα στη δήλωση της συνάρτησης ή της κλάσης καθώς και στον ορισμό του
σώματός τους μπορούμε να χρησιμοποιήσουμε τα ονόματα που δηλώθηκαν με
βάση το πρότυπο σαν να ήταν πραγματικοί τύποι ή σταθερές.
Κατά τη χρήση μια κλάσης ή συνάρτησης οι παράμετροι πρέπει να είναι
σταθερές.
Το παρακάτω παράδειγμα δηλώνει την πρότυπη κλάση tstack
ως μια στοίβα της οποίας ο τύπος
των στοιχείων και το μέγεθος ορίζονται με βάση το πρότυπό της και
το αντικείμενο instack ως μια στοίβα 20 ακεραίων:
template <class T, int i> class tstack
{
T data[i];
int items;
public:
tstack(void);
void push(T item);
T pop(void);
};
tstack <int, 20> intstack;
Η υλοποίηση προτύπων που χρησιμοποιούμε
(Microsoft Visual C++ 5.0)
απαιτεί ο ορισμός του προτύπου να
είναι στο ίδιο αρχείο με τη χρήση του.
Για το λόγο αυτό οι κλάσεις και οι συναρτήσεις που ορίζονται με βάση τα
πρότυπα ορίζονται και δηλώνονται μέσα σε αρχείο επικεφαλίδων.
Πρότυπα συναρτήσεων
- Ο ορισμός συναρτήσεων με βάση ένα πρότυπο γίνεται με τη
σύνταξη:
template <δηλώσεις τύπων>
δήλωση συνάρτησης με βάση τους τύπους
Το παρακάτω παράδειγμα ορίζει τη συνάρτηση swap που εναλλάσσει μεταξύ τους
τις δύο μεταβλητές - ορίσματα της συνάρτησης:
// File tswap.h
template <class T>
void
swap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
}
- Η υλοποίηση της συνάρτησης γίνεται αυτόματα για τον τύπο για τον
οποίο χρησιμοποιείται:
// File swaptest.cpp
#include <iostream.h>
#include "tswap.h"
main()
{
int a = 1, b = 2;
double da = 1.1, db = 2.2;
swap(a, b); // Swap two integers
swap(da, db); // Swap two floating point numbers
cout << a << "," << b << "\n";
cout << da << "," << db << "\n";
}
- Μπορούμε ακόμα να καθορίσουμε κατά τη χρήση της συνάρτησης τον
τύπο της συνάρτησης που θέλουμε να χρησιμοποιηθεί αρκεί οι παράμετροι
να είναι συμβατοί (ακόμα και μέσω μετατροπών) με το συγκεκριμένο τύπο.
Το παρακάτω παράδειγμα θα τυπώσει 3:
#include <iostream.h>
template <typename T>
T
max(T a, T b)
{
if (a > b)
return (a);
else
return (b);
}
main()
{
cout << max<int>(3.1415, 1.4142) << "\n";
}
Πρότυπα κλάσεων
- Ο ορισμός κλάσεων με βάση πρότυπα επιτρέπει τον ορισμό συλλογών δεδομένων
(όπως στοίβες, ουρές, πίνακες, κ.λπ.) ανεξάρτητα από το τελικό περιεχόμενό
τους.
- Όταν ορίζουμε μεταβλητές με βάση μια τέτοια πρότυπη κλάση πρέπει
να δίνουμε και συγκεκριμένη τιμή σε όλες τις παραμέτρους του προτύπου
(π.χ. τον τύπο των στοιχείων που θέλουμε να αποθηκεύσουμε).
- Το παρακάτω παράδειγμα ορίζει μια πρότυπη κλάση για στοίβα με
πεπερασμένο μέγεθος καθώς και δύο αντικείμενα της κλάσης αυτής,
μια στοίβα 20 ακεραίων και μια από 10 double.
#include <iostream.h>
#include <assert.h>
template <class T, int i>
class tstack
{
private:
T data[i];
int items;
public:
tstack();
void push(T item);
T pop(void);
void print(void);
};
// Constructor
template <class T, int i>
tstack<T, i>::tstack(void)
{
items = 0;
}
template <class T, int i>
void
tstack<T, i>::push(T item)
{
assert(items < i);
data[items++] = item;
}
template <class T, int i>
T
tstack<T, i>::pop(void)
{
assert(items > 0);
return (data[--items]);
}
main()
{
tstack <int, 20> int_stack;
tstack <double, 10> double_stack;
int_stack.push(7);
int_stack.push(45);
double_stack.push(3.14);
double_stack.push(1.41);
cout << int_stack.pop() << "\n";
cout << int_stack.pop() << "\n";
cout << double_stack.pop() << "\n";
cout << double_stack.pop() << "\n";
}
Άσκηση
Άσκηση 6
- Να παραμετροποιήσετε με τη χρήση προτύπων τους
ασφαλείς πίνακες που παρουσιάζονται ως παράδειγμα καθοριζόμενων τελεστών.
- Γράψτε ένα μικρό πρόγραμμα που να ελέγχει τη λειτουργία των κλάσεων αυτών.