Προσπαθούμε να δώσουμε τις βασικές αρχές προγραμματισμού μέσα σε τρεις δίωρες διαλέξεις.
Η γλώσσα που χρησιμοποιείται είναι η qbasic. Η "αρχαία" αυτή γλώσσα επελέγη εν πολλοίς επειδή είναι ταυτόχρονα εύχρηστη και το περιβάλλον προγραμματισμού της δεν είναι πολύ φορτωμένο όπως τα περισσότερα περιβάλλοντα προγραμματισμού σήμερα. Πραγματικά το περιβάλλον προγραμματισμού της qbasic περιλαμβάνει ότι χρειάζεται για αυτά που θέλουμε να μάθουμε σε αυτές τις 6 ώρες χωρίς να είναι υπερφορτωμένο (π.χ. όπως αυτό το περιβάλλον μιας σύγχρονης BASIC). Τέλος, η qbasic διατίθεται δωρεάν και μπορεί να τρέξει σχεδόν σε οποιοδήποτε σημερινό υπολογιστή, με οποιοδήποτε λειτουργικό σύστημα (αρκεί να διαθέτει ένα DOS emulator).
Είναι μια εξελιγμένη μορφή (1985) της γλώσσας BASIC (Beginner's All-purpose Symbolic Instruction Code) η οποία υπάρχει από to 1964 και για πάρα πολλά χρόνια υπήρξε η πιο διαδεδομένη γλώσσα προγραμματισμού για μικρούς "οικιακούς" υπολογιστές και ήταν η γλώσσα με την οποία πάρα πολλά άτομα της δικιάς μου γενιάς έμαθαν να προγραμματίζουν υπολογιστές. (Όχι όμως εγώ, που έμαθα να προγραμματίζω σε ένα Apple IIe με τη γλώσσα PASCAL σε ένα σεμινάριο του Πανεπιστημίου Κρήτης το 1983 σε μαθητές Λυκείου.)
Η γλώσσα qbasic (για να είμαστε λίγο πιο ακριβείς, ο interpreter για τη γλώσσα qbasic, το πρόγραμμα δηλ. που διαβάζει το πρόγραμμα qbasic που γράφουμε και το εκτελεί) γράφτηκε για το λειτουργικό σύστημα DOS, τη δεκαετία του 1980. Το DOS δεν χρησιμοποιείται πια αλλά πολλά προγράμματα που γράφτηκαν κάποτε για το DOS μπορούμε να τα τρέχουμε σήμερα στα μοντέρνα λειτουργικά συστήματα (π.χ. Windows XP, Linux) χρησιμοποιώντας κάποιον προσομοιωτή (emulator).
Ξεκινείστε λοιπόν κατεβάζοντας τα τρία αρχεία που θα βρείτε εδώ και αποθηκεύσετέ τα σε ένα directory (κατάλογο) του συστήματός σας, κατά προτίμηση ένα directory που έχετε δημιουργήσει ειδικά για αυτό το σκοπό και είναι αρχικά κενό.
Περιηγηθείτε στον υπολογιστή σας με τον Windows Explorer και πηγαίνετε στον κατάλογο όπου είναι τα αρχεία αυτά. Κάντε διπλό κλικ στο QBASIC.EXE για να τρέξετε το πρόγραμμα.
Για να τρέξετε την qbasic σε λειτουργικό σύστημα Linux πρέπει πρώτα να εγκαταστήσετε (αν δεν υπάρχει ήδη εγκατεστημένος) έναν DOS emulator (ένα πρόγραμμα δηλ. που ποσομοιώνει ένα υπολογιστή που τρέχει το παλιό λειτουργικό σύστημα DOS) όπως π.χ. τον dosemu. Πρέπει συνεπώς να εγκαταστήσετε πρώτα το «πακέτο» dosemu. Αυτό γίνεται συνήθως από τον package manager του συστήματος. Για παράδειγμα, αν το Linux που χρησιμοποιείτε είναι το Ubuntu (ή άλλο που βασίζεται στο Debian) τότε αρκεί να δώσετε την εντολή
Αφού εγκαταστήσετε το dosemu το τρέχετε (στο command line δίνετε xdosemu) και πηγαίνετε μέσα από το προσομοιωμένο DOS στον κατάλογο όπου έχετε αποθκεύσει την qbasic. Εκεί δίνετε την εντολή qbasic.
1 CLS 2 PRINT "Give me a number: " 3 INPUT n 4 PRINT SQR(n)
Το αρχείο sq.bas
Ένα πρόγραμμα στη γλώσσα BASIC και στις περισσότερες άλλες γλώσσες προγραμματισμού είναι μια ακολουθία από εντολές οι οποίες εκτελούνται από τον υπολογιστή η μια μετά την άλλη. Στο πρόγραμμα sq.bas που φαίνεται παραπάνω υπάρχουν 4 εντολές, μια σε κάθε γραμμή (οι αριθμοί μπροστά από τις γραμμές δεν υπάρχουν πραγματικά στο πρόγραμμα αλλά τις έχουμε βάλει μέσα για να μπορούμε να αναφερόμαστε στις γραμμές με κάποιο τρόπο).
Η εντολή CLS καθαρίζει την οθόνη του υπολογιστή και τη χρησιμοποιούμε ως πρώτη εντολή σχεδόν σε κάθε πρόγραμμα από αυτά που θα γράψουμε. Στη γραμμή 2 η εντολή PRINT τυπώνει στην οθόνη το μήνυμα που ακολουθεί εντός εισαγωγικών. Το μήνυμα αυτό προτρέπει το χρήστη του προγράμματος να δώσει ένα αριθμό (πληκτρολογώντας τον στο πληκτρολόγιο). Ο αριθμός αυτός διαβάζεται από τον υπολογιστή στην επόμενη εντολή INPUT n, και τοποθετείται στη μεταβλητή n.
Η έννοια της μεταβλητής είναι κεντρική στον προγραμματισμό. Μπορούμε να φανταζόμαστε μια μεταβλητή ως ένα κουτί που έχει ένα όνομα (n στην περίπτωση αυτή) και κάποια περιεχόμενα. Μετά την εκτέλεση της εντολής της γραμμής 3 τα περιεχόμενα του κουτιού n είναι ο αριθμός που έδωσε ο χρήστης.
Τέλος στη γραμμή 4 εκτυπώνεται στην οθόνη η τετραγωνική ρίζα του αριθμού που έδωσε ο χρήστης, η οποία υπολογίζεται από την έκφραση SQR(n).
Στο πρόγραμμα αυτό υπάρχουν κάποιες λέξεις που είναι δεσμευμένες από την BASIC. Αυτές οι λέξεις έχουν ειδικό νόημα για τη γλώσσα και δεν επιτρέπεται να τις χρησιμοποιήσουμε αλλιώς, π.χ. ως ονόματα μεταβλητών. Οι δεσμευμένες λέξεις που εμφανίζονται στο πρόγραμμα αυτό είναι οι CLS, PRINT, INPUT, SQR. Οι τρεις πρώτες είναι εντολές (για καθάρισμα οθόνης, γράψιμο στην οθόνη και διάβασμα από το πληκτρολόγιο αντίστοιχα) και η τελευταία είναι συνάρτηση (της δίνουμε ένα αριθμό και μας επιστρέφει την τετραγωνική του ρίζα).
Άσκηση 1.Αλλάξτε την γραμμή 4 σε: PRINT SQR(n), n*n. Τι κάνει τώρα το πρόγραμμα;
Άσκηση 2.Γράψτε ένα πρόγραμμα το οποίο να διαβάζει από το χρήστη τα μήκη των δύο κάθετων πλευρών ορθογωνίου τριγώνου και να υπολογίζει το μήκος της υποτείνουσας.
1 CLS 2 3 ' Data entry below 4 5 t1input: 6 7 PRINT "Give T1: " 8 INPUT t1 9 IF (t1 < 0) OR (t1 > 10) THEN 10 PRINT "Error" 11 GOTO t1input 12 END IF 13 14 t2input: 15 16 PRINT "Give T2: " 17 INPUT t2 18 IF (t2 < 0) OR (t2 > 10) THEN 19 PRINT "Error" 20 GOTO t2input 21 END IF 22 23 finput: 24 25 PRINT "Give F: " 26 INPUT f 27 IF (f < 0) OR (f > 10) THEN 28 PRINT "Error" 29 GOTO finput 30 END IF 31 32 'First case 33 IF f < 3 THEN 34 PRINT "Sorry, next time." 35 END 36 END IF 37 38 'Second case 39 f1 = (2 / 3) * f + t1 / 6 + t2 / 6 40 IF f1 > f THEN 41 grade = f1 42 ELSE 43 grade = f 44 END IF 45 46 ' output 47 PRINT "Your grade is", grade
Το αρχείο grades.bas
Κάποιος καθηγητής ακολουθεί το εξής βαθμολογικό σύστημα σε ένα μάθημά του: οι φοιτητές του
γράφουν δύο ενδιάμεσα διαγωνίσματα και ένα τελικό διαγώνισμα, έστω τους βαθμούς , και .
Αν τότε ο βαθμός είναι
Το παραπάνω πρόγραμμα υπολογίζει τον τελικό βαθμό δοθέντων των , και .
Τι καινούργια στοιχεία υπάρχουν εδώ;
Κατ' αρχήν οι γραμμές με τα σχόλια, αυτές δηλ. που αρχίζουν
με το χαρακτήρα '
(single quote), στις γραμμές 3, 32, 38, 46. Οι γραμμές αυτές παίζουν μόνο διακοσμητικό
ρόλο μέσα στο πρόγραμμα και ο υπολογιστής τις αγνοεί. Συνεισφέρουν όμως τα μέγιστα στο να είναι ένα πρόγραμμα
ευανάγνωστο, και είναι ένας από τους σημαντικότερους κανόνες καλού προγραμματισμού (και σίγουρα αυτός που
παραβιάζεται συχνότερα, πάνω στη βιασύνη μας) το να έχει ένα πρόγραμμα αρκετά σχόλια γραμμένα μέσα του.
Είναι πολύ συνηθισμένο να διαβάζει κανείς προγράμματα που έχει γράψει ο ίδιος πριν από μερικά χρόνια και, επειδή
δεν είχε τότε κάνει τον κόπο να γράψει αρκετά σχόλια μέσα, να μην τα καταλαβαίνει.
Το επόμενο νέο στοιχείο είναι οι ετικέτες (labels) στις γραμμές 5, 14, 23. Οι ετικέτες δεν είναι ακριβώς εντολές του προγράμματος. Αποτελούν ουσιαστικά την ονομασία μιας θέσης μέσα στο πρόγραμμα. Για παράδειγμα η ετικέτα t1input: περιγράφει τη θέση του προγράμματος αμέσως πριν την εντολή PRINT "Give T1: ". Όταν εκτελεστεί από τον υπολογιστήη γραμμή 11, GOTO t1input, τότε ο έλεγχος (η τρέχουσα θέση δηλαδή του προγράμματος) μεταβιβάζεται αμέσως στη θέση t1input: και έτσι η επόμενη εντολή που εκτελείται είναι η γραμμή 7.
Το επόμενο σοβαρό νέο στοιχείο που βλέπουμε είναι η σύνθετη εντολή IF ... THEN ... END IF καθώς και η IF ... THEN ... ELSE ... END IF (εκτέλεση εντολών υπό συνθήκη).
Ας πάρουμε π.χ. τις γραμμές 33-36. Οι εντολές στις γραμμές 34, 35 εκτελούνται μόνο αν η συνθήκη του IF, δηλ. το f<3 είναι αληθής (στη μεταβλητή f αποθηκεύουμε το βαθμό του τελικού διαγωνίσματος που μας έχει δώσει ο χρήστης στις γραμμές 25-30). Σε αυτή την περίπτωση εκτυπώνεται ένα μήνυμα (Sorry, next time.) και το πρόγραμμα σταματάει (εντολή END).
Ας δούμε λίγο τι γίνεται στις γραμμές 5-12 (και παρόμοια στις γραμμές 14-21, όπου διαβάζεται από το χρήστη η μεταβλητή t1, και στις γραμμές 23-30, όπου διαβάζεται η f). Στη γραμμή 7 τυπώνεται ένα μήνυμα και στην 8 διαβάζουμε από τη πληκτρολόγιο το βαθμού του πρώτου διαγωνίσματος και τον αποθηκεύουμε στη μεταβλητή t1. Στις γραμμές 9-12 ελέγχουμε αν η τιμή της t1 είναι εκτός ορίων (είναι δηλ. <0 ή >10) και σε αυτή την περίπτωση τυπώνουμε ένα μήνυμα (Error) και αμέσως μετά, με την εντολή GOTO t1input), o έλεγχος μεταβιβάζεται στη γραμμή 7, και έτσι ο χρήστης έχει την ευκαιρία να διορθώσει το λάθος του και να δώσει μια τιμή για το εντός των επιτρεπτών ορίων. Αν και πάλι αποτύχει το πρόγραμμα τον ξαναστέλνει πίσω, επ' άπειρον.
Στη γραμμή 39 υπολογίζουμε στη μεταβλητή f1 την τιμή της έκφρασης .
Στο IF ... THEN ... ELSE ... END IF των γραμμών 40-44 κοιτάμε αν f1>f και σε αυτή την περίπτωση βάζουμε στη μεταβλητή grade την τιμή της f1, αλλιώς της βάζουμε την τιμή της f.
Πρέπει επίσης να κάνουμε την παρατήρηση εδώ ότι όταν ο έλεγχος φτάσει στη γραμμή 39 είμαστε σίγουροι ότι δεν ισχύει f<3 αφού σε αυτή την περίπτωση το πρόγραμμα θα είχε τελειώσει με την εκτέλεση της γραμμής 35.
Τέλος, στη γραμμή 47, τυπώνουμε δύο πράγματα. Το κείμενο (string) "Your grade is" (χωρίς τα εισαγωγικά, τα οποία δεν τυπώνουνται και βρίσκονται εκεί μόνο για να υποδηλώσουν τα όρια του string) ακολουθούμενο από ένα αριθμό, την τιμή της μεταβλητής grade. Τυπώνονται το ένα δίπλα στο άλλο χωρίς να αλλάξει γραμμή ανάμεσά τους.
Άσκηση 3.Τροποποιείστε το πρόγραμμα grades.bas ώστε να υπολογίζει τον τελικό βαθμό αν αυτός δίδεται από τον τύπο (1), αλλά μόνο στην περίπτωση που όλοι οι βαθμοί είναι τουλάχιστον 3, αλλιώς ο τελικός βαθμός είναι 0.
1 CLS 2 3 start: 4 5 ' data input 6 PRINT "Give the coefficients A, B, C: " 7 INPUT a, b, c 8 9 ' linear equation 10 IF a = 0 THEN 11 IF NOT (b = 0) THEN 12 PRINT "Single root: ", -c / b 13 GOTO check 14 ELSE 15 IF c = 0 THEN 16 PRINT "All real numbers are roots." 17 GOTO check 18 ELSE 19 PRINT "No roots." 20 GOTO check 21 END IF 22 END IF 23 END IF 24 25 ' quadratic equation 26 d = b ^ 2 - 4 * a * c 27 IF d < 0 THEN 28 PRINT "No roots (discriminant ="; d; ")" 29 GOTO check 30 END IF 31 32 IF d = 0 THEN 33 PRINT "One double root: "; -b / (2 * a) 34 GOTO check 35 END IF 36 37 ' Here the discriminant is positive 38 r1 = (-b + SQR(d)) / (2 * a) 39 r2 = (-b - SQR(d)) / (2 * a) 40 41 PRINT "Two roots: "; r1; " and "; r2 42 43 check: 44 45 PRINT "Give 1 to continue, 0 to stop: " 46 INPUT ans 47 IF ans = 1 THEN 48 GOTO start 49 END IF
Το αρχείο q.bas
Το άνω πρόγραμμα διαβάζει από το χρήστη τους συντελεστές της γενικής τετραγωνικής
εξίσωσης
Αυτή η επανάληψη του προγράμματος επιτυγχάνεται με τις γραμμές 43-49 που αρχίζουν με την ετικέτα check:. Στη γραμμή 49 ρωτάμε το χρήστη αν θέλει να συνεχίσει. Πρέπει να απαντήσει με τον αριθμό 1 αν ναι και με τον αριθμό 0 αν όχι. Διαβάζουμε την απάντησή του στη μεταβλητή ans και στο IF που ακολουθεί ελέγχουμε αν έχει δώσει 1, και αν ναι ξαναπηγαίνουμε στην αρχή του προγράμματος (ετικέτα start:). Αν όχι τότε το πρόγραμμα τελειώνει εφόσον αυτό το IF είναι και η τελευταία (σύνθετη) εντολή στο πρόγραμμα.
Έχοντας βάλει την ετικέτα start κάτω από την εντολή CLS επιτυγχάνουμε να μην καθαρίζει η οθόνη σε κάθε επανάληψη του προγράμματος μια και ο χρήστης ίσως να θέλει να βλέπει και τα αποτελέσματα της προηγούμενης επίλυσης. Αν θέλαμε να καθαρίζει η οθόνη σε κάθε επανάληψη τότε θα βάζαμε την ετικέτα start: πριν την εντολή CLS.
Στις γραμμές 6 και 7 διαβάζουμε τους συντελεστές της εξίσωσης και τους βάζουμε στις μεταβλητές a, b, c. Παρατηρείστε ότι χρησιμοποιούμε την εντολή INPUT μόνο μια φορά και όχι τρεις, αλλά με τρία ορίσματα (έχει αυτή τη δυνατότητα, να παίρνει δηλ. περισσότερα του ένα ορίσματα).
Στις γραμμές 10-23 αντιμετωπίζουμε την περίπτωση οπότε έχουμε να λύσουμε τη γραμμική εξίσωση . Όλες οι εντολές αυτής της ομάδας βρίσκονται μέσα στο IF της γραμμής 10 και εκτελούνται συνεπώς μόνο αν η μεταβλητή a έχει την τιμή 0.
Στις γραμμές 12 και 13 αντιμετωπίζουμε την περίπτωση όπου . Το αν αυτό ισχύει το ελέγχουμε με τη συνθήκη NOT (b=0) η οποία αληθεύει αν και μόνο αν δεν αληθεύει η συνθήκη b=0. Θα μπορούσαμε να είχαμε γράψει τη συνθήκη NOT (b=0) και ως b <> 0, μια και ο τελεστής <> σημαίνει διάφορο στην qbasic (και σε πολλές άλλες γλώσσες προγραμματισμού). Αν λοιπόν τότε στη γραμμή 12 τυπώνουμε τη μοναδική ρίζα της εξίσωσης και πηγαίνουμε με τη γραμμή 13 στο τέλος του προγράμματος. Δε δίνουμε όμως την εντολή END για να τελειώσουμε το πρόγραμμα εδώ αλλά πηγαίνουμε στο block γραμμών που αρχίζουν με την ετικέτα check: όπου και ρωτάμε το χρήστη αν θέλει να συνεχίσει να λύνει εξισώσεις ή θέλει να σταματήσει. Αν και (έλεγχος στη γραμμή 15) τότε κάθε πραγματικός αριθμός είναι λύση της εξίσωσης και αναφέρουμε αυτό στη γραμμή 16, αλλιώς (δηλ. είμαστε τώρα στην περίπτωση ) δεν υπάρχει λύση, το οποίο αναφέρουμε στη γραμμή 19.
Παρατηρείστε τα IF σε διάφορα επίπεδα (nested) στις γραμμές 10-23. Το IF της γραμμής 10 κλείνει στη γραμμή 23, ενώ στις γραμμές 11, 14, 22 έχουμε ένα IF ...THEN ... ELSE ... END IF, και στις γραμμές 15, 18, 21 άλλο ένα τέτοιο. Προσέξτε επίσης ότι όταν γράφουμε ένα τέτοιο σύνθετο block εντολών καλό είναι να στοιχίζουμε τις εντολές (να τις γράφουμε δηλ. αριστερότερα ή δεξιότερα στη γραμμή τους) με τέτοιο τρόπο ώστε να κάνουμε τη δομή του προγράμματος πιο εμφανή, πράγμα που βοηθάει πολύ στην κατανόηση του κώδικα. Υπάρχουν διάφοροι τρόποι να γίνει αυτό και το ποιον τρόπο ακολουθεί κανείς είναι και θέμα αισθητικής του καθενός. Ο τρόπος που ακολουθώ εδώ είναι ότι όταν γράφω ένα IF ... ELSE ... END IF τότε φροντίζω αυτές οι 3 λέξεις να είναι στοιχισμένες στην ίδια στήλη ενώ όσες εντολές είναι ανάμεσά τους στοιχίζονται μια θέση δεξιότερα.
Από τη γραμμή 25 και κάτω είμαστε πλέον σίγουροι ότι μια και αν ήταν 0 θα το είχε «πιάσει»
το IF της γραμμής 10 και δε θα έφτανε ποτέ ο έλεγχος στη γραμμή 25.
Στη γραμμή 26 υπολογίζουμε τη διακρινουσα (discriminant) της εξίσωσης .
Στην qbasic η έκφραση a^b
παριστάνει το (και πρέπει φυσικά να ισχύει τη στιγμή
του υπολογισμού της έφρασης ότι , αλλιώς ο υπολογιστής βγάζει μήνυμα σφάλματος).
Θα μπορούσαμε φυσικά να είχαμε υπολογίσει τη διακρίνουσα με την εντολή d = b*b-4*a*c (που είναι
και πιο γρήγορη στην εκτέλεσή της).
Στις γραμμές 27-30 χειριζόμαστε την περίπτωση αρνητικής διακρίνουσας και στις γραμμές 32-35 την περίπτωση που η διακρίνουσα είναι 0. Στην πρώτη περίπτωση τυπώνουμε στη γραμμή 28 ένα μήνυμα που λέει ότι δεν έχουμε ρίζες λόγω αρνητικής διακρίνουσας και λέμε και πόσο είναι η διακρίνουσα. Στη δεύτερη περίπτωση τυπώνουμε τη μοναδική (διπλή) ρίζα της εξίσωσης . Η εντολή PRINT της γραμμής 28 παίρνει τρία ορίσματα: το κείμενο (string) "No roots (discriminant =", τον αριθμό d και το κείμενο ")". Αυτά τα διαχωρίζουμε με το χαρακτήρα ; και όχι με κόμα για να μην παρεμβάλονται μεγάλα κενά ανάμεσά τους κατά την εκτύπωσή τους. Έτσι αυτό που τυπώνεται μοιάζει, π.χ., με
Στις γραμμές 38 και 39 είμαστε σίγουροι ότι η διακρίνουσα είναι θετική και υπολογίζουμε τις
δύο ρίζες
Άσκηση 4.
Γράψτε ένα πρόγραμμα που να λύνει ένα γραμμικό σύστημα
Τα δεδομένα που θα πρέπει να διαβάζει το πρόγραμμά σας από το χρήστη είναι οι αριθμοί και θα πρέπει να βρίσκει όλα τα ζεύγη που ικανοποιούν το σύστημα (2).
Αρχίστε υπολογίζοντας τις ορίζουσες των πινάκων
1 ' some declarations of variables 2 DIM n AS INTEGER 3 DIM a(100) AS INTEGER 4 5 6 CLS 7 8 ' read the length of the list of numbers 9 ninput: 10 PRINT "How many numbers? " 11 INPUT n 12 IF 0 > n OR n > 100 THEN 13 PRINT "Bad number." 14 GOTO ninput 15 END IF 16 17 ' input the numbers 18 FOR i = 1 TO n 19 PRINT "Give me number No "; i; ": " 20 INPUT a(i) 21 NEXT 22 23 ' compute the sum 24 s = 0 25 FOR i = 1 TO n 26 s = s + a(i) 27 NEXT 28 PRINT "The sum is"; s 29 30 ' compute the maximum and the minimum number 31 mm = a(1) 32 m = a(1) 33 FOR i = 2 TO n 34 IF a(i) > mm THEN 35 mm = a(i) 36 ELSEIF m > a(i) THEN 37 m = a(i) 38 END IF 39 NEXT 40 PRINT "The maximum is "; mm; " and the minimum is "; m
Το αρχείο list.bas
Το πρόγραμμα αυτό διαβάζει μια λίστα από αριθμούς, τους οποίους δίνει ο χρήστης του από το πληκτρολόγιο, υπολογίζει το άθροισμά τους καθώς και το μέγιστο και τον ελάχιστό τους, και τα τυπώνει.
Είναι η πρώτη φορά που βλέπουμε μια ανακύκλωση (loop) να υλοποείται με τη σύνθετη εντολή FOR i=1 TO ... NEXT.
Επίσης, στις γραμμές 2-3 βλέπουμε, για πρώτη φορά, δύο δηλώσεις μεταβλητών. Στη γραμμή 2 δηλώνεται η μεταβλητή n ως μεταβλητή ακεραίου τύπου (INTEGER). Από δω και πέρα αν επιχειρήσουμε να δώσουμε μια εντολή όπως η n = 1.2 που δεν είναι συμβατή με αυτό το είδος μεταβλητής θα προκληθεί σφάλμα. Οι δηλώσεις μεταβλητών, αν και συνήθως περιττές για τα πολύ απλά προγράμματα σε qbasic (ενώ σε άλλες γλώσσες όπως η Pascal και η C είναι υποχρεωτικές) είναι πολύ καλή τακτική προγραμματισμού γιατί μας προφυλάσσουν από το να κάνουμε εμείς λάθη. Πολλά λάθη του προγραμματιστή "πιάνονται" αν έχουν δηλωθεί οι τύποι των μεταβλητών κατ' αρχήν, χώρια που τα προγράμματα είναι πιο αποτελεσματικά (τρέχουν πιο γρήγορα).
Στη γραμμή 3 βλέπουμε μια δήλωση της μεταβλητής a ως ένας πίνακας (array) από ακεραίους μήκους 100. Είναι σα να έχουμε δηλώσει ταυτόχρονα 100 μεταβλητές των οποίων τα ονόματα είναι τα a(1), a(2), ... , a(100). Οι πίνακες είναι απολύτως απαραίτητα κατασκευάσματα ακόμη και για τα απλούστερα προγράμματα που μπορεί να θέλει κανείς να γράψει.
Στις γραμμές 9-15 ερωτάται ο χρήστης για το πόσους αριθμούς θα έχει η λίστα του. Το πλήθος αυτό αποθηκεύεται στη μεταβλητή n, και το πρόγραμμα ελέγχει αν η τιμή που έδωσε ο χρήστης είναι νόμιμη: το n δε μπορεί να είναι φυσικά αρνητικό αλλά ούτε και μεγαλύτερο του 100, αφού η λίστα που θα δώσει μετέπειτα ο χρήστης θα αποθηκευθεί στις θέσεις του πίνακα a. Αν δεν είναι νόμιμη η τιμή τότε επαναλαμβάνεται η ερώτηση έως ότου η τιμή είναι αποδεκτή. Αυτό επιτυγχάνεται με το GOTO ninput.
Στις γραμμές 18-21 ο χρήστης δίνει τις n αυτές τιμές. Το FOR loop εκτελείται για όλες τις τιμές i=1,2,...,n και κάθε φορά τυπώνεται ένα μήνυμα στο χρήστη για να δώσει το αντίστοιχο στοιχείο. Αυτό μπαίνει στη θέση a(i). Την πρώτη φορά που θα εκτελεστεί η ανακύκλωση γεμίζει η μεταβλητή a(1), τη δεύτερη η μεταβλητή a(2), κλπ.
Σε αυτό το σημείο έχουμε διαβάσει τα δεδομένα μας και τα έχουμε αποθηκεύεσει στον πίνακα a.
Στις γραμμές 24-27 υπολογίζεται το άθροισμα των αριθμών στη μεταβλητή s και τυπώνεται στη γραμμή 28. Η μεταβλητή s τίθεται αρχικά ίση με το 0 και σε κάθε επανάληψη του FOR loop της προστίθεται η τιμή a(i). Αυτό είναι το νόημα της εντολής-ανάθεσης
Στις γραμμές 31-39 υπολογίζονται η ελάχιστη τιμή (στη μεταβλητή m) και η μέγιστη τιμή (στη μεταβλητή mm) της λίστας μας των αριθμών και τυπώνεται το αποτέλεσμα στη γραμμή 40. Κατ' αρχήν θέτουμε και τις δύο αυτές μεταβλητές ίσες με το a(1). Έπειτα χρησιμοποιούμε το FOR loop για i=2,3,...,n ώστε να συγκρίνουμε κάθε ένα από τα υπόλοιπα a(i) με την τρέχουσα τιμή του μέγιστου και του ελάχιστου. Αν, όταν εξετάζουμε την τιμή a(i) τη βρούμε μεγαλύτερο του τρέχοντος μεγίστου (γραμμή 34) τότε αναθέτουμε την τιμή αυτή στο τρέχον μέγιστο (γραμμή 35), αλλιώς αν βρούμε το a(i) μικότερο του τρέχοντος ελαχίστου (γραμμή 36) τότε αναθέτουμε την τιμή a(i) στο τρέχον ελάχιστο (γραμμή 37).
1 DIM n AS INTEGER 2 DIM a(100) AS SINGLE 3 4 5 CLS 6 7 PRINT "Give me the numbers. Finish with 0." 8 9 ' input the numbers 10 i = 0 11 readanumber: 12 i = i + 1 13 PRINT "Give me number No "; i; ": " 14 INPUT a(i) 15 IF a(i) = 0 THEN 16 n = i - 1 17 ELSE 18 IF i < 100 THEN 19 GOTO readanumber 20 ELSE 21 PRINT "Cannot read any more. Bye." 22 END IF 23 END IF 24 25 ' compute the sum 26 s = 0 27 FOR i = 1 TO n 28 s = s + a(i) 29 NEXT 30 PRINT "The sum is"; s 31 32 ' compute the maximum and the minimum number 33 mm = a(1) 34 m = a(1) 35 FOR i = 2 TO n 36 IF a(i) > mm THEN 37 mm = a(i) 38 ELSEIF m > a(i) THEN 39 m = a(i) 40 END IF 41 NEXT 42 PRINT "The maximum is "; mm; " and the minimum is "; m
Το αρχείο list2.bas
Το πρόγραμμα list2.bas είναι μια διαφορετική (βελτιωμένη) έκδοση του list.bas. Η σημαντική διαφορά τους είναι ότι το πρόγραμμα list2.bas έχει γραφτεί με τέτοιο τρόπο ώστε ο χρήστης να μη χρειάζεται να δίνει εκ των προτέρων το μήκος της λίστας των αριθμών που πρόκειται να ακολουθήσει. Αντί γι' αυτό κάνουμε την υπόθεση (περιορισμός του προγράμματος αν θέλετε) ότι κανείς από τους αριθμούς της λίστας δεν είναι 0, και αν ο χρήστης δώσει 0 τότε απλά σηματοδοτεί με αυτό τον τρόπο το τέλος της λίστας. Δε χρειάζεται έτσι να ξέρουμε εξ αρχής το μήκος της λίστας (μπορεί κι ο χρήστης να μη το γνωρίζει) και απλά το υπολογίζουμε από το πόσους αριθμούς έδωσε ο χρήστης μέχρι να δώσει 0.
Μια άλλη αλλαγή που έχουμε κάνει είναι ότι τώρα οι αριθμοί που διαβάζουμε δεν είναι εν γένει ακέραιοι αλλά πραγματικοί αριθμοί. Αυτό είναι το νόημα του τύπου SINGLE για τη μεταβλητή a(100).
Στις γραμμές 12-23 γίνεται λοιπόν η ανάγνωση της λίστας χρησιμοποιώντας ένα δείκτη i ο οποίος είναι τέτοιος ώστε ανά πάσα στιγμή διαβάζουμε το στοιχείο a(i). Στις γραμμές 13-14 ο χρήστης δίνει το a(i). Αν αυτό είναι 0 (γραμμές 15-16) τότε σταματάμε το διάβασμα και θέτουμε το μήκος της λίστας n ίσο με i-1 (αφού το τελευταίο στοιχείο που διαβάσαμε, το a(i) είναι το 0 και δε μετέχει στη λίστα σύμφωνα με τη σύμβαση που κάναμε.
Πρέπει φυσικά να προσέξουμε να μη διαβάσουμε περισσότερους αριθμούς απ΄ όσους μπορούμε να αποθηκεύσουμε, δηλ. 100. Ο έλεγχος αυτός γίνεται στις γραμμές 18-22. Το διάβασμα συνεχίζεται εφόσον έχουμε διαβάσει λιγότερους από 100 αριθμούς, αλλιώς τυπώνεται ένα μήνυμα, σταματάει το διάβασμα αριθμών και συνεχίζει το πρόγραμμα με τους υπολογισμούς που πρέπει να κάνει. Αυτοί γίνεται ακριβώς όπως και στο πρόγραμμα list.bas που σχολιάσαμε παραπάνω.
Άσκηση 5. Στα προγράμματα list.bas και list2.bas κρατήσαμε τα δεδομένα (τη λίστα αριθμών) σε ένα πίνακα κατά το διάβασμά τους από το χρήστη και κατόπιν τα επεξεργαστήκαμε από εκεί μέσα. Δεν είναι όμως απαραίτητο γι' αυτό το πρόγραμμα να κρατηθούν τα νούμερα αυτά στη μνήμη, αφού μπορούν όλα τα ζητούμενα να υπολογίστούν από τους αριθμούς που διαβάζουμε χωρίς να τους κρατάμε στη μνήμη, αλλά απλά χρησιμοποιώντας κάθε νέο αριθμό που διαβάζουμε για να ενημερώνουμε τις μεταβλητές s, mm και m που κρατάμε για τις τρέχουσες τιμές του αθροίσματος, του μέγιστους και του ελάχιστου αριθμού.
Γράψτε λοιπόν ένα πρόγραμμα που να κάνει τη δουλειά που κάνει το list.bas ή το list2.bas αλλά χωρίς να χρησιμοποιεί κάποιον πίνακα και χωρίς να αποθηκεύει τα δεδομένα του πριν τα επεξεργαστεί.
1 DIM a(1000) AS SINGLE 2 DIM n AS INTEGER 3 4 5 CLS 6 7 PRINT "How many numbers?" 8 INPUT n 9 10 FOR i = 1 TO n 11 PRINT "Give me number No"; i 12 INPUT a(i) 13 NEXT 14 15 FOR i = n TO 1 STEP -1 16 PRINT "A("; i; ")="; a(i) 17 NEXT
Το αρχείο reverse.bas
Το πρόγραμμα reverse.bas διαβάζει n αριθμούς από το χρήστη και έπειτα τους τυπώνει με την ανάποδη σειρά. Το input των αριθμών γίνεται ακριβώς όπως και στο πρόγραμμα list.bas παραπάνω: ο χρήστης πρώτα δίνει το μήκος της λίστας (μεταβλητή n) και έπειτα δίνει τους n αριθμούς (γραμμές 7-13).
Στις γραμμές 15-17 τυπώνεται η λίστα με την ανάποδη σειρά, δηλ. οι αριθμοί a(n), a(n-1), ... , a(2), a(1). Αυτό το πετυχαίνουμε με ένα FOR loop, στο οποίο ο δείκτης i διατρέχει τις τιμές n έως 1, ξεκινώντας από το n και μειωνόμενος κάθε φορά κατά 1. Αυτό ακριβώς είναι το νόημα του STEP -1. Στην απλή μορφή του FOR loop, όταν η παράμετρος STEP παραλείπεται, αυτή θεωρείται πάντα ίση με +1.
Άσκηση 6. Φτιάξτε ένα πρόγραμμα που να διαβάζει μια λίστα αριθμών από το χρήστη και μετά να τυπώνει στην οθόνη την ίδια λίστα όπου οι αριθμοί (με περιττούς δείκτες δηλ.) να είναι στις θέσεις τους αλλά οι αριθμοί να τυπώνονται με την ανάποδη σειρά. Προσέξτε τις περιπτώσεις όπου το είναι περιττό ή άρτιο.
1 DIM a(1000), t AS SINGLE 2 DIM n, i, j AS INTEGER 3 4 5 CLS 6 7 PRINT "How many numbers?" 8 INPUT n 9 10 FOR i = 1 TO n 11 PRINT "Give me number No"; i 12 INPUT a(i) 13 NEXT 14 15 FOR i = 1 TO n - 1 16 FOR j = i + 1 TO n 17 IF a(i) > a(j) THEN 18 t = a(i) 19 a(i) = a(j) 20 a(j) = t 21 END IF 22 NEXT 23 NEXT 24 25 FOR i = 1 TO n 26 PRINT a(i); 27 NEXT
Το αρχείο sort.bas
Στο πρόγραμμα αυτό σκοπός μας είναι να διαβάσουμε μια λίστα από n αριθμούς (αυτό γίνεται στις γραμμές 7-13) και να τους τυπώσουμε αλλά σε αύξουσα σειρά. Για παράδειγμα, αν ο χρήστης δώσει ως input τους αριθμούς 3, 2, 1, 4, 4, 5, 2 τότε το output θα είναι 1, 2, 2, 3, 4, 4, 5. Το input γίνεται ακριβώς όπως στα προγράμματα list.bas και reverse.bas παραπάνω. Η μόνη διαφορά είναι ότι ο πίνακας a έχει 1000 θέσεις αντί για 100 (κι έτσι χωράει μεγαλύτερες λίστες).
Η μέθοδος που ακολουθούμε για την ταξινόμηση της λίστας αριθμών είναι το λεγόμενο bubble sort. Στη μέθοδο αυτή συγκρίνουμε συνεχώς μεταξύ τους ζεύγη θέσεων του πίνακα a, ας πούμε τις θέσεις i και j, με i<j, και αν τα περιεχόμενα τους δεν είναι στη σωστή σειρά, αν δηλ. a(i)>a(j) (γραμμή 17) τότε εναλλάσσουμε τα περιεχόμενά τους (γραμμές 18-20). Η εναλλαγή αυτή των δύο τιμών γίνεται με τη βοιηθητική μεταβλητή t (η οποία έχει δηλωθεί του ίδιου τύπου SINGLE όπως και οι αριθμοί που διατάσσουμε).
Η παρατήρηση κλειδί για τη μέθοδο bubble sort είναι ότι αν πρώτα ελέγξουμε την πρώτη θέση με όλες τις επόμενές της (δηλ. τις a(2), a(3), ... , a(n)), έπειτα τη δεύτερη θέση με όλες τις επόμενές της (δηλ. τις a(3), a(4), ... , a(n)), κλπ, έως ότου στο τέλος ελέγξουμε τις θέσεις a(n-1), a(n), τότε, στο τέλος αυτής της διαδικασίας ο πίνακας a είναι ταξινομημένος σε αύξουσα σειρά.
Αυτή η διπλή ανακύκλωση υλοποιείται στις γραμμές 15-23. Υπάρχει ένα εξωτερικό FOR loop στο οποίο ο δείκτης i διατρέχει τις τιμές 1, 2, ... , n. Στο εσωτερικό FOR loop, που ανοίγει στη γραμμή 16 και κλείνει με το NEXT της γραμμής 22, ο δείκτης j διατρέχει όλες τις τιμές μεγαλύτερες του i (έως το n). Το IF της γραμμής 17 υπάγεται στο εσωτερικό loop και άρα εκτελείται ακριβώς μια φορά για κάθε ζεύγος i, j με i<j.
Η διατεταγμένη σε αύξουσα σειρά λίστα τυπώνεται στις γραμμές 25-27. Το μόνο αξιοσημείωτο εδώ είναι το σύμβολο ; στο τέλος της εντολής PRINT. Το βάζουμε αυτό για να τυπωθούν όλα τα νούμερα στην ίδια γραμμή. Αν δεν το βάζαμε θα τυπώνονταν κάθε νούμερο στη γραμμή του.
Άσκηση 7. Τι αλλαγές πρέπει να κάνετε στο πρόγραμμα sort.bas ώστε να διατάσσει μια λίστα αριθμών σε αύξουσα σειρά του ημιτόνου της; Αν δηλ. τότε στην διατεταγμένη λίστα θα πρέπει να ισχύει . Η συνάρτηση υπολογίζεται στην qbasic με τη συνάρτηση βιβλιοθήκης SIN(x).
1 DIM a, b, s, l AS SINGLE 2 DIM N, i AS INTEGER 3 4 CLS 5 6 PRINT "Give me a, b, N:" 7 INPUT a, b, N 8 9 s = 0 10 l = (b - a) / N 11 12 FOR i = 0 TO N - 1 13 t = a + i * l 14 s = s + EXP(-t * t) 15 NEXT 16 17 s = s * l 18 PRINT "Approximation to the integral is"; s 19
Το αρχείο integral.bas
Σε αυτό το πρόγραμμα υπολογίζουμε προσεγγιστικά την τιμή του ολοκληρώματος
Στη γραμμή 9 δίνουμε αρχική τιμή 0 στη μεταβλητή s που θα χρησιμοποιήσουμε για το τρέχον άθροισμα, και υπολογίζουμε στη μεταβλητή l το μήκος των μικρών διαστημάτων. Τέλος, το άθροισμα υπολογίζεται με ένα FOR loop στις γραμμές 8-15. Στη γραμμή 13 υπολογίζεται το αριστερό άκρο του διαστήματος και στη γραμμή 14 υπολογίζεται η συνάρτηση σε αυτό το σημείο και προστίθεται η τιμή του στο τρέχον άθροισμα. (Η συνάρτηση EXP(x) είναι συνάρτηση βιβλιοθήκης της qbasic και υπολογίζει την εκθετική συνάρτηση .) Στη γραμμή 17 πολλαπλασιάζεται το άθροισμα που υπολογίστηκε (το άθροισμα δηλ. όλων των υψών των ορθογωνίων) επί την κοινή του βάση.
Άσκηση 8. Τροποποιείστε το πρόγραμμα integral.bas ώστε να χρησιμοποιεί ως ύψος των ορθογωνίων όχι την τιμή της συνάρτησης στο αριστερό άκρο του διαστήματος αλλά την τιμή της συνάρτησης στο μέσο του διαστήματος αυτού.
Το αρχείο base.bas βρίσκεται εδώ.
Στην τρίτη διάλεξη φτιάξαμε ένα σχετικά μεγάλο πρόγραμμα το οποίο διαχειρίζεται μια απλή βάση δεδομένων. Τα στοιχεία (εγγραφές, records, entries) στη βάση αυτή είναι ζεύγη (Όνομα, Τηλέφωνο) (ποιος έχει ποιο νούμερο).
Η βάση μας έχει ως μόνιμο αποθηκευτικό χώρο ένα αρχείο στο δίσκο του οποίου το όνομα περιγράφεται μέσα στο πρόγραμμά μας (δε μπορεί να το αλλάξει ο χρήστης του προγράμματος παρά μόνο ο προγραμματιστής) με την εντολή
filename = "d:\src\qbasic\database"
Η βάση πρέπει να διαβαστεί στη μνήμη για να γίνουν οποιεσδήποτε πράξεις σε αυτή (προσθήκες, διαγραφές, επιθεώρηση της βάσης). Ο χώρος που καταλαμβάνει η βάση μας στο δίσκο είναι οι τρεις μεταβλητές n, nm, phone που ορίζονται ως
' n is the number of entries in the database ' the variable changed is 1 if the database has been changed but not saved to disk DIM SHARED n, changed AS INTEGER ' the array nm holds the names DIM SHARED nm(100) AS STRING ' the arrray phone holds the telephone numbers. ' phone(1) is the phone number of person with name nm(1), etc DIM SHARED phone(100) AS INTEGER
n είναι το πλήθος των εγγραφών (ζευγών) στη βάση μας. Τα ονόματα αποθηκεύονται στον πίνακα nm(100) και τα τηλέφωνα στον πίνακα phone(100) (100 είναι λοιπόν το μέγιστο μέγεθος που μπορεί να φτάσει η βάση μας).
Η σύμβαση που ακολουθούμε είναι ότι η πρώτη γραμμή του αρχείου μας έχει μόνο ένα ακέραιο (μη αρνητικό) αριθμό ο οποίος μας λέει πόσες εγγραφές υπάρχουν στη βάση μας. Μετά την πρώτη γραμμή του αρχείου, κάθε εγγραφή καταλαμβάνει δύο γραμμές στο αρχείο, η πρώτη από τις οποίες περιέχει το όνομα και η δεύτερη το τηλέφωνο (που κάνουμε τη σύμβαση ότι είναι ακέραιος αριθμός, άρα δεν περιέχει κενά, παύλες κλπ).
Ένα παράδειγμα ενός τέτοιου αρχείου μπορείτε να δείτε εδώ:
1 3 2 Mihalis 3 3834 4 Maria 5 3741 6 Yannis 7 1234
Το αρχείο database
Για την περίπτωση που μας ενδιαφέρει (αρχεία τύπου text) το διάβασμα και το γράψιμο ενός αρχείου γίνεται με τις εντολές input και print όπως και το i/o από το πρηκτρολόγιο, οι οποίες όμως παίρνουν μια έπιπλέον παράμετρο που προσδιορίζει από πού διαβάζουν ή πού γράφουν.
Πρέπει πρώτα απ' όλα να «ανοίξουμε» το αρχείο για διάβασμα
OPEN filename FOR INPUT AS #1ή για γράψιμο
OPEN filename FOR OUTPUT AS #1όπου έχουμε πριν θέσει για τη μεταβλητή filename τύπου STRING
filename = "d:\src\qbasic\database"Η εντολή OPEN λοιπόν ανοίγει το αρχείο (του οποίο το όνομα έχουμε δώσει) για διάβασμα ή για γράψιμο, ως «ανοιχτό αρχείο»
#1
.
Έπειτα το διάβασμα ή γράψιμο από αυτό ή σε αυτό γίνονται με τις εντολές INPUT #1, ...
ή
PRINT #1, ...
.
Υποπρόγραμμα (subroutine, procedure, function, ανάλογα με τη γλώσσα προγραμματισμού) είναι το πακετάρισμα ενός κομματιού κώδικα που κάνει μια συγκεκριμένη δουλειά, με τρόπο ώστε αυτός ο κώδικας να μπορεί να χρησιμοποιείται σε διάφορα σημεία του προγράμματος χωρίς να επαναλαμβάνεται. Στη διάλεξη αυτή θα δούμε υποπρογράμματα της qbasic τύπου sub, και δε θα δούμε υποπρογράμματα με παραμέτρους (που είναι ένα πολύ ουσιαστικό κομμάτι της χρήσης των υποπρογραμμάτων).
Η δομή του προγράμματός μας είναι το κυρίως πρόγραμμα και μια συλλογή από υπορουτίνες, συγκεκριμένα οι υπορουτίνες
Ας δούμε το παράδειγμα της υπορουτίνας (sub) με το όνομα listdata η οποία σκοπό έχει, όποτε καλείται, να τυπώνει τα περιεχόμενα της βάσης δεδομένων που φτιάχνουμε:
1 ' Display the contents of the database 2 SUB listdata 3 4 DIM i AS INTEGER 5 6 PRINT "--------------------------------------" 7 FOR i = 1 TO n 8 PRINT i, nm(i); " Tel: "; phone(i) 9 NEXT 10 PRINT "--------------------------------------" 11 12 END SUB
Το αρχείο listdata.sub
Ο κώδικας που αντιστοιχεί στην υπορουτίνα γράφεται ανάμεσα στις γραμμές 2 και 12. Παρατηρείστε ότι οι μεταβλητές n, nm και phone είναι οι ίδιες οι μεταβλητές που έχουν δηλωθεί στο κυρίως πρόγραμμα ως SHARED. Η μεταβλητή i που δηλώνεται μέσα στη SUB listdata είναι αυτό που ονομάζουμε «τοπική μεταβλητή» και είναι ορατή μόνο μέσα στη SUB listdata. Αν κάπου αλλού στον κώδικά μας (μέσα σε μια άλλη υπορουτίνα ή μέσα στο κυρίως πρόγραμμα) υπάρχει μια μεταβλητή i αυτή αντιστοιχεί σε άλλη θέση μνήμης (σε άλλο «κουτί») από την i της SUB listdata.
Αν θέλουμε τώρα να «καλέσουμε» την υπορουτίνα listdata τότε δεν έχουμε παρά να δώσουμε την εντολή CALL listdata στο σημείο όπου θέλουμε να τυπωθεί στην οθόνη η λίστα με τα ονόματα και τα τηλέφωνα.
Δείτε την υπορουτίνα menu η οποία χειρίζεται την αλληλεπίδραση με το χρήστη:
1 ' This subroutine interacts with the user 2 SUB menu 3 4 DIM choice AS INTEGER 5 6 ' display the user's options 7 menutop: 8 9 PRINT 10 PRINT "My first database program" 11 PRINT 12 PRINT "1. Read database from disk" 13 PRINT "2. Write database to disk" 14 PRINT "3. List the database" 15 PRINT "4. Add to the database" 16 PRINT "5. Delete from the database" 17 PRINT "0. Quit" 18 19 PRINT 20 INPUT "Your choice:", choice 21 22 SELECT CASE choice 23 CASE 1 24 CALL infile 25 CASE 2 26 CALL outfile 27 CASE 3 28 CALL listdata 29 CASE 4 30 CALL add 31 CASE 5 32 CALL del 33 CASE 0 34 CALL finish 35 CASE ELSE 36 PRINT "Choose again" 37 GOTO menutop 38 END SELECT 39 40 GOTO menutop 41 42 END SUB
Το αρχείο menu.sub
Η ρουτίνα αυτή τυπώνει μια λίστα με δυνατές επιλογές του χρήστη (γραμμές 9-17) και ρωτάει το χρήστη για το τι θέλει να κάνει (μεταβλητή choice, γραμμές 19-20). Στις γραμμές 22-38, με τη σύνθετη εντολή
Φανταστείτε πόσο πιο δυσανάγνωστος θα ήταν ο κώδικας της ρουτίνας menu αν αντί για την κλήση μιας υπορουτίνας σε κάθε CASE είχαμε ολόκληρο τον κώδικα της κάθε υπορουτίνας στην αντίστοιχη θέση.
Η υπορουτίνα menu είναι η μόνη που καλείται από το κυρίως πρόγραμμα:
1 ' declarations of subroutines 2 DECLARE SUB finish () 3 DECLARE SUB del () 4 DECLARE SUB add () 5 DECLARE SUB outfile () 6 DECLARE SUB menu () 7 DECLARE SUB infile () 8 DECLARE SUB listdata () 9 10 ' start of the main program 11 12 DIM i AS INTEGER 13 14 ' variables declared as SHARED are visible inside the subroutines 15 16 ' this is the file holding the database 17 DIM SHARED filename AS STRING 18 ' n is the number of entries in the database 19 ' the variable changed is 1 if the database has been changed but not saved to disk 20 DIM SHARED n, changed AS INTEGER 21 ' the array nm holds the names 22 DIM SHARED nm(100) AS STRING 23 ' the arrray phone holds the telephone numbers. 24 ' phone(1) is the phone number of person with name nm(1), etc 25 DIM SHARED phone(100) AS INTEGER 26 27 ' You may have to change this to run this 28 ' program on your system 29 filename = "d:\src\qbasic\database" 30 31 ' initially the database is empty 32 n = 0 33 changed = 0 34 35 ' clear the screen 36 CLS 37 38 ' call subroutine menu which handles the interaction with the user 39 CALL menu 40 41 END 42 43 ' end of main program 44
Το αρχείο main.sub
Οι γραμμές 2-8 του κυριως προγράμματος έχουν μπεί στη θέση αυτή αυτόματα από την qbasic αφότου γράψαμε την κάθε υπορουτίνα. Στην qbasic για να προσθέσουμε μια υπορουτίνα επιλέγουμε Edit -> New Sub από τα μενού στην πάνω γραμμή της οθόνης. Το αρχείο του προγράμματός μας όμως αποθηκεύεται από την qbasic ως ένα ενιαίο αρχείο (δείτε το εδώ).
Η SHARED μεταβλητή filename που δηλώνεται στη γραμμή 17 και παίρνει τιμή 29 είναι το όνομα του αρχείου στο δίσκο όπου αποθηκεύεται η βάση μας μετά το πέρας του προγράμματος (κατά τη διάρκεια ζωής του οποίου η βάση μας βρίσκεται αποθηκευμένη στους πίνακες nm, phone οι οποίοι είναι στην κεντρική μνήμη (RAM) του υπολογιστή).
Στη γραμμή 32 η μεταβλητή n που αντιπροσωπεύει το πλήθος των εγγραφών στη βάση μας γίνεται 0 (κενή βάση) και η μεταβλητή changed γίνεται επίσης ίση με 0. Το νόημα της μεταβλητής changed είναι ότι θέλουμε να είναι 1 αν και μόνο αν η βάση μας έχει «πειραχτεί» (έχουμε αλλάξει κάτι σε αυτή, π.χ. σβήνοντας ή προσθέτοντας εγγραφές) και δεν έχει ακόμη αποθηκευτεί στο δίσκο. Αυτό μας βοηθάει να προλάβουμε μια απροσεξία του χρήστη ο οποίος μπορεί να φύγει από το πρόγραμμα (επιλογή 0 στο μενού) χωρίς να έχει αποθηκεύσει τη βάση στο δίσκο (έτσι οι αλλαγές του θα χαθούν).
Πέρα από το να καθαρίσει την οθόνη το μόνο πράγμα που κάνει το κυρίως πρόγραμμα είναι να καλέσει την υπορουτίνα menu η οποία θα αλληλεπιδρά με το χρήστη.
Δείτε την υπορουτίνα finish που χειρίζεται το τελείωμα του προγράμματος:
1 ' This subroutine handles exiting the program 2 SUB finish 3 4 DIM ans AS INTEGER 5 6 ' if the database is unsaved, doublecheck the user's intentions 7 IF changed = 1 THEN 8 PRINT "The database has been changed." 9 INPUT "Do you still want to quit (1 for yes, 0 for no)? ", ans 10 IF ans <> 1 THEN 11 EXIT SUB 12 END IF 13 END IF 14 15 PRINT "Bye bye" 16 END 17 18 END SUB
Το αρχείο finish.sub
Στις γραμμές 7-13 ελέγχει αν πάμε να φύγουμε χωρίς να έχουμε «σώσει» τη βάση στο δίσκο (changed=1). Σε αυτή την περίπτωση ο χρήστης ερωτάται ξανά και, αν επιμένει (ans=1) τότε μόνο φεύγουμε από το πρόγραμμα (γραμμές 15-16) αλλιώς η υπορουτίνα επιστρέφει (EXIT SUB στη γραμμή 11) τον έλεγχο στην υπορουτίνα menu από όπου και κλήθηκε.
Παρατηρείστε επίσης ότι στη γραμμή 9 χρησιμοποιούμε μια νέα μορφή της εντολής INPUT, στην οποία η πρώτη παράμετρος είναι ένα κείμενο που τυπώνεται στο χρήστη και η δεύτερη είναι η μεταβλητή που διαβάζουμε. Στη θέση της γραμμής 9 δηλ. θα μπορούσαμε να είχαμε γράψει τις δύο γραμμές
PRINT "Do you still want to quit (1 for yes, 0 for no)? "; INPUT ansμε το ίδιο αποτέλεσμα.
Οι υπορουτίνες infile και outfile που διαβάζουν από το δίσκο τη βάση και τη γράφουν στο δίσκο φαίνονται εδώ:
1 ' Read the database from the disk, into the arrays nm(100) and phone(100) 2 SUB infile 3 4 DIM i AS INTEGER 5 6 ' open the file holding the database for reading 7 OPEN filename FOR INPUT AS #1 8 9 ' read the database length from the first line of the file 10 INPUT #1, n 11 ' the read the names and phone numbers, one line per item 12 FOR i = 1 TO n 13 INPUT #1, nm(i) 14 INPUT #1, phone(i) 15 NEXT 16 ' close the file 17 CLOSE #1 18 19 PRINT "The database file has been read" 20 21 END SUB
Το αρχείο infile.sub
1 ' This subroutine writes the database (arrrays nm and phone) to the disk 2 SUB outfile 3 4 DIM ans, i AS INTEGER 5 6 ' if the database to be saved to disk is empty, doublecheck with the user 7 IF n = 0 THEN 8 PRINT "Database is empty." 9 INPUT "Are you sure (1 for yes, 0 for no)? ", ans 10 IF ans <> 1 THEN 11 PRINT "Save operation cancelled" 12 EXIT SUB 13 END IF 14 END IF 15 16 ' open the file to write 17 OPEN filename FOR OUTPUT AS #1 18 19 ' write the data to the file in a way that makes it possible to read them back 20 ' with subroutine infile 21 PRINT #1, n 22 FOR i = 1 TO n 23 PRINT #1, nm(i) 24 PRINT #1, phone(i) 25 NEXT 26 27 ' close the file 28 CLOSE #1 29 30 ' mark the database as unchanged (saved) 31 changed = 0 32 33 PRINT "Database saved to file" 34 35 END SUB
Το αρχείο outfile.sub
Το αρχείο ανοίγεται ως #1
και κλείνεται στο τέλος (γραμμές 7 και 17 στην infile και γραμμές 17 και
28 στην outfile).
Αφού η infile διαβάσει την πρώτη γραμμή του αρχείου (γραμμή 10) ξέρει μετά πόσες ακόμη
γραμμές έχει να διαβάσει και έτσι γεμίζει τους πίνακες nm και phone (γραμμές 12-15)
πριν κλείσει το αρχείο.
Στις γραμμές 7-11 της outfile χειρζόμαστε ξεχωριστά την περίπτωση όπου ο χρήστης πάει να γράψει μια κενή βάση στο δίσκο (n=0). Επειδή είναι πολύ πιθανό να πρόκειται για λάθος (φανταστείτε πόσο εύκολο είναι ο χρήστης να τρέξει το πρόγραμμα και η πρώτη εντολή που θα δώσει να είναι η 2 που σημαίνει ότι θα πάει να γράψει μια κενή βάση στο δίσκο καταστρέφοντας την παλιά) ξαναρωτάμε το χρήστη και προχωράμε μόνο αν μας το επιβεβαιώσει (ans=1) αλλιώς φεύγουμε από την outfile (γραμμή 12).
Στη γραμμή 31 η μεταβλητή changed γίνεται 0, δείχνοντάς μας ότι τα περιεχόμενα της βάσης στο δίσκο αντιστοιχούν σε αυτά της βάσης στη μνήμη (πίνακες nm και phone).
Ακολουθεί η ρουτίνα add για την προσθήκη μιας εγγραφής στη βάση (στο τέλος των πινάκων):
1 ' The following subroutine adds a person to the database 2 SUB add 3 4 IF n = 100 THEN 5 PRINT "Database if full" 6 EXIT SUB 7 END IF 8 9 ' the person is added at the end of the list 10 INPUT "Give name: ", nm(n + 1) 11 INPUT "Give phone number: ", phone(n + 1) 12 13 ' increment the length by one and mark the database as changed (unsaved) 14 n = n + 1 15 changed = 1 16 17 END SUB
Το αρχείο add.sub
Στις γραμμές 4-7 ελέγχουμε αν η βάση μας έχει το μέγιστο αριθμό εγγραφών (100) που έχουμε δεσμεύσει όταν δηλώσαμε τους πίνακές μας, και αν αυτό ισχύει τότε η ρουτίνα επιστρέφει (γραμμή 6) χωρίς να προσθέσει καμία εγγραφή. Η προσθήκη γίνεται στις γραμμές 10 και 11 και στη γραμμή 14 το n αυξάνεται κατά 1 ώστε η νέα μας εγγραφή να φαίνεται. Στη γραμμή 15 κάνουμε 1 το changed ώστε να φαίνεται ότι η βάση έχει αλλαχτεί αλλά όχι ακόμη εγγραφεί στο δίσκο.
Η διαγραφή μιας εγγραφής από τη βάση γίνεται από την ρουτίνα del:
1 ' The following subroutine deletes a person from the database 2 SUB del 3 4 DIM i, ans AS INTEGER 5 6 ' call the subroutine listdata which lists the database for the user to choose whom to delete 7 CALL listdata 8 9 INPUT "Which one to delete? ", ans 10 11 ' check if out of range 12 ' this also allows the user to change his mind 13 IF ans <= 0 OR ans > n THEN 14 EXIT SUB 15 END IF 16 17 ' shift one place up all persons following the one deleted 18 FOR i = ans + 1 TO n 19 nm(i - 1) = nm(i) 20 phone(i - 1) = phone(i) 21 NEXT 22 23 ' reduce n by 1, mark the database as changed (unsaved) 24 n = n - 1 25 changed = 1 26 27 PRINT "Element No "; ans; " deleted" 28 29 END SUB
Το αρχείο del.sub
Στη γραμμή 7 καλείται η ρουτίνα listdata ώστε να υπενθυμίσουμε στο χρήστη το ποιες είναι οι εγγραφές και να επιλέξει ποια εγγραφή θέλει να σβήσει. Εδώ φαίνεται ξεκάθαρα το πλεονέκτημα που έχει το να κάνουμε την listdata υπορουτίνα. Αν δεν το είχαμε κάνει και είχαμε απλά γράψει τον κώδικά της στο αντίστοιχο CASE της ρουτίνας menu τότε θε έπρεπε να το ξαναγράψουμε εδώ.
Στη γραμμή 9 ο χρήστης ερωτάται ποια εγγραφή θέλει να σβήσει και αν η απάντηση του χρήστη είναι εκτός των ορίων 1 .. n (γραμμές 13-14) τότε η υπορουτίνα επιστρέφει (αυτός είναι και ένας τρόπος να επιτρέψουμε στο χρήστη να αλλάξει γνώμη και να μη σβήσει τίποτα από τη βάση).
Για να σβήσουμε την εγγραφή ans πρέπει οι εγγραφές που την ακολουθούν στους πίνακες να ανέβουν μια θέση πρς τα πάνω. Πρέπει δηλ. η εγγραφή ans+1 να πάει στη θέση της ans, έπειτα η εγγραφή ans+2 να πάει στη θέση της ans+1, και τέλος η εγγραφή n να πάει στη θέση n-1 (γραμμές 18-21) και το n να μειωθεί κατά 1 (γραμμή 24). Τέλος (γραμμή 25) μαρκάρουμε τη βάση ως αλλαγμένη και μη αποθηκευμένη στο δίσκο.
Άσκηση 9. Στο πρόγραμμα base.bas η οθόνη καθαρίζεται μόνο μια φορά, στην αρχή του κυρίως προγράμματος. Αυτό κάνει τα μηνύματα του προγράμματος κάπως δυσανάγνωστα. Μια λύση θα ήταν να συμπεριλάβουμε την εντολή CLS ακριβώς μετά την ετικέτα menutop: στη ρουτίνα menu. Όμως αυτό θα καθάριζε την οθόνη αμέσως μετά τα όποια μηνύματα του προγράμματος προς το χρήστη με αποτέλεσμα ο χρήστης να μη προλαβαίνει να τα διαβάζει. Πώς μπορεί να διορθωθεί αυτή η προσέγγιση, ώστε και ο χρήστης να έχει όσο χρόνο χρειάζεται για να διαβάσει τα μηνύματα και η οθόνη να καθαρίζει πριν από κάθε τύπωμα του μενού εντολών;
Ένα παρόμοιο πρόβλημα υπάρχει με τη ρουτίνα listdata. Αν η βάση έχει πολλά άτομα τότε, επειδή ο οθόνη του υπολογιστή έχει λίγες γραμμές (π.χ. 25), αν τα τυπώσουμε όλα δεν πρόκειται να δούμε τα πρώτα μια και θα κυλήσουν προς την πάνω μεριά της οθόνης και θα αντικατασταθούν από τα μετέπειτά τους. Πώς μπορούμε να το λύσουμε αυτό το πρόβλημα ώστε να τυπώνουμε κάθε φορά μόνο όσα ονόματα χωράνε στην οθόνη;
Άσκηση 10. Αλλάξτε το πρόγραμμα ώστε να μπορεί ο χρήστης να καθορίζει το όνομα του αρχείου που κρατάμε τη βάση. Για παράδειγμα, θα μπορούσατε να προσθέσετε μια νέα εντολή στο μενού που να επιτρέπει στο χρήστη να κάνει ακριβώς αυτό. Επίσης, αν ο χρήστης δεν το κάνει και, παρ'όλ' αυτά, επιχειρήσει να διαβάσει τη βάση από το δίσκο, τότε το πρόγραμμα να του βγάζει ένα διευκρινιστικό μήνυμα ότι πρέπει πρώτα να καθορίσει τη βάση και μετά να πάει να τη διαβάσει.
Επίσης προσθέστε και μια εντολή τύπου "Save As", που επιτρέπει δηλ. στο χρήστη να σώσει τη βάση σε άλλο αρχείο, το οποίο αρχείο γίνεται από εκείνη τη στιγμή και πέρα, το τρέχον αρχείο.
Τέλος προσθέστε και μια εντολή τύπου "Close", που επιτρέπει στο χρήστη να μηδενίσει τη βάση που υπάρχει στη μνήμη ώστε ενδεχομένως αργότερα να ανοίξει μια άλλη (δίνοντας άλλο όνομα αρχείου). Η ρουτίνα αυτή θα πρέπει να εξετάζει αν έχει σωθεί ήδη στο δίσκο η υπάρχουσα βάση και, αν όχι, να δίνει την ευκαιρία στο χρήστη να το κάνει πριν την «κλείσει».