Γλώσσες προγραμματισμού

Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr

Εισαγωγή - Η γλώσσα C++

Καλώς ήρθατε

Γλώσσες προγραμματισμού

Τι περιλαμβάνει το μάθημα

  1. Εισαγωγή - Η γλώσσα C++
  2. Κλάσεις και αντικείμενα
  3. Καθοριζόμενοι τελεστές
  4. Κληρονομικότητα
  5. Αντικειμενοστρεφής σχεδιασμός με UML
  6. Πρότυπα στη C++
  7. Υλοποίηση με έτοιμες βιβλιοθήκες
  8. Υλοποίηση διεπαφών σε Visual Basic
  9. Χειρισμός δεδομένων με SQL
  10. Συναρτησιακός προγραμματισμός στη Lisp
  11. Κατηγορήματα στην Prolog
  12. Ταυτοχρονισμός στα Windows NT
  13. Ολοκληρωμένα περιβάλλοντα ανάπτυξης
  14. Ανασκόπηση - επανάληψη

Οι σημειώσεις

Εισαγωγή

Το πρώτο πρόγραμμα σε C++

Το πρώτο πρόγραμμα σε C++ θα τυπώσει, όπως θα περίμενε κανείς, τις λέξεις hello, world:
#include <iostream.h>

main()
{
        cout << "hello, world\n";
}

Μεταγλώττιση

Στο περιβάλλον του εργαστηρίου (Windows NT, Visual C++) το πρόγραμμα πρέπει να έχει επίθεμα .cpp αντί για .c για να το αναγνωρίσει ο μεταγλωττιστής ως πρόγραμμα C++. Ο μεταγλωττιστής παραμένει ο ίδιος (cl).

Σε εγκαταστάσεις Linux το πρόγραμμα πρέπει να έχει επίθεμα .C. Για να μεταγλωττιστεί χρησιμοποιούμε την εντολή g++.

C++ ως καλύτερη C

Η C++ προσφέρει πρόσθετες δυνατότητες σε σχέση με τη C:

Είσοδος και έξοδος

Βιβλιογραφία

Πηγές στο Internet

Γενική βιβλιογραφία

Ασκήσεις

Άσκηση 1 (προαιρετική)

  1. Να μεταγράψετε σε C++ και να μεταγλωττίσετε ένα από τα προγράμματα που έχετε γράψει σε C. Επιλέξτε το πρόγραμμα έτσι ώστε να χρησιμοποιήσετε όσο περισσότερες βελτιώσεις της C++ γίνεται.

Κλάσεις και αντικείμενα

Ορισμός κλάσεων

Μέθοδοι κατασκευής και καταστροφής

Ιδιότητες και μέθοδοι της κλάσης

Συναρτήσεις 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(12);

        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(12);
        point b, *c;

        c = new point(55);
        b.setpos(66);

        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.
Με βάση το παραπάνω να υλοποιηθεί πρόγραμμα που
  1. Ζητάει από το χρήστη τον αριθμό των αντικειμένων που θέλει να δημιουργήσει.
  2. Για κάθε ένα από τα αντικείμενα ζητάει από το χρήστη την αρχική του θέση.
  3. Διαρκώς ζητάει από το χρήστη τον αύξοντα αριθμό ενός αντικειμένου, την ταχύτητά του και ένα χρόνο 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

  1. Να υλοποιηθούν σε C++ οι κλάσεις andgate, orgate, notgate. Οι κλάσεις αυτές θα προσομοιάζουν τις αντίστοιχες λογικές πύλες. Κάθε κλάση να έχει μεθόδους που να θέτουν τις εισόδους (π.χ. seta, setb) και μέθοδο που να επιστρέφει την τιμή της εξόδου (π.χ. getoutput).
  2. Με τη χρήση των παραπάνω κλάσεων (και μόνο) να υλοποιήσετε μια κλάση που να προσομοιάζει έναν ημιαθροιστή. Η κλάση αυτή να έχει μεθόδους που να θέτουν τις δύο εισόδους και μεθόδους που να επιστρέφουν το άθροισμα και το κρατούμενο.
  3. Να γράψετε ένα πρόγραμμα σε C++ που να τυπώνει τους πίνακες αλήθειας για τις παραπάνω κλάσεις.
  4. (Προαιρετικά) Με τη χρήση των παραπάνω κλάσεων να υλοποιήσετε μια κλάση που να προσομοιάζει έναν πλήρη αθροιστή. Η κλάση αυτή πρέπει να έχει μεθόδους που να θέτουν τις δύο εισόδους και το κρατούμενο εισόδου καθώς και μεθόδους που να επιστρέφουν το άθροισμα και το κρατούμενο εξόδου. Με τη χρήση του πλήρη αθροιστή και τους τελεστές 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();
    Δοκιμάστε το!

Καθοριζόμενοι τελεστές

Καθοριζόμενοι τελεστές

Παράδειγμα: ασφαλείς πίνακες

Στη C και τη C++ οι δείκτες ενός πίνακα δεν ελέγχονται αν είναι συμβατοί με τις διαστάσεις του πίνακα. Έτσι το παρακάτω παράδειγμα δε θα εμφανίσει κάποιο λάθος κατά τη μεταγλώττιση ή (πιθανά) και την εκτέλεση:
int a[20];

main()
{
        a[50] = 42;                     // Out of bounds index
}
Το αποτέλεσμα είναι πως το παραπάνω πρόγραμμα μπορεί να γράψει σε θέσεις μνήμης που φυλάσσονται άλλες μεταβλητές δημιουργώντας λάθη που είναι δύσκολο να βρεθούν.

Στο επόμενο παράδειγμα, με τη χρήση καθοριζόμενων τελεστών, ορίζουμε την κλάση intarray που υλοποιεί πίνακες ακεραίων μιας διάστασης. Τα αντικείμενα της κλάσης αυτής μοιάζουν στη συμπεριφορά με πίνακες, αλλά ελέγχουν αν ο δείκτης του πίνακα είναι συμβατός με τη διάσταση με την οποία έχει οριστεί ο πίνακας.

#include <iostream.h>
#include <stdlib.h>

class intarray {
public:
        intarray(unsigned int ssize = 10);
        intarray(const intarray& sa);
        ~intarray(void);
        intarray& operator=(const intarray& sa);
        intoperator[](unsigned int i);
        int elemnum();
protected:
        unsigned int size;              // Number of elements
        int *values;                    // Values
};

intarray::intarray(unsigned int ssize)
{
        size = ssize;
        values = new int[ssize];
}

intarray::intarray(const intarray& sa)
{
        size = sa.size;
        values = new int[sa.size];
        for (int i = 0; i < sa.size; i++)
                values[i] = sa.values[i];
}

intarray::~intarray(void)
{
        delete[] values;
}

intarray&
intarray::operator=(const intarray& sa)
{
        delete values;
        size = sa.size;
        values = new int[size];
        for (unsigned int i = 0; i < size; i++)
                values[i] = sa.values[i];
        return (*this);
}

int&
intarray::operator[](unsigned int i)
{
        if (i >= size) {
                cerr << "Array index " << i << " out of bounds\n";
                exit(1);
        }
        return (values[i]);
}

int
intarray::elemnum()
{
        return (size);
}

main()
{
        intarray a(5);
        a[2] = 8;
        intarray b = a;
        cout << b[2] << "\n";           // Will print 8
        b[23] = 4;                      // Will exit with an error

        return (0);
}

Είσοδος και έξοδος με τελεστές

Με καθορισμό μεθόδων για τους τελεστές << και >> μπορούμε να ορίσουμε τον τρόπο που θα γίνεται η είσοδος και έξοδος για κλάσεις που ορίζουμε εμείς. Το παρακάτω παράδειγμα ορίζει μια κλάση date και τον τρόπο που αντικείμενα της κλάσης αυτής θα εμφανίζονται στην οθόνη (ο ορισμός με τη χρήση friend επιτρέπει στη συνάρτηση operator << να έχει ως πρώτο ορίσμα μεταβλητή τύπου ostream).
#include <iostream.h>

class date {
private:
        int month;
        int day;
        int year;
public:
        date(int d, int m, int y);
        friend ostream& operator<< (ostream& os, date& dt);
};

// Constructor
date::date(int d, int m, int y)
{
        day = d;
        month = m;
        year = y;
}

// Overload << to define output
ostream& operator<< ( ostream& os, date& dt )
{
        os << dt.day << '-' << dt.month << '-' << dt.year;
        return os;
}

int
main()
{
        date dt(2362000);           // Create a new date
        cout << dt;                     // Output the date
        return 0;
}

Ασκήσεις

Άσκηση 3 (προαιρετική)

  1. Να υλοποιηθεί σε C++ μια κλάση που να υποστηρίζει πράξεις μεταξύ μιγαδικών αριθμών. Με τη βοήθεια της κλάσης αυτής να υλοποιήσετε μια αριθμομηχανή που να υποστηρίζει μιγαδικούς αριθμούς.

Κληρονομικότητα

Εισαγωγή

Κληρονομικότητα σε κλάσεις

Ιδεατές συναρτήσεις

Παράδειγμα

Το παρακάτω παράδειγμα ορίζει τη βασική κλάση shape και τις υποκλάσεις της circle και rectangle. Η μέθοδος area ορίζεται ως virtual με αποτέλεσμα να μπορεί να οριστεί για τις υποκλάσεις και κατά την εκτέλεση του προγράμματος να εκτελεστεί η σωστή έκδοσή της. Η συνάρτηση printarea της shape εκμεταλλεύεται τη δυνατότητα αυτή και μπορεί να κληθεί (και να δουλέψει σωστά) με όρισμα οποιαδήποτε από τις υποκλάσεις της shape.
#include <iostream.h>

class shape {
private:
        double x, y;            // Position
public:
        void setposition(double x, double y);
        void printarea();
        virtual double area();
};

double
shape::area()
{
        return (0);
}

void
shape::printarea()
{
        cout << area() << "\n";
}


void shape::setposition(double x, double y)
{
        shape::x = x;
        shape::y = y;
}

class circle : public shape {
private:
        double radius;
public:
        void setradius(double r);
        virtual double area();
};

void
circle::setradius(double r)
{
        radius = r;
}

double
circle::area()
{
        return (3.1415927 * radius * radius);
}


class rectangle : public shape {
private:
        double height, width;
public:
        void setdimensions(double h, double w);
        virtual double area();
};

void
rectangle::setdimensions(double h, double w)
{
        height = h;
        width = w;
}

double
rectangle::area()
{
        return (height * width);
}

main()
{
        circle c;
        rectangle r;
        shape *sp;

        r.setposition(12);
        c.setposition(34);
        r.setdimensions(1010);
        c.setradius(1);
        c.printarea();
        r.printarea();
        sp = &r;
        cout << sp->area();
}

Αφηρημένες κλάσεις

Για παράδειγμα, ένας σχεδιασμός του πληροφοριακού συστήματος του Πανεπιστημίου μπορεί να ορίσει την αφηρημένη κλάση person ως βασική κλάση για τις υποκλάσεις student, employee και visitor. Αν και δε θα μπορεί να οριστεί μια μεταβλητή για την αφηρημένη κλάση person, αυτή μπορεί να περιέχει ορισμένα βασικά χαρακτηριστικά όπως birth_date και να επιβάλει την υλοποίηση συγκεκριμένων μεθόδων όπως home_page_URL() ορίζοντάς τις ως θεωρητικές.

Ασκήσεις

Άσκηση 4 (προαιρετική)

  1. Να υλοποιήσετε σε C++ τις κλάσεις icsd_student και math_student. Οι κλάσεις αυτές πρέπει να έχουν ως ιδιότητα τον αριθμό μαθημάτων που έχει περάσει ένας φοιτητής, αντίστοιχες μεθόδους πρόσβασης στην ιδιότητα αυτή και μια μέθοδο που να επιστρέφει αληθές αν ο φοιτητής μπορεί να πάρει πτυχίο (έχοντας περάσει 40 μαθήματα για την icsd_student και 38 για την math_student). Να χρησιμοποιήσετε κληρονομικότητα για να ελαχιστοπιήσετε τις μεθόδους των κλάσεων.
  2. Γράψτε ένα μικρό πρόγραμμα που να ελέγχει τη λειτουργία των κλάσεων αυτών.

Δείκτες σε μέλη, ενώσεις

Δείκτες σε μέλη κλάσεων

Δομές και ενώσεις

Η δήλωση μιας δομής στη C++ με τη μορφή:
struct s { ...
είναι μια συντομογραφία για τη δήλωση:
class s {public: ...
δηλαδή για μια κλάση της οποίας όλα τα μέλη είναι δημόσια.

Αντίστοιχα η δήλωση μιας ένωσης (union) επιτρέπει τη χρήση του ίδιου χώρου από όλα τα μέλη της ένωσης. Η C++ επιτρέπει τον ορισμό συναρτήσεων κατασκευής ως μέλη της ένωσης. Με τη χρήση πολυμορφικών συναρτήσεων κατασκευής μπορεί κανείς να αποδώσει αρχική τιμή σε οποιοδήποτε μέλος της ένωσης κατά την αρχικοποίησή του. Παράδειγμα:

union token {
        char c;
        int i;
        double d;

        token(char sc) {c = sc;}
        token(int si) {i = si;}
        token(double sd) {d = sd;}
};

void
f()
{
        token a = 3.14;         // Assign value to a.d
        token b = 't';          // Assign value to b.c
}
Σε περίπτωση που θέλουμε να αποφύγουμε λάθη με την ανάθεση σε ένα μέλος ενός τύπου και την ανάκτηση από ένα μέλος άλλου τύπου μπορούμε να εμφωλιάσουμε την ένωση σε μια κλάση:
class token {
private:
        enum e_type {CHAR, INT, DOUBLE} type;
        union {
                char c;
                int i;
                double d;
        };
        void check(e_type t)    {if (type != t) error();}
public:
        token(char sc)          {c = sc; type = CHAR;}
        token(int si)           {i = si;} type = INT;}
        token(double sd)        {d = sd;} type = DOUBLE;}

        int &cval()             {check(CHAR); return c;}
        int &ival()             {check(INT); return i;}
        int &dval()             {check(DOUBLE); return d;}

        token(char sc) {c = sc;}
        token(int si) {i = si;}
        token(double sd) {d = sd;}
};

void
f()
{
        token a = 3.14;                 // Assign value to a.d
        printf("%g\n", a.dval());       // Ok
        printf("%d\n", a.ival());       // Check will fail
}

Αντικειμενοστρεφής σχεδιασμός με UML

Εισαγωγή

Η ενοποιημένη γλώσσα σχεδιασμού (unified modeling language) (UML) είναι μια γραφική γλώσσα για την οπτική παράσταση, τη διαμόρφωση προδιαγραφών και την τεκμηρίωση συστημάτων που βασίζονται σε λογισμικό. Η UML στοχεύει στο σχεδιασμό αντικειμενοστρεφών συστημάτων. Το σχέδιο είναι μια απλοποιημένη παράσταση της πραγματικότητας.

Σχεδιάζουμε για να μπορέσουμε να καταλάβουμε το σύστημα που αναπτύσσουμε. Έτσι δημιουργώντας ένα σχέδια επιτυγχάνουμε τέσσερις στόχους:

  1. παριστάνουμε οπτικά το σύστημα που έχουμε ή θέλουμε να κατασκευάσουμε,
  2. προσδιορίζουμε τη δομή και τη συμπεριφορά του συστήματος,
  3. δημιουργούμε ένα πρότυπο για να βασίσουμε την κατασκευή του συστήματος,
  4. τεκμηριώνουμε τις αποφάσεις που λάβαμε.

Σε όλους τους τεχνολογικούς τομείς ο σχεδιασμός βασίζεται σε τέσερις βασικές αρχές:

  1. η επιλογή του είδους του σχεδίου έχει επίπτωση στον τρόπο και την μορφή επίλυσης του προβλήματος,
  2. όλα τα σχέδια εκφράζονται σε διαφορετικές βαθμίδες ακρίβειας,
  3. τα καλύτερα σχέδια σχετίζονται με την πραγματικότητα,
  4. ένα είδος σχεδίων δεν είναι ποτέ αρκετό.

Η UML περιλαμβάνει τρία βασικά στοιχεία:

  1. Οντότητες
  2. Σχέσεις
  3. Διαγράμματα
Η UML είναι μια πλήρης και πλούσια γλώσσα με εξαιρετικά ευρύ πεδίο εφαρμογής. Στο μάθημα αυτό θα εξετάσουμε εξαιρετικά συνοπτικά τον τρόπο παράστασης ορισμένων αντικειμενοστρεφών δομών σε UML.

Κλάσεις

Σχέσεις

Στη UML ορίζονται τρεις βασικές σχέσεις:
  1. εξάρτηση (dependency)
  2. γενίκευση (generalisation)
  3. σύνδεση (association)

Εξάρτηση

Η εξάρτηση δηλώνει πως μια αλλαγή σε μιαν οντότητα θα επηρεάσει μιαν άλλη αλλά όχι απαραίτητα και το αντίστροφο. Παριστάνεται με μια διακεκομμένη γραμμή με ανοιχτό βέλος που δείχνει προς την οντότητα που υπάρχει εξάρτηση:

Γενίκευση

Η γενίκευση δηλώνει μια σχέση ανάμεσα σε κάτι γενικό (τη βασική κλάση ή αλλιώς γονέα) και κάτι ειδικό (μιαν υποκλάση ή αλλιώς παιδί της). Παριστάνεται με μια συνεχή γραμμή με κλειστό βέλος που δείχνει προς τη βασική κλάση:

Σύνδεση

Η σύνδεση αναφέρεται σε αντικείμενα τα οποία συνδέονται με κάποιο τρόπο με άλλα. Όταν δύο κλάσεις είναι συνδεδεμένες μπορεί κανείς να μεταβεί από αντικείμενα της μιας σε αντικείμενα της άλλης. Η σύνδεση παριστάνεται με μια ευθεία γραμμή ανάμεσα στα δύο αντικείμενα.

Αν σε μια σχέση τα αντικείμενα απαρτίζουν τμήματα ενός όλου, τότε αυτή απεικονίζεται ως συγκρότημα (aggregation) με την παράσταση ενός διαμαντιού στην άκρη του "όλου".

Αν σχέση τα αντικείμενα που απαρτίζουν τμήματα ενός όλου έχουν την ίδια διάρκεια ζωής με το όλο, τότε αυτή απεικονίζεται ως σύνθεση (composition) με την παράσταση ενός γεμάτου διαμαντιού στην άκρη του "όλου".

Είδη εξαρτήσεων

Με τη χρήση μιας εξάρτησης εκφράζουμε σημασιολογικές (semantic) σχέσεις ανάμεσα σε στοιχεία του μοντέλου. Σε τέτοιες περιπτώσεις μια αλλαγή σε ένα στοιχείο της εξάρτησης μπορεί να έχει επίπτωση στο άλλο. Διακρίνουμε τα παρακάτω είδη εξάρτησης:
πρόσβαση (access)
Άδεια σε κάποια στοιχεία από ένα τμήμα να έχουν πρόσβαση σε στοιχεία από άλλο τμήμα (access).
σύνδεση (binding)
Παροχή τιμών σε ένα πρότυπο για να δημιουργήσει ένα νέο στοιχείο (bind).
κλήση (call)
Μια μέθοδος καλεί μια άλλη (call).
απόρρεια (derivation)
Ένα στοιχείο που μπορεί να υπολογιστεί από κάποιο άλλο (derive).
friend
Ένα στοιχείο μπορεί να έχει πρόσβαση σε στοιχεία άλλης κλάσης παρά τους όποιους περιορισμούς (friend).
εισαγωγή (import)
Άδεια σε ένα τμήμα να εισάγει και να χρησιμοποιήσει τα στοιχεία ενός άλλου τμήματος (import).
δημιουργία (instantiation)
Μια μέθοδος μιας κλάσης δημιουργεί αντικείμενα μιας άλλης κλάσης (instantiate).
παράμετρος (parameter)
Σχέση ανάμεσα σε μια λειτουργία και τις παραμέτρους της (parameter).
δημιουργία (realization)
Σχέση ανάμεσα σε μια προδιαγραφή και την υλοποίησή της (realize).
εκλέπτυνση (refinement)
Δήλωση για την ύπαρξη απεικόνισης ανάμεσα σε δύο σημασιολογικά επίπεδα (refine).
αποστολή (send)
Σχέση ανάμεσα στον αποστολέα και τον παραλήπτη ενός μηνύματος (send).
ίχνος (trace)
Σχέση ανάμεσα σε δύο στοιχεία δύο διαφορετικών μοντέλων που δεν αποτελεί όμως απεικόνιση (trace).
χρήση (usage)
Ένα στοιχείο απαιτεί την ύπαρξη ενός άλλου στοιχείο για τη λειτουργία του (π.χ. call, instantiate, parameter, send) (use).
Στο διάγραμμα της UML γράφουμε μέσα σε εισαγωγικά το είδος της εξάρτησης (αυτό που εμφανίζεται στο τέλος κάθε ορισμού στον παραπάνω πίνακα) πάνω από την αντίστοιχη γραμμή με το βέλος:

Διάγραμμα κλάσεων

Το διάγραμμα των κλάσεων ενός συστήματος περιέχει τις κλάσεις μαζί με του αντίστοιχους δεσμούς εξάρτησης, γενίκευσης και σύνδεσης. Έτσι ένα διάγραμμα κλάσεων μπορεί να απεικονίσει τη χρήση της κληρονομικότητας στο σχεδιασμό με τη χρήση δεσμών γενίκευσης όπως στο παρακάτω σχήμα:

Διάγραμμα αντικειμένων

Τα διαγράμματα αντικειμένων χρησιμοποιούνται για το σχεδιασμό της στατικής κατάστασης του συστήματος κατά μια συγκεκριμένη χρονική στιγμή. Κάθε αντικείμενο σχεδιάζεται ως ένα ορθογώνιο με την παρακάτω μορφή:

Το σύνολο των αντικειμένων σχεδιάζεται με βάση τους συνδέσμους που ορίζονται πάνω σε αυτό.

Βιβλιογραφία

Άσκηση

Άσκηση 5

Η άσκηση αυτή πρέπει να παραδωθεί σε γραπτή μορφή στη Γραμματεία μέχρι την Τρίτη 27 Μαρτίου 2001 11:59 πμ. Για κάθε μέρα καθυστέρησης θα αφαιρείται ένας βαθμός από τον τελικό βαθμό της άσκησης. Στη βαθμολογία θα ληφθεί υπόψη και η εμφάνιση του παραδοτέου.
  1. Να σχεδιάσετε σε UML τις κλάσεις icsd_student και math_student. Οι κλάσεις αυτές πρέπει να έχουν ως ιδιότητα τον αριθμό μαθημάτων που έχει περάσει ένας φοιτητής, αντίστοιχες μεθόδους πρόσβασης στην ιδιότητα αυτή και μια μέθοδο που να επιστρέφει αληθές αν ο φοιτητής μπορεί να πάρει πτυχίο (έχοντας περάσει 40 μαθήματα για την icsd_student και 38 για την math_student). Να χρησιμοποιήσετε κληρονομικότητα για να ελαχιστοπιήσετε τις μεθόδους των κλάσεων.
  2. Ορισμένες φορές χρειάζεται να ανακτήσουμε το σχεδιασμό ενός συστήματος από την υλοποίησή του. Η διαδιακασία αυτή καλείται αντίστροφος σχεδιασμός (reverse engineering) και απαιτείται όταν δεν υπάρχει ο αρχικός σχεδιασμός ή αυτός δεν έχει συντηρηθεί κατά τη διάρκεια ζωής του έργου. Για το παρακάτω σύνολο από αρχεία C++ που δηλώνουν αντίστοιχες κλάσεις να σχεδιάσετε σε UML ένα διάγραμμα που να απεικονίζει την κάθε κλάση και τη σχέση της με τις υπόλοιπες. (Τα αρχεία αυτά υλοποιούν ένα σύστημα μετατροπής χαρακτήρων ανάμεσα σε διάφορα πρότυπα κωδικοποίησης, μεταγραφής και μεταγραμματισμού.) Προσοχή: για την διαδικασία του αντίστροφου σχεδιασμού δεν απατείται η πλήρης κατανόηση του κώδικα.

    filter.h

    #ifndef FILTER_
    #define FILTER_

    class filter {
    protected:
            filter *input;
    public:
            virtual int getcharacter() = 0;
            void setinput(filter *i) {input = i;};
    };
    #endif

    htmll1.h

    #ifndef HTMLL1_
    #define HTMLL1_
    #include "filter.h"
    #include "queue.h"

    class htmll1: public filter, public queue {
    private:
            void fillbuff();                 // Fill the queue buffer
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif


    lex.h

    #ifndef LEX_
    #define LEX_
    #include "filter.h"
    #include "queue.h"

    class lex: public filter, public queue {
    private:
            void (*lexfun)(lex *lf);
            void fillbuff() { lexfun(this); }; // Fill the queue buffer
    public:
            lex(void (*lf)(lex *l)) { lexfun = lf; };
            inline int getinputcharacter() { return input->getcharacter(); };
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    map.h

    #ifndef MAP_
    #define MAP_
    #include "filter.h"

    class map: public filter {
    private:
            int mapsize;                    // Size of character map
            int *charmap;                   // Map from source to target
            char default_char;              // Default map character
    public:
            map(char *source, char *target, char dflt);
            int getcharacter();
    };
    #endif

    queue.h

    #ifndef QUEUE_
    #define QUEUE_
    #include "filter.h"

    class queue {
    private:
            int *q;                 // Output queue
            int qlen;               // Characters in output queue
            int qhead;
            int qtail;
            virtual void fillbuff() = 0;    // Fill the queue buffer
    public:
            void nq(int c);         // Add a character to the output queue
            unsigned int getcharacter();    // Get a character from the output queue
            queue(const int qlen = 100);
            ~queue() { delete[] q; };
    };
    #endif

    stdinput.h

    #ifndef STDINPUT_
    #define STDINPUT_
    #include "filter.h"
    #include <iostream.h>

    class stdinput: public filter {
    public:
            int getcharacter()
            {
                    unsigned char c;
                    cin.read(&c, 1);
                    return (cin.eof() ? EOF : c);
            };
    };
    #endif

    translit.h

    #ifndef TRANSLIT_
    #define TRANSLIT_
    #include "filter.h"
    #include "queue.h"

    class translit: public filter, public queue {
    private:
            void fillbuff();                 // Fill the queue buffer
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    ucs2i.h

    #ifndef UCS2I_
    #define UCS2I_
    #include "filter.h"

    class ucs2i: public filter {
    public:
            int getcharacter()
            {
                    int c1, c2;

                    c1 = input->getcharacter();
                    c2 = input->getcharacter();
                    return ((c1 << 8) | c2);
            }
    };
    #endif

    ucs2o.h

    #ifndef UCS2O_
    #define UCS2O_
    #include "filter.h"
    #include "queue.h"

    class ucs2o: public filter, public queue {
    private:
            void fillbuff()          // Fill the queue buffer
            {
                    unsigned int c;

                    c = input->getcharacter();
                    nq(c >> 8);
                    nq(c & 0xff);
            }
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    utf7.h

    #ifndef UTF7_
    #define UTF7_

    // See RFC 1642
    class utf7 {
    protected:
            static char base64[];
            static short invbase64[128];
            static char direct[];
            static char optional[];
            static char spaces[];           /* space, tab, return, line feed */
            static char mustshiftsafe[128];
            static char mustshiftopt[128];
            static const int SHIFT_IN;
            static const int SHIFT_OUT;

            unsigned long BITbuffer;
            unsigned long buffertemp;
            int bufferbits;

            inline void write_n_bits(int x, int n)
            {
                    BITbuffer |= ((x & ~(-1L<<n)) << (32-n-bufferbits));
                    bufferbits += n;
            }

            inline int read_n_bits(int n)
            {
                    buffertemp = (BITbuffer >> (32-n));
                    BITbuffer <<= n;
                    bufferbits -= n; 
                    return (buffertemp);
            }
    public:
            utf7();
    };


    #endif

    utf7i.h

    #ifndef UTF7I_
    #define UTF7I_
    #include "filter.h"
    #include "queue.h"
    #include "utf7.h"

    // See RFC 1642
    class utf7i: public filter, public queue, public utf7 {
    private:
            void fillbuff();
            int shifted, first, wroteone;
    public:
            utf7i() { shifted = first = wroteone = 0; };
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

    utf7o.h

    #ifndef UTF7O_
    #define UTF7O_
    #include "filter.h"
    #include "queue.h"
    #include "utf7.h"

    // See RFC 1642
    class utf7o: public filter, public queue, public utf7 {
    private:
            char *mustshift;
            int shifted, needshift, done;

            void fillbuff();
    public:
            int getcharacter() { return (queue::getcharacter()); };
            void optional(bool opt);
            utf7o()
            { 
                    mustshift = mustshiftopt;
                    shifted = needshift = done = 0;
            }
    };
    #endif

    utf8i.h

    #ifndef UTF8I_
    #define UTF8I_
    #include "filter.h"

    class utf8i: public filter {
    public:
            int getcharacter()
            {
                    int c1, c2, c3, c4;
                    int cout;

                    c1 = input->getcharacter();
                    if ((c1 & 0x80) == 0) {
                            cout = c1;
                    } else if ((c1 & 0xe0) == 0xc0) {
                            c2 = input->getcharacter();
                            cout = (c2 & 0x3f) | ((c1 & 0x1f) << 6);
                    } else if ((c1 & 0xf0) == 0xe0) {
                            c2 = input->getcharacter();
                            c3 = input->getcharacter();
                            cout = ((c1 & 0xf) << 12) |
                                   ((c2 & 0x3f) << 6) |
                                   (c3 & 0x3f);
                    } else {
                            error("UTF-8 input: UCS characters above 0xffff are not supported\n");
                    }
                    return (cout);
            }
    };
    #endif

    utf8o.h

    #ifndef UTF8O_
    #define UTF8O_
    #include "filter.h"
    #include "queue.h"

    class utf8o: public filter, public queue {
    private:
            void fillbuff()          // Fill the queue buffer
            {
                    unsigned int c;

                    c = input->getcharacter();
                    if (c < 0x80) {
                            nq(c);
                    } else if (c < 0x800) {                 // bbb bbbb bbbb
                            nq(0xC0 | c>>6);                // 110b bbbb
                            nq(0x80 | c & 0x3F);            // 10bb bbbb
                    } else if (c < 0x10000) {               // bbbb bbbb bbbb bbbb
                            nq(0xE0 | c>>12);               // 1110 bbbb
                            nq(0x80 | c>>6 & 0x3F);         // 10bb bbbb
                            nq(0x80 | c & 0x3F);            // 10bb bbbb
                    } else {
                            error("UCS characters above 0xffff are not supported\n");
                    }
            }
    public:
            int getcharacter() { return (queue::getcharacter()); };
    };
    #endif

Πρότυπα στη C++

Παραμετροποίηση προγραμμάτων με πρότυπα

Η 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(342) << "\n";
        cout << max(3.14151.4142) << "\n";
}
Χρησιμοποιούμε πρότυπα: Σε αντίθεση με πολυμορφικές συναρτήσεις που ορίζονται με υπερφόρτωση, συναρτήσεις που ορίζονται με πρότυπα απαιτούν έναν μόνο ορισμό για όλους τους πιθανούς τύπους. Ο μεταγλωττιστής αυτόματα υλοποιεί την κάθε συνάρτηση για το συγκεκριμένο τύπο που χρησιμοποιείται. Με τη χρήση των προτύπων αυξάνουμε την ευελιξία του κώδικα που γράφουμε και μειώνουμε το μέγεθος του κώδικα που απαιτείται για μια συγκεκριμένη υλοποίηση.

Ο ορισμός του προτύπου περιέχει μετά τη δεσμευμένη λέξη 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 <int20> intstack;

Η υλοποίηση προτύπων που χρησιμοποιούμε (Microsoft Visual C++ 5.0) απαιτεί ο ορισμός του προτύπου να είναι στο ίδιο αρχείο με τη χρήση του. Για το λόγο αυτό οι κλάσεις και οι συναρτήσεις που ορίζονται με βάση τα πρότυπα ορίζονται και δηλώνονται μέσα σε αρχείο επικεφαλίδων.

Πρότυπα συναρτήσεων

Πρότυπα κλάσεων

Άσκηση

Άσκηση 6

  1. Να παραμετροποιήσετε με τη χρήση προτύπων τους ασφαλείς πίνακες που παρουσιάζονται ως παράδειγμα καθοριζόμενων τελεστών.
  2. Γράψτε ένα μικρό πρόγραμμα που να ελέγχει τη λειτουργία των κλάσεων αυτών.

Υλοποίηση με έτοιμες βιβλιοθήκες

Η βιβλιοθήκη της C++

Η βιβλιοθήκη της C++ μπορεί να χωριστεί στις παρακάτω κατηγορίες:
  1. Γενικές λειτουργίες
  2. Λειτουργίες μετατροπής και αρχείων (iostreams)
  3. Αλγόριθμοι με τη χρήση ενός επαναλήπτη (iterator) πάνω σε έναν περιέχοντα (container). Οι λειτουργίες αυτές αποτελούν τη βιβλιοθήκη με το όνομα Standard Template Library (STL).
  4. Συμβατότητα με τη C
Οι τρεις πρώτες κατηγορίες της βιβλιοθήκης ορίζονται σε επικεφαλίδες χωρίς το επίθεμα .h. Για να μην υπάρχει πιθανότητα τα ονόματα που ορίζονται στις επικεφαλίδες να ταυτίζονται με ονόματα που ορίζει ο χρήστης, τα ονόματα που ορίζουν οι επικεφαλίδες αυτές βρίσκονται απομονωμένα σε έναν ξεχωριστό χώρο ονομάτων (namespace) με το πρόθεμα "std::". Για να τα χρησιμοποιήσουμε στο πρόγραμμά μας χωρίς το πρόθεμα αυτό μπορούμε μετά τις επικεφαλίδες να δηλώσουμε:
using namespace std;
Στις επόμενες ενότητες περιγράφουμε συνοπτικά μόνο τις γενικές λειτουργίες της βιβλιοθήκης και με περισσότερες λεπτομέρειες τις λειτουργίες της STL.

Γενικές λειτουργίες

Η γενικές λειτουργίες της βιβλιοθήκης της C++ είναι οι παρακάτω:
bitset
κλάση προτύπων (ως προς το μέγιστο πληθάριθμο του συνόλου) που επεξεργάζεται σύνολο από bit
complex
κλάση προτύπων (ως προς τον τύπου των δύο στοιχείων) μιγαδικών αριθμών. Εξειδικεύσεις της κλάσης είναι μιγαδικοί αριθμοί float, double και long double.
exception
συναρτήσεις χειρισμού διακοπών
limits
ιδιότητες αριθμητικών τιμών
locale
προσαρμογή του προγράμματος σε τοπικά χαρακτηριστικά. Περιλαμβάνονται πρότυπες κλάσεις που ορίζουν:
stdexcept
κλάσεις για αναφορά διακοπών
string
πρότυπη κλάση που ορίζει σειρές αντικειμένων μεταβλητού μήκους. Εξειδίκευση της κλάσης αυτής είναι οι συμβολοσειρές.
valarray
πρότυπη (ως προς το περιεχόμενο του πίνακα) κλάση που επιτρέπει το μαζικό χειρισμό πινάκων

Παράδειγμα χρήσης μιγαδικών αριθμών

Το παρακάτω παράδειγμα αθροίζει δύο μιγαδικούς αριθμούς:
#include <complex>
#include <iostream>

using namespace std;

main()
{
        complex <double> a, b, c;

        cin >> a >> b;
        c = a + b;
        cout << c << "\n";
}
Είσοδος:
(4,2)
(1,10)
Αποτέλεσμα:
(5,12)

Παράδειγμα χρήσης συμβολοσειρών

Το παρακάτω παράδειγμα αθροίζει δύο συμβολοσειρές:
#include <string>
#include <iostream>

using namespace std ;

void main()
{
   string result;
   string s1="hello";
   string s2=", world";

   cout << "s1 is " << s1 << "\n";
   cout << "s2 is " << s2 << "\n";

   result=s1+s2;
   cout << "s1+s2 is " << result << "\n";
}
Αποτέλεσμα:
s1 is hello
s2 is , world
s1+s2 is hello, world

Λειτουργίες μετατροπής και αρχείων

Οι επικεφαλίδες iostreams υποστηρίζουν τη μετατροπή μεταξύ κειμένων και της εσωτερική μορφή παράστασης των τύπων καθώς και είσοδο και έξοδο από και σε εξωτερικά αρχεία. Ορίζονται οι παρακάτω επικεφαλίδες:
fstream
πρότυπες κλάσεις iostreams για το χειρισμό εξωτερικών αρχείων
iomanip
δήλωση χειριστών iostreams
ios
βασική κλάση προτύπων iostreams
iosfwd
δήλωση προτύπων κλάσεων iostreams πριν από τον ορισμό τους
iostream
δήλωση των βασικών αντικειμένων iostreams
istream
πρότυπη κλάση που εξάγει στοιχεία
ostream
πρότυπη κλάση που εισάγει στοιχεία
sstream
πρότυπες κλάσεις iostreams που αναφέρονται σε συμβολοσειρές
streambuf
πρότυπες κλάσεις που προσφέρουν ενταμιευτές σε κλάσεις iostreams
strstream
κλάσεις iostreams που λειτουργούν σε στοιχεία που βρίσκονται στη μνήμη

Περιέχοντες και επαναλήπτες στην STL

Η STL ορίζει μια σειρά από περιέχοντες (containers) όπως την ουρά, τη στοίβα, την απεικόνιση και τον πίνακα. Πάνω στους περιέχοντες αυτούς επιτρέπει την εκτέλεση αλγορίθμων, όπως την εύρεση ενός στοιχείου, η ένωση δύο περιεχόντων, η ταξινόμηση, κ.λπ.

Στην STL ορίζονται οι παρακάτω πρότυποι (ως προς τα στοιχεία που περιέχουν) περιέχοντες:

list
διπλά συνδεδεμένη λίστα
queue
ουρά
deque
ουρά με πρόσβαση και στις δύο άκρες
map
απεικόνιση (π.χ. από συμβολοσειρές σε ακέραιους)
set
απεικόνιση με μοναδικά στοιχεία στο πεδίο τιμών
stack
στοίβα
vector
πίνακας
Η πρόσβαση των στοιχείων ενός περιέχοντα από τους αλγορίθμους γίνεται μέσω επαναληπτών (iterators). Οι επαναλήπτες - ανάλογα με τη δομή των δεδομένων του περιέχοντα - μπορούν να επιτρέπουν την παρακάτω ιεραρχία κινήσεων: Επίσης, μπορούν να επιτρέπουν την παρακάτω ιεραρχία πρόσβασης στα δεδομένα που δείχνουν: Η κλάση των επαναληπτών ορίζεται στην επικεφαλίδα iterator. Μερικές μέθοδοι που ορίζονται σε επαναλήπτες είναι οι παρακάτω: Για κάθε περιέχοντα ορίζονται μέθοδοι όπως (στην περίπτωση της διπλής ουράς):
assign
ανάθεση τιμής
at
αναφορά σε στοιχείο
back
το τελευταίο στοιχείο
begin
επαναλήπτης που δείχνει στην αρχή της δομής
clear
διαγραφή όλων των στοιχείων
empty
αληθές αν η δομή είναι άδεια
end
επαναλήπτης που δείχνει στο τέλος της δομής
erase
διαγραφή σειράς στοιχείων
front
το πρώτο στοιχείο
insert
προσθήκη στοιχείου
operator[]
πρόσβαση σε στοιχείο
pop_back
αφαίρεση στοιχείου από το τέλος
pop_front
αφαίρεση στοιχείου από την αρχή
push_back
προσθήκη στοιχείου στο τέλος
push_front
προσθήκη στοιχείου στην αρχή
rbegin
ανάστροφος επαναλήπτης που δείχνει στην αρχή της δομής
rend
ανάστροφος επαναλήπτης που δείχνει στο τέλος της δομής
resize
καθορισμός του αριθμού των στοιχείων
size
αριθμός των στοιχείων
swap
εναλλαγή δύο στοιχείων μεταξύ τους

Παράδειγμα 1: ουρές

Το παρακάτω παράδειγμα ορίζει μια διπλή ουρά ακεραίων, προσθέτει 5 στοιχεία και τα τυπώνει:
#include <iostream>
#include <deque>

using namespace std;


typedef deque <int>  INTDEQUE;

void main()
{

        // Create A and fill it with elements 1,2,3,4 and 5
        // using push_back function
        INTDEQUE  A;

        A.push_back(1);
        A.push_back(2);
        A.push_back(3);
        A.push_back(4);
        A.push_back(5);

        // Print the contents of A using iterator
        // and functions begin() and end()
        INTDEQUE::iterator pi;

        for (pi = A.begin();  pi != A.end(); pi++)
                cout << *pi << " ";
        cout << "\n";
}

Παράδειγμα 2: απεικόνιση

Το παρακάτω παράδειγμα τυπώνει πόσες φορές εμφανίστηκε κάθε λέξη στην είσοδο του προγράμματος:
#include <map>
#include <iostream>
#include <string>

using namespace std;

typedef map <string, int> smap_t;

int
main()
{
        string s;
        smap_t m;                       // Our map

        while (!cin.eof()) {
                cin >> s;
                m[s]++;                 // Use overloaded [] operator
        };

        smap_t::iterator i;             // Iterator for printing the results
        for (i = m.begin(); i != m.end(); i++)
                cout << i->first << " " << i->second << endl;
        return (0);
}

Πρόσθετες λειτουργίες στην STL

Επικεφαλίδα algorithm

Στην επικεφαλίδα algorithm ορίζονται μέθοδοι που ενεργούν πάνω σε περιέχοντες:
adjacent_find
βρίσκει δύο ίσα στοιχεία σε διπλανές θέσεις
binary_search
δυαδική ανίχνευση
copy
αντιγραφή περιοχής
copy_backward
αντίστροφη αντιγραφή περιοχής
count
μέτρημα
count_if
μέτρημα υπό συνθήκη
equal
σύγκριση περιοχών
equal_range
σύγκριση περιοχών με συγκεκριμένη ακρίβεια
fill
πλήρωση περιοχής με τιμή
fill_n
πλήρωση αριθμού στοιχείων με τιμή
find
εύρεση στοιχείου
find_end
εύρεση στοιχείου από το τέλος
find_first_of
εύρεση στοιχείου ίσου με κάποιο μέλος από σύνολο στοιχείων
find_if
εύρεση στοιχείου που να ικανοποιεί συνθήκη
for_each
εκτέλεση συνάρτησης για όλα τα στοιχεία σε περιοχή
generate
πλήρωση περιοχής με αποτέλεσμα συνάρτησης
generate_n
πλήρωση αριθμού στοιχείων με αποτέλεσμα συνάρτησης
includes
έλεγχος αν μια περιοχή εμπεριέχει μια άλλη
inplace_merge
σύζευξη δεδομένων στον ίδιο περιέχοντα
iter_swap
εναλλαγή δύο τιμών
lexicographical_compare
σύγκριση δύο περιοχών α, β για α < β
lower_bound
εύρεση μιας ελάχιστης τιμής σε περιοχή σε σχέση με μιαν άλλη τιμή
make_heap
μετατροπή περιοχής σε σωρό (heap) (δυαδικό δένδρο στο οποίο τα παιδιά έχουν τιμή μικρότερη ή ίση από αυτή των γονέων τους).
max
το μέγιστο από δύο στοιχεία
max_element
εύρεση του μέγιστου στοιχείου σε περιοχή
merge
σύζευξη δύο περιοχών σε τρίτη
min
το ελάχιστο από δύο στοιχεία
min_element
εύρεση του ελαχίστου στοιχείου σε περιοχή
mismatch
εύρεση του πρώτου διαφορετικού στοιχείου ανάμεσα σε δύο περιοχές
next_permutation
υπολογισμός της επόμενης μετάθεσης σε μια περιοχή
nth_element
θέτει ένα στοιχείο στη θέση που θα έπρεπε να έχει αν η περιοχή ήταν ταξινομημένη
partial_sort
ταξινομεί τα πρώτα στοιχεία μιας περιοχής
partial_sort_copy
ταξινομεί τα πρώτα στοιχεία μιας περιοχής σε μιαν άλλη
partition
χωρίζει μια περιοχή στα δύο με βάση μια συνάρτηση και επιστρέφει το σημείο που είναι ο χωρισμός
pop_heap
αφαίρεση στοιχείου από σωρό
prev_permutation
υπολογισμός της προηγούμενης μετάθεσης σε μια περιοχή
push_heap
προσθήκη στοιχείου από σωρό
random_shuffle
ανακατεύει μια περιοχή
remove
αφαιρεί στοιχεία ίσα με μια τιμή
remove_copy
αφαιρεί στοιχεία ίσα με μια τιμή μεταφέροντας το αποτέλεσμα σε μιαν άλλη περιοχή
remove_copy_if
αφαιρεί στοιχεία για τα οποία μια συνάρτηση είναι αληθής μεταφέροντας το αποτέλεσμα σε μιαν άλλη περιοχή
remove_if
αφαιρεί στοιχεία για τα οποία μια συνάρτηση είναι αληθής
replace
αλλάζει τιμή σε στοιχεία ίσα με μια τιμή
replace_copy
αλλάζει τιμή σε στοιχεία ίσα με μια τιμή μεταφέροντας το αποτέλεσμα σε μιαν άλλη περιοχή
replace_copy_if
αλλάζει τιμή σε στοιχεία για τα οποία μια συνάρτηση είναι αληθής μεταφέροντας το αποτέλεσμα σε μιαν άλλη περιοχή
replace_if
αλλάζει τιμή σε στοιχεία για τα οποία μια συνάρτηση είναι αληθής
reverse
αντιστρέφει τη σειρά σε μια περιοχή
reverse_copy
αντιστρέφει τη σειρά σε μια περιοχή μεταφέροντάς την σε μιαν άλλη περιοχή
rotate
περιστρέφει τη σειρά των στοιχείων σε μια περιοχή
rotate_copy
περιστρέφει τη σειρά των στοιχείων σε μια περιοχή μεταφέροντάς την σε μιαν άλλη περιοχή
search
εύρεση σειράς στοιχείων σε μια περιοχή ίσης με στοιχεία μιας άλλης
search_n
εύρεση σειράς στοιχείων σε μια περιοχή ίσης με αριθμό στοιχείων μιας άλλης
set_difference
θέτει μια περιοχή ίση με τη διαφορά των στοιχείων δύο άλλων περιοχών (διαφορά συνόλων)
set_intersection
θέτει μια περιοχή ίση με την τομή των στοιχείων δύο άλλων περιοχών (τομή συνόλων)
set_symmetric_difference
θέτει μια περιοχή ίση με τα μη κοινά των στοιχείων δύο άλλων περιοχών
set_union
θέτει μια περιοχή ίση με την ένωση των στοιχείων δύο άλλων περιοχών (ένωση συνόλων)
sort
ταξινομεί μια περιοχή
sort_heap
ταξινομεί έναν σωρό
stable_partition
χωρίζει μια περιοχή στα δύο με βάση μια συνάρτηση και επιστρέφει το σημείο που είναι ο χωρισμός. Ο χωρισμός γίνεται χωρίς να αλλάξει η σχετική σειρά των στοιχείων.
stable_sort
ταξινομεί μια περιοχή. Η ταξινόμηση γίνεται χωρίς να αλλάξει η σχετική σειρά των στοιχείων που είναι μεταξύ τους ίσα.
swap
αντιστρέφει μεταξύ τους δύο στοιχεία
swap_ranges
αντιστρέφει μεταξύ τους δύο περιοχές
transform
εφαρμόζει έναν τελεστή σε μια περιοχή ή μεταξύ δύο περιοχών
unique
αφαιρεί τα όμοια στοιχεία από μια περιοχή
unique_copy
αφαιρεί τα όμοια στοιχεία από μια περιοχή μεταφέροντάς την σε μιαν άλλη περιοχή
upper_bound
εύρεση μιας μέγιστης τιμής σε περιοχή σε σχέση με μια άλλη τιμή

Επικεφαλίδα numeric

Στην επικεφαλίδα algorithm ορίζονται αριθμητικές μέθοδοι που ενεργούν πάνω σε περιέχοντες:
accumulate
υπολογίζει ένα σύνολο πάνω σε μια περιοχή
adjacent_difference
υπολογίζει τις διαφορές τιμών μεταξύ στοιχείων μιας περιοχής
inner_product
υπολογίζει ένα εσωτερικό γινόμενο μεταξύ δύο περιοχών
partial_sum
υπολογίζει ένα μερικό άθροισμα τιμών μιας περιοχής σε μιαν άλλη

Άλλες επικεφαλίδες

Ακόμα στην STL ορίζονται οι παρακάτω επικεφαλίδες:
utility
πρότυπη κλάση που ορίζει διάταξη σε ζεύγη τιμών
functional
κλάση που επιτρέπει συναρτησιακό προγραμματισμό
memory
ορίζει την κλάση allocator η οποία κατανέμει τη μνήμη σε όλους τους περιέχοντες. Ο επανακαθορισμός της επιτρέπει την υλοποίηση άλλων στρατηγικών καταμερισμού και πρόσβασης στη μνήμη.

Συμβατότητα με τη C

Ακόμα παρέχονται επικεφαλίδες που παρέχουν υπηρεσίες όμοιες με τις αντίστοιχες της γλώσσας C. Καλό είναι οι επικεφαλίδες αυτές να χρησιμοποιούνται μόνο όταν απαιτείται συμβατότητα με κληρονομημένο (legacy) (παλιό) κώδικα.
cassert
απόδειξη ιδιοτήτων κατά την εκτέλεση συναρτήσεων
cctype
χαρακτηρισμός χαρακτήρων
cerrno
πρόσβαση στα λάθη από την εκτέλεση συναρτήσεων της βιβλιοθήκης
cfloat
ιδιότητες αριθμών κινητής υποδιαστολής
ciso646
ορισμός λέξεων που επιτρέπουν τον προγραμματισμό σε συστήματα με τις κωδικοσελίδες ISO 646 (δεν αφορά την Ελλάδα)
climits
ιδιότητες των ακεραίων
clocale
προσαρμογή του προγράμματος σε τοπικά χαρακτηριστικά
cmath
μαθηματικές συναρτήσεις
csetjmp
αδόμητη μετάβαση ελέγχου μεταξύ συναρτήσεων
csignal
έλεγχος διακοπών
cstdarg
πρόσβαση σε ορίσματα με μεταβλητό αριθμό παραμέτρων
cstddef
ορισμός χρησίμων τύπων
cstdio
είσοδος και έξοδος όμοια με τη C
cstdlib
πρόσβαση στις αντίστοιχες συναρτήσεις της C
cstring
επεξεργασία συμβολοσειρών της C
ctime
χρήση ώρας και ημερομηνίας
cwchar
επεξεργασία "πλατιών" χαρακτήρων
cwctype
χαρακτηρισμός "πλατιών" χαρακτήρων

Βιβλιογραφία

Ασκήσεις

Άσκηση 7

  1. Με βάση τον περιέχοντα της STL queue και μονοτονικά αυξανόμενη μεταβλητή να υλοποιηθεί πρόγραμμα το οποίο να υλοποιεί ουρά εξυπηρέτησης πελατών ως εξής:
    1. Όταν εισάγεται ο χαρακτήρας I (In) το πρόγραμμα τυπώνει τον αριθμό προτεραιότητας του νέου πελάτη.
    2. Όταν εισάγεται ο χαρακτήρας O (Out) το πρόγραμμα τυπώνει τον αριθμό προτεραιότητας του επόμενου πελάτη που θα εξυπηρετηθεί.
    Παράδειγμα:
    I
    1
    I
    2
    I
    3
    O
    1
    I
    4
    O
    2
    ...
    

Υλοποίηση διεπαφών σε Visual Basic

Παράθυρα, εικονίδια, μενού, και δείκτης

Στο σύγχρονο περιβάλλον διεπαφής με το χρήστη βασίζεται στα παράθυρα (windows), τα εικονίδια (icons), τα μενού (menus) και το δείκτη (pointer).

Το περιβάλλον αυτό σχεδιάστηκε για πρώτη φορά στο Palo Alto Research Center της Xerox (PARC) και υλοποιήθηκε με επιτυχία από την Apple για τους υπολογιστές Macintosh και από τη Micosoft στην οικογένεια Windows. Η επιφάνεια εργασίας χρησιμοποιεί ως βάση τη μεταφορά του γραφείου (desktop metaphor). Ο χρήστης μετακινεί πάνω στην οθόνη παράθυρα με τη χρήση του δείκτη όπως θα κινούσε έγγραφα στο γραφείο με τα χέρια του. Βασικά τεχνολογικά στοιχεία για τη λειτουργία του περιβάλλοντος αυτού είναι η οθόνη χαρτογραφικής απεικόνισης (bitmap display) και το ποντίκι (mouse) ή κάποιος άλλος αντίστοιχος μηχανισμός που επιτρέπει στο χρήστη να δείχνει αντικείμενα στην οθόνη. Με τη χρήση εικονιδίων ορισμένα στοιχεία του περιβάλλοντος μπορούν να παρασταθούν με αμεσότητα, ενώ τα μενού κάνουν τις λειτουργίες του περιβάλλοντος προσιτές χωρίς να χρειάζεται ο χρήστης να απομνημονεύει εντολές και τη σύνταξή τους.


Εικονίδια στο περιβάλλον Windows


Εισαγωγή εικόνας στο κείμενο με τη χρήση μενού στο πρόγραμμα Microsoft Word

<img src="./vb/inspic.gif"> 
Εισαγωγή εικόνας στο κείμενο στη γλώσσα HTML

Εσωτερική δομή

Εσωτερικά τα συστήματα γραφικής διεπαφής που βασίζονται σε παράθυρα λειτουργούν με βάση γεγονότα (events) τα οποία στέλνονται στις εφαρμογές. Το κάθε παράθυρο είναι μια (τυπικά ορθογώνια) περιοχή της οθόνης η οποία σχεδιάζεται αυτόνομα και λαμβάνει γεγονότα. Κάθε εφαρμογή μπορεί να απαρτίζεται από ένα ή περισσότερα παράθυρα. Γεγονότα μπορεί να είναι π.χ. η επιλογή μιας εντολής από το μενού, η αλλαγή του κειμένου που γράφει ο χρήστης, το πάτημα ενός πλήκτρου, η κίνηση του ποντικιού ή, η αλλαγή της ώρας. Τα γεγονότα μπορεί να δημιουργούνται άμεσα από το χρήστη ή να συνθέτονται από το σύστημα ως αποτέλεσμα εντολών του χρήστη, ή άλλων εξωτερικών διεπαφών. Το κάθε γεγονός στέλνεται σε μια ή περισσότερες εφαρμογές. Οι εφαρμογές με τη σειρά τους στέλνουν τα γεγονότα στα παράθυρα που μπορούν να τα επεξεργαστούν. Έτσι, αντί η εφαρμογή να ζητά στοιχεία από το χρήστη, τα στοιχεία από το χρήστη στέλνονται ως γεγονότα στην εφαρμογή. Στο περιβάλλον των Windows ορίζονται πάνω από 800 διαφορετικά γεγονότα.

Για την επιβολή συνέπειας στη διεπαφή με το χρήστη αλλά και τη διευκόλυνση υλοποίησης των εφαρμογών τα παραθυρικά περιβάλλοντα ορίζουν μια σειρά από όργανα (controls) (ή widgets) τα οποία ενσωματώνουν και διαθέτουν ένα σύνολο από λειτουργίες που είναι συχνά χρήσιμες. Παραδείγματα τέτοιων οργάνων είναι το πλαίσιο εισαγωγής κειμένου (edit box), το πιεζόμενο κουμπί (push button) και το πλαίσιο επιλογής αρχείου.

Η μεταφορά των γεγονότων από το σύστημα στις εφαρμογές και τα παράθυρά τους βασίζεται συχνά στην κληρονομικότητα. Έτσι, μια εφαρμογή μπορεί να δημιουργήσει υποκλάσεις για ένα όργανο που να δίνουν στο όργανο πιο εξειδικευμένη λειτουργικότητα. Αντίστοιχα, τα μηνύματα από το σύστημα κατεβαίνουν το δέντρο της κληρονομικότητας μιας εφαρμογής μέχρι και το τελευταίο στην ιεραρχία παράθυρο ή όργανο να δει το γεγονός και να έχει την ευκαιρία να το χειριστεί. Μια εφαρμογή πρέπει να παραλαμβάνει συνέχεια όλα τα μηνύματα που στέλνονται σε αυτή, να τα διαχωρίζει και να τα προωθεί στους αντίστοιχους παραλήπτες. Ο παρακάτω κώδικας αποτελεί τυπικό παράδειγμα του βρόχου που παραλαμβάνει και προωθεί μηνύματα σε εφαρμογές Windows:

        MSG             msg;

        while (GetMessage(&msg, NULL00)) {
                if (!IsDialogMessage(hWnd, &msg)) {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                }
        }
Αντίστοιχα ο παρακάτω κώδικας αποτελεί ένα απλό παράδειγμα για το πως μπορεί να γίνει απλοϊκά η επεξεργασία των μηνυμάτων:
long
WdInWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
        static BOOL     fFirstPaint;
        static HWND     hWndList;
        static HWND     hWndEdit;
        HWND            focus;
        int i;

        switch (iMessage) {
        case WM_CREATE:
                fFirstPaint = TRUE;
                break;
        case WM_PAINT:
                if (fFirstPaint) {
                        fFirstPaint = FALSE;
                        /* Set focus to the input string box */
                        hWndEdit = GetDlgItem(hDlgWnd, ID_STRINGEDIT);
                        hWndList = GetDlgItem(hDlgWnd, ID_LISTBOX);
                        /* Blank all lines so that we start from the bottom */
                        for (i = 0; i < HLEN; i++)
                                (void)SendMessage(hWndList, LB_INSERTSTRING,
                                        (WPARAM)-1, (LPARAM)(LPCSTR)"");
                        SetFocus(hWndEdit);
                }
                return (DefWindowProc(hWnd, iMessage, wParam, lParam));
        case WM_DESTROY:
                PostQuitMessage(0);
                break;
        case WM_COMMAND:
                switch (wParam) {
                case IDOK:                      /* Enter key */
                        focus = GetFocus();
                        if (focus == hWndEdit)
                                newline(hWndEdit, hWndList);
                        else if (focus == hWndList)
                                histline(hWndList, hWndEdit);
                        else
                                goto aka_default;
                        break;
                case ID_LISTBOX:
                        if (HIWORD(lParam) == LBN_DBLCLK)
                                histline(hWndList, hWndEdit);
                        else
                                goto aka_default;
                        break;
                default:
                        goto aka_default;
                }
                break;
        default:
        aka_default:
                return (DefWindowProc(hWnd, iMessage, wParam, lParam));
        }
        return 0L;
}
Είναι φανερό πως το παραπάνω μοντέλο δε μπορεί να εφαρμοστεί αποτελεσματικά σε εφαρμογές με εκατοντάδες εντολές και αντίστοιχα μηνύματα.

Απεικόνιση της δομής στη Visual Basic

Το περιβάλλον προγραμματισμού Visual Basic επιτρέπει τη γραφική σχεδίαση εφαρμογών με τη χρήση οργάνων και τον άμεσο ορισμό κώδικα που θα αποκρίνεται σε συγκεκριμένα γεγονότα. Για το σκοπό αυτό η Visual Basic απλοποιεί το περιβάλλον προγραμματισμού που ορίζουν τα Windows δίνοντας στο χρήστη μια σειρά από εξειδικευμένα όργανα (βασισμένα σε αυτά των Windows) και έναν τρόπο να ορίζει ο χρήστης την εμφάνιση του κάθε οργάνου, τις λειτουργίες του και τον τρόπο που αυτό ανταποκρίνεται σε γεγονότα. Έτσι, κάθε όργανο παριστάνεται ως ένα αντικείμενο που διαθέτει: Ο έλεγχος που έχει ο χρήστης πάνω στα όργανα διαφέρει ανάλογα με την κατάσταση της εφαρμογής:

Όργανα

Όργανα τοποθετούνται μέσα στην εφαρμογή με γραφικό τρόπο κατά την υλοποίησή της. Κάθε όργανο διακρίνεται από το όνομά του. Αν πολλά όργανα ίδιου τύπου έχουν το ίδιο όνομα τότε αυτά ορίζουν έναν πίνακα οργάνων τον οποίο μπορούμε να διατρέξουμε με τη χρήση ενός δείκτη (π.χ. txtTextBox(i).text = "hello").

Το περιβάλλον της Visual Basic ορίζει τα παρακάτω βασικά όργανα:

TextBox
Όργανο για είσοδο κειμένου
Label
Εμφάνιση κειμένου
PictureBox
Περιοχή για εμφάνιση και σχεδίαση γραφικών κατά τη διάρκεια εκτέλεσης του προγράμματος.
CheckBox
Επιλογή τύπου ναι/όχι (μπορούν να είναι αληθείς πολλαπλές τέτοιες επιλογές)
OptionButton
Επιλογή τύπου ναι/όχι (δεν μπορούν να είναι αληθείς πολλαπλές τέτοιες επιλογές)
CommandButton
Κουμπί για εντολές
Image
Χαρτογραφική εικόνα που μπορεί να ορίσει ο χρήστης
Shape
Σχήμα που μπορεί να οριστεί κατά τη σχεδίαση της εφαρμογής.
Timer
Χρονόμετρο που δημιουργεί γεγονότα σε τακτά διαστήματα.
ListBox
Λίστα με επιλογές.
ComboBox
Λίστα με επιλογές σε συνδυασμό με περιοχή που μπορεί να γραφτεί κείμενο
VScrollBar
Κάθετο όργανο ελέγχου της θέσης στο παράθυρο.
HScrollBar
Οριζόντιο όργανο ελέγχου της θέσης στο παράθυρο.
Frame
Περιοχή στην οποία μπορούν να ομαδοποιηθούν πολλά όργανα (ειδικά OptionButton)
Εκτός από τα παραπάνω βασικά όργανα μπορεί κανείς να ορίσει ή να αγοράσει πολλά άλλα όργανα που εκτελούν χρήσιμες λειτουργίες όπως π.χ. σύνδεση με βάσεις δεδομένων και το Internet, εμφάνιση γραφημάτων και πινάκων. Τα όργανα αυτά μπορούν να θεωρηθούν εξαρτήματα (components) για την υλοποίηση εφαρμογών.

Γεγονότα

Κάθε όργανο έχει το δικό του σύνολο από γεγονότα τα οποία μπορεί να δεχτεί. Ορισμένα από τα γεγονότα τα οποία αφορούν πολλά όργανα είναι τα παρακάτω:
Click
Click του ποντικιού πάνω στο όργανο.
DblClick
Διπλό click του ποντικιού πάνω στο όργανο.
GotFocus
Το όργανο γίνεται η εστία εισόδου.
LostFocus
Το όργανο παύει να είναι η εστία εισόδου.
KeyDown
Ένα πλήκτρο πατιέται πάνω από το όργανο.
KeyUp
Ένα πλήκτρο αφήνεται πάνω από το όργανο.
KeyPress
Ένας χαρακτήρας γράφεται πάνω από το όργανο.
MouseDown
Ένα πλήκτρο του ποντικιού πατιέται πάνω από το όργανο.
MouseUp
Ένα πλήκτρο του ποντικιού αφήνεται πάνω από το όργανο.
MouseMove
Το ποντίκι κινείται πάνω από το όργανο.
Change
Τα δεδομένα που περιέχει το όργανο άλλαξαν.
Η σύνδεση του γεγονότος με κώδικα της Visual Basic γίνεται με τον ορισμό μιας συνάρτησης που έχει ως όρισμα παραμέτρους που αφορούν το συγκεκριμένο όργανο. Παράδειγμα:
Private Sub Check1_KeyPress(KeyAscii As Integer)

End Sub

Private Sub Check1_KeyUp(KeyCode As Integer, Shift As Integer)

End Sub

Private Sub Check1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

End Sub

Private Sub Command1_Click()

End Sub

Private Sub Text1_Change()

End Sub

Ιδιότητες

Οι ιδιότητες που αφορούν κάθε όργανο μπορούν να μεταβληθούν τόσο κατά το σχεδιασμό, όσο και κατά την εκτέλεση της εφαρμογής. Πρόσβαση στις ιδιότητες κατά την εκτέλεση της εφαρμογής έχουμε με τη σύνταξη όργανο.ιδιότητα. Μερικές ιδιότητες που εμφανίζονται σε πολλά όργανα είναι οι παρακάτω:
Name
Το όνομα του συγκεκριμένου οργάνου.
BackColor
Το χρώμα του φόντου.
ForeColor
Το χρώμα σχεδίασης.
Enabled
Αληθές αν το όργανο είναι ενεργό.
Visible
Αληθές αν το όργανο είναι ορατό.
Text
Το κείμενο που περιέχει το όργανο.
Caption
Το κείμενο που εμφανίζει το όργανο.
Value
Η τιμή που έχει το όργανο (π.χ. αληθές/ψευδές).
ToolTipText
Το αναδυόμενο κείμενο σύντομης βοήθειας.
Height
Το ύψος του οργάνου.
Left
Η θέση του οργάνου στη φόρμα από αριστερά.
Top
Η θέση του οργάνου στη φόρμα από πάνω.
Width
Το πλάτος του οργάνου.

Ολοκληρωμένο περιβάλλον ανάπτυξης

Μια εφαρμογή Visual Basic υλοποιείται με βάση διάφορες φόρμες (forms) που ορίζουν τα περιεχόμενα για αντίστοιχα παράθυρα. Κάθε φόρμα έχει, όπως και τα όργανα, ιδιότητες και μεθόδους.

Το περιβάλλον υλοποίησης της Visual Basic έχει την παρακάτω μορφή:

Για να ορίσουμε τον κώδικα που εκτελείται για κάποιο γεγονός κάνουμε διπλό κλικ σε κάποιο όργανο και αυτόματα δημιουργείται η δήλωση για τη συνάρτηση που αφορά το πιο χρήσιμο γεγονός για το αντίστοιχο όργανο. Από επιλογές στην κορυφή του παραθύρου μπορούμε να επιλέξουμε το όργανο και το γεγονός για τα οποία θέλουμε να γράψουμε κώδικα:

Η γλώσσα Visual Basic

Η γλώσσα Visual Basic είναι μια δομημένη γλώσσα με τύπους που υποστηρίζει, μερικώς, τον προγραμματισμό με αντικείμενα (δεν υποστηρίζει κληρονομικότητα). Στις επόμενες παραγράφους περιγράφουμε πολύ συνοπτικά ορισμένα βασικά στοιχεία της:

Ορισμός συναρτήσεων

function funcname(param1 as type1, param2 as type2) as returntype
end function
Παράδειγμα:
function square(x as double) as double
        square = x * x
end function
Διαδικασίες (συναρτήσεις που δεν επιστρέφουν τιμή) ορίζονται αντίστοιχα, αλλά με τη λέξη sub αντί για function.

Τύποι

Ορισμένοι βασικοί τύποι είναι οι boolean, integer, single, double, string.

Ορισμός μεταβλητών

Μεταβλητές ορίζονται με τη σύνταξη:
dim varname as type
Παράδειγμα:
        dim x as double
        dim k as integer

Εντολές

Οι εντολές τερματίζονται από το τέλος της γραμμής Παράδειγμα:
        x = 3
        txt.Text = "Hello"

Σχόλια

Τα σχόλια αρχίζουν με το χαρακτήρα '

Δομές ελέγχου

Η Visual Basic παρέχει τις παρακάτω δομές ελέγχου (σε σχόλιο η αντίστοιχη δομή της C).
' if (...) { ... } else { ... }
if boolean then
	statement
	...
else
	statement
	...
end if

' for (v = start; v <= end; v++) { ... }
for variable = start to end
	statement
	...
next variable

' while (...) {  ... }
do while boolean
	statement
	...
loop

' do { ... } while (...)
do
	statement
	..
loop while boolean

' switch (c) { case ... default: }
Select Case testexpression
Case expressionlist
	statement
	...
Case Else
	statement
	...
End Select

Βιβλιογραφία

Ασκήσεις

Άσκηση 7

  1. Να προδιαγράψετε ένα χρήσιμο και πρωτότυπο πρόγραμμα που να μπορείτε να υλοποιήσετε σε Visual Basic. Το πρόγραμμα πρέπει να χρησιμοποιεί τις δυνατότητες του περιβάλλοντος Windows και να είναι φιλικό προς το χρήστη. Η προδιαγραφή πρέπει να είναι πλήρης και δομημένη (μπορεί π.χ. να ακολουθεί τη δομή που έχει διδαχτεί στο μάθημα σχεδιασμός και υλοποίηση λογισμικού).
  2. Με βάση την προδιαγραφή αυτή να υλοποιήσετε το αντίστοιχο πρόγραμμα.
  3. Με την άσκηση θα παραδώσετε εκτυπωμένα την προδιαγραφή καθώς και τις βασικές οθόνες του προγράμματος.
Σημείωση: Μπορείτε να μεταφέρετε την εικόνα από ένα παράθυρο στο clipboard, για να την επικολλήσετε στον επεξεργαστή κειμένου, με το συνδυασμό πλήκτρων ALT-PrtSc.

Ολοκληρωμένα περιβάλλοντα ανάπτυξης

Εισαγωγή

Ο διορθωτής

Ο διορθωτής σε ένα ολοκληρωμένο περιβάλλον ανάπτυξης περιλαμβάνει ισχυρές δυνατότητες εισαγωγής του κώδικα καθώς και δυνατότητες που σχετίζονται με τη γλώσσα προγραμματισμού όπως: Σε περιβάλλοντα που στοχεύουν στην ανάπτυξη προγραμμάτων με γραφικό σύστημα διεπαφής με το χρήστη συχνά υπάρχει και διορθωτής ο οποίος επιτρέπει το σχεδιασμό του συστήματος διεπαφής. Ο διορθωτής αυτό επιτρέπει την εισαγωγή στοιχείων του γραφικού περιβάλλοντος. Μερικά στοιχεία που απαντώνται συχνά είναι: Το παρακάτω σχήμα δείχνει ένα πλαίσιο διαλόγου σχεδιασμένο μέσα στο περιβάλλον του διορθωτή.

Το σύστημα βοήθειας

Το σύστημα βοήθειας περιλαμβάνει συχνά σε ψηφιακή μορφή τεκμηρίωση για: Το σύστημα βοήθειας είναι τις περισσότερες φορές παρουσιασμένο σε μορφή υπερκειμένου με πίνακες περιεχομένων και συνδέσεις ανάμεσα σε τμήματα, όπως φαίνεται στο παρακάτω σχήμα:

Μερικές φορές το σύστημα βοηθείας συμπληρώνεται από οδηγούς (wizards) που επιτρέπουν με διαλογικό τρόπο τη βήμα με βήμα ανάπτυξη μιας εφαρμογής. Τα παρακάτω σχήμα παριστάνει ένα στάδιο από την εκτέλεση ενός οδηγού:

Συχνά υπάρχει άμεση σύνδεση του διορθωτή με το σύστημα βοήθειας έτσι ώστε την ώρα που π.χ. πληκτρολογούμε την κλήση σε μια συνάρτηση της βιβλιοθήκης να μπορούμε να δούμε τον ορισμό της.

Η διαδικασία μεταγλώττισης

Η διαδικασία της μεταγλώττισης περιλαμβάνει αρκετές ευκολίες σε ένα ολοκληρωμένο περιβάλλον.

Αποσφαλμάτωση

Ο αποσφαλματωτής επιτρέπει τον πλήρη έλεγχο της ροής εκτέλεσης και των δεδομένων του προγράμματος που εκτελείται. Περιλαμβάνει δυνατότητες όπως: To παρακάτω σχήμα εμφανίζει ένα πρόγραμμα που εκτελείται σε περιβάλλον αποσφαλματωτή.

Φυλλομέτρηση πηγαίου κώδικα και κλάσεων

Συνδεδεμένο με το διορθωτή είναι συχνά ένα εργαλείο που επιτρέπει τη φυλλομέτρηση και την εμφάνιση της δομής του κώδικα και των κλάσεων που τον απαρτίζουν. Δυνατότητες του φυλλομετρητή μπορεί να είναι οι παρακάτω:

Βιβλιογραφία

Χειρισμός δεδομένων με SQL

Το σχεσιακό μοντέλο δεδομένων

Ένα σύστημα διαχείρισης βάσης δεδομένων (ΣΔΒΔ) (database management system (DBMS)) αποτελείται από ένα σύνολο δεδομένων και προγράμματα πρόσβασης στα δεδομένα αυτά. Το σύνολο των δεδομένων καλείται βάση δεδομένων (database). Στόχος του ΣΔΒΔ είναι η εύκολη και γρήγορη χρήση και ανάκτηση των δεδομένων. Η διαχείριση των δεδομένων περιλαμβάνει: Ο ορισμός της δομής της βάσης δεδομένων βασίζεται σε ένα μοντέλο δεδομένων το οποίο ορίζει τον τρόπο που περιγράφονται τα δεδομένα, οι σχέσεις τους, η σημασία τους και οι περιορισμοί πάνω στα δεδομένα αυτά.

Το σχεσιακό μοντέλο (relational model) δεδομένων παριστάνει δεδομένα και τις σχέσεις τους ως ένα σύνολο πινάκων. Κάθε πίνακας (table) αποτελείται από στήλες (columns) με μοναδικά ονόματα. Μια γραμμή (row) του πίνακα παριστάνει μια σχέση (relationship) ανάμεσα σε ένα σύνολο από τιμές. Ο πίνακας που ακολουθεί παριστάνει έναν τηλεφωνικό κατάλογο. Αποτελείται από δύο στήλες και πέντε γραμμές.
ΌνομαΤηλέφωνο
Γιώργος32560
Μαρία61359
Θανάσης98756
Λίνα78999
Πέτρος12356

Η SQL (structured query language) αποτελεί σήμερα την πιο διαδεδομένη γλώσσα διαχείρισης σχεσιακών βάσεων δεδομένων. Η SQL παρέχει δυνατότητες για:

Η SQL είναι ορισμένη ως διεθνές πρότυπο. Στις επόμενες ενότητες θα εξετάσουμε ένα υποσύνολο της SQL όπως υποστηρίζεται από την εγκατεστημένη στα εργαστήρια βάση δεδομένων Microsoft Access.

Στην περιγραφή της σύνταξης της SQL θα χρησιμοποιήσουμε τα παρακάτω σύμβολα:

[ έκφραση ]
η έκφραση εμφανίζεται προαιρετικά
έκφραση1 | έκφραση2
μπορεί να γραφεί η έκφραση1 ή η έκφραση2
έκφραση ...
η έκφραση μπορεί να επαναληφθεί

Τύποι δεδομένων

Τα δεδομένα κάθε στήλης ενός πίνακα πρέπει να έχουν ένα συγκεκριμένο τύπο. Οι βασικοί τύποι που υποστηρίζονται από την SQL είναι οι παρακάτω:
ΒΙΤ
Ναι ή Όχι
CURRENCY
Τιμή που παριστάνει με ακρίβεια αριθμούς από -922.337.203.685.477,5808 έως 922.337.203.685.477,5807
DATETIME
Χρόνος
SINGLE
Αριθμός κινητής υποδιαστολή μονής ακρίβειας
DOUBLE
Αριθμός κινητής υποδιαστολή διπλής ακρίβειας
SHORT
Ακέραιος 2 byte (-32768 έως 32767)
LONG
Ακέραιος 4 byte (-2.147.483.648 έως 2.147.483.647)
TEXT
Κείμενο μέχρι 255 χαρακτήρες
LONGTEXT
Κείμενο μέχρι 1.2GB

Δημιουργία πινάκων

Νέοι πίνακες δημιουργούνται με την εντολή CREATE TABLE. Αυτή συντάσσεται ως εξής:
CREATE TABLE όνομα_πίνακα (πεδίο1 τύπος [(μέγεθος)] [, πεδίο2 τύπος [(μέγεθος)] [, ...]] 
Για παράδειγμα η εντολή
CREATE TABLE Customer (Name TEXT (20), AccountNum SHORT)
δημιουργεί τον πίνακα με όνομα Customer και με δύο στήλες: την Name και την AccountNum.

Αλλαγές σε πίνακες

Η εντολή ALTER TABLE επιτρέπει την προσθήκη νέων στηλών ή τη διαγραφή υπαρχόντων. Η προσθήκη νέων στηλών γίνεται με τη σύνταξη:
ALTER TABLE όνομα_πίνακα ADD COLUMN πεδίο τύπος[(μέγεθος)]
Για παράδειγμα η εντολή:
ALTER TABLE Customer ADD COLUMN Notes TEXT(25)
προσθέτει μια νέα στήλη στον πίνακα Customer.

Η διαγραφή υπαρχόντων στηλών γίνεται με τη σύνταξη:

ALTER TABLE όνομα_πίνακα DROP COLUMN πεδίο 
Για παράδειγμα η εντολή:
ALTER TABLE Customer DROP COLUMN Notes
αφαιρεί τη στήλη Notes από τον πίνακα Customer.

Τέλος, η εντολή DROP TABLE επιτρέπει τη διαγραφή πινάκων. Για παράδειγμα η εντολή

DROP TABLE Customer
θα διαγράψει τον πίνακα Customer

Δείκτες

Η πρόσβαση στα στοιχεία ενός πίνακα γίνεται ταχύτερα όταν αυτά οργανωθούν με τη βοήθεια δεικτών. Ένας δείκτης ορίζεται για μια συγκεκριμένη στήλη ή στήλες και επιτρέπει τη γρήγορη πρόσβαση σε γραμμές με βάση τιμές της συγκεκριμένης στήλης. Ουσιαστικά όταν ορίζουμε έναν δείκτη το ΣΔΒΔ υλοποιεί μια δομή δεδομένων (π.χ. ταξινομημένο ή κατακερματισμένο πίνακα ή δένδρο) για γρήγορη πρόσβαση στα αντίστοιχα δεδομένα. Δείκτες δημιουργούνται με την εντολή CREATE INDEX. Η σύνταξή της είναι η παρακάτω:
CREATE [ UNIQUE ] INDEX όνομα_δείκτη
ON όνομα_πίνακα (πεδίο [ASC|DESC][, πεδίο [ASC|DESC], ...])
Η λέξη UNIQUE ορίζει πως δε θα επιτρέπονται διπλές εμφανίσεις μιας τιμής για το συγκεκριμένο δείκτη. Οι λέξεις ASC και DESC ορίζουν αύξουσα ή φθίνουσα ταξινόμηση του πίνακα με βάση το δείκτη. Παράδειγμα:
CREATE INDEX NameIdx ON Customer (Name)
Δημιουργεί το δείκτη NameIdx για τη στήλη Name στον πίνακα Customer.

Ένας δείκτης μπορεί να διαγραφεί με τη σύνταξη:

DROP INDEX όνομα_δείκτη ON όνομα_πίνακα

Προσθήκη στοιχείων

Προσθήκη δεδομένων σε έναν πίνακα γίνεται με την εντολή INSERT INTO σύμφωνα με τη σύνταξη:
INSERT INTO όνομα_πίνακα [(πεδίο1[, πεδίο2[, ...]])]
VALUES (τιμή1[, τιμή2[, ...])
Παράδειγμα:
INSERT INTO Customer (Name, AccountNum) VALUES ("Papadopoulos"1234)

Επιλογή

Επιλογή δεδομένων από έναν ή περισσότερους πίνακες γίνεται με την εντολή SELECT σύμφωνα με την παρακάτω βασική σύνταξη:
SELECT πεδία
FROM πίνακες
[ WHERE κριτήρια ]
Απλό παράδειγμα:
SELECT Name, AccountNum FROM Customer WHERE Name LIKE "Pap*"

Τα πεδία μπορούν να είναι ονόματα πεδίων ή συγκεντρωτικές συναρτήσεις της SQL πάνω σε πεδία. Τέτοιες συναρτήσεις είναι οι παρακάτω:

Avg
Μέσος όρος
Count
Μέτρηση
Min
Ελάχιστο
Max
Μέγιστο
Sum
Σύνολο
Παράδειγμα:
SELECT Sum(AccountBalance) FROM Customer
Ο αστερίσκος ως ορισμός πεδίου επιλέγει όλα τα πεδία.

Τα κριτήρια αναζήτησης είναι εκφράσεις πάνω στα πεδία. Ορισμένοι βασικοί τελεστές είναι οι:

Παράδειγμα:
SELECT * FROM Customer WHERE Balance > 10000 AND Name LIKE "Papad*"

Βασικό στοιχείο του σχεσιακού μοντέλου είναι η αποθήκευση δεδομένων σε πίνακες που σχετίζονται μεταξύ τους. Έτσι για παράδειγμα ένας πίνακας μπορεί να φυλάει τα στοιχεία των πελατών και ένας άλλος τους λογαριασμούς. Με τον τρόπο αυτό τα στοιχεία ενός πελάτη που έχει πολλούς λογαριασμούς σε μια τράπεζα φυλάσσονται μόνο μια φορά. Ανάκτηση από δύο τέτοιους πίνακες γίνεται με τον προσδιορισμό τους στο SELECT μαζί με τη χρήση της κατάλληλης έκφρασης που θα ενώσει τους πίνακες. Παράδειγμα:

SELECT Customer, Balance FROM Customers, Accounts WHERE
Customer.AccountId = Accounts.AccountId

Αλλαγή

Αλλαγή σε στοιχεία γίνεται με την εντολή UPDATE σύμφωνα με τη σύνταξη:
UPDATE όνομα_πίνακα
SET πεδίο = νέα_τιμή
WHERE κριτήρια;
Παράδειγμα:
UPDATE Accounts
SET Balance=100000
WHERE AccountId = 12233

Διαγραφή

Διαγραφή γραμμών από έναν πίνακα γίνεται με την εντολή DELETE σύμφωνα με τη σύνταξη:
DELETE 
FROM όνομα_πίνακα
WHERE κριτήρια
Παράδειγμα:
DELETE
FROM Accounts
WHERE
AccountId = 123232

SQL στη Microsoft Access

Για να δώσουμε εντολές SQL στη Microsoft Access ακολουθούμε την παρακάτω διαδικασία: Τα αποτελέσματα της εντολής μπορούμε να τα δούμε στην περιοχή Tables.

Βιβλιογραφία

Ασκήσεις

Άσκηση 8 (προαιρετική)

  1. Ορίστε με βάση SQL έναν τηλεφωνικό κατάλογο με τους φίλους σας.
  2. Εισάγετε μερικά ονόματα και τα τηλέφωνα
  3. Ανακτήστε ένα τηλέφωνο με τον τελεστή LIKE
  4. Μετρήστε τον αριθμό των φίλων σας με την έκφραση Count

Συναρτησιακός προγραμματισμός στη Lisp

Συναρτησιακός προγραμματισμός και η γλώσσα Lisp

Ο συναρτησιακός προγραμματισμός (functional programming) βασίζεται στην αποτίμηση συναρτήσεων αντί για την εκτέλεση εντολών. Προγράμματα βασισμένα στο συναρτησιακό προγραμματισμό είναι γραμμένα με βάση συναρτήσεις που αποτιμούν βασικές τιμές. Μια συναρτησιακή γλώσσα προγραμματισμού υποστηρίζει και ενθαρρύνει το συναρτησιακό προγραμματισμό.

Βασικά χαρακτηριστικά του συναρτησιακού προγραμματισμού είναι:

Ορισμένες γνωστές συναρτησιακές γλώσσες προγραμματισμού είναι οι:

Οι βασικές ιδέες της γλώσσας Lisp αναπτύχθηκαν από τον John McCarthy το 1956 σε ένα ερευνητικό πρόγραμμα για τεχνητή νοημοσύνη. Στόχος του McCarthy ήταν η ανάπτυξη μιας αλγευρικής γλώσσας επεξεργασίας λιστών για έρευνα στην τεχνητή νοημοσύνη. Ανάμεσα στα έτη 1960 και 1965 υλοποιήθηκε η διάλεκτος Lisp 1.5 η οποία τη δεκαετία του 1970 είχε οδηγήσει στις διαλέκτους MacLisp και InterLisp. Στα μέσα της δεκαετίας του 1970 οι Sussman και Steele Jr. ανέπτυξαν τη διάλεκτο Scheme με βάση ιδέες από τη σημασιολογία των γλωσσών προγραμματισμού. Στη δεκαετία του 1980 έγινε προσπάθεια για ενοποίηση των διαλέκτων της Lisp κάτω από το πρότυπο της Common Lisp.

Απλές συναρτήσεις

Παράδειγμα (ορισμός της συνάρτησης του παραγοντικού):
(defun factorial (n)
        (if (equal n 0)
        1
        (* n (factorial (- n 1)))))

Λίστες

Παράδειγμα:
'(1 2 3 4 42)
'word
'('Maria 'John 'Aliki)
Βασικές συναρτήσεις επεξεργασίας λιστών είναι οι παρακάτω:
(null list)
επιστρέφει αληθές αν η λίστα είναι κενή
(cons val list)
επιστρέφει τη λίστα list με πρώτο το στοιχείο val,
(car list)
επιστρέφει το πρώτο στοιχείο μιας λίστας,
(cdr list)
επιστρέφει τα υπόλοιπα (όλα εκτός από το πρώτο) στοιχεία μιας λίστας ή nil αν αυτά δεν υπάρχουν.
Με βάση τις συναρτήσεις αυτές μπορούμε να ορίσουμε πιο σύνθετες συναρτήσεις.

Length

Επιστρέφει το μήκος μιας λίστας.
(defun mylength (a) 
        (if (null a) 
                0 
                (+ (mylength (cdr a)) 1)))

Append

Ενώνει δύο λίστες.
(defun myappend (a b)
        (if (null a)
                b
                (cons (car a) (myappend (cdr a) b))))

Reverse

Αντιστρέφει μια λίστα.
(defun myreverse (a)
        (if (null a)
                nil
                (myappend (myreverse (cdr a)) (cons (car a) nil))))

Συναρτήσεις ανωτέρου βαθμού

Το παρακάτω παράδειγμα συνθέτουμε τις έννοιες αυτές για να ορίσουμε τις συναρτήσεις map και reduce.

Map

Η συνάρτηση map μετατρέπει μια λίστα σε μια άλλη με βάση μια συνάρτηση που έχει δοθεί ως όρισμα.
(defun mymap (f lst)
        (if (null lst) 
                nil 
                (cons (apply f (cons (car lst) nil)) (mymap f (cdr lst)))))
Έτσι μπορούμε π.χ. να διπλασιάσουμε να στοιχεία της λίστας '(1 2 3) με την κλήση:
(mymap (lambda (x) (* 2 x)) '(1 2 3))      
(2 4 6)

Reduce

Η συνάρτηση reduce συμπυκνώνει μια λίστα εφαρμόζοντας αναδρομικά τη συνάρτηση σε κάθε στοιχείο της λίστας αρχίζοντας από μια αρχική τιμή.
(defun myreduce (f v lst)
        (if (null lst) 
                v 
                (apply f (cons (car lst) (cons (myreduce f v (cdr lst)) nil)))))
Έτσι μπορούμε να ορίσουμε συναρτήσεις όπως τις:

Διερμηνευτής Lisp

Βιβλιογραφία

Ασκήσεις

Άσκηση 9

  1. Οι δύο πρώτοι αριθμοί της ακολουθίας Fibonacci είναι 0 και 1. Κάθε επόμενος αριθμός είναι το άθροισμα των δύο προηγούμενων. Γράψτε τη συνάρτηση που επιστρέφει τον νοστό αριθμό Fibonacci. (Οι αριθμοί Fibonacci απαντώνται συχνά στη φύση π.χ. στον αριθμό πετάλων των λουλουδιών και στις σπείρες από τα κουκουνάρια και τις αχιβάδες).
  2. Γράψτε τη συνάρτηση (member list val) που επιστρέφει αληθές αν η τιμή val περιέχεται στη λίστα list.

Κατηγορήματα στην Prolog

Εισαγωγή

Η προστακτικές γλώσσες προγραμματισμού όπως η Fortran, η Pascal και η C έχουν κατασκευαστεί και να διευκολύνουν τον προγραμματισμό υπολογιστών αρχιτεκτονικής von Neumann (επεξεργαστής που εκτελεί κώδικα και επεξεργάζεται δεδομένα από τη μνήμη). Ιστορικά, ο στόχος για το σχεδιασμό των γλωσσών αυτών είναι να γίνει ο προγραμματισμός του υπολογιστή προσιτός στον άνθρωπο. Έτσι, η διαδικασία της λύσης ενός προβλήματος με υπολογιστή εμφανίζεται διχοτομημένη ανάμεσα στην ανάλυση του προβλήματος και, στη συνέχεια, την κωδικοποίησή του σε υπολογιστή.

Η εξέλιξη της ισχύος των υπολογιστών και του αντίστοιχου θεωρητικού υπόβαθρου μας έχει επιτρέψει να εξετάζουμε διαφορετικές προσεγγίσεις επίλυσης προβλημάτων με υπολογιστή που να βασίζονται όχι στο να κάνουν την αρχιτεκτονική του υπολογιστή προσιτή στον άνθρωπο, αλλά στο να κάνουν τη διατύπωση των προβλημάτων από τον άνθρωπο προσιτή στον υπολογιστή. Έτσι, τόσο ο συναρτησιακός όσο και ο λογικός προγραμματισμός επιτρέπουν τη διατύπωση του προβλήματος που πρέπει να επιλυθεί με βάση έναν συμβολισμό και αφήνουν στον υπολογιστή να λύσει το πρόβλημα με βάση τη διατύπωσή του. Η βάση του λογικού προγραμματισμού μπορεί να διατυπωθεί ως εξής:

ή τη σύνοψή της από τον Bob Kowalski: Η ιστορία του λογικού προγραμματισμού και της Prolog μπορεί να συνοψιστεί από τους παρακάτω σταθμούς:
J. Robinson (1965)
Διατύπωση του αλγόριθμου της ταυτοποίησης (unification). Ο αλγόριθμος αυτός βρίσκει ένα σύνολο από στοιχειώδεις αντικαταστάσεις σε μεταβλητές για να καταστήσει δύο όρους ταυτόσημους.
R. Kowalski (1974)
Διαδικασιακή ερμηνεία προτάσεων Horn. Προτάσεις της μορφής Α αν Β1 και Β2 και ... Βν μπορούν να ερμηνευτούν διαδικασιακά ως: για να ισχύει το Α, πρέπει να ισχύει το Β1 και το Β2 και ... το Βν.
A. Colmerauer (1973)
Υλοποίηση διερμηνευτή της Prolog σε Fortran.
D. Warren (1977)
Υλοποίηση μεταγλωττιστή της Prolog (σε ενδιάμεση γλώσσα την Warren Abstract Machine).
Ιαπωνία (1981)
Πρόγραμμα υπολογιστών πέμπτης γενιάς. Έδωσε ώθηση στην Prolog υιοθετώντας το λογικό προγραμματισμό ως βασική τεχνολογία του προγράμματος.

Σχέσεις

Η βάση της Prolog είναι τα γεγονότα (facts), οι κανόνες (rules) και οι ερωτήσεις (queries). Υπάρχει μόνο μια δομή δεδομένων, ο όρος (term). Τα γεγονότα ορίζουν μια αληθή σχέση ανάμεσα σε αντικείμενα.

Παράδειγμα 1 (ορισμός σχέσεων ανάμεσα σε ακέραιούς):

sum(0, 0, 0).
sum(0, 1, 1).
sum(1, 0, 1).
sum(1, 1, 2).

Παράδειγμα 2 (ορισμός σχέσεων ανάμεσα σε ανθρώπους):

father(anthi, giannis). father(petros, giannis). father(thanasis, giannis). father(iro, giannis). father(nikos, thanasis). father(giorgos, thanasis). mother(anthi, maria). Οι λέξεις nikos, petros, anthi, στο παραπάνω παράδειγμα είναι σταθερές της γλώσσας (όπως και οι ακέραιοι στο προηγούμενο). Ονομάζονται άτομα (atoms) και γράφονται με αρχικό μικρό γράμμα.

Με βάση τα παραπάνω γεγονότα μπορούμε να εκτελέσουμε ερωτήσεις για να μάθουμε αν μια σχέση είναι αληθής ή ψευδής. Όταν η Arity Prolog βρει μια αληθή λύση στην ερώτησή μας μας εμφανίζει το σύμβολο ->. Στο σημείο εκείνο μπορούμε να πατήσουμε Enter για να δηλώσουμε πως μας ικανοποιεί η λύση, ή ; για να δηλώσουμε πως θέλουμε να δούμε και άλλες λύσεις. Παράδειγμα:

?- sum(0, 0, 0).
 ->

yes
?- sum(1, 1, 2).
 ->

yes
?- sum(1, 1, 0).

no
?- sum(5, 3, 9).

no

Λογικές μεταβλητές

Ερωτήσεις με μεταβλητές

Οι λογικές μεταβλητές στην Prolog (γράφονται με αρχικό κεφαλαίο γράμμα) μπορούν να χρησιμοποιηθούν για να τεθούν ερωτήσεις για να μάθουμε κάποιο στοιχείο από τα γεγονότα που έχουμε ορίσει. Παράδειγμα:
?- sum(1, 1, X).

X = 2
yes
?- sum(X, 1, X).

no
?- sum(X, X, 2).

X = 1
yes
?- father(iro, X).

X = giannis
yes
?- father(X, Y).

X = anthi
Y = giannis ->;

X = petros
Y = giannis ->;

X = thanasis
Y = giannis ->;

X = iro
Y = giannis ->;

X = nikos
Y = thanasis ->
yes

Γεγονότα με μεταβλητές

Με βάση τις μεταβλητές μπορούμε ακόμα να ορίσουμε και γενικευμένα γεγονότα (universal facts). Για παράδειγμα για να ορίσουμε πως όλοι αγαπούν τη Μαίρη γράφουμε:

loves(X, mairi).

Όροι

Με βάση τα παραπάνω μπορούμε να ορίσουμε τη δομή δεδομένων της Prolog, τον όρο. Οι όροι ορίζονται αναδρομικά. Ένας όρος μπορεί να είναι:

Παραδείγματα όρων:
sum(0, 0, 1)
car(color(red), engine(1600), abs, speed(172), doors(2,2,1))
loves(X, Y)

Σύζευξη

Μπορούμε να ενώσουμε ερωτήσεις μεταξύ τους με το κόμμα (,) για να απαιτήσουμε να είναι και οι δύο αληθείς:
?- sum(0, 1, 1), sum(1, 1, 2).

yes
?- sum(0, 1, 1), sum(1, 1, 3).

no
Μπορούμε να ορίσουμε ακόμα κανόνες με βάση ένα γεγονός και κάποιες σχετικές ερωτήσεις με την παρακάτω σύνταξη:
a :-
        b1,
        b2,
        bn.
Ο κανόνας διαβάζεται ως εξής: ισχύει το a αν ισχύει το b1 και το b2 και το bn. Αν ορίσουμε παραπάνω από έναν κανόνα για το ίδιο γεγονός τότε οι κανόνες ισχύουν διαζευκτικά (αρκεί να ισχύει ένας).

Παράδειγμα:


parent(X, Y) :-
        father(X, Y).
parent(X, Y) :-
        mother(X, Y).
?- parent(anthi, X).

X = giannis ->;

X = maria
yes
?- parent(X, giannis).

X = anthi ->;

X = petros ->;

X = thanasis ->;

X = iro ->;

no

Παράδειγμα:

grandfather(X, Y) :-
        parent(X, Z),
        father(Z, Y).
?- grandfather(X, Y).

X = giannis
Y = nikos ->;

X = giannis
Y = giorgos ->;

no

Αριθμητική με αναδρομή

Με βάση τον αναδρομικό ορισμό των φυσικών αριθμών μπορούμε να τους ορίσουμε αξιωματικά και στην Prolog. Ορίζουμε τον όρο s(X) να παριστάνει τον επόμενο φυσικό αριθμό από το X. Έτσι, ορίζοντας το 0 το 1 είναι s(0), το 2 είναι s(s(0)), κ.λπ. Ο κανόνας nat ορίζει τους φυσικούς αριθμούς. Μπορούμε με επαγωγή να αποδείξουμε πως όλοι οι φυσικοί παράγονται και αναγνωρίζονται από τον nat.
nat(0).
nat(s(X)) :-
	nat(X).
?- nat(0).

yes
?- nat(s(0)).

yes
?- nat(s(s(s(s(0))))).

yes
?- nat(X).

X = 0 ->;

X = s(0) ->;

X = s(s(0)) ->;

X = s(s(s(0))) ->;

X = s(s(s(s(0)))) ->

X = s(s(s(s(s(0))))) ->;

X = s(s(s(s(s(s(0)))))) ->;

X = s(s(s(s(s(s(s(0))))))) ->;

X = s(s(s(s(s(s(s(s(0)))))))) ->;

X = s(s(s(s(s(s(s(s(s(0))))))))) ->;

X = s(s(s(s(s(s(s(s(s(s(0)))))))))) ->
...
Με βάση τον ορισμό αυτό μπορούμε να ορίσουμε μια σχέση διάταξης:
less(0, X) :-
	nat(X).

less(s(X), s(Y)) :-
	less(X, Y).

?- less(s(0), 0).

no
?- less(s(0), s(s(0))).

yes
?- less(X, s(s(s(s(0))))).

 X = 0 ->;

 X = s(0) ->;

 X = s(s(0)) ->;

 X = s(s(s(0))) ->;

 X = s(s(s(s(0)))) ->;

no
Με παρόμοιο τρόπο μπορούμε να ορίσουμε και την πρόσθεση:
plus(0, X, X) :-
        nat(X).

plus(s(X), Y, s(Z)) :-
        plus(X, Y, Z).
Είναι αξιοσημείωτο πως το κατηγόρημα της πρόσθεσης με τη μορφή που έχει οριστεί μπορεί να υπολογίσει άθροισμα, διαφορά, όρους που δημιουργούν ένα άθροισμα, ή να ελέγξει αν ένα άθροισμα είναι αληθές:
?- plus(s(0), s(0), X).

X = s(s(0))
yes
?- plus(s(0), X, s(s(s(0)))).

X = s(s(0))
yes
?- plus(X, Y, s(s(0))).

X = 0
Y = s(s(0)) ->;

X = s(0)
Y = s(0) ->;

X = s(s(0))
Y = 0 ->;

no
?- plus(s(0), 0, 0).

no
Με τη χρήση του αθροίσματος μπορούμε να ορίσουμε και το γινόμενο:
times(0, X, 0)

times(s(X), Y, Z) :-
        times(X, Y, W),
        plus(W, Y, Z).
Το κατηγόρημα για το γινόμενο που έχει οριστεί με τον τρόπο αυτό έχει και αυτό παρόμοια ευελιξία:
?- times(s(s(0)), s(s(s(0))), X).

X = s(s(s(s(s(s(0))))))
yes
?- times(X, s(s(0)), s(s(s(s(0))))).

X = s(s(0)) ->.

yes
?- times(X, Y, s(s(s(s(0))))).

X = s(0)
Y = s(s(s(s(0)))) ->;

X = s(s(0))
Y = s(s(0)) ->;

?- times(s(0), s(0), s(0)).

yes

Υπολογισμοί στην Prolog

Αν και ο παραπάνω ορισμός των ακεραίων είναι αξιοθαύμαστος για τη λιτότητα και την πληρότητά του, οι υλοποιήσεις της Prolog για υπολογισμούς αριθμών χρησιμοποιούν το κατηγόρημα is(X, Y) και την υλοποίηση των αριθμών από τον επεξεργαστή του υπολογιστή. Αυτό υπολογίζει το αποτέλεσμα της αριθμητικής έκφρασης στο Y και την ταυτοποιεί με το X. Το κατηγόρημα αυτό είναι μεν αποδοτικό, δε συμπεριφέρεται όμως με την ευελιξία που δείξαμε στα παραπάνω παραδείγματα. Παράδειγμα:
?- is(X, 1 + 2).

X = 3

?- is(2, 1 + 1).

yes
?- is(Y, 3 * 8 + 4 / 5).

Y = 24.8
yes
?- is(2, X + X).

no

Λίστες

Οι λίστες στην Prolog ορίζονται με τη σύνταξη [Head | Tail] όπου Head είναι το στοιχείο που αποτελεί την κεφαλή της λίστας και Tail τα υπόλοιπα στοιχεία. Η σύνταξη αυτή αποτελεί απλώς συντομογραφία για όρους των οποίων το όνομα είναι η τελεία (.). Με βάση τον ορισμό των λιστών μπορούμε πολύ εύκολα να ορίσουμε σύνθετους αλγόριθμους όπως την ταξινόμηση quick sort:
quicksort([X|Xs], Ys) :-
        partition(Xs, X, L, B),
        quicksort(L, Ls),
        quicksort(B, Bs),
        append(Ls, [X | Bs], Ys).

quicksort([], []).

partition([X|Xs], Y, [X|Ls], Bs) :-
        X =< Y,
        partition(Xs, Y, Ls, Bs).

partition([X|Xs], Y, Ls, [X|Bs]) :-
        X > Y,
        partition(Xs, Y, Ls, Bs).

partition([], Y, [], []).

append([], X, X).

append([H | T], X, [H | Z]) :-
        append(T, X, Z).

?- quicksort([4,2,8,12,1,7], X).

X = [1,2,4,7,8,12] ->.

yes

Συμβολική επεξεργασία

Με βάση μεταβλητές και όρους μπορούμε να γράψουμε ένα πρόγραμμα που να αναγνωρίζει πολυώνυμα ορισμένα με τον παρακάτω τρόπο:
polynomial(X, X).

polynomial(N, X) :-
        number(N).

polynomial(sum(A, B), X) :-
        polynomial(A, X),
        polynomial(B, X).

polynomial(diff(A, B), X) :-
        polynomial(A, X),
        polynomial(B, X).

polynomial(prod(A, B), X) :-
        polynomial(A, X),
        polynomial(B, X).

polynomial(quot(A, B), X) :-
        polynomial(A, X),
        number(B).

polynomial(pow(A, B), X) :-
        polynomial(A, X),
        integer(B).
Ερωτήσεις:
?- p(sum(x, 1), x).
 ->.

yes
?- p(sum(x, 1), y).

no
?- polynomial(sum(prod(5,pow(x, 2)), prod(2, x)), x).
 ->.

yes
?- polynomial(sum(prod(5,pow(x, 2)), pow(y, 3)), x).

no
?- polynomial(sum(prod(5,pow(x, 2.1)), prod(2, x)), x).

no

Σύντομες οδηγίες για τη χρήση της Arity Prolog

Βιβλιογραφία

Ασκήσεις

Άσκηση 10

  1. Ορίστε σε Prolog το κατηγόρημα
    polynomial_value(P, X, V).
    
    όπου:
    • Pείναι ένα πολυώνυμο ορισμένο με βάση τους όρους:
      • sum(A, B) (άθροισμα)
      • diff(A, B) (διαφορά)
      • prod(A, B) (γινόμενο)
      • pow(A, B) (ύψωση σε δύναμη)
      • τη μεταβλητή x (προσοχή, μικρό γράμμα)
      • καθώς και ακεραίους.
    • X η τιμή της μεταβλητής x
    • V η τιμή του πολυωνύμου.

Παράδειγμα:

?- polynomial_value(prod(5, pow(x, 3)), 3, X).

X = 135 ->

yes
?- polynomial_value(sum(prod(5, pow(x, 3)), prod(x, 5)), 4, X).

X = 340 ->.

yes
Την ύψωση ενός αριθμού σε δύναμη μπορείτε να την ορίσετε ως εξής:
raise(X, 0, 1).
raise(X, N, Y) :-
        N2 is N - 1,
        raise(X, N2, K),
        Y is K * X.

Ταυτοχρονισμός στα Windows NT

Παραλληλία σε επίπεδο υλικού

Παραλληλία με τη χρήση αγωγών

Παραδείγματα

Ανάγνωση αποτελεσμάτων
υπολογισμός |
μετατροπή σε λέξη |
φωνητική ανάγνωση

bc | 
number | 
speak
Συχνότητα λέξεων
Μετατροπή κειμένου σε λέξεις |
φιλτράρισμα λέξεων |
ταξινόμηση |
μέτρηση κοινών γραμμών |
ταξινόμηση

tr -cs A-Za-z '\012' | 
grep '^A-Za-z' | 
sort | 
uniq -c | 
sort -rn

Συνθήκες ανταγωνισμού

Συνθήκες ανταγωνισμού ονομάζονται οι συνθήκες κατά τις οποίες το τελικό αποτέλεσμα της εκτέλεσης μιας σειράς διεργασιών κάτω από αυτές εξαρτάται από τη σειρά εκτέλεσής τους.

Παράδειγμα

int counter;

process_one()
{
        int i;

        i = counter;
        i = i + 1;
        counter = i;
}

process_two()
{
        int i;

        i = counter;
        i = i + 1;
        counter = i;
}

Κρίσιμα τμήματα

Το τμήμα του κώδικα μιας διεργασίας το οποίο δεν είναι ασφαλές να εκτελεστεί παράλληλα με άλλη διεργασία ονομάζεται κρίσιμο τμήμα (critical section).

Κάθε λύση στο πρόβλημα του αμοιβαίου αποκλεισμού (mutual exclusion) μεταξύ των κρίσιμων τμημάτων πρέπει να εξασφαλίζει:

  1. Μια το πολύ διεργασία μπορεί να εκτελεί ένα κρίσιμο τμήμα.
  2. Δεν επιτρέπονται υποθέσεις σχετικά με την ταχύτητα ή το πλήθος των επεξεργαστών.
  3. Διεργασία που δε βρίσκεται σε κρίσιμο τμήμα δεν επιτρέπεται να αναστείλει άλλες διεργασίες
  4. Ο χρόνος στον οποίο μια διεργασία θα εισέλθει στο κρίσιμο τμήμα της πρέπει να είναι πεπερασμένος.

Ενεργός αναμονή

Ορισμένοι τρόποι για την επίτευξη του αμοιβαίου αποκλεισμού είναι οι παρακάτω: Όλες οι παραπάνω λύσεις επιβάλουν στη διαδικασία που αναμένει να εκτελεί εντολές.

Απενεργοποίηση και αφύπνιση

Η οργάνωση της διαδιεργασιακής επικοινωνίας γίνεται χρησιμοποιώντας αφηρημένες βασικές αρχές διαδιεργασιακής επικοινωνίας (interprocess communication primitives). To ζεύγος είναι μια τέτοια αρχή. Με τις κλήσεις αυτές μπορεί να εκφραστεί το πρόβλημα του παραγωγού-καταναλωτή (producer-consumer).

const N = 100;

int queue[N];
int count;

void
producer(void)
{
        int item;

        for (;;) {
                create(&item);
                if (count == N)
                        sleep();
                count = count + 1;
                queue[count - 1] = item;
                if (count == 1)
                        wakeup(consumer);
        }
}

void
consumer(void)
{
        int item;

        for (;;) {
                if (count == 0)
                        sleep();
                item = queue[count - 1];
                count = count - 1;
                if (count == N - 1)
                        wakeup(producer);
                consume(&item);
        }
}
Η παραπάνω λύση έχει πρόβλημα συνθήκης ανταγωνισμού ως προς τη χρήση της μεταβλητής count.

Σημαφόροι

To ζεύγος των σημαφόρων (semaphores) επιτρέπει την οργάνωση της διαδιεργασιακής επικοινωνίας μια και αυτοί εκφράζουν ατομικές ενέργειες (atomic action). Με τις κλήσεις αυτές μπορεί να λυθεί το πρόβλημα του παραγωγού-καταναλωτή.
const N = 100;

semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;

void
producer(void)
{
        int item;

        for (;;) {
                create(&item);
                signal(&empty);

                signal(&mutex);
                count = count + 1;
                queue[count - 1] = item;
                wait(&mutex)

                wait(&full);
        }
}

void
consumer(void)
{
        int item;

        for (;;) {
                signal(&full);

                signal(&mutex);
                item = queue[count - 1];
                count = count - 1;
                wait(&mutex);

                wait(&empty);
                consume(&item);
        }
}

Παρακολουθητές

Οι παρακολουθητές (monitors) (ή ελεγκτές) είναι δομή της γλώσσας προγραμματισμού που εξασφαλίζει ότι σε μια συγκεκριμένη χρονική στιγμή μπορεί να εκτελείται. το πολύ μια διαδικασία μέσα στον παρακολουθητή.

Η δομή αυτή εξασφαλίζει τον αμοιβαίο αποκλεισμό κρισίμων τμημάτων. H αναστολή διεργασιών που δεν μπορούν να συνεχίσουν εξασφαλίζεται με τη χρήση των μεταβλητών συνθήκης (condition variables)

Μεταβίβαση μηνύματος

Άλλος τρόπος διαδιεργασιακής επικοινωνίας είναι η χρήση της μεταβίβαση μηνύματος (message passing) που μπορεί να εκφραστεί με τις διαδικασίες: Ο συντονισμός της αποστολής και της παραλαβής μπορεί να γίνει: Με τις κλήσεις αυτές μπορεί να εκφραστεί το πρόβλημα του παραγωγού-καταναλωτή.

const N = 100;

int queue[N];

void
producer(void)
{
        int item;
        message m;

        for (;;) {
                create(&item);
                receive(consumer, &m);
                build_message(item, &m);
                send(consumer, &m);
        }
}

void
consumer(void)
{
        int item;
        message m;

        for (i = 0;  i < 100; i++)
                send(producer, &m);
        for (;;) {
                receive(producer, &m);
                extract_item(&m, &item);
                send(producer, &m);
                consume(&item);
        }
}

Κλασικά προβλήματα

To πρόβλημα των συνδαιτημόνων φιλοσόφων

Λύσεις (σωστές και λανθασμένες):

Ταυτοχρονισμός στα Windows NT

Νηματικές διεργασίες

Οι νηματικές διεργασίες (threads) είναι ένας τρόπος ταυτοχρονισμού στον οποίο οι διεργασίες μοιράζονται τα όλα τα δεδομένα εκτός από αυτά που υπάρχουν στη στοίβα (δηλ. τις τοπικές μεταβλητές). Η δημιουργία τους υποστηρίζεται συνήθως από το λειτουργικό σύστημα και το μεταγλωττιστή. Στα Windows NT μπορούμε να δημιουργήσουμε πολλαπλές νηματικές διεργασίες με τη συνάρτηση _beginthread Χρησιμοποιείται ως εξής:
void
thread_function(int *parameter)
{
}

/* Used to pass a parameter to the thread */
int parameter_value;

main()
{
        parameter = 42;
        _beginthread(thread_function, 0, &parameter);
}
Ένα νήμα μπορεί να τερματίσει τη λειτουργία του με τη συνάρτηση _endthread.

Σημαφόροι

Στα Windows 32 (95/98/NT/2000/...) μια σειρά από συναρτήσεις επιτρέπουν το χειρισμό σημαφόρων. Παράδειγμα αποκλεισμού περιοχής:
HANDLE    mutex= CreateMutex(NULL, FALSE, NULL);

WaitForSingleObject(mutex, INFINITE);
/* Critical work starts here */
fseek(fp, desired_position, 0L );
fwrite(data, sizeof( data ), 1, fp );
/* Critical work ends here */
ReleaseMutex(mutex);

Μεταγλώττιση

Δηλώσεις για τις συναρτήσεις που αναφέραμε στις προηγούμενες παραγράφους υπάρχουν στην επικεφαλίδα <windows.h> και <process.h>. Για να μεταγλωττίσουμε ένα πρόγραμμα που περιέχει νηματικές διεργασίες προσθέτουμε στην εντολή του μεταγλωττιστή τις παραμέτρους /D_MT /MT . Παράδειγμα:
cl -D_MT /MT test.c

Παράδειγμα εφαρμογής

Το παρακάτω πρόγραμμα, bounce.c (βασισμένο σε πρόγραμμα που περιέχεται ως παράδειγμα στην τεκμηρίωση της Microsoft Visual C++) δημιουργεί ένα νέο νήμα που κινεί ένα πρόσωπο στην οθόνη κάθε φορά που πληκτρολογούμε A ή a. Τερματίζει τη λειτουργία του όταν πληκτρολογήσουμε q ή Q.
/*
 * Bounce - Creates a new thread each time the letter 'a' is typed. Each
 * thread bounces a happy face of a different color around the screen. All
 * threads are terminated when the letter 'Q' is entered. 
 *
 * This program requires the multithread library. For example, compile with the
 * following command line: CL /MT BOUNCE.C 
 */

#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <process.h>

const int MAX_THREADS=32;

/*
 * getrandom returns a random number between min and max, which must be in
 * integer range. 
 */
static int
getrandom(int min, int max)
{
        return ((rand() % (int)(((max) + 1) - (min))) + (min));
}

void main(void);                        /* Thread 1: main */
void KbdFunc(void);                     /* Keyboard input, thread dispatch */
void BounceProc(char *MyID);            /* Threads 2 to n: display */
void ClearScreen(void);                 /* Screen clear */
void ShutDown(void);                    /* Program shutdown */
void WriteTitle(int ThreadNum);         /* Display title bar information */

HANDLE          hConsoleOut;    /* Handle to the console */
HANDLE          hRunMutex;      /* "Keep Running" mutex */
HANDLE          hScreenMutex;   /* "Screen update" mutex  */
int             ThreadNr;       /* Number of threads started */
CONSOLE_SCREEN_BUFFER_INFO csbiInfo;    /* Console information */


void 
main()
{                               /* Thread One */
        /* Get display screen information & clear the screen. */
        hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE);
        GetConsoleScreenBufferInfo(hConsoleOut, &csbiInfo);
        ClearScreen();
        WriteTitle(0);
        /* Create the mutexes and reset thread count. */
        hScreenMutex = CreateMutex(NULL, FALSE, NULL);  /* Cleared */
        hRunMutex = CreateMutex(NULL, TRUE, NULL);      /* Set */
        ThreadNr = 0;

        /* Start waiting for keyboard input to dispatch threads or exit. */
        KbdFunc();

        /* All threads done. Clean up handles. */
        CloseHandle(hScreenMutex);
        CloseHandle(hRunMutex);
        CloseHandle(hConsoleOut);
}

/*
 * Finish processing
 */
void 
ShutDown(void)
{                               /* Shut down threads */
        while (ThreadNr > 0) {
                /* Tell thread to die and record its death. */
                ReleaseMutex(hRunMutex);
                ThreadNr--;
        }
        /* Clean up display when done */
        WaitForSingleObject(hScreenMutex, INFINITE);
        ClearScreen();
}

/*
 * Read an process keyboard commands
 */
void 
KbdFunc(void)
{                               /* Dispatch and count threads. */
        int             KeyInfo;

        do {
                KeyInfo = _getch();
                if (tolower(KeyInfo) == 'a' && ThreadNr < MAX_THREADS) {
                        ThreadNr++;
                        _beginthread(BounceProc, 0, &ThreadNr);
                        WriteTitle(ThreadNr);
                }
        } while (tolower(KeyInfo) != 'q');

        ShutDown();
}

/*
 * Bounce the face around the screen.
 * This procedure is run by each thread.
 */
void 
BounceProc(char *MyID)
{
        char            MyCell, OldCell;
        WORD            MyAttrib, OldAttrib;
        char            BlankCell = 0x20;
        COORD           Coords, Delta;
        COORD           Old = {00};
        DWORD           Dummy;

        /* Generate update increments and initial display coordinates. */
        srand((unsigned) *MyID * 3);
        Coords.X = getrandom(0, csbiInfo.dwSize.X - 1);
        Coords.Y = getrandom(0, csbiInfo.dwSize.Y - 1);
        Delta.X = getrandom(-33);
        Delta.Y = getrandom(-33);

        /* Set up "happy face" & generate color attribute from thread number. */
        if (*MyID > 16)
                MyCell = 0x01;  /* outline face */
        else
                MyCell = 0x02;  /* solid face */
        MyAttrib = *MyID & 0x0F;/* force black background */

        do {
                /* Wait for display to be available, then lock it. */
                WaitForSingleObject(hScreenMutex, INFINITE);

                /* If we still occupy the old screen position, blank it out. */
                ReadConsoleOutputCharacter(hConsoleOut, &OldCell, 1, Old, &Dummy);
                ReadConsoleOutputAttribute(hConsoleOut, &OldAttrib, 1, Old, &Dummy);
                if ((OldCell == MyCell) && (OldAttrib == MyAttrib))
                        WriteConsoleOutputCharacter(hConsoleOut, &BlankCell, 1, Old, &Dummy);

                /* Draw new face, then clear screen lock */
                WriteConsoleOutputCharacter(hConsoleOut, &MyCell, 1, Coords, &Dummy);
                WriteConsoleOutputAttribute(hConsoleOut, &MyAttrib, 1, Coords, &Dummy);
                ReleaseMutex(hScreenMutex);

                /* Increment the coordinates for next placement of the block. */
                Old.X = Coords.X;
                Old.Y = Coords.Y;
                Coords.X += Delta.X;
                Coords.Y += Delta.Y;

                /* If we are about to go off the screen, reverse direction */
                if (Coords.X < 0 || Coords.X >= csbiInfo.dwSize.X) {
                        Delta.X = -Delta.X;
                        Beep(40050);
                }
                if (Coords.Y < 0 || Coords.Y > csbiInfo.dwSize.Y) {
                        Delta.Y = -Delta.Y;
                        Beep(60050);
                }
        }
        /* Repeat while RunMutex is still taken. */
        while (WaitForSingleObject(hRunMutex, 75L) == WAIT_TIMEOUT);

}

void 
WriteTitle(int ThreadNum)
{
        char            NThreadMsg[80];

        sprintf(NThreadMsg, "Threads running: %02d.  Press 'A' to start a thread,'Q' to quit.", ThreadNum);
        SetConsoleTitle(NThreadMsg);
}

void 
ClearScreen(void)
{
        DWORD           dummy;
        COORD           Home = {00};
        FillConsoleOutputCharacter(hConsoleOut, ' ', csbiInfo.dwSize.X * csbiInfo.dwSize.Y, Home, &dummy);
}

Βιβλιογραφία

Κατανεμημένος προγραμματισμός

Εισαγωγή

Σημείωση
Οι σημειώσεις της ενότητας αυτής έχουν γραφεί από τον υποψήφιο διδάκτορα του Τμήματος Πληροφοριακών και Επικοινωνιακών Συστημάτων του Πανεπιστημίου Αιγαίου, Κωνσταντίνο Ράπτη (mailto:krap@aegean.gr).

Πριν δύο δεκαετίες, η φύση των υπολογιστικών συστημάτων εκφράζονταν με την ύπαρξη ισχυρών κεντρικών υπολογιστικών συστημάτων (mainframes) στα οποία ήταν εγκατεστημένες εφαρμογές οι οποίες μπορούσαν να προσπελαστούν από τους χρήστες μέσα από τα τερματικά του συστήματος.

Με την εμφάνιση και την ανάπτυξη των προσωπικών υπολογιστών και καθώς οι εφαρμογές άρχισαν να γίνονται ολοένα και πιο μεγάλες και σύνθετες, άρχισαν να μεταμορφώνονται από ενιαία προγράμματα σε κομμάτια ικανά να συνεργάζονται μεταξύ τους. Στην διάσπαση των εφαρμογών σε περισσότερα από ένα μέρη, συνέβαλλε η ανάπτυξη της αρχιτεκτονικής πελάτη/εξυπηρετητή (client/server) βάσει της οποίας γινόταν η υλοποίησή τους. Κατά την αρχιτεκτονική πελάτη/εξυπηρετητή, μία διεργασία καλείται πελάτης (client process) όταν αιτείται την υλοποίηση κάποιων υπηρεσιών-μεθόδων από μία διεργασία η οποία είναι ικανή να της προσφέρει τις επιθυμητές υπηρεσίες. Η τελευταία αυτή διεργασία καλείται διεργασία του εξυπηρετητή (server process).

Αργότερα με την ανάπτυξη των υπολογιστικών δικτύων τα συνθετικά μέρη των εφαρμογών, προκειμένου να υπάρξει καλύτερη και αποτελεσματικότερη εκμετάλλευση των νέων δυνατοτήτων, άρχισαν να διαμοιράζονται στους υπολογιστές του δικτύου. Με τον τρόπο αυτό αυξανόταν η απόδοση και εκμετάλλευση των πόρων του δικτύου. Αυτού του είδους οι εφαρμογές ονομάζονται κατανεμημένες εφαρμογές (distributed applications).

Η δυνατότητα διασύνδεσης, σε δίκτυα, υπολογιστικών συστημάτων διαφορετικής αρχιτεκτονικής είχε σαν αποτέλεσμα την δημιουργία ενός ανομοιογενούς υπολογιστικού περιβάλλοντος. Θα έπρεπε, λοιπόν, και οι εφαρμογές να μπορέσουν να εξελιχθούν έτσι ώστε να είναι δυνατή η λειτουργία τους σε τέτοια ετερογενή συστήματα υπολογιστών. Αυτή η εξέλιξη οδήγησε στην εμφάνιση των ετερογενών κατανεμημένων εφαρμογών (heterogeneous distributed applications).

Λογισμικό βασισμένο σε εξαρτήματα

Καθώς όμως η ανάπτυξη του υλικού (hardware) είχε ως αποτέλεσμα την δημιουργία όλο και πιο ισχυρών, ταχύτερων, μικρότερων και ταυτόχρονα φθηνότερων προϊόντων, το λογισμικό εξακολουθούσε να είναι όλο και πιο σύνθετο, πολύπλοκο και ακριβό. Για να ξεπεραστεί αυτή η αδυναμία του λογισμικού θα έπρεπε να υπάρχει η δυνατότητα κάθε νέο λογισμικό να μην είναι απαραίτητο να αναπτυχθεί από την αρχή αλλά να δίνετε η δυνατότητα να προστίθενται νέα κομμάτια λογισμικού τα οποία θα μπορούσαν άμεσα να χρησιμοποιηθούν μέσα από το λογισμικό. Αυτή η εξέλιξη οδήγησε στην ανάπτυξη της τεχνολογίας των εξαρτημάτων λογισμικού (software components technology). Η νέα αυτή τεχνολογία έχει ως σκοπό να επιτρέψει την χρησιμοποίηση κομματιών λογισμικού τα οποία θα είναι κατασκευασμένα από διαφορετικούς κατασκευαστές, θα είναι υλοποιημένα σε διαφορετικές γλώσσες προγραμματισμού και θα τρέχουν κάτω από διαφορετικά λειτουργικά συστήματα.

Τεχνολογίες διαλειτουργικότητας κομματιών λογισμικού

Συνοψίζοντας τα παραπάνω, μπορούμε τα πούμε ότι τα βασικά στοιχεία που χαρακτηρίζουν ένα σύγχρονο υπολογιστικό περιβάλλον είναι τα εξής: Με βάση τα παραπάνω στοιχεία η χρησιμοποίηση μοντέλων-μεθοδολογιών τα οποία θα διευκολύνουν την λειτουργία των εφαρμογών κάτω από τις συνθήκες ενός ετερογενούς περιβάλλοντος και θα επιτελούν τις λειτουργίες επικοινωνίας με τρόπο τέτοιο έτσι ώστε να είναι διαφανής προς τους τελικούς χρήστες, είναι επιβεβλημένη και η υιοθέτησή τους αποτελεί πλέον ζήτημα για τους οργανισμούς και τις επιχειρήσεις οι οποίες συντηρούν τέτοιου είδους υπολογιστικά συστήματα και δίκτυα.

Τρία είναι τα βασικά μοντέλα των οποίων σκοπός είναι η υλοποίηση των παραπάνω και αυτά είναι:

Και τα τρία αυτά μοντέλα βασίζονται πάνω στις ίδιες αρχές (και οι τρεις τεχνολογίες λειτουργούν βασιζόμενες στην έννοια του αντικειμένου) και εξυπηρετούν τον ίδιο σκοπό. Κάθε ένα από αυτά έχει όμως τις δικές του ιδιαιτερότητες από τις οποίες κρίνονται και επιλέγονται.

OMG CORBA

Η OMG (Object Management Group) αναπτύσσει πρότυπα βασιζόμενη στην τεχνολογία του αντικειμένου.

Σύμφωνα με την αρχιτεκτονική που προτείνεται από την OMG, την OMA (Object Management Architecture), το κάθε κομμάτι λογισμικού παρουσιάζεται σαν αντικείμενο (Object).

Τα διάφορα αντικείμενα επικοινωνούν μεταξύ τους μέσω του ORB (Object Request Broker), ο οποίος αποτελεί το στοιχείο-κλειδί γι' αυτήν την επικοινωνία. Ο ORB παρέχει τους μηχανισμούς εκείνους μέσω των οποίων τα αντικείμενα κάνουν τις αιτήσεις και συλλέγουν τις αποκρίσεις.

Η CORBA (Common Object Request Broker Architecture) αποτελεί το πρότυπο της OMG για τον ORB.

Κατά την CORBA, ένας πελάτης ο οποίος ζητάει κάποιες υπηρεσίες από κάποιο αντικείμενο, κάνει μία αίτηση (request) η οποία μεταβιβάζεται στον ORB και ο οποίος είναι στη συνέχεια υπεύθυνος για την προώθηση της αίτησης προς τον κατάλληλο εξυπηρετητή.

Η αίτηση αυτή περιέχει όλες τις απαραίτητες πληροφορίες που απαιτούνται προκειμένου να υλοποιηθεί και αυτές είναι:

Για να είναι κατανοητή όμως η αίτηση θα πρέπει να έχει μία κατάλληλη μορφή και γι' αυτό το λόγο γίνεται προς τον ORB μέσω διασυνδετών (interfaces: "Dynamic Invocation" και "IDL Stubs").

Για τον ίδιο λόγο η προώθηση της αίτησης προς τον εξυπηρετητή γίνεται μέσω διασυνδετών ("Static IDL Skeleton" και "IDL Skeleton").

Ένας διασυνδέτης αποτελεί μία περιγραφή ενός συνόλου από πιθανές λειτουργίες τις οποίες ένας πελάτης μπορεί να αιτηθεί ενός αντικειμένου.

Ένα αντικείμενο ικανοποιεί έναν διασυνδέτη εάν μπορεί να προσδιοριστεί σαν το αντικείμενο-στόχος (target object) για κάθε δυνατή αίτηση η οποία περιγράφεται από τον διασυνδέτη.

Στο σημείο αυτό δεν θα πρέπει να αγνοήσουμε την ιδιότητα της κληρονομικότητας που χαρακτηρίζει τους διασυνδέτες και η οποία παρέχει τον συνθετικό εκείνο μηχανισμό που επιτρέπει σ' ένα αντικείμενο να υποστηρίζει πολλαπλούς διασυνδέτες.

Οι διασυνδέτες καθορίζονται πλήρως μέσω της OMG IDL (Interface Definition Language). Η IDL δεν αποτελεί γλώσσα προγραμματισμού, όπως οι γνωστές γλώσσες προγραμματισμού, αλλά μία καθαρά περιγραφική γλώσσα η οποία δεν περιλαμβάνει δομές αλγορίθμων ή μεταβλητές. Η γραμματική της αποτελεί υποσύνολο της ANSI C++ με πρόσθετες κατασκευές για την υποστήριξη μηχανισμών για την επίκληση απομακρυσμένων λειτουργιών.

Οι πελάτες δεν είναι "γραμμένοι" σε OMG IDL αλλά σε γλώσσα για την οποία έχει προσδιοριστεί η αντιστοιχία της με την OMG IDL.

Microsoft COM/DCOM

Το δεύτερο μοντέλο βασίζεται και αυτό στην τεχνολογία του αντικειμένου με την έννοια όμως εδώ των αυτόνομων συνθετικών εξαρτημάτων μίας εφαρμογής.

Και εδώ, ο βασικός σκοπός του μοντέλου είναι να καταστήσει δυνατή την επικοινωνία δύο ή περισσοτέρων εφαρμογών ή συνθετικών, ακόμα και στην περίπτωση που αυτά διαφέρουν ως προς την προέλευση, τη γλώσσα προγραμματισμού, τα μηχανήματα στα οποία βρίσκονται και τα λειτουργικά συστήματα κάτω από τα οποία τρέχουν.

Ο "μηχανισμός λειτουργίας του μοντέλου είναι παρόμοιος με αυτόν της CORBA. Βασικό ρόλο παίζουν οι διασυνδέτες τα οποία δεν είναι τίποτα άλλο από ένα σαφές διατυπωμένο "συμβόλαιο" μεταξύ των "κομματιών" λογισμικού, το οποίο περιέχει ένα σύνολο από σχετικές λειτουργίες. Όταν λέμε ότι ένα αντικείμενο υλοποιεί έναν διασυνδέτη αυτό σημαίνει ότι το αντικείμενο αυτό υλοποιεί κάθε λειτουργία του.

Πως γίνεται, όμως, η διαδικασία της αιτήσεως κάποιων λειτουργιών από έναν πελάτη;

Για να μπορέσει ένας πελάτης να εξυπηρετηθεί από κάποιο αντικείμενο, θα πρέπει η γλώσσα προγραμματισμού, στην οποία είναι υλοποιημένος, να έχει τη δυνατότητα δημιουργίας δεικτών και να καλεί συναρτήσεις μέσω των δεικτών. Θα πρέπει λοιπόν ο πελάτης να έχει τον κατάλληλο δείκτη προς τον επιθυμητό διασυνδέτη τον οποίο περιέχει τις επιθυμητές από τον πελάτη λειτουργίες.

Στην περίπτωση όμως που ο πελάτης δεν έχει τον κατάλληλο δείκτη προς το επιθυμητό διασυνδέτη, τότε απευθύνεται προς την COM δίνοντας σαν στοιχεία την ταυτότητα της κλάσης του εξυπηρετητή που επιθυμεί ο πελάτης και τον τύπο του, δηλαδή εάν είναι:

Η COM τότε αναλαμβάνει να βρει τον επιθυμητό εξυπηρετητή και να επιστρέψει στον πελάτη τον επιθυμητό δείκτη.

Όπως έγινε σαφές, ο κάθε πελάτης μπορεί να είναι υλοποιημένος σε οποιαδήποτε γλώσσα προγραμματισμού, αρκεί αυτή να υποστηρίζει την κατασκευή και την χρήση δεικτών. Για τα "interfaces", όμως, υπάρχει και εδώ μία "IDL" (interface Definition Language) η οποία επιτρέπει και βοηθάει τους σχεδιαστές να κατασκευάζουν διασυνδέτες.

Sun Java RMI

Το μοντέλο RMI της Sun, βασίζεται και αυτό στην τεχνολογία του αντικειμένου και είναι σχεδιασμένο για να προάγει την διαλειτουργικότητα μεταξύ αντικειμένων, κατασκευασμένων σε Java, μέσα σε ένα κατανεμημένο και ετερογενές περιβάλλον.

Η τεχνολογία RMI αφορά αποκλειστικά αντικείμενα τα οποία είναι κατασκευασμένα με τη γλώσσα προγραμματισμού Java. Αυτό έχει ως αποτέλεσμα να δίνει την δυνατότητα της πλήρους εκμετάλλευσης των δυνατοτήτων της Java αλλά και το πλεονέκτημα του ομοιογενούς, ως προς τη γλώσσα προγραμματισμού, περιβάλλοντος.

Η αρχιτεκτονική ενός RMI συστήματος ακολουθεί την δομή των στρωμάτων-επιπέδων (layers). Υπάρχουν τρία (συν ένα) επίπεδα:

Υπάρχει, επίσης, το επίπεδο της Εφαρμογής (Application) το οποίο όμως δεν αποτελεί καθαρό κομμάτι της τεχνολογίας και το οποίο βρίσκεται πάνω απΤ τα υπόλοιπα.

Η διαδικασία επίκλησης κάποιων υπηρεσιών από έναν πελάτη, δεν διαφέρει, ιδιαίτερα, σε σχέση με τις προαναφερθείσες τεχνολογίες. Ένας πελάτης, ο οποίος επιθυμεί την υλοποίηση κάποιων υπηρεσιών, υποβάλλει μία αίτηση προς το RMI-σύστημα. Στην αίτηση αυτή θα πρέπει να αναφέρονται οι κατάλληλες εκείνες πληροφορίες οι οποίες απαιτούνται για την αποστολή της αίτησης προς τον κατάλληλο εξυπηρετητή. Αυτές οι πληροφορίες-παράμετροι είναι: μία αναφορά του επιθυμητού αντικειμένου (object reference), οι επιθυμητές μέθοδοι και οι κατάλληλες παράμετροι για την υλοποίηση των μεθόδων.

Από τη στιγμή που ο πελάτης καταθέσει την αίτησή του, το RMI-σύστημα είναι υπεύθυνο για την ανεύρεση του κατάλληλου εξυπηρετητή, την μεταβίβαση της αιτήσεων και την επιστροφή των αποτελεσμάτων στον πελάτη.

Όμοια με την CORBA και την COM/DCOM, σε ένα RMI-σύστημα, οι πελάτες δεν έρχονται ποτέ σε απευθείας επικοινωνία με τα επιθυμητά αντικείμενα παρά μόνο μέσω των διασυνδετών αυτών των αντικειμένων. Και εδώ ο διασυνδέτης είναι αυτός που περιέχει τις μεθόδους-υπηρεσίες τις οποίες μπορεί να υλοποιήσει το αντικείμενο.

Η επικοινωνία πελάτη-αντικειμένου, σΤ ένα σύστημα RMI, είναι η ίδια ανεξάρτητα με το που βρίσκεται το επιθυμητό αντικείμενο (αν δηλαδή βρίσκεται στην ίδια διεργασία με τον πελάτη, αν βρίσκεται τοπικά ή απομακρυσμένα).

Πηγές για έρευνα στο Internet

Βιβλιογραφία

Θέματα εξετάσεων και άλλες σχετικές πληροφορίες

Εξεταστική περιόδος Ιουνίου 2000

ΠΑΝΕΠΙΣΤΗΜΙΟ ΑΙΓΑΙΟΥ

Τμήμα Πληροφοριακών και Επικοινωνιακών Συστημάτων

Γλώσσες προγραμματισμού

Διδάσκων: Επικ. Καθηγητής Διομήδης Σπινέλλης

Εξεταστική περίοδος

Ιουνίου 2000

Μια οικολογική οργάνωση σας ζητά να υλοποιήσετε ένα πρόγραμμα για την παρακολούθηση της πανίδας της Ελλάδας. Το πρόγραμμα θα περιλαμβάνει στοιχεία για κάθε ξεχωριστό οργανισμό (π.χ. έναν συγκεκριμένο αετό). Τα στοιχεία αυτά είναι: κωδικός αριθμός, γεωγραφικές συντεταγμένες που έχει εμφανιστεί τελευταία φορά (γεωγραφικό μήκος και πλάτος) και συνολική απόσταση που εμφανίζεται να έχει διανύσει. Η συνολική απόσταση πρέπει να ενημερώνεται αυτόματα κάθε φορά που καταχωρούνται νέες συντεταγμένες. Επίσης, για κάθε κατηγορία οργανισμών (π.χ. για όλους τους αετούς) πρέπει να τηρείται αυτόματα ο αριθμός των οργανισμών που παρακολουθούνται. Ειδικά για τους θαλάσσιους οργανισμούς θα φυλάσσεται το μέγιστο βάθος στο οποίο έχει εμφανιστεί ένας συγκεκριμένος οργανισμός και για τα πτηνά το αντίστοιχο μέγιστο ύψος. Επίσης, για κάθε δελφίνι θα πρέπει να φυλάσσεται ο αριθμός των παιδιών του.

Θέμα 1ο: (2.5 βαθμοί)

Σχεδιάστε σε UML ένα διάγραμμα κλάσεων για το παραπάνω πρόγραμμα. Στο διάγραμμα πρέπει να φαίνονται οι ιδιότητες καθώς και μέθοδοι που εσείς κρίνετε απαραίτητες. Σχεδιάστε ένα αντιπροσωπευτικό διάγραμμα αντικειμένων.

Θέμα 2ο: (2.5 βαθμοί)

Να γράψετε σε C++ μια κλάση που να υλοποιεί τις γεωγραφικές συντεταγμένες. Η κλάση να επανακαθορίζει τον τελεστή - έτσι ώστε να επιστρέφει την απόσταση ανάμεσα σε δύο σημεία. Σημείωση: θεωρήσετε πως οι γεωγραφικές συντεταγμένες καταχωρούνται σε μέτρα και πως η απόσταση μεταξύ δύο σημείων μπορεί να υπολογιστεί ως η υποτείνουσα του αντίστοιχου τριγώνου.

Θέμα 3ο: (2.5 βαθμοί)

Γράψτε σε C++ τις δηλώσεις για τις κλάσεις που απαιτούνται για το πρόγραμμα παρακολούθησης της πανίδας καθώς και τους ορισμούς των συναρτήσεων κατασκευής και της συνάρτησης που ενημερώνει τις συντεταγμένες της τελευταίας εμφάνισης. Αν τα στοιχεία για τους οργανισμούς φυλάσσονται σε έναν πίνακα με δείκτες στα αντίστοιχα αντικείμενα, γράψτε μια συνάρτηση που να επιστρέφει τη συνολική απόσταση που έχει διανυθεί από όλους τους καταχωρημένους οργανισμούς.

Θέμα 4ο: (2.5 βαθμοί)

Τα παρακάτω κατηγορήματα της Prolog ορίζουν αναδρομικά μια οικογένεια δελφινιών:

familydolphin(d).
familydolphin(child(X)) :-
        familydolphin(X).
Να ορίσετε, βασισμένοι στα παραπάνω, το κατηγόρημα familygen(X, Y) που είναι αληθές όταν και μόνο όταν το Υ είναι ο αριθμός των γενεών της οικογένειας X. Για παράδειγμα το κατηγόρημα familygen(child(child(d)), 2) είναι αληθές (η οικογένεια X είναι δύο γενεών).

Διάρκεια εξέτασης 2,5 ώρες Καλή επιτυχία!

Εξεταστική περιόδος Σεπτεμβρίου 2000

ΠΑΝΕΠΙΣΤΗΜΙΟ ΑΙΓΑΙΟΥ

Τμήμα Πληροφοριακών και Επικοινωνιακών Συστημάτων

Γλώσσες προγραμματισμού

Διδάσκων: Επικ. Καθηγητής Διομήδης Σπινέλλης

Εξεταστική περίοδος

Σεπτεμβρίου 2000

Ο οργανισμός "Αθήνα 2004" σας αναθέτει να υλοποιήσετε ένα δοκιμαστικό πρόγραμμα για την παρακολούθηση των αθλητών των Ολυμπιακών αγώνων. Το πρόγραμμα θα περιλαμβάνει στοιχεία για κάθε ξεχωριστό αθλητή. Τα στοιχεία αυτά είναι: όνομα, εθνικότητα, άθλημα που αγωνίζεται, στοιχεία σχετικές με τις προσπάθειές του στο άθλημα και η καλύτερη επίδοση στο άθλημα αυτό. Κάθε αθλητής λαμβάνει μέρος σε ένα και μόνο ένα άθλημα. Για κάθε άθλημα πρέπει να τηρείται αυτόματα ο αριθμός των αθλητών που αγωνίζονται. Στα αθλήματα ταχύτητας φυλάσσεται η επίδοση (χρόνος) ως αριθμός κινητής υποδιαστολής, στα άλματα φυλάσσεται η απόσταση ως αριθμός κινητής υποδιαστολής, και στις άρσεις φυλάσσεται το βάρος ως ακέραιος αριθμός. Σε ορισμένα αθλήματα πρέπει να φυλάσσεται και ο αριθμός των προσπαθειών. Το πρόγραμμα πρέπει να μπορεί να εμφανίζει τα στοιχεία για έναν συγκεκριμένο αθλητή, να ενημερώνει τα στοιχεία του αθλητή με τα αποτελέσματα από μια προσπάθεια και να εμφανίζει τους αθλητές για ένα άθλημα με τη σωστή σειρά κατάταξης.

Θέμα 1ο: (2 βαθμοί)

Σχεδιάστε σε UML ένα διάγραμμα κλάσεων για το παραπάνω πρόγραμμα. Στο διάγραμμα πρέπει να φαίνονται οι ιδιότητες καθώς και οι μέθοδοι που εσείς κρίνετε απαραίτητες. Σχεδιάστε ένα αντιπροσωπευτικό διάγραμμα αντικειμένων.

Θέμα 2ο: (2 βαθμοί)

Για κάθε άθλημα να γράψετε σε C++ τη μέθοδο compare(a1, a2) που επιστρέφει -1, 0, ή 1 ανάλογα με το αν η επίδoση του αθλητή a1 είναι καλύτερη, ίση ή χειρότερη με την επίδοση του αθλητή a2.

Θέμα 3ο: (2 βαθμοί)

Να γράψετε σε C++ μια κλάση που να υλοποιεί τις "προσπάθειες". Η κλάση να έχει συνάρτηση κατασκευής που να λαμβάνει ως όρισμα τον αριθμό των προσπαθειών που επιτρέπει ένα αγώνισμα και τις μεθόδους NewTry που εκτελείται πριν από κάθε προσπάθεια και εμφανίζει τον αριθμό της και αυτές που απομένουν και την MoreTries που επιστρέφει αληθές αν επιτρέπονται άλλες προσπάθειες.

Θέμα 4ο: (2 βαθμοί)

Γράψτε σε C++ μια μέθοδο για την κλάση του αθλητή που λαμβάνει ως όρισμα έναν πίνακα με αθλητές που αγωνίστηκαν σε ένα αγώνισμα, και τον αριθμό τους. Η μέθοδος πρέπει με τη χρήση της compare να εμφανίζει τον αθλητή που έχει την καλύτερη επίδοση στο συγκεκριμένο αγώνισμα.

Θέμα 5ο: (2 βαθμοί)

Η παρακάτω συνάρτηση της Lisp επιστρέφει τον αριθμό των στοιχείων μιας λίστας:

(defun length (a) 
        (if (null a) 
                0 
                (+ (length (cdr a)) 1)))
Αν η συνάρτηση listp είναι αληθής όταν το όρισμά της είναι λίστα και η car επιστρέφει το πρώτο στοιχείο μιας λίστας, να ορίσετε μια συνάρτηση expandlength που να επιστρέφει τον αριθμό των στοιχείων μια λίστας λαμβάνοντας υπόψη και τα στοιχεία της λίστας που και αυτά είναι λίστες. Για παράδειγμα ενώ η (length '(5 8 40 '(60 90))) έχει την τιμή 4 η (expandlength '(5 8 40 '(60 90))) έχει την τιμή 5.

Διάρκεια εξέτασης 2,5 ώρες Καλή επιτυχία!

Βοηθήματα στις εξετάσεις

Στην εξέταση για το μάθημα Γλώσσες προγραμματισμού οι εξεταζόμενοι μπορούν (προαιρετικά) να έχουν μαζί τους ένα φύλλο με σημειώσεις σύμφωνα με τους παρακάτω όρους:
  1. Οι σημειώσεις πρέπει να είναι όλες γραμμένες σε ένα φύλλο μεγέθους Α4.
  2. Επιτρέπεται η χρήση και των δύο όψεων του φύλλου.
  3. Το φύλλο παραδίδεται μαζί με το γραπτό.
  4. Οι πρώτες λέξεις και στις δύο όψεις του φύλλου πρέπει να είναι το ονοματεπώνυμο και ο αριθμός μητρώου του εξεταζόμενου.
  5. Οι σημειώσεις πρέπει να είναι γραμμένες στα ελληνικά ή αγγλικά, να διαβάζονται με γυμνό μάτι και να μην είναι κωδικοποιημένες.
  6. Οι σημειώσεις επιτρέπεται να περιέχουν κώδικα στις γλώσσες προγραμματισμού που έχουν διδαχτεί στο μάθημα καθώς και σχήματα UML.
  7. Στο τέλος της δεύτερης όψης πρέπει να υπάρχει η παρακάτω φράση
    Η σημειώσεις στο φύλλο αυτό έχουν συνταχθεί από εμένα. Κανένα τμήμα τους δεν έχει αντιγραφεί αυτούσιο από άλλη πηγή.
    μαζί με το ονοματεπώνυμο, την ημερομηνία και υπογραφή του εξεταζόμενου.
  8. Όλα τα περιεχόμενα του φύλλου (με εξαίρεση την υπογραφή) πρέπει να είναι τυπωμένα.
  9. Απαγορεύονται οι χειρόγραφες προσθήκες ή σημειώσεις στο φύλλο, τόσο πριν, όσο και κατά τη διάρκεια της εξέτασης.
  10. Παράβαση των παραπάνω όρων συνεπάγεται μηδενισμό του γραπτού.