Μ2000 Έκδοση
9.8 - Αντικείμενα τύπου Ομάδα (Group)
Η μετάβαση
στην Ομάδα
Στα πρώτα
στάδια εξέλιξης της Μ2000, υπήρχαν
μεταβλητές δύο κύριων τύπων, οι αριθμητικές
και οι αλφαριθμητικές. Στις αριθμητικές
όσες μεταβλητές είχαν στο όνομά τους
ως τελευταίο χαρακτήρα το % ήταν οι
ακέραιες. Οι αλφαριθμητικές ξεχωρίζουν
με τον τελευταίο χαρακτήρα το $. Αυτά
προέρχονται από την BBC Basic, μια γλώσσα
προγραμματισμού με διαδικασίες και
συναρτήσεις με πολλές γραμμές (υπήρχαν
άλλες μορφοποιήσεις της BASIC που έπαιρναν
συναρτήσεις χρήστη μόνο σε μια γραμμή).
Το ερώτημα
που τέθηκε ήταν πώς θα μπορούσαμε να
ομαδοποιήσουμε μεταβλητές. Ο στόχος
ήταν να υπάρχει ένα όνομα που να δηλώνει
την ομάδα, και σε αυτό να υπάρχουν οι
μεταβλητές. Επίσης το ενδιαφέρον ήταν
να μπορούμε να περνάμε με αναφορά την
ομάδα, και να έχουμε συνάμα με αναφορά
όλες τις μεταβλητές μέσα σε αυτήν. Αυτό
θα βοηθούσε να έχουμε συναρτήσεις και
τμήματα (δεν λέγονται διαδικασίες στη
Μ2000) που να δουλεύουν για κάποια ομάδα,
την οποία όμως δεν θα την έχουν εσωτερικά,
αλλά θα την παίρνουν με αναφορά, και θα
κάνουν αλλαγές.
Επιλέχθηκε
ένας τρόπος απόδοσης της έννοιας της
ομάδας με τη χρήση ενός νέου αντικειμένου,
του Group, γραμμένου σε Visual Basic 6 (όπως και
ο διερμηνευτής της γλώσσας). Επειδή ο
διερμηνευτής εκτελεί πράξεις μόνο με
μεταβλητές που υπάρχουν καταγεγραμμένες
στη λίστα μεταβλητών, επιλέχθηκε το
αντικείμενο να μην κρατάει τις μεταβλητές
εντός του. Αυτό που κρατάει είναι μια
λίστα του τι είναι δικό του. Έτσι όταν
μια ομάδα ΑΛΦΑ έχει μεταβλητή την Χ,
στην ουσία υπάρχει η ΑΛΦΑ.Χ ως ξεχωριστή
εγγραφή στη λίστα μεταβλητών. Έτσι
μπορεί να περάσει μόνη της με αναφορά
ως &ΑΛΦΑ.Χ, αφού ο διερμηνευτής την
βρίσκει στη λίστα και δίνει το πλήρες
όνομά της (υπάρχει ένα πρόθεμα σχετικά
με το τμήμα ή τη συνάρτηση όπου
δημιουργήθηκε), ως ισχνή αναφορά, ώστε
εκεί που καλούμε μια Διάβασε &Β να
συνδέσει την Β με την ΑΛΦΑ.Χ. Η σύνδεση
γίνεται κατά την δημιουργία της Β όπου
αντί να δοθεί χώρος μεταβλητής, δίνεται
ο υπάρχον της ΑΛΦΑ.Χ (τον οποίο βρίσκει
ο διερμηνευτής από την λίστα μεταβλητών,
γρήγορα με συνάρτηση κατακερματισμού,
Hash Function).
Πάνω σε αυτό
το σχέδιο, οι ομάδες φτιάχτηκαν ως
"τιμές" και όχι ως αναφορές. Για να
γίνει κατανοητό αυτό, μια μεταβλητή Α
λέγεται τιμή γιατί παίρνει τιμές. Ένα
αντικείμενο Α είναι αναφορά στο
αντικείμενο, και μια Α=Β δίνει στο Α την
αναφορά που έχει το Β. Τέτοια αντικείμενα
έχει η Μ2000, όπως η Κατάσταση, ο Σωρός και
ο δείκτης σε πίνακα. Σε αυτά τα αντικείμενα
μπορούμε να έχουμε μια Α και μια Β που
να δείχνουν το ίδιο αντικείμενο. Αν
περάσουμε με τιμή το Α δίνουμε το δείκτη,
και ότι αλλαγή κάνουμε στο Α θα φανεί
και στο Β, εκτός αν αλλάξουμε αναφορά
στο Α, δηλαδή αν δώσουμε ένα νέο
αντικείμενο. Οι ομάδες όμως φτιάχτηκαν
ως τιμές. Όπως είδαμε παραπάνω αν μια
ομάδα έχει δυο μέλη, θα έχει τρεις
καταχωρήσεις στην λίστα μεταβλητών. Αν
λοιπόν η ομάδα Αλφα έχει τα μέλη Χ και
Υ τότε για ένα νέο όνομα, έστω το Βήτα,
η εκχώρηση Βήτα=Άλφα θα κάνει το Βήτα
μια άλλη ομάδα, με επίσης δυο μέλη, με
τιμές αντίγραφα των τιμών των Α.Χ και
Α.Υ. Δηλαδή θα πάρουμε άλλες τρεις
καταχωρήσεις.
Το επόμενο
ερώτημα πάνω στις ομάδες, είναι τι
γίνεται σε μια εκχώρηση μιας Άλφα σε
μια υπάρχουσα Βήτα, όταν έχουν διαφορετικά
μέλη; Γενικά τα αντικείμενα σε πολλές
γλώσσες έχουν ένα τύπο, ή θα το λέγαμε
διεπαφή (interface) το οποίο λέει τι πρέπει
να έχει υλοποιημένο, και όταν υπάρχει
η έννοια του δυνατού τύπου (Strong Type), τότε
για να χρησιμοποιήσουμε σε μια συνάρτηση
ένα αντικείμενο πρέπει να ταυτίζονται
η προσφερόμενη διεπαφή (του αντικειμένου
μας) με την ζητούμενη διεπαφή (της
παραμέτρου της συνάρτησης). Μερικές
μάλιστα έχουν περισσότερες από μια
διεπαφές για το ίδιο αντικείμενο. Στη
java φτιάχνουμε αντικείμενα με καταβολές
σε κάποιες διεπαφές. Αυτό δηλώνεται με
μια δήλωση implements όνομα_διεπαφής, το
οποίο είναι συνάμα ένα άλλο αντικείμενο.
Η χρήση διεπαφής ή interface είναι συνάμα
και μια μέθοδος κληρονομικότητας. Το
νέο αντικείμενο κληρονομεί ότι έχει η
πρόσθετη διεπαφή. Ο διερμηνευτής της
java επιλέγει τη διεπαφή ανάλογα με το
ζητούμενο της παραμέτρου σε μια συνάρτηση,
αλλά ουσιαστικά περνάει το ίδιο
αντικείμενο. Η συνάρτηση που δέχεται
το αντικείμενο αγνοεί τυχόν άλλες
διεπαφές, και ασχολείται με ότι ξέρει
για τη ζητούμενη διεπαφή. Δείτε όμως
κάτι "παράξενο", που δεν έχει
λογαριαστεί. Αν κάποιος γράψει μια
συνάρτηση για ένα αντικείμενο με δυο
μέλη, τα Χ και Υ, και πάει να την προσαρμόσει
σε ένα άλλο πρόγραμμα, που έχει ένα άλλο
αντικείμενο με ίδια μέλη, τότε θα βρεθεί
προ εκπλήξεως, γιατί τα δυο αντικείμενα
αν και έχουν τα ίδια μέλη αποτελούν
άλλες κλάσεις, άλλο τύπο, άλλο όνομα
τύπου. Θα πρέπει δηλαδή να προσαρμόσουμε
το όνομα στη συνάρτηση για να ταυτιστούν
οι τύποι. Εξαιρώ εδώ έναν μηχανισμό
reflection, που έχει η java για να κοιτάει τι
έχει ένα αντικείμενο, γιατί χρειάζεται
να πάμε σε συναρτήσεις που να έχουν
γραφτεί με σκοπό να ανιχνεύουν τι
παίρνουν, ζητώντας όμως τον βασικό τύπο,
από αυτόν δηλαδή που όλα τα αντικείμενα
της java κληρονομούν. Στην πράξη αυτό που
φτιάχνουμε ως κλάση στη java, παίρνει μια
σειρά τιμών (ιδιοτήτων) και μεθόδων από
το βασικό αντικείμενο, αλλά συνήθως δεν
μας απασχολούν αυτά. Η ουσία εδώ είναι
ότι η κλάση μπορεί να είναι στατική, να
έχει δηλαδή παρουσία όπως τη γράφουμε
ή να φτιάχνουμε νέες παρουσίες. Κάθε
παρουσία είναι ένα πραγματικό αντικείμενο,
και ένας δείκτης σε αυτήν γράφεται σε
μια μεταβλητή. Εδώ δηλαδή λέμε για
αντικείμενα τύπου αναφορά. Το κακό με
τους δείκτες στα αντικείμενα είναι ότι
περνούν τα μέλη με αναφορά (και ας έχει
περάσει ο δείκτης με τιμή. αφού στην
ουσία δείχνει το ίδιο αντικείμενο). Στη
java λοιπόν πρέπει να γράψει κάποιος μια
μέθοδο αντιγραφής, δηλαδή μια μέθοδο
που θα φτιάχνει μια νέα παρουσία και θα
γράφει ό,τι πρέπει να αντιγραφεί. Άλλος
τρόπος είναι η χρήση της clone, που όμως
δεν συνίσταται στη βιβλιογραφία. Το
πρόβλημα της αντιγραφής είναι στο πόσο
βαθιά πηγαίνει. Η μια σχετική έννοια
λέγεται ρηχή αντιγραφή ή shallow copy, όπου
αντιγράφονται τα μέλη ως τιμές. Έτσι αν
έχουν αριθμό θα αντιγραφεί ο αριθμός,
ενώ αν έχουν άλλο αντικείμενο θα
αντιγραφεί ο δείκτης στο αντικείμενο,
και έτσι το αντίγραφο θα έχει σύνδεση
με το το ίδιο αντικείμενο όπως το αρχικό.
Η άλλη έννοια αντιγραφής είναι η βαθιά
αντιγραφή. Εκεί κάθε δείκτης σε αντικείμενο
προκαλεί πρώτα μια αντιγραφή του
αντικειμένου. Αυτό γίνεται αναδρομικά,
με συνέπειες όμως αρνητικές αν κάποιο
αντικείμενο έχει κατά κάποιο τρόπο
αυτοαναφορά άμεσα ή μέσω άλλου εσωτερικού
αντικειμένου. Αυτός είναι ο λόγος που
φτιάχνουμε μια συνάρτηση αντιγραφής
ώστε να γίνεται η σωστή επιλογή του τι
και πώς αντιγράφεται. Για τους λόγους
αυτούς επιλέχθηκε, στις πρώτες εκδόσεις
στη Μ2000, οι ομάδες, να μην παρέχονται
με αναφορά, αλλά με τιμή. Αυτό σημαίνει
ότι κάθε ομάδα έχει μια παρουσία και
ένα μοναδικό δείκτη σε αυτές. Αν μια
ομάδα έχει άλλες ομάδες τότε αυτές είναι
επίσης μοναδικές. Με αυτό το σκεπτικό
οι διεπαφές δεν έχουν νόημα, αν
αναλογιστούμε ότι οι συναρτήσεις της
Μ2000 δεν ζητούν συγκεκριμένο τύπο ομάδας
αλλά όλες τις βλέπουν ως ένα τύπο. Έτσι
αν έχουμε δυο ομάδες ΑΛΦΑ και ΒΗΤΑ με
μέλη και στις δυο, δυο μέλη με ονόματα
τα Χ και Υ και μια συνάρτηση δουλεύει
με ομάδες με μέλη τα Χ και Υ, τότε και οι
δυο ομάδες μπορούν να περαστούν στην
συνάρτηση. Επίσης επιλέχθηκε η μέθοδος
της συγχώνευσης ομάδας σε ομάδα. Αν η
ΑΛΦΑ έχει τα μέλη Κ και Λ και η Βήτα τα
μέλη Χ, Υ και Λ, και δώσουμε το ΑΛΦΑ=ΒΗΤΑ
τότε η ΑΛΦΑ θα έχει πια τα μέλη Χ,Υ, Κ και
Λ, με τιμή νέα στη Λ. Δηλαδή ότι είναι
όμοιο παίρνει νέα τιμή, ενώ ότι είναι
νέο από την ομάδα στα δεξιά της εκχώρησης
"συγχωνεύεται" στη ομάδα στα
αριστερά της εκχώρησης. Σε κάθε περίπτωση
έχουμε αντίγραφα. Στην πορεία των
εκδόσεων της Μ2000 μπήκαν μέλη με αναφορά,
και σε αυτά γίνεται ρηχή αντιγραφή.
Στο πρώτο
παράδειγμα παρακάτω χρησιμοποιούμε
μια ομάδα που περιέχει ένα μέλος χ και
έναν μέλος πίνακας μ(). Οι πίνακες είναι
"τιμές" για την Μ2000, δηλαδή
αντιγράφονται. Εδώ όμως πάλι αντιγράφονται
με ρηχή αντιγραφή. Εδώ έχει νόημα αυτό
γιατί σε μια θέση πίνακα μπορούμε να
έχουμε αντικείμενα εκτός από αριθμούς
ή αλφαριθμητικά. Έστω ότι εδώ δεν θα
βάλουμε αντικείμενα. Η αντιγραφή θα
γίνει για όλα τα στοιχεία του πίνακα.
Θέλουμε να δείξουμε ότι η δημιουργία
μας δεύτερης ομάδας θα έχει αντίγραφο
πίνακα. Αφού κάνουμε τη δημιουργία
αλλάζουμε τιμές στο πίνακα της δεύτερης
ομάδας και εμφανίζουμε τα περιεχόμενα
των πινάκων της πρώτης και της δεύτερης
ομάδας για να τεκμηριώσουμε την αντιγραφή:
ομάδα άλφα { χ=10 πίνακας μ(3) } άλφα.μ(0)=1,2,3 βήτα=άλφα βήτα.μ(0)=5,6,7 Τύπωσε άλφα.μ() ' 1 2 3 Τύπωσε βήτα.μ() ' 5 6 7
Στο επόμενο παράδειγμα, θα χρησιμοποιήσουμε
το κώδικα του προηγούμενου και θα
προσθέσουμε κώδικα, όπου μια νέα μεταβλητή
η δέλτα θα ξεκινήσει με δυο μέλη τα Χ
και Υ με αρχικές τιμές 20 και 20 (οι τιμές
εξ ορισμού είναι τύπου double, 64bit κινητής
υποδιαστολής)
ομάδα δέλτα { Χ=20, Υ=20 } δέλτα=βήτα Τύπωσε δέλτα.μ() ' 5 6 7 δέλτα=άλφα Τύπωσε δέλτα.μ() ' 1 2 3 Τύπωσε δέλτα.χ=10
Το μέλος χ της δέλτα άλλαξε σε 10. Η δέλτα
πήρε μια φορά αντίγραφο πίνακα από το
βήτα και μια φορά από το άλφα.
Όπως αναφέρθηκε
παραπάνω, στις αρχικές εκδόσεις οι
ομάδες έπρεπε να βρίσκονται με μοναδικό
δείκτη. Εδώ τα άλφα, βήτα και δέλτα έχουν
διαφορετικό δείκτη. Επίσης και οι τρεις
ομάδες λέγονται ανοικτά αντικείμενα,
ή επώνυμα. Δηλαδή έχουν όνομα, και τα
μέλη τους είναι άμεσα διαθέσιμα. Στη
λίστα μεταβλητών στο τέλος του
παραδείγματος παραπάνω υπάρχουν τα
αλφα, αλφα.χ, αλφα.μ(), βήτα, βήτα.χ,
βήτα.μ(), δέλτα, δέλτα.χ, δέλτα.υ, δέλτα.μ().
Αυτά χάνονται όταν τερματίσει το
παράδειγμα (όταν το τμήμα που τα περιέχει
επιστρέψει).
Κάτι που δεν
έχει φανεί από τα παραπάνω είναι η ύπαρξη
του ανώνυμου, ή κλειστού αντικειμένου.
Όταν μια ομάδα βρίσκεται σε μια έκφραση,
όπως στα δεξιά μιας εκχώρησης ή ως
παράμετρος σε μια κλήση συνάρτησης πχ
κάππα=συν1(Αλφα), ο διερμηνευτής παράγει
το κλειστό αντικείμενο. Αυτό είναι
επίσης μια ομάδα, όπου αντί να έχει μια
λίστα που να δείχνει τι έχει δικό του
στη λίστα μεταβλητών, έχει μια λίστα
μεταβλητών ιδιωτική. Κατά την εκχώρηση
σε νέο, ή τη συγχώνευση, αυτό που γίνεται
είναι να διαβάσει ο διερμηνευτής το
κλειστό αντικείμενο και ή να φτιάξει
ένα νέο ή να το συγχωνεύσει. Ουσιαστικά
σε μια κλήση συνάρτησης αυτό που παίρνει
η συνάρτηση είναι το αντίγραφο, πριν
ακόμα το δώσει σε κάποιο όνομα εντός
της. Έτσι πραγματικά έχουμε πέρασμα με
τιμή των ομάδων. Στη java θα έπρεπε να
κάνει κανείς clone για να περάσει έτσι ένα
αντικείμενο. Αν θέλουμε βέβαια περνάμε
με αναφορά το αντικείμενο. Θα πρέπει
όμως η συνάρτηση να περιμένει με αναφορά
το αντικείμενο. Πχ η κάππα=συν2(&Αλφα)
θα δώσει στην συν2() ένα αλφαριθμητικό
με την ισχνή αναφορά του Άλφα. Εντός της
Άλφα μπορεί να χρησιμοποιηθεί η ισχνή
αναφορά για να φτιαχτεί ένα νέο όνομα,
με όλα τα μέλη ως αναφορά στα μέλη της
άλφα, ή να χρησιμοποιηθεί η αναφορά
άμεσα. Συνεχίζουμε τον κώδικα στο
προηγούμενο παράδειγμα.
Συνάρτηση Συν2 { Διάβασε &Κάτι =Κάτι Κάτι.μ(0)=8, 9 ,10 } παλιό=Συν2(&δέλτα) Τύπωσε δέλτα.μ() ' 8 9 10 Τύπωσε παλιό.μ() ' 1 2 3
Βλέπουμε εδώ ότι η Συν2() γύρισε ένα
αντίγραφο της Κάτι, που είναι αναφορά
της δέλτα, και άλλαξε τιμές στο πίνακα
της δέλτα. Δείτε επίσης ότι δώσαμε την
επιστροφή τιμής (μια ομάδα) και ότι
συνεχίσαμε την εκτέλεση στην συνάρτηση
(σε άλλες γλώσσες η επιστροφή τιμής
συνεπάγεται και τέλος εκτέλεσης της
συνάρτησης).
Παρακάτω
(συνεχίζουμε στο ίδιο παράδειγμα)
χρησιμοποιούμε την ισχνή αναφορά χωρίς
να δημιουργήσουμε νέα ομάδα (ως αναφορά
στην παρεχόμενη). Βάζουμε την ισχνή
αναφορά σε ένα αλφαριθμητικό. Όταν αυτό
είναι σε αριστερή έκφραση (όπως το
δ$.μ(0)=...) ο διερμηνευτής διαβάζει την
ισχνή αναφορά και την τιμή της ως
μεταβλητή γενική (η ισχνή αναφορά έχει
και το πρόθεμα του τμήματος που ανήκει
η μεταβλητή, εδώ η ομάδα). Αυτός ο τρόπος
είναι πιο γρήγορος, γιατί δεν απαιτεί
να φτιαχτούν αναφορές για όλα τα μέλη.
Όμως αν έχουμε πολλές εργασίες με μέλη
της ομάδας θα μπορούσαμε να χρησιμοποιήσουμε
το τρόπο της Συν2(). Δείτε ότι για την
δεξιά έκφραση πρέπει να χρησιμοποιούμε
την εκφρ() ή την εκφρ$() για τιμές
αλφαριθμητικές (και ειδικά στην εκφρ$()
χρειάζεται μια τελεία στο τέλος του
αλφαριθμητικού για να κατανοήσει ο
διερμηνευτής ότι μας ενδιαφέρει ο
δείκτης και όχι το αλφαριθμητικό που
του δίνουμε ως αλφαριθμητικό. Στην
εκφρ() η τελεία είναι πλεονασμός (μπορούμε
να την βάλουμε), γιατί τα αλφαριθμητικά
όπως και να έχει τα δέχεται ως εκφράσεις,
έτσι το Εκφρ("10+5") δίνει το 15. Στα
αγγλικά η Εκφρ() είναι η Eval().
Συνάρτηση Συν3(δ$) { =εκφρ(δ$) δ$.μ(0)=11, 12, 13 } παλιό=Συν3(&δέλτα) Τύπωσε δέλτα.μ() ' 11, 12, 13 Τύπωσε παλιό.μ() ' 8 9 10
Στην εγκατάσταση της Μ2000, έκδοση 9.8
υπάρχει το αρχείο info.gsb με διάφορα
προγράμματα, ένα εκ των οποίων είναι το
OOP1, το οποίο δείχνει το πώς φτιάχνουμε
ένα σύστημα γεγονότων με το πρότυπο του
παρατηρητή, και στο οποίο χρησιμοποιούνται
ισχνές αναφορές για να συνδέουν
αντικείμενα.
Το πρόβλημα
των ισχνών αναφορών είναι η εγκυρότητά
τους. Δηλαδή μια ισχνή αναφορά είναι
έγκυρη αν αυτό που δείχνει υπάρχει! Όταν
καλούμε, στο παράδειγμα, την Συν3() η
ισχνή αναφορά δημιουργείται κατά τη
κλήση και επειδή ο κύκλος ζωής του δέλτα
δεν εξαρτιέται από την Συν3(), η αναφορά
θα είναι έγκυρη όσο η εκτέλεση παραμένει
στη συνάρτηση, ή σε ότι καλέσει αυτή η
συνάρτηση. Πώς λοιπόν χάνεται η εγκυρότητα;
Αν καταχωρήσουμε
την αναφορά σε μια ομάδα και αυτή
επιστραφεί ως τιμή, και αν γίνει αυτό
και ένα επίπεδο πάνω από το επίπεδο που
δημιουργήθηκε η δέλτα, τότε η δέλτα θα
έχει διαγραφεί ενώ η ισχνή αναφορά δεν
θα έχει διαγραφεί, αλλά δεν θα είναι
έγκυρη.
Κάτι άλλο
που μπορεί να γίνει, είναι να διατηρήσουμε
το κλειστό αντικείμενο. Στις πρώτες
εκδόσεις με ομάδες, τα κλειστά αντικείμενα
μπορούσαν να βρίσκονται ή στιγμιαία σε
εκφράσεις, ή να καταχωρούνται σε πίνακες,
σε σωρό, ή σε κατάσταση (λίστα τιμών με
κλειδί). Περί σωρού, δείτε λίγο την
Συν2() και την Συν3(), στην πρώτη έχουμε
διάβασμα από το σωρό του &Κάτι, στην
δεύτερη έχουμε γράψει στον ορισμό το
Συν3(δ$) το οποίο όμως ο διερμηνευτής το
κάνει Διάβασε δ$, δηλαδή το διαβάζει από
το σωρό τιμών. Ο σωρός τιμών στις
συναρτήσεις είναι πάντα νέος, και
περιέχει ότι έχουμε δώσει (η Μ2000 αφήνει
ελεύθερα να βάλουμε ότι θέλουμε, και
είναι στη Διάβασε ο έλεγχος εγκυρότητας).
Στα τμήματα ο σωρός κληρονομείται από
αυτόν που τα καλεί, που σημαίνει ότι τα
τμήματα μπορούν να τον αλλάξουν, άρα να
επιστρέψουν τιμές αν θέλουμε. Στα τμήματα
ο σωρός είναι σαν ένα αρχείο εισόδου/εξόδου,
στα οποία τα πρώτα στοιχεία είναι οι
παράμετροι που δίνουμε.
Ένα κλειστό
αντικείμενο, όπως έχει αναφερθεί παραπάνω
έχει όλα τα στοιχεία εντός, δηλαδή εκτός
της λίστας μεταβλητών. Σκοπίμως δεν έχω
αναφέρει και την λίστα τμημάτων-συναρτήσεων,
στα προηγούμενα παραδείγματα, αλλά εδώ
να γίνει κατανοητό ότι οι ομάδες μπορούν
να έχουν μέλη τμήματα και συναρτήσεις.
Στη συγχώνευση γίνεται και σε αυτές
συγχώνευση, και έχουμε αλλαγή κώδικα
σε ίδια ονόματα, εκτός από αυτά που
έχουν δηλωθεί τελικά.
Για να
χρησιμοποιήσει ο διερμηνευτής τα
αντικείμενα, κλειστού τύπου, πρέπει να
τα ανοίξει πρώτα. Αυτό σημαίνει ότι για
να διαβάσουμε τη χ μεταβλητή πρέπει να
δοθεί όνομα στο αντικείμενο, να βγουν
όλα τα μέλη στις λίστες μεταβλητών και
τμημάτων και τότε να προσαρμοστεί το
όνομα με το Χ για να βγει η τιμή που
θέλουμε. Χρονοβόρο αυτό, αλλά υπάρχει
τρόπος ένα κλειστό αντικείμενο να το
διατηρούμε ανοικτό. Επίσης επειδή κάθε
κλειστό αντικείμενο φέρει τμήματα και
συναρτήσεις με το κώδικά τους εντός,
σημαίνει ότι ένας πίνακας με χίλιες
ομάδες θα έχει χίλιες φορές αποθηκεύσει
κώδικα για το κάθε ομάδα ξεχωριστά!
Ευτυχώς υπάρχει τρόπος να έχουμε πίνακα
με ομάδες με κοινό ορισμό τμημάτων και
συναρτήσεων. Σε περίπτωση που πάρουμε
μια ομάδα από αυτόν τον πίνακα θα πάρουμε
και αντίγραφο των συναρτήσεων-τμημάτων.
Άλλες γλώσσες το αποφεύγουν αυτό με
τον ορισμό κλάσεων, όπου σε κάθε
αντικείμενο υπάρχει ένας δείκτης προς
τη διεπαφή, και από εκεί υπάρχει ένας
πίνακας μεθόδων - άλλος τρόπος να πούμε
τα μέλη που εκτελούνται.
Συνεχίζουμε
στο αρχικό παράδειγμα, και προσθέτουμε
τα παρακάτω
Πίνακας Ένα(100)=δέλτα Τύπωσε Ένα(1).μ() ' 11 12 13 Ένα(2)=Παλιό Τύπωσε Ένα(2).μ() ' 8 9 10 Για Ένα(1), Ένα(2) { Πίνακας ν() ν()=.μ() .μ()=..μ() ..μ()=ν() } Τύπωσε Ένα(1).μ() ' 8 9 10 Τύπωσε Ένα(2).μ() ' 11 12 13
Ο πίνακας Ένα() γίνεται πίνακας με κάθε
τιμή ένα αντίγραφο από την ομάδα δέλτα.
Βλέπουμε όντως ότι η Ένα(1).μ() είναι
πίνακας με τρία στοιχεία τα 11, 12 και 13.
Στην Ένα(2) βάζουμε το Παλιό, που είχαμε
κρατήσει από την κλήση της Συν3(). Στα
κλειστά αντικείμενα δεν ισχύει η
συγχώνευση (εκτός αν την κάνουμε σε αυτά
που έχουμε ήδη ανοίξει, θα το δούμε άλλη
στιγμή). Οπότε ο διερμηνευτής απλά πετάει
το κλειστό αντικείμενο στην Ένα(2) και
δίνει το κλειστό που παράγει από το
Παλιό (στη δεξιά έκφραση παράγεται
κλειστό αντικείμενο).
Πράγματι
βλέπουμε ότι το Ένα(2).μ() έχει τις τιμές
8, 9 και 10. Τώρα πάμε να ανοίξουμε και τα
δυο αντικείμενα με την Για αντικείμενο
[, αντικείμενο] { }. Αυτή η δομή έχει μια
ιδιαιτερότητα. Ότι νέο φτιάχνει το
διαγράφει. Έτσι φτιάχνουμε ένα κενό
πίνακα, το ν(), ο οποίος θα διαγραφεί.
Επίσης υπάρχει ο κανόνας με τις τελείες.
Σε κάθε τμήμα όσα Για ανοίγουμε, με κάθε
πρόσθετο αντικείμενο, αποδίδεται το
μέλος του αντικειμένου με ισάριθμο
αριθμό τελειών. Αυτό γίνεται γιατί δεν
μας δίνει το όνομα ο διερμηνευτής, αυτό
δηλαδή που δημιουργεί για κάθε αντικείμενο,
που ανοίγει. Έτσι το .μ() σχετίζεται με
το Ένα(1) και το ..μ() σχετίζεται με το
Ένα(2).
Οι πίνακες
είναι "τιμές", και έτσι το ν()=.μ()
παίρνει αντίγραφο του .μ() που είναι το
Ένα(2).μ() και μετά αφού βάλουμε αντίγραφο
από το ..μ() στο .μ(), ολοκληρώνουμε την
μετάθεση των τιμών των πινάκων, με την
..μ()=ν() και στο τέλος βλέπουμε ότι έγινε
η αλλαγή.
Στις τελευταίες
εκδόσεις της γλώσσας μπορούμε να δώσουμε
το παρακάτω αντί του προηγούμενου, γιατί
τώρα ο διερμηνευτής ξέρει αν ένα κλειστό
αντικείμενο είναι ήδη ανοικτό και
χρησιμοποιεί αυτό, ενώ οι παλαιότερες
εκδόσεις, θα άνοιγαν πάλι το αντικείμενο
με συνέπεια να χαθούν οι αλλαγές στο
κλείσιμο της Για.
Για Ένα(1), Ένα(2) { Πίνακας ν() ν()=Ένα(1).μ() Ένα(1).μ()=Ένα(2).μ() Ένα(2).μ()=ν() } Τύπωσε Ένα(1).μ() ' 8 9 10 Τύπωσε Ένα(2).μ() ' 11 12 13
Δείτε τώρα ένα σκόπιμο λάθος, για την
κατανόηση του σωρού τιμών και των
πινάκων. Οι πίνακες είναι αντικείμενα
στην Μ2000. Όταν αφήνουμε έναν πίνακα στο
σωρό τιμών, δεν βγαίνει εκείνη τη στιγμή
αντίγραφο, απλά αφήνουμε το δείκτη του.
Εδώ αφήνουμε το δείκτη του πίνακα στο
Ένα(1) και μετά το δείκτη του πίνακα στο
Ένα(2). Διαβάζουμε το Ένα(1).μ() και παίρνουμε
αντίγραφο από τον τελευταίο που μπήκε
στο σωρό (δείτε παραπάνω, ο Ένα(2).μ() έχει
τιμή το 11 12 13). Εσωτερικά δεν αλλάζει ο
δείκτης του πίνακα του Ένα(1).μ(). Αυτό
που άλλαξε είναι το περιεχόμενο του
πίνακα. Όταν λοιπόν διαβάσουμε τον
επόμενο πίνακα, από το σωρό, ο δείκτης
θα δείχνει το Ένα(1).μ() με τις νέες τιμές.
Έτσι το Ένα(2).μ() θα πάρει αντίγραφο, των
ίδιων τιμών που είχε, και γράφτηκαν πριν
στο Ένα(1).μ()
Βάλε
Ένα(1).μ(),
Ένα(2).μ()
Διάβασε Ένα(1).μ(), Ένα(2).μ()
Διάβασε Ένα(1).μ(), Ένα(2).μ()
Τύπωσε Ένα(1).μ() ' 11 12 13
Τύπωσε Ένα(2).μ() ' 11 12 13
Ας διορθώσουμε
το προηγούμενο, με χρήση δεικτών σε
πίνακες. Οι δείκτες σε πίνακες, διαφέρουν
από του πίνακες. Η διαφορά είναι σε αυτό
που λέμε διεπαφή (interface). Ο διερμηνευτής
χειρίζεται διαφορετικά τους δείκτες
σε πίνακες. Οι δείκτες δεν έχουν δικά
τους δεδομένα, αλλά δείχνουν αυτά που
θέλουμε. Πρώτα λοιπόν βάζουμε στο Ένα(1)
την τιμή του Παλιό, οπότε θα πάρει
αντίγραφο του πίνακα που έχουμε στο
Παλιό, με τιμές τα 8,9 και 10. Μετά φτιάχνουμε
δυο δείκτες τους π1 και π2. Αν αλλάξουμε
τιμές στο Ένα(1).μ() θα φανεί και στο π1,
και ομοίως αν αλλάξουμε τιμές με χρήση
του π1, θα φανεί και στο Ένα(1).μ() ακόμα
και αν δεν είναι ανοιχτό (ο δείκτης
δείχνει το αντικείμενο ανεξάρτητα από
τη θέση αυτού). Αμέσως μετά βάζουμε τους
δείκτες στο σωρό. Ο διερμηνευτής απλά
φτιάχνει δυο νέους δείκτες που δείχνουν
ό,τι έδειχναν και οι π1 και π2. Το σημαντικό
είναι στη Διάβασε. Η διάβασε βλέπει ότι
έχει δείκτη σε πίνακα, και δεν δημιουργεί
αντίγραφο απλά κάνει το Ένα(1).μ() να
δείχνει τον ίδιο πίνακα με αυτόν του
δείκτη. Τώρα το Ένα(1).μ() έχει νέο
αντικείμενο, ενώ το π1 δείχνει το παλιό,
το κρατάει ζωντανό όπως λέμε. Η για την
ακρίβεια, ο κύκλος ζωής ενός αντικειμένου
σχετίζεται με το αν υπάρχει δείκτης
προς αυτό. Όταν ένας δείκτης διαγραφεί,
τότε εσωτερικά το σύστημα μειώνει έναν
απαριθμητή συνδέσεων, και αν αυτός
μηδενίσει τότε καλεί μια μέθοδο διάλυσης
η οποία μια δουλεία της είναι να αποδώσει
την μνήμη του αντικειμένου στο σύστημα
και έτσι διαγράφεται το αντικείμενο.
Τα αντικείμενα Ομάδες όπως τα έχουμε
δει μέχρι τώρα έχουν μοναδικό δείκτη,
που σημαίνει ότι σίγουρα θα διαγραφούν
όταν αυτό που τα περιέχει θα διαγραφεί.
Αυτό ήταν βασική προδιαγραφή για τις
ομάδες, στις πρώτες εκδόσεις της γλώσσας
γιατί απλοποιεί την χρήση των αντικειμένων.
Δεν ισχύει αυτό στους δείκτες στους
πίνακες. Μπορεί ένας δείκτης να δείχνει
έναν πίνακα που περιέχει ένα δείκτη στο
ίδιο πίνακα. Αυτός ο πίνακας δεν πρόκειται
να διαγραφεί αν δεν διαγραφούν πρώτα
τα στοιχεία του, ώστε να χαθεί και ο
τελευταίος δείκτης προς αυτόν.
Συνεχίζοντας
την ανάλυση του παραδείγματος, όταν
διαβάσουμε το Ένα(2).μ() θα βρούμε το
δείκτη π1 (η Βάλε βάζει πάντα στην κορυφή
του σωρού, έτσι το τελευταίο που βάζει
διαβάζεται πρώτο). Ο δείκτης αυτός
κρατάει τον παλιό πίνακα του Ένα(1).μ()
και κάνει το Ένα(2).μ() να δείχνει αυτό
που δείχνει και αυτό. Δηλαδή δεν έγινε
καμία αντιγραφή στοιχείων, αλλά άλλαξαν
οι δείκτες εσωτερικά στα Ένα(1).μ() και
Ένα(2).μ.
Ένα(1)=Παλιό
π1=Ένα(1).μ()
π2=Ένα(2).μ()
Βάλε π1, π2
Διάβασε Ένα(1).μ(), Ένα(2).μ()
π1=Ένα(1).μ()
π2=Ένα(2).μ()
Βάλε π1, π2
Διάβασε Ένα(1).μ(), Ένα(2).μ()
Τύπωσε Ένα(1).μ() ' 11 12 13
Τύπωσε Ένα(2).μ() ' 8 9 10
Ενώ τα Α()=Β() και Α()=α_δείκτης κάνουν αντιγραφή στα Α(), τα παρακάτω κάνουν αντιγραφή δείκτη:
α_δείκτης=Α()
Βάλε α_δείκτης : Διάβασε Β()
Όπως και στην εντολή Στη (let) η οποία είναι ένα ζευγάρι Βάλε/Διάβασε (η περισσότερα γιατί παίρνει λίστα εκχωρήσεων), χρησιμοποιείται όταν θέλουμε πρώτα να υπολογιστεί το αριστερό μέρος και μετά το δεξιό. Πχ στο Α(α(&χ))=10*χ το α(&χ) υπολογίζεται και αλλάζει το χ πριν εκτελεστεί το 10*χ, ενώ στην Στη Α(α(&χ))=10*χ πρώτα υπολογίζεται το 10*χ και μετά το α(&χ). για να βγει ο δείκτης του πίνακα.
Στη Β()=α_δείκτης
Όλος ο κώδικας του πρώτου μέρους
Περιέχει μια Σημ (σημείωση) όπου κρύβει μια Προς. Αν αποκαλύψουμε την Προς (goto), με ένα enter μετά την άνω κάτω τελεία, την βάζουμε σε νέα γραμμή, θα επιλεχθεί ο εναλλακτικός κώδικας. Την προς μας βολεύει να την χρησιμοποιούμε για να αφαιρούμε κώδικα, χωρίς να τον διαγράψουμε. Δεν χρειάζεται να φτιάξουμε μια Αν Τότε...είναι υπερβολικό, μια Προς κάνει την δουλειά.
ομάδα άλφα { χ=10 πίνακας μ(3) } άλφα.μ(0)=1,2,3 βήτα=άλφα βήτα.μ(0)=5,6,7 Τύπωσε άλφα.μ() ' 1 2 3 Τύπωσε βήτα.μ() ' 5 6 7 ομάδα δέλτα { Χ=20, Υ=20 } δέλτα=βήτα Τύπωσε δέλτα.μ() ' 5 6 7 δέλτα=άλφα Τύπωσε δέλτα.μ() ' 1 2 3 Τύπωσε δέλτα.χ=10 Συνάρτηση Συν2 { Διάβασε &Κάτι =Κάτι Κάτι.μ(0)=8, 9 ,10 } παλιό=Συν2(&δέλτα) Τύπωσε δέλτα.μ() ' 8 9 10 Τύπωσε παλιό.μ() ' 1 2 3 Συνάρτηση Συν3(δ$) { =εκφρ(δ$) δ$.μ(0)=11, 12, 13 } παλιό=Συν3(&δέλτα) Τύπωσε δέλτα.μ() ' 11, 12, 13 Τύπωσε παλιό.μ() ' 8 9 10 Πίνακας Ένα(100)=δέλτα Τύπωσε Ένα(1).μ() ' 11 12 13 Ένα(2)=Παλιό Τύπωσε Ένα(2).μ() ' 8 9 10 Προς άλλο Για Ένα(1), Ένα(2) { Πίνακας ν() ν()=.μ() .μ()=..μ() ..μ()=ν() } Τύπωσε Ένα(1).μ() ' 8 9 10 Τύπωσε Ένα(2).μ() ' 11 12 13 Σημ : Προς άλλο1 ' χωρίστε το Σημ : με την Προς (Goto) για επιλογή άλλου κώδικα άλλο: Για Ένα(1), Ένα(2) { Πίνακας ν() ν()=Ένα(1).μ() Ένα(1).μ()=Ένα(2).μ() Ένα(2).μ()=ν() } Τύπωσε Ένα(1).μ() ' 8 9 10 Τύπωσε Ένα(2).μ() ' 11 12 13 άλλο1: Βάλε Ένα(1).μ(), Ένα(2).μ() Διάβασε Ένα(1).μ(), Ένα(2).μ() Τύπωσε Ένα(1).μ() ' 11 12 13 Τύπωσε Ένα(2).μ() ' 11 12 13 Ένα(1)=Παλιό π1=Ένα(1).μ() π2=Ένα(2).μ() Βάλε π1, π2 Διάβασε Ένα(1).μ(), Ένα(2).μ() Τύπωσε Ένα(1).μ() ' 11 12 13 Τύπωσε Ένα(2).μ() ' 8 9 10
Δείκτες σε
ομάδες
Το προηγούμενο
παράδειγμα τελείωσε με την χρήση δεικτών
σε πίνακες, όπου αναφέρθηκε και το ζήτημα
του κύκλου ζωής των αντικειμένων με
μέτρηση δεικτών (πριν διαγραφεί ο
τελευταίος δείκτης το σύστημα διαγράφει
το αντικείμενο).
Ένας δείκτης
σε ομάδα κρατάει μια ομάδα ως κλειστό
αντικείμενο. Το ενδιαφέρον εδώ είναι
ότι μπορούμε να έχουμε πολλούς δείκτες
για την ίδια ομάδα. Η ομάδα διαγράφεται
όταν και διαγραφεί και ο τελευταίος
δείκτης της. Για να χρησιμοποιηθεί η
ομάδα σε δείκτη πρέπει να ανοιχτεί όπως
και στους πίνακες. Η νέα έκδοση 9.8 κάνει
τους άλλους δείκτες που δείχνουν σε μια
ομάδα που τι στιγμή εκείνη είναι ανοιχτή
να χρησιμοποιούν την ανοικτή ομάδα.
Στην 9.7 γίνονταν στο ίδιο τμήμα που
ανοίχτηκε η ομάδα, ενώ στην 9.8 γίνεται
οπουδήποτε και αν καλέσουμε και περάσουμε
το δείκτη. Επίσης στην 9.8 μια ομάδα που
είναι σε δείκτη μπορεί να γυρίσει το
δείκτη της. Έτσι το πρώτο πράγμα που θα
δούμε εδώ είναι για μεν τις επώνυμες
ομάδες τι είναι το Αυτό και για τους
δείκτες σε ομάδα τι παραλλαγή έχουν στο
Αυτό.
Κλάση αλφα { χ=10, ψ=20 Τμήμα Βήτα (&συν1()) { Τύπωσε συν1(&αυτό) } } Κλάση βήτα { μετρητής συνάρτηση δέλτα(&κάτι) { .μετρητής++ κάτι.χ+=.μετρητής =.μετρητής } } Α=αλφα() Β=βήτα() Α.Βήτα &Β.δέλτα() ' 1 Α.Βήτα &Β.δέλτα() ' 2 Τύπωσε Α.χ=13 Τύπωσε Β.μετρητής=2
Οι κλάσεις στην Μ2000 είναι συναρτήσεις
που επιστρέφουν κλειστά αντικείμενα
τύπου ομάδα. Όταν ο ορισμός βρίσκεται
σε ένα τμήμα ή μια συνάρτηση δημιουργεί
γενική συνάρτηση με το όνομα της κλάσης,
η οποία διαγράφεται στο τέλος εκτέλεσης
του τμήματος ή συνάρτησης. Όταν ο ορισμός
βρίσκεται σε μια ομάδα ή άλλη κλάση (η
κλάση περιέχει τον ορισμό μιας ομάδας),
τότε η συνάρτηση αποτελεί μέλος της
ομάδας. Εδώ έχουμε δυο κλάσεις που
φτιάχνουν από μια ομάδα, την Α και Β. Η
Α έχει το τμήμα Βήτα το οποίο διαβάζει
με αναφορά μια συνάρτηση. Καλούμε την
Α.Βήτα δυο φορές με συνάρτηση την
Β.δέλτα(). Το τμήμα Βήτα (ή μέθοδος) παίρνει
τη συνάρτηση, τις δίνει ένα όνομα, και
την χρησιμοποιεί βάζοντας το &αυτό,
δηλαδή μια αναφορά στον εαυτό της, το
αντικείμενο που ανήκει, το Α. Η συνάρτηση
εκτελείται αλλά είναι συνδεδεμένη με
το Β οπότε το .μετρητής είναι γνωστό,
και το πλήρες όνομα είναι αυτό.μετρητής,
όπου το αυτό είναι το Β. Πριν όμως να
αυξήσει τον μετρητή, δημιουργεί το κάτι,
ως αναφορά του Α, και έτσι το κάτι.χ είναι
το Α.χ και τέλος επιστρέφει την τιμή του
μετρητή. Για να εκχωρήσουμε τιμές σε
μεταβλητές που είναι μέλη ομάδων εντός
τμημάτων ή συναρτήσεων που είναι μέλη
της ίδιας ομάδας, χρησιμοποιούμε το <=
και όχι το =. Το απλό = δημιουργεί τοπικές
μεταβλητές, ενώ τα μέλη δεν είναι τοπικές
μεταβλητές. Για τους πίνακες δεν
χρησιμοποιούμε το <= γιατί δεν φτιάχνουμε
τοπικούς πίνακες με το =.
Στο τέλος
του παραδείγματος βλέπουμε ότι έχουν
αλλάξει τιμές τόσο στο Α όσο και στο Β.
Προσέξτε εδώ, το μήνυμα στο Α είναι η
μέθοδός του μαζί με όποιες παραμέτρους
έχουμε. Η μέθοδος όμως χρησιμοποιεί μια
συνάρτηση χωρίς να γνωρίζει αν αυτή
είναι απλή συνάρτηση ή συνάρτηση άλλου
αντικειμένου. Αυτό που γνωρίζει είναι
ότι θα την καλέσει δίνοντας τον εαυτό
της ως ισχνή αναφορά, με σκοπό να έχει
μια επιστροφή, όχι μόνο ως τιμή συνάρτησης
αλλά και σε ό,τι προσφέρει η Α (ότι είναι
δημόσιο, θα αναφερθούμε άλλη φορά και
στα ιδιωτικά μέλη). Το ότι άλλαξε τιμή
η Β στο μέλος της με όνομα μετρητής,
είναι άσχετο με την Α.
Ομοίως η Β
δεν γνωρίζει το τύπο αντικειμένου, αλλά
επειδή χρησιμοποιεί το κάτι.χ, εκ των
πραγμάτων το κάτι οφείλει να είναι
ομάδα και να έχει ένα μέλος που να λέγεται
χ και να είναι αριθμός. Αν είχαμε τύπους
ομάδων, και στην αναφορά βάζαμε το τύπο
της ομάδας υποχρεωτικά, θα μας εξυπηρετούσε
αν ετοιμάζαμε κώδικα, μεταφράζοντας
τον πηγαίο κώδικα σε εκτελέσιμο. Η Μ2000
δεν έχει αυτή την διαδικασία μετάφρασης.
Για το λόγο αυτό λέγεται διερμηνευτής,
όπου εκτελεί βάσει του πηγαίου κώδικα
φτιάχνοντας κατά την εκτέλεση ότι
χρειάζεται για να επιτύχει την εκτέλεση,
διαφορετικά βγάζει λάθος. Έτσι αντί το
λάθος να βγει στην εξέταση του τύπου,
βγαίνει για παράδειγμα αν δεν υπάρχει
το χ ως μέλος στο αντικείμενο κάτι. Σε
διαδικασίες μετάφρασης, τα λάθη τύπων
βγαίνουν πριν την εκτέλεση. Οι τύποι
όμως περιορίζουν την ελευθερία στον
προγραμματισμό, κάνουν τον προγραμματιστή
"ρομπότ" που πιστεύει ότι ο τύπος
τον σώνει από λάθη, πράγμα που δεν ισχύει.
Για παράδειγμα μια νεότερη έκδοση του
"γνωστού" τύπου μπορεί να αλλάξει
την συμπεριφορά του προγράμματος. Στο
παράδειγμά μας η Β αν έχει διαφορετική
υλοποίηση στο δέλτα, τότε θα επιφέρει
άλλες αλλαγές στο Α.χ, και αυτό δεν θα
το ξέρουμε αν δεν έχουμε δει στα σίγουρα
το κώδικα και των δυο κλάσεων. Έτσι ακόμα
και οι τύποι δεν θα μας βοηθήσουν αν δεν
έχουμε τον έλεγχο του προγράμματος, του
τι συμβαίνει και πότε.
Πάμε πάλι
στο παράδειγμα και διαγράφουμε από το
Α=άλφα() και μετά, γιατί δεν μπορούμε σε
μια επώνυμη ομάδα να βάλουμε έναν δείκτη
σε ομάδα. Κατόπιν προσθέτουμε τα παρακάτω:
Α->αλφα() Β->βήτα() Για Α, Β { .Βήτα &..δέλτα() '1 .Βήτα &..δέλτα() '2 } Τύπωσε Α=>χ=13 Τύπωσε Β=>μετρητής=2
Τώρα στα Α και Β με τη χρήση του ->
βάζουμε δείκτες στα κλειστά αντικείμενα
που φτιάχνουν οι συναρτήσεις αλφα() και
βήτα(). Ανοίγουμε με την Για {} ένα μπλοκ
για αντικείμενα. Τα αντικείμενα στα Α
και Β θα γίνουν ανοικτά αντικείμενα, θα
συνδεθούν δηλαδή οι μεταβλητές και τα
τμήματα και οι συναρτήσεις με τις λίστες
μεταβλητών και τμημάτων και συναρτήσεων
του διερμηνευτή. Έτσι το &αυτό θα
είναι η ισχνή αναφορά στο ανοικτό
αντικείμενο του Α.
Φτάσαμε
λοιπόν στο σημείο που η 9.8 φέρνει νέα
πράγματα. Αλλάζουμε κάπως τις κλάσεις,
ώστε η Βήτα στην Άλφα να παίρνει δείκτη
σε ομάδα, και η δέλτα στην Βήτα επίσης
να παίρνει δείκτη σε ομάδα. Επίσης άλλαξε
το κάτι.χ σε κάτι=>χ που είναι για τους
δείκτες το ανάλογο. Αντί να περάσουμε
το &αυτό που βγάζει όντως την ισχνή
αναφορά στην ομάδα, περνάμε το δείκτης(αυτό)
που δίνει το δείκτη της ομάδας. Θα
μπορούσαμε να δώσουμε το Μ->αυτό και
να περνάγαμε το Μ. Στο παράδειγμα έχουμε
βάλει το κώδικα σε άλλο τμήμα στο τρέχον
(έστω το Α), και πρώτη εντολή έχουμε την
δημιουργία μιας γενικής, η οποία γίνεται
με το σύμβολο -> και ο αριθμός είναι
τύπου μακρύς (0&) δηλαδή 32bit ακέραιος.
Ακριβώς αυτού του τύπου είναι όλοι οι
δείκτες. Το βελάκι λέει στον διερμηνευτή
ότι η μεταβλητή θα χρησιμοποιηθεί για
δείκτης σε ομάδα. Το 0& λέει ότι θα
πάρει κενή ομάδα. Δηλαδή δεν βάζουμε
πραγματικά το μηδέν στον δείκτη, αλλά
είναι ένα συμβολισμός που δηλώνει ότι
θα έχουμε μια κενή ομάδα. Αν έχουμε δυο
δείκτες με κενές ομάδες, τότε αυτές δεν
θα είναι δείκτες στην ίδια ομάδα, εκτός
και αν ο δεύτερος πήρε το δείκτη του
πρώτου. Γενικά σχεδόν σε όλα τα αντικείμενα
στη Μ2000, δεν έχουμε κενούς δείκτες, αλλά
έχουμε άδεια αντικείμενα, όπως άδειος
πίνακας, άδεια κατάσταση, άδειος σωρός.
Για το καθένα υπάρχει ο συμβολισμός του
άδειου. Πχ Α=Σωρός θα πάρει το Α έναν
άδειο σωρό. Α=Λίστα ή Α=Ουρά θα πάρει το
Α μια λίστα ή μια ουρά (οι δυο τύποι της
κατάστασης), ή Α=(,) θα πάρει το Α το κενό
πίνακα. Θέλουμε λοιπόν από την διεργασία
στο τμήμα δοκ1 να δοθεί κάποια στιγμή ο
δείκτης του αντικειμένου που στο τμήμα
θα το έχει το Β, και εκτός τμήματος, όταν
όλες οι εσωτερικές μεταβλητές, ή οι
τοπικές όπως τις λέμε, θα έχουν σβήσει,
να διαβάσουμε ένα μέλος του, τον μετρητή.
Ο δείκτης οφείλει να δείξει ακόμα και
την πιο πρόσφατη αλλαγή, ακόμα και
"συνδέθηκε" με το αντικείμενο πριν
τις τελευταίες αλλαγές. Ο δείκτης δεικ,
κρατάει το αντικείμενο, όταν τελειώσει
το τμήμα δοκ1
Γενική δεικ->0&
Τμήμα δοκ1 {
Κλάση αλφα {
χ=10, ψ=20
Τμήμα Βήτα (κάππα) {
Τύπωσε κάππα=>δέλτα(δείκτης(αυτό))
}
}
Κλάση βήτα {
μετρητής
συνάρτηση δέλτα(κάτι) {
.μετρητής++
κάτι=>χ+=.μετρητής
=.μετρητής
}
}
Α->αλφα()
Β->βήτα()
Α=>Βήτα Β '1
Α=>Βήτα Β '2
Τύπωσε Α=>χ=13
Τύπωσε Β=>μετρητής=2
Α=>Βήτα Β '3
Α=>Βήτα Β '4
Τύπωσε Α=>χ=20
Τύπωσε Β=>μετρητής=4
Δεικ<=Δείκτης(Β)
}
δοκ1
Τύπωσε δεικ=>μετρητής=4
Είναι σημαντικό εδώ να τονίσουμε ότι
δεν μπορούμε να περάσουμε με αναφορά
μια συνάρτηση ενός αντικειμένου που
βρίσκεται ως κλειστό αντικείμενο. Θα
πρέπει να το ανοίξουμε όπως κάναμε στο
προηγούμενο παράδειγμα. Δεν δουλεύει
το &Β=>δέλτα() αν θέλουμε να το
περάσουμε στο τμήμα κάππα, θα έπρεπε να
κάναμε αυτό Για Β { κάππα &.δέλτα() }
διότι μέσα στις αγκύλες το Β είναι
ανοικτό, έχει όνομα παρόλο που δεν το
βλέπουμε, και το χρησιμοποιούμε με την
απλή τελεία πριν το δέλτα().
Ορισμένες
γλώσσες όπως η julia αποφεύγουν την χρήση
μεθόδων μέσα στα αντικείμενα. Για το
σκοπό αυτό χρησιμοποιούν τους δείκτες
των αντικειμένων. Θα κάνουμε παρακάτω
το ίδιο, στο προηγούμενο παράδειγμα. Θα
μεταφέρουμε εκτός κλάσεων το τμήμα βήτα
και το τη συνάρτηση δέλτα(). Θα βάλουμε
την δέλτα() μέσα στο τμήμα βήτα ως τοπική
συνάρτηση.
Το πρόγραμμα
κάνει ότι και το παραπάνω αλλά γίνεται
πιο δυσνόητο. Εκεί που είχαμε το αυτό,
τώρα έχουμε δείκτες τοπικούς με την
λέξη εκείνο. Και το τμήμα και η συνάρτηση
καλούνται με πρώτη παράμετρο ένα δείκτη
σε αντικείμενο. Εδώ έχει προκύψει και
το άλλο γεγονός ότι και η δεύτερη
παράμετρος είναι δείκτης σε αντικείμενο
και στα δύο, στο τμήμα και στη συνάρτηση.
Είναι σαν κάποιος να μας μπερδεύει με
την τράπουλα. Ποιο είναι το εκείνο, ποιο
είναι το κάππα, ποιο θα γίνει το εκείνο
στη δέλτα. Ασφαλώς αν το περάσουμε δυο
τρεις φορές, και το διαβάσουμε καλά, θα
βρούμε την άκρη, αλλά είναι χρονοβόρο.
Όσο αφαιρούμε από τα αντικείμενα
μεθόδους τόσο περισσότερο δυσκολεύουμε
το πρόγραμμα.
Γενική δεικ->0& Τμήμα δοκ1 { Κλάση αλφα { χ=10, ψ=20 } Κλάση βήτα { μετρητής } Τμήμα Βήτα (εκείνο, κάππα) { συνάρτηση δέλτα(εκείνο, κάτι) { δεικ<=εκείνο Για εκείνο { .μετρητής++ κάτι=>χ+=.μετρητής =.μετρητής } } Τύπωσε δέλτα(κάππα, εκείνο) } Α->αλφα() Β->βήτα() Βήτα Α, Β '1 Βήτα Α, Β '2 Τύπωσε Α=>χ=13 Τύπωσε Β=>μετρητής=2 Βήτα Α, Β '3 Βήτα Α, Β '4 Τύπωσε Α=>χ=20 Τύπωσε Β=>μετρητής=4 } δοκ1 Τύπωσε δεικ=>μετρητής=4
Ένα πράγμα
που ίσως δεν σκέφτηκε ο αναγνώστης,
είναι η μετακίνηση κώδικα. Με την αναφορά
σε συνάρτηση ο κώδικας μετακινείται
προς το βάθος μόνο. Αν εξαιρέσουμε τις
επώνυμες ομάδες που παραμένουν στο τόπο
"γέννησής" τους, μέχρι το θάνατο,
οι δείκτες σε ομάδες καθώς και οι κλειστές
ομάδες που βρίσκονται σε άλλα αντικείμενα,
όπως πίνακες, σωρούς και καταστάσεις,
μπορούν να πάνε οπουδήποτε. Στο προηγούμενο
παράδειγμα η Β έδωσε τον δείκτη της στην
δεικ και "σώθηκε" από την διαγραφή
από το τερματισμό του εσωτερικού τμήματος
δοκ1.
Επίσης
παρατηρούμε ότι από ένα τμήμα έστω Α
(όπου γράφουμε το παράδειγμα) έχουμε
ένα επίπεδο το Δοκ1, μετά άλλο ένα το
τμήμα Βήτα και μετά άλλο ένα τη συνάρτηση
δέλτα(). Κάθε τμήμα/συνάρτηση στη Μ2000
δημιουργεί ένα όνομα χώρου, και εκεί
μπορεί να φανούν μόνο οι τοπικές ή οι
γενικές αλλά όχι οι τοπικές άλλων
τμημάτων. Υπάρχει τρόπος να φέρουμε τη
θέαση μέσα στο τμήμα από άλλο, τον οποίο
θα δούμε στα γεγονότα, εκεί δηλαδή που
χρειάζεται κάτι τέτοιο. Μπορούμε όμως
να χρησιμοποιήσουμε Ρουτίνες.
Οι ρουτίνες
είναι μέρη ενός τμήματος ή μιας συνάρτησης
και μπορούν να βλέπουν τις μεταβλητές
του "πατρικού" . Επίσης έχουν και
αυτές την ίδια ιδιότητα με τα τμήματα
και τις συναρτήσεις, να καθαρίζουν τις
δικές τους τοπικές μεταβλητές, ακόμα
και ότι άλλο φτιάχνουμε. Στο παράδειγμα
έχουμε την συνάρτηση μέσα στη ρουτίνα,
αλλά θα μπορούσαμε να το έχουμε απλά
μέσα στο τμήμα και έξω από τη ρουτίνα,
και πάλι θα έβλεπε η ρουτίνα (εφόσον
είχε περάσει η εκτέλεση από τον ορισμό
της συνάρτησης). Οι ρουτίνες μπαίνουν
στο τέλος, και εδώ στη πρώτη κλήση γίνεται
αναζήτηση, και γράφεται η θέση της ώστε
να κληθεί γρήγορα μετά. Ρουτίνες μέσα
σε ρουτίνες δεν έχει νόημα να βάλουμε.
Όλες οι ρουτίνες βλέπουν τις άλλες,
αφενός και επειδή μόνο ο δείκτης τους
(η θέση στο κώδικα) μπαίνει σε λίστα. Το
προσόν των ρουτινών είναι ότι έχουν
μεγάλο αριθμό βάθους αναδρομής. Ξεκινά
από το 10000, και μπορούμε με την Όριο.Αναδρομής
να το αλλάξουμε ας πούμε στο 100000. Σε
αντικείμενα βάζουμε τμήματα ή συναρτήσεις,
και σε αυτά μπορούμε να έχουμε ρουτίνες.
Γενική δεικ->0& Τμήμα δοκ1 { Κλάση αλφα { χ=10, ψ=20 } Κλάση βήτα { μετρητής } α->αλφα() Β->βήτα() Ζήτα(α, Β) '1 Ζήτα(α, Β) '2 Τύπωσε α=>χ=13 Τύπωσε Β=>μετρητής=2 Ζήτα(α, Β) '3 Ζήτα(α, Β) '4 Τύπωσε α=>χ=20 Τύπωσε Β=>μετρητής=4 Ρουτίνα Ζήτα(εκείνο, κάππα) συνάρτηση δέλτα(εκείνο, κάτι) { δεικ<=εκείνο Για εκείνο { .μετρητής++ κάτι=>χ+=.μετρητής =.μετρητής } } Τύπωσε δέλτα(κάππα, εκείνο) Τέλος Ρουτίνας } δοκ1 Τύπωσε δεικ=>μετρητής=4
Ακολουθεί
ένα πρόγραμμα με αγγλικές εντολές (της
Μ2000), όπου έχουμε μια συνδεδεμένη λίστα
με δείκτες ομάδων, από το info.gsb που
περιέχεται στην εγκατάσταση της Μ2000.
Αρκεί όταν γίνει η εγκατάσταση να
γράψουμε dir appdir$ <enter> και load info <ender>
τέλος πατάμε το F1 και σώνεται το αρχείο
τμημάτων, στο φάκελο χρήστη. Την επόμενη
φορά αρκεί το load info, και το φέρνει στη
μνήμη.
Στην αποδόμηση
της λίστας χρησιμοποιούμε ένα τρόπο
που μας δείχνει αν ένα αντικείμενο
διαγράφτηκε. Το remove { } μέλος καλείται
κατά την δοκιμή με την clear και μόνο. Εάν
είναι ο τελευταίος δείκτης τότε πριν
διαλύσει την ομάδα καλεί την remove.
Επίσης
βλέπουμε την ετικέτα Class: όπου μετά από
αυτή ότι γράφουμε δεν θα υπάρχει στην
επιστροφή ομάδας από τη κλάση. Μέσα εκεί
έχουμε τον κατασκευαστή, με ίδιο όνομα
με την κλάση. αλλά εδώ ως τμήμα. Στον
κατασκευαστή δεχόμαστε μια παράμετρο
αν γίνει match() με το τύπο της εισαγωγής
που επιθυμούμε.
\\ This example use
pointers to groups.
\\ Class Node
\\ Node type has two pointers to groups: pred and succ
\\ Class
\\ Class Node
\\ Node type has two pointers to groups: pred and succ
\\ Class
Module Checkit {
Form 80, 50
Class Null {}
Global Null->Null()
Class Node {
group pred, succ
dat=0
Remove {
Print "destroyed", .dat
}
class:
module Node {
.pred->Null
.succ->Null
if match("N") Then Read .dat
}
}
Class LList {
Group Head, Tail
Module PushTail(k as pointer) {
if .Tail is Null then {
.Head<=k
.Tail<=k
} else {
n=.Tail
.Tail<=k
k=>pred=n=>pred
n=>pred=k
k=>succ=n
}
}
Function RemoveTail {
n=.Tail
if n is .Head then {
.Head->Null
.Tail->Null
} Else {
.Tail<=n=>succ
.Tail=>pred=n=>pred
n=>pred->Null
}
for n {
.succ->Null
.pred->Null
}
=n
}
Module PushHead(k as pointer) {
if .head is Null then {
.Head<=k
.Tail<=k
} else {
n=.head
.head<=k
k=>succ=n=>succ
n=>succ=k
k=>pred=n
}
}
Function RemoveHead {
n=.Head
if n is .Tail then {
.Head->Null
.Tail->Null
} Else {
.Head<=n=>pred
.Head=>succ=n=>succ
n=>succ->Null
}
for n {
.succ->Null
.pred->Null
}
=n
}
Module RemoveNode(k as pointer) {
pred=k=>pred
succ=k=>succ
if pred is succ then {
if .head is k else Error "Can't remove this node"
k=.RemoveHead()
clear k
} else {
pred=>succ=succ
succ=>pred=pred
}
}
Module InsertAfter(k as pointer, n as pointer) {
pred=k=>pred
n=>pred=pred
n=>succ=k
pred=>succ=n
k=>pred=n
}
Function IsEmpty {
= .Head is null or .tail is null
}
class:
Module LList {
.Head->Null
.Tail->Null
}
}
m->Node(100)
L=LList()
L.PushTail m
If not L.Head is Null then Print L.Head=>dat=100
for i=101 to 103 {
m->Node(i)
L.PushTail m
Print "ok....", i
}
for i=104 to 106 {
m->Node(i)
L.PushHead m
Print "ok....", i
}
Print "Use Head to display from last to first"
m=L.Head
do {
Print m=>dat
m=m=>pred
} Until m is null
Print "ok, now find 3rd and remove it"
m1=L.Head
i=1
Index=3
While i<Index {
if m1 is null then exit
m1=m1=>pred
i++
}
If i<>Index then {
Print "List has less than "; Index;" Items"
} Else {
Print "First add one new node"
newNode->Node(1000)
L.InsertAfter m1, newNode
L.RemoveNode m1
clear m1 ' last time m1 used here
newNode=Null
Print "ok.............."
}
Print "Use Tail to display from first to last"
m=L.Tail
do {
Print m=>dat
m=m=>succ
} Until m is null
useother=True
While not L.IsEmpty(){
For This {
\\ we have to use a temporary variable name, here A
A=If(useother->L.RemoveTail(),L.RemoveHead())
? A=>dat
useother~
\\ now we can try to perform removing
clear A
}
}
Print "list is empty:"; L.IsEmpty()
}
Checkit
Υπάρχουν και άλλα αντικείμενα που μας εξυπηρετούν για δομές. Όπως το παράδειγμα παρακάτω που δείχνει τους τέσσερις τόπους προσπέλασης ενός δυαδικού δένδρου, με τέσσερις παραλλαγές προγραμμάτων.
Report
{
Tree traversal
1
/ \
/ \
/ \
2 3
/ \ /
4 5 6
/ / \
7 8 9
Tree traversal
1
/ \
/ \
/ \
2 3
/ \ /
4 5 6
/ / \
7 8 9
}
Pen 15 {Print "Using tuple (arrays)"}
Module CheckIt {
Null=(,)
Tree=((((Null,7,Null),4,Null),2,(Null,5,Null)),1,(((Null,8,Null),6,(Null,9,Null)),3,Null))
Module preorder (T) {
Print "preorder: ";
printtree(T)
sub printtree(T)
Print T#val(1);" ";
If len(T#val(0))>0 then printtree(T#val(0))
If len(T#val(2))>0 then printtree(T#val(2))
end sub
}
preorder Tree
Module inorder (T) {
Print "inorder: ";
printtree(T)
sub printtree(T)
If len(T#val(0))>0 then printtree(T#val(0))
Print T#val(1);" ";
If len(T#val(2))>0 then printtree(T#val(2))
end sub
}
inorder Tree
Module postorder (T) {
Print "postorder: ";
printtree(T)
sub printtree(T)
If len(T#val(0))>0 then printtree(T#val(0))
If len(T#val(2))>0 then printtree(T#val(2))
Print T#val(1);" ";
end sub
}
postorder Tree
Module level_order (T) {
Print "level-order: ";
Stack New {
printtree(T)
if empty then exit
Read T
Loop
}
sub printtree(T)
If Len(T)>0 then
Print T#val(1);" ";
Data T#val(0), T#val(2)
end if
end sub
}
level_order Tree
}
CheckIt
Pen 15 {Print "Using OOP - passing objects to modules"}
Module OOP {
\\ Class is a global function (until this module end)
Class Null {
}
\\ Null is a pointer to an object returned from class Null()
Global Null->Null()
Class Node {
Public:
x, Group LeftNode, Group RightNode
Class:
\\ after class: anything exist one time,
\\ not included in final object
Module Node {
.LeftNode<=Null
.RightNode<=Null
Read .x
\\ read ? for optional values
Read ? .LeftNode, .RightNode
}
}
\\ NodeTree return a pointer to a new Node
Function NodeTree {
\\ ![] pass currrent stack to Node()
->Node(![])
}
Tree=NodeTree(1, NodeTree(2,NodeTree(4, NodeTree(7)), NodeTree(5)), NodeTree(3, NodeTree(6, NodeTree(8), NodeTree(9))))
Module preorder (T) {
Print "preorder: ";
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
Print T=>x;" ";
printtree(T=>LeftNode)
printtree(T=>RightNode)
end sub
}
preorder Tree
Module inorder (T) {
Print "inorder: ";
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
Print T=>x;" ";
printtree(T=>RightNode)
end sub
}
inorder Tree
Module postorder (T) {
Print "postorder: ";
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
printtree(T=>RightNode)
Print T=>x;" ";
end sub
}
postorder Tree
Module level_order (T) {
Print "level-order: ";
Stack New {
printtree(T)
if empty then exit
Read T
Loop
}
sub printtree(T as pointer)
If T is Null else
Print T=>x;" ";
Data T=>LeftNode, T=>RightNode
end if
end sub
}
level_order Tree
}
OOP
Pen 15 {Print "Using OOP - modules are members of objects "}
Module OOP {
\\ Class is a global function (until this module end)
Class Null {
}
\\ Null is a pointer to an object returned from class Null()
Global Null->Null()
Class Node {
Public:
x, Group LeftNode, Group RightNode
Module preorder (visitor){
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
call visitor(T=>x)
printtree(T=>LeftNode)
printtree(T=>RightNode)
end sub
}
Module inorder (visitor){
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
call visitor(T=>x)
printtree(T=>RightNode)
end sub
}
Module postorder (visitor) {
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
printtree(T=>RightNode)
call visitor(T=>x)
end sub
}
Module level_order (visitor){
T->This
Stack New {
printtree(T)
if empty then exit
Read T
Loop
}
sub printtree(T as pointer)
If T is Null else
call visitor(T=>x)
Data T=>LeftNode, T=>RightNode
end if
end sub
}
Class:
\\ after class: anything exist one time,
\\ not included in final object
Module Node {
.LeftNode<=Null
.RightNode<=Null
Read .x
\\ read ? for optional values
Read ? .LeftNode, .RightNode
}
}
\\ NodeTree return a pointer to a new Node
Function NodeTree {
\\ ![] pass currrent stack to Node()
->Node(![])
}
Tree=NodeTree(1, NodeTree(2,NodeTree(4, NodeTree(7)), NodeTree(5)), NodeTree(3, NodeTree(6, NodeTree(8), NodeTree(9))))
printnum=lambda (title$) -> {
Print title$;
=lambda (x)-> {
Print x;" ";
}
}
Tree=>preorder printnum("preorder: ")
Tree=>inorder printnum("inorder: ")
Tree=>postorder printnum("postorder: ")
Tree=>level_order printnum("level-order: ")
}
OOP
Pen 15 {Print "OOP - Event object as visitor"}
Module OOP {
\\ Class is a global function (until this module end)
Class Null {
}
\\ Null is a pointer to an object returned from class Null()
Global Null->Null()
Class Node {
Public:
x, Group LeftNode, Group RightNode
Module preorder (visitor){
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
call event visitor, T=>x
printtree(T=>LeftNode)
printtree(T=>RightNode)
end sub
}
Module inorder (visitor){
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
call event visitor, T=>x
printtree(T=>RightNode)
end sub
}
Module postorder (visitor) {
T->This
printtree(T)
sub printtree(T as pointer)
If T is Null then Exit sub
printtree(T=>LeftNode)
printtree(T=>RightNode)
call event visitor, T=>x
end sub
}
Module level_order (visitor){
T->This
Stack New {
printtree(T)
if empty then exit
Read T
Loop
}
sub printtree(T as pointer)
If T is Null else
call event visitor, T=>x
Data T=>LeftNode, T=>RightNode
end if
end sub
}
Class:
\\ after class: anything exist one time,
\\ not included in final object
Module Node {
.LeftNode<=Null
.RightNode<=Null
Read .x
\\ read ? for optional values
Read ? .LeftNode, .RightNode
}
}
\\ NodeTree return a pointer to a new Node
Function NodeTree {
\\ ![] pass currrent stack to Node()
->Node(![])
}
Tree=NodeTree(1, NodeTree(2,NodeTree(4, NodeTree(7)), NodeTree(5)), NodeTree(3, NodeTree(6, NodeTree(8), NodeTree(9))))
Event PrintAnum {
read x
}
Function PrintThis(x) {
Print x;" ";
}
Event PrintAnum New PrintThis()
printnum=lambda PrintAnum (title$) -> {
Print title$;
=PrintAnum
}
Tree=>preorder printnum("preorder: ")
Tree=>inorder printnum("inorder: ")
Tree=>postorder printnum("postorder: ")
Tree=>level_order printnum("level-order: ")
}
OOP