Georgia Dis Intro C

122
Σημειώσεις Γλώσσας Προγραμματισμού C Γεωργιάδης Κ . Χρήστος Φθινόπωρο 1998 Εισαγωγή Γιατί αρέσει η C Η γλώσσα C είναι μια ταχύτατη γλώσσα . Δηλαδή τα προγράμματα που φτιάχνουμε μ αυτήν εκτελούνται από το μηχάνημά μας αρκετές φορές γρηγορότερα από οποιαδήποτε άλλη καθιερωμένη γλώσσα ( όπως Pascal , Cobol κλπ ). Η μόνη που την ξεπερνάει λίγο σε ταχύτητα είναι η γλώσσα Assembly ή Συμβολική γλώσσα . Ακόμα όμως και οι προγραμματιστές Συστημάτων ( δηλαδή αυτοί που κατασκευάζουν τα Λειτουργικά Συστήματα , τους συντάκτες ( editors ) και γενικά τις πολύπλοκες κατασκευές λογισμικού ), προτιμούν πια να προγραμματίζουν σε C. Και αυτό γιατί η C υπερτερεί σε δυο σημαντικά σημεία : α ) είναι δομημένη β ) είναι φορητή Το ότι είναι δομημένη σημαίνει ότι μπορεί να αποκόπτει και να κρύβει από το υπόλοιπο πρόγραμμα όλες τις πληροφορίες και τις εντολές που είναι απαραίτητες για την εκτέλεση μιας συγκεκριμένης εργασίας . Έτσι μπορούμε και γράφουμε κομμάτια προγράμματος με τέτοιο τρόπο που τα γεγονότα που συμβαίνουν στο εσωτερικό τους να μην προκαλούν παρενέργειες σε άλλα τμήματα του προγράμματος . Τα τμήματα αυτά του προγράμματος τα λέμε υποπρογράμματα ή ρουτίνες και στη C συγκεκριμένα αποκαλούνται συναρτήσεις ( functions ). Το ότι είναι φορητή σημαίνει ότι τα προγράμματα σε C που έχουν γραφτεί για ένα σύστημα , μπορούν να τρέξουν σε άλλο σύστημα με σχεδόν καμιά τροποποίηση . Αντίθετα , ρουτίνες σε γλώσσα 1

Transcript of Georgia Dis Intro C

Page 1: Georgia Dis Intro C

Σημειώσεις Γλώσσας Προγραμματισμού C

Γεωργιάδης Κ. Χρήστος

Φθινόπωρο 1998

Εισαγωγή Γιατί αρέσει η C

Η γλώσσα C είναι μια ταχύτατη γλώσσα . Δηλαδή τα

προγράμματα που φτιάχνουμε μ’ αυτήν εκτελούνται από το μηχάνημά

μας αρκετές φορές γρηγορότερα από οποιαδήποτε άλλη καθιερωμένη

γλώσσα (όπως Pascal , Cobol κλπ). Η μόνη που την ξεπερνάει λίγο σε

ταχύτητα είναι η γλώσσα Assembly ή Συμβολική γλώσσα . Ακόμα όμως

και οι προγραμματιστές Συστημάτων (δηλαδή αυτοί που

κατασκευάζουν τα Λειτουργικά Συστήματα , τους συντάκτες (editors)

και γενικά τις πολύπλοκες κατασκευές λογισμικού), προτιμούν πια να

προγραμματίζουν σε C. Και αυτό γιατί η C υπερτερεί σε δυο σημαντικά

σημεία :

α) είναι δομημένη

β) είναι φορητή

Το ότι είναι δομημένη σημαίνει ότι μπορεί να αποκόπτει και να

κρύβει από το υπόλοιπο πρόγραμμα όλες τις πληροφορίες και τις

εντολές που είναι απαραίτητες για την εκτέλεση μιας συγκεκριμένης

εργασίας . Έτσι μπορούμε και γράφουμε κομμάτια προγράμματος με

τέτοιο τρόπο που τα γεγονότα που συμβαίνουν στο εσωτερικό τους να

μην προκαλούν παρενέργειες σε άλλα τμήματα του προγράμματος . Τα

τμήματα αυτά του προγράμματος τα λέμε υποπρογράμματα ή ρουτίνες

και στη C συγκεκριμένα αποκαλούνται συναρτήσεις ( functions ).

Το ότι είναι φορητή σημαίνει ότι τα προγράμματα σε C που

έχουν γραφτεί για ένα σύστημα , μπορούν να τρέξουν σε άλλο σύστημα

με σχεδόν καμιά τροποποίηση . Αντίθετα , ρουτίνες σε γλώσσα

1

Page 2: Georgia Dis Intro C

Αssembly δεν μπορούν να μεταφερθούν εύκολα σε μηχανήματα με

διαφορετικούς επεξεργαστές .

Θεωρείται λοιπόν η C γλώσσα μέσου επιπέδου γιατί συνδυάζει

χαρακτηριστικά των γλωσσών υψηλού επιπέδου όπως η ευκολία στην

εκμάθηση (φιλικότητα), η δόμηση και η φορητότητα , με

χαρακτηριστικά της γλώσσας χαμηλού επιπέδου Assembly . Και δεν

είναι μόνο η ταχύτητα της . Είναι επιπλέον η δυνατότητα να

διαχειριζόμαστε bits , bytes και διευθύνσεις μνήμης που αποτελούν τα

βασικά στοιχεία με τα οποία λειτουργεί το υλικό (hardware) του

υπολογιστή μας .

Αξίζει ακόμη να σημειωθεί μια ευελιξία που διακρίνει την

γλώσσα C. Τι εννοούμε με αυτό ; Ότι για λόγους καλής σχεδίασης

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

άλλες γλώσσες (π .χ . Pascal) διάφορους τύπους δεδομένων (data

types). Δεν υπάρχει όμως αυστηρότητα . Δηλαδή ο προγραμματιστής

της C, ανάλογα με τις ανάγκες του μπορεί σχεδόν ελεύθερα να αναμίξει

τους διάφορους τύπους . Γενικότερα μπορεί να απενεργοποιήσει

διάφορους ελέγχους που κανονικά θα έκανε η γλώσσα C, για να

κερδίσει σε ταχύτητα . Φυσικά τέτοια βήματα πρέπει να γίνονται

προσεκτικά για να μην δημιουργηθούν σοβαρά προβλήματα

λειτουργίας στο μηχάνημα .

Από που ήρθε και προς τα που πάει η C

Όλα ξεκίνησαν από τον Richards που στα τέλη του ‘60 ανέπτυξε

την γλώσσα BCPL . H γλώσσα αυτή επηρέασε τον Τhompson που

ανέπτυξε μια γλώσσα με το όνομα Β . Ήταν η πρόγονος της C. Και όλη

η προσπάθεια διαδραματιζόταν στα εργαστήρια Βell της American

Telephone & Telegraph Company. O Thompson και ο Richie ήταν

επικεφαλείς στον αγώνα ανάπτυξης αρχικά ενός οικονομικότερου

Λειτουργικού Συστήματος . Μαζί δούλεψαν για την δημιουργία του

2

Page 3: Georgia Dis Intro C

Unix στην αρχή με Συμβολική γλώσσα . Η έλλειψη φορητότητας όμως ,

που αναφέραμε προηγουμένως , της γλώσσας Assembly τους οδήγησε

στην ανάπτυξη της γλώσσας C το 1973. Έτσι το Λειτουργικό Σύστημα

Unix ξαναγράφηκε σε γλώσσα C πλέον . Αυτό το γεγονός έδωσε νέα

πνοή και στο Unix αλλά και στη C.

Εύκολα καταλαβαίνουμε ότι αρχικά η C συνδέθηκε αποκλειστικά

με το Unix και επικράτησε στους χώρους των multi-user συστημάτων .

Έτσι από το 1978 και για πολλά χρόνια ο πρότυπος ορισμός της ήταν

αυτός που δόθηκε στο βιβλίο “The C Programming Language” των

Kernighan και Richie. Και φυσικά ήταν η C που έτρεχε στην έκδοση V

του Unix. Στη δεκαετία όμως του ‘80 έχουμε τρία σημαντικά γεγονότα

για την C :

i) Ξεκίνησε από το Αmerican National S tandards Institute η

ανάπτυξη ενός προτύπου C για όλα τα περιβάλλοντα (Unix, Apple, Dos

κλπ). Η ΑΝSI-C περιλαμβάνει όλα τα χαρακτηριστικά της Unix-C μαζί

με κάποια νέα . Έτσι η C περνάει πλέον και στους χώρους των

προσωπικών υπολογιστών , οι οποίοι με την εξέλιξη της τεχνολογίας

έχουν πια την απαραίτητη ισχύ για να υποστηρίξουν την γλώσσα C.

i i) Στα εργαστήρια Bell πάλι , ο Stroustrup δημιουργεί μια

επέκταση της γλώσσας C, που ονομάζεται C++. H γλώσσα αυτή είναι

σήμερα η βασικότερη γλώσσα προγραμματισμού με αντικείμενα . Ο

αντικειμενοστραφής προγραμματισμός (object - oriented programming)

θεωρείται στις μέρες μας η τελευταία λέξη στην ανάπτυξη

προγραμμάτων . Σε πάρα πολλές περιπτώσεις , κυρίως σε γραφικά

περιβάλλοντα με εικονίδια ενεργειών τύπου Windows και όχι μόνο , η

C++ προσφέρει άμεσους τρόπους για την κατανόηση και επίλυση

προβλημάτων . Η καλή γνώση λοιπόν της C θα βοηθήσει αργότερα

όλους όσους θελήσουν να περάσουν σε μεθόδους αντικειμένων .

i i i) H Εταιρεία Borland ξεκίνησε την ανάπτυξη της Turbo-C που

ακολουθεί πλήρως την ANSI-C και προσφέρει φοβερή ταχύτητα στη

3

Page 4: Georgia Dis Intro C

μεταγλώττιση , δηλαδή στο πέρασμα που ο Η/Υ κάνει από τις εντολές

κώδικα C σε εντολές που εκτελεί το μηχάνημα . Στη συνέχεια

κυκλοφορεί το 1990 την Turbo C++, υποστηρίζοντας τους πρότυπους

ορισμούς ΑΝSI-C και ΑΝSI-C++. Οι εκδόσεις ΤURBO-C και ΤURBO-

C++ είναι οι πιο δημοφιλείς σήμερα στους χρήστες DOS. Προσφέρουν

ολοκληρωμένο περιβάλλον για να γράφουμε εντολές κώδικα , να

ελέγχουμε λάθη και να τρέχουμε τα προγράμματά μας . Είναι οι

καθιερωμένες εκδόσεις C/C++ για τους προσωπικούς υπολογιστές σε

επίπεδο DOS και μ’ αυτές θα ασχοληθούμε . Ας τονιστεί εδώ ότι η

εταιρεία Βorland έχει ήδη κυκλοφορήσει εκδόσεις Borland-C++ για το

περιβάλλον Microsoft Windows. Είναι επίσης πασίγνωστες και οι

εκδόσεις Turbo/Borland Pascal για τη γλώσσα αυτή

Πως γράφουμε και πως εκτελούμε προγράμματα C

Για να κατασκευάσουμε προγράμματα σε οποιαδήποτε γλώσσα

προγραμματισμού πρέπει πρώτα απ’ όλα να γράψουμε τις εντολές του

προγράμματός μας και στη συνέχεια να ζητήσουμε την εκτέλεσή του .

Ορισμένες γλώσσες (π .χ . Basic) κάνουν διερμήνευση των εντολών .

Βλέπουν δηλαδή και εκτελούν την κάθε εντολή χωριστά . Μια τέτοια

υλοποίηση γλώσσας με interpreter δεν εκμεταλλεύεται πλήρως τις

δυνατότητες του μηχανήματος . Αντίθετα η Turbo-C είναι

μεταγλωττιστής (compiler). Δηλαδή διαβάζει ολόκληρο το πρόγραμμα

(το λέμε και πηγαίο κώδικα) και το μετατρέπει σε μια μορφή που

μπορεί να εκτελεστεί απευθείας από τον μεταγλωττιστή (είναι ο

αντικειμενικός κώδικας). O compiler της Borland είναι αυτή τη στιγμή

ο ταχύτερος που κυκλοφορεί . Και οφείλεται σε ειδικούς χειρισμούς στο

κώδικα που παράγει οι οποίοι αποκαλούνται βελτιστοποιήσεις

(optimizations).

Όσοι έχουν ήδη χρησιμοποιήσει τη γλώσσα Τurbo Pascal για

προγραμματισμό δεν δυσκολεύονται καθόλου να προσαρμοστούν στο

4

Page 5: Georgia Dis Intro C

περιβάλλον (Ιntegrated Development Environment) της Turbo-C. Είναι

σχεδόν ίδιο . Δηλαδή έχουμε να κάνουμε με ένα πλήρες πακέτο που

περιλαμβάνει :

i) διορθωτή κειμένου ( text editor)

i i) μεταγλωττιστή (compiler)

i i i) συνδέτη ( l inker)

iv) βιβλιοθήκη συναρτήσεων (function library)

v) βοηθητικά προγράμματα (util i t ies) πχ . ανιχνευτής

λαθών (debugger)

Ξεκινάμε την διαδικασία καλώντας το αρχείο TC ή ΒC.

Παρουσιάζεται τότε η κεντρική οθόνη του περιβάλλοντος . Αργότερα θα

εξερευνήσουμε εντελώς τις διάφορες επιλογές που προσφέρονται . Για

αρχή όμως χρειάζονται πολύ λίγες από αυτές . Από το μενού FILE όταν

θελήσουμε να γράψουμε ένα νέο πρόγραμμα C από την αρχή ,

επιλέγουμε ΝΕW. Αν όμως θέλουμε να δούμε ή να τροποποιήσουμε ένα

πρόγραμμα που ήδη έχουμε αποθηκεύσει σε περιφερειακή μνήμη

(σκληρό δίσκο ή δισκέτα), τότε επιλέγουμε OPEN (ή LOAD σε

ορισμένες εκδόσεις).

H επιλογή NEW ανοίγει ένα παράθυρο με όνομα

“ΝΟΝΑΜΕ00.C“. Έχει δηλαδή ενεργοποιήσει τον συντάκτη κειμένου

και έτσι μπορούμε να αρχίσουμε να γράφουμε τις εντολές του

προγράμματός μας . Όταν θελήσουμε να σώσουμε αυτά που έχουμε

γράψει , πάλι από το μενού FILE επιλέγουμε SAVE ή “SAVE AS...”.

Με το SAVE το πρόγραμμά μας αποθηκεύεται με τον τίτλο του

παραθύρου “NONAME00.C”, ενώ με το “SAVE AS...” μπορούμε να

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

Βάζουμε όμως πάντα ως προέκταση το “.C” για τα προγράμματά μας C,

ενώ για τα προγράμματα C++ βάζουμε “.CPP”.

H επιλογή OPEN μας οδηγεί σε οποιοδήποτε δίσκο ή

υποκατάλογο δίσκου , για να επιλέξουμε το όνομα του αποθηκευμένου

5

Page 6: Georgia Dis Intro C

αρχείου που θέλουμε να φορτώσουμε στη κεντρική μνήμη (RAM). O

editor και πάλι αναλαμβάνει ρόλο και εμφανίζεται παράθυρο που έχει

τίτλο το πλήρες όνομα του αρχείου που επιλέξαμε και περιεχόμενα τις

εντολές του προγράμματός μας . Αξίζει να τονιστεί ότι μπορούμε

ταυτόχρονα να έχουμε ανοιχτά αρκετά παράθυρα με τα ίδια ή

διαφορετικά αρχεία . Έτσι μπορούμε πολύ εύκολα να μετακινούμε και

να αντιγράφουμε κομμάτια προγραμμάτων από αρχείο σε αρχείο .

Ακολουθεί η διαδικασία μετατροπής του προγράμματος της

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

καταλαβαίνει . Η διαδικασία αυτή είναι καλύτερα να γίνεται σε δύο

βήματα όταν ακόμη είμαστε στη φάση δοκιμών ενός προγράμματος .

Ζητάμε πρώτα από το μενού COMPILE την επιλογή “COMPILE TO

OBJ”. O μεταγλωττιστής τότε ελέγχει το συντακτικό των εντολών του

προγράμματος . Αν βρει λάθος τότε φωτίζεται η γραμμή του

προγράμματος με το λάθος και ανοίγει στο κάτω μέρος της οθόνης ένα

παράθυρο με τίτλο “MESSAGE” (μήνυμα), που εξηγεί με λίγες λέξεις

ενδεχομένως το πρόβλημα . Αυτές είναι και οι ευκολίες των

ολοκληρωμένων περιβαλλόντων ανάπτυξης (IDE). Γιατί έχει ήδη

ενεργοποιηθεί ο editor και μπορούμε να διορθώσουμε το λάθος μας και

να ξαναζητήσουμε μετά COMPILE. Αυτή η διαδικασία ελέγχου του

προγράμματός μας από τον compiler και οι διορθώσεις με τη βοήθεια

του editor συνεχίζονται μέχρι να εμφανιστεί παράθυρο με την ένδειξη

SUCCESS : Press any key to continue

Η ΕΠΙΤΥΧΙΑ είναι ότι στο δίσκο δημιουργήθηκε ένα αρχείο με

το όνομα του προγράμματός μας και προέκταση “.OBJ”. Μπορούμε πια

να ζητήσουμε από το μενού RUN την πρώτη επιλογή RUN και έτσι να

εκτελεστεί το πρόγραμμά μας . Τα αποτελέσματα ορισμένες φορές δεν

προλαβαίνουμε να τα δούμε γιατί γρήγορα το περιβάλλον μας

επιστρέφει στον editor με τον κώδικα για ενδεχόμενες διορθώσεις . Από

το μενού WINDOW όμως μπορούμε να επιλέξουμε την επιλογή USER

6

Page 7: Georgia Dis Intro C

SCREEN (οθόνη χρήστη) για να δούμε σε πλήρη οθόνη τα

αποτελέσματα . Και η επιλογή WINDOW/ΟUTPUT εμφανίζει τα

αποτελέσματα της εκτέλεσης του προγράμματος αλλά σε μικρότερες

διαστάσεις παραθύρου .

Μπορούμε και απευθείας να ζητήσουμε την εκτέλεση ενός

προγράμματος C που έχουμε στην οθόνη μας , ζητώντας από το μενού

RUN την επιλογή RUN. Θα αναλάβει αρχικά ο compiler και αν δεν

βρει λάθη όπως αναφέραμε θα εκτελέσει το πρόγραμμά μας . Αυτό που

πραγματικά συμβαίνει , χωρίς να είναι εμφανές , είναι η ενεργοποίηση

του συνδέτη ( l inker) που συνδέει το αρχείο (*.obj) που δημιούργησε ο

compiler με έτοιμα μεταγλωττισμένα κομμάτια προγραμμάτων που

περιέχει η βιβλιοθήκη συναρτήσεων (function library) . Ο l inker

δημιουργεί στο δίσκο αρχείο με όνομα το όνομα του προγράμματός μας

και προέκταση “.ΕΧΕ”. Είναι το λεγόμενο εκτελέσιμο αρχείο που

μπορεί να σταθεί μόνο του . Δηλαδή και χωρίς τη βοήθεια πια του

περιβάλλοντος , έχοντας σε κάποια δισκέτα αυτό μόνο το αρχείο ΕΧΕ ,

μπορούμε από τη γραμμή του DOS να εκτελέσουμε το πρόγραμμά μας .

Το αρχείο αυτό είναι σε μέγεθος πολύ μεγαλύτερο από τους

“προγόνους” του ΟBJ και C, γιατί έχει ενσωματώσει και ρουτίνες από

τη βιβλιοθήκη συναρτήσεων .

Τι είναι όμως αυτές οι συναρτήσεις της βιβλιοθήκης ;

H C δεν έχει ενσωματωμένη τη δυνατότητα προσπέλασης στην

οθόνη , στο πληκτρολόγιο , στον εκτυπωτή ή στους δίσκους . Επίσης δεν

είναι σε θέση να εκτελέσει βασικές λειτουργίες όπως είναι η εύρεση

του ημιτόνου μιας γωνίας ή η συνένωση δυο αλφαριθμητικών

εκφράσεων . Όλα αυτά μπορεί να τα κάνει η C υπό τον όρο ότι έχουν

γραφτεί οι κατάλληλες functions. Μην απελπίζεστε όμως , γιατί δεν

χρειάζεται να γραφτούν από εσάς όλες αυτές οι συναρτήσεις , μιας και

7

Page 8: Georgia Dis Intro C

περιέχονται σε μια βιβλιοθήκη που συνοδεύει τον compiler και αρκεί

απλά να τις καλέσει κανείς σωστά . Η βιβλιοθήκη των functions στη

πραγματικότητα δεν αποτελεί μέρος της γλώσσας C. O

προγραμματιστής χρησιμοποιεί και τις συναρτήσεις αυτές αλλά και

δημιουργεί δικές του όπως αργότερα θα δούμε . Καθώς κάποιος

προγραμματιστής φτιάχνει τα δικά του προγράμματα δημιουργεί και

functions κατάλληλες για γενική χρήση οι οποίες μπορούν να

προστεθούν στην προσωπική του βιβλιοθήκη . Έτσι μπορούν και

δημιουργούνται βιβλιοθήκες εξειδικευμένης χρήσης .

Ένα απλό πρόγραμμα

#include <stdio.h>

/* HELLO.C -- Ψυχραιμία . . . ξεκινήσαμε ήδη */

void main()

int yr ;

printf ("Ψυχραιμία . . . \nξεκινήσαμε ήδη \n") ;

printf (“πόσων χρονών είσαι ; “) ;

scanf (“%d”, &yr) ;

printf (“δεν είναι και άσχημα να έχεις μόνο %d χρόνια \n“,

yr);

Η C δεν χρησιμοποιεί αριθμημένες γραμμές και ολόκληρο το

πρόγραμμα θα μπορούσατε να το γράψετε σε μια μόνο γραμμή , αν και

δεν θα ήταν και πολύ έξυπνο αυτό . Θα δυσκολευόσασταν πάρα πολύ

μετά από λίγο καιρό να καταλάβετε την λογική του . Η ελευθερία

δηλαδή που έχουμε στη διάταξη των εντολών , χρησιμοποιείται από

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

Έτσι που ένα έμπειρο μάτι από τη πρώτη στιγμή αντιλαμβάνεται ποια

εντολή εξαρτάται από ποια και ποια κομμάτια κώδικα είναι ανεξάρτητα

8

Page 9: Georgia Dis Intro C

το ένα από το άλλο . Οι τύποι και τα ονόματα των μεταβλητών πρέπει

να δηλωθούν πριν χρησιμοποιηθούν , πράγμα που δεν συμβαίνει στην

BASIC η οποία μπορεί να δεχτεί σαν μεταβλητές ακόμη και τα λάθη

μας στη πληκτρολόγηση .

Τα άγκιστρα , περικλείουν ομάδες εντολών όπως ακριβώς στη

PASCAL κάνουν τα ΒΕGIN και END. Κάθε εντολή τελειώνει με το

σύμβολο ; (ελληνικό ερωτηματικό). Προσοχή στα κεφαλαία και στα

μικρά γράμματα . Η C κάνει διάκριση ανάμεσά τους και έτσι yr, YR,

Yr, και yr είναι 4 εντελώς διαφορετικά πράγματα . Συνηθισμένοι στο

κόσμο του DOS όπου κεφαλαία - μικρά είναι το ίδιο πράγμα , πρέπει να

συνηθίσουμε την διαφορά αυτή που κουβαλάει η γλώσσα C από τα

χρόνια του εναγκαλισμού της με το UNIX.

Ας δούμε με λίγα σχόλια την κάθε εντολή του προγράμματός μας

:

#include <stdio.h> : ζητάμε από τον compiler να συμπεριλάβει το

αρχείο stdio.h στη μεταγλώττιση .

/* Ηello ... */ : επιβάλλουμε στον compiler να αγνοήσει όλα όσα

βρίσκονται μετά το /* και πριν το */. Έτσι δηλαδή περνάμε

κάποια χρήσιμα σχόλια στο πρόγραμμά μας .

void main() : καθορίζουμε τον τύπο (void) και το όνομα (main)

μιας συνάρτησης . Ότι είναι όνομα συνάρτησης φαίνεται από τις

παρενθέσεις δίπλα στο όνομα . Void δηλώνονται οι συναρτήσεις που

δεν έχουν καμιά τιμή επιστροφής και απλά κάνουν κάτι . Κάθε

πρόγραμμα μπορεί να αποτελείται από αρκετές functions. Για να είναι

όμως εκτελέσιμο πρέπει να έχει μία με το όνομα main, γιατί ο compiler

ξεκινά την εκτέλεση από την main όπου και αν είναι τοποθετημένη

μέσα στο πρόγραμμα .

, : δηλώνουν την αρχή και το τέλος της συνάρτησης

main().

9

Page 10: Georgia Dis Intro C

int yr; : δηλώνει μια μεταβλητή yr και ο compiler δεσμεύει

μνήμη ικανή να αποθηκεύσει έναν ακέραιο .

printf(“ χαρακτήρες”, μεταβλητή) : εμφανίζει όσους χαρακτήρες

βρίσκονται μέσα στα διπλά εισαγωγικά εκτός από ορισμένα σύμβολα

όπως ‘ \n’ που πληροφορεί για αλλαγή γραμμής ή ‘%d’ που μας δείχνει

που θα εμφανιστεί η τιμή της μεταβλητής που ακολουθεί μετά το

κόμμα .

scanf(“%χαρακτήρας”,&μεταβλητή) : διαβάζει από το πληκτρολόγιο ,

δηλαδή από το χρήστη την τιμή της μεταβλητής .

10

Page 11: Georgia Dis Intro C

Βασικά στοιχεία : Μεταβλητές - Τύποι - Σταθερές

Περιεχόμενα ενός προγράμματος

Γενικά στη C τα προγράμματα αποτελούνται από δηλώσεις

δεδομένων και δηλώσεις πράξεων πάνω σ’ αυτά τα δεδομένα . Τρεις

είναι οι μορφές που μπορούμε να παραστήσουμε τα δεδομένα μας :

i) Σαν σταθερές (Constants)

i i) Σαν μεταβλητές (Variables)

i i i) Σαν τύποι (Types)

Όσον αφορά τις πράξεις των δεδομένων , οι οποίες ονομάζονται

και εντολές (statements), τις συγκεντρώνουμε σε ομάδες που καλούνται

Συναρτήσεις (Functions). Μόνο όταν καλείται η συνάρτησή τους

εκτελούνται οι πράξεις . Και όπως είδαμε , η εκτέλεση ενός

προγράμματος αρχίζει από την συνάρτηση με το όνομα main().

Στη πραγματικότητα ένα πρόγραμμα C δεν αποτελείται από ένα

μόνο αρχείο της περιφερειακής μας μνήμης . Ο λόγος είναι ότι κανένας

μεταγλωττιστής δεν μπορεί να χειριστεί πολύ μεγάλο αριθμό γραμμών

προγράμματος . Έτσι συνήθως ένα από τα αρχεία περιέχει το κύριο

πρόγραμμα , δηλαδή τη συνάρτηση main, ενώ τα υπόλοιπα αρχεία

περιλαμβάνουν μόνο συναρτήσεις και δεδομένα .

Λεπτομέρειες . . .

Σε κάθε πρόγραμμα συναντάμε οδηγίες (directives), σχόλια ,

δηλώσεις σφαιρικών δεδομένων (Global Data Declarations) ,

συναρτήσεις που περιέχουν δηλώσεις τοπικών δεδομένων (Local Data

Declarations) και εκτελέσιμες εντολές . Για την κατασκευή όλων αυτών

των παραπάνω , χρησιμοποιούνται τα παρακάτω δομικά στοιχεία :

11

Page 12: Georgia Dis Intro C

1) identifiers πχ . pinakas_mou, onoma

2) keywords πχ . while, for

3) constants πχ . 12000, 0

4) character strings πχ . “ καλημέρα”, “want it”

5) operators πχ . +, *

6) separators πχ . ; , ‘ ‘

Τα identifiers είναι κατάλληλα ονόματα που δίνουμε σ’ αυτά που

φτιάχνουμε μέσα σε ένα πρόγραμμα , δηλαδή στις μεταβλητές , στους

τύπους , στις σταθερές και στις συναρτήσεις μας . Πρέπει να αρχίζουν

με κάποιο γράμμα ή με το σύμβολο της υπογράμμισης και οι επόμενοι

χαρακτήρες μπορεί να είναι ψηφία , γράμματα ή το σύμβολο της

υπογράμμισης . Αν και μπορούν να έχουν μεγάλο μήκος , λαμβάνεται

υπόψη μόνο ένας περιορισμένος αριθμός χαρακτήρων (8 στην ANSI-C

και 32 στην TURBO-C). Αυτά τα ονόματα (λέγονται και προσδιοριστές

ή αναγνωριστικά) δεν πρέπει να είναι ορισμένα keywords της C ή

κάποια ονόματα σταθερών , μεταβλητών , συναρτήσεων και τύπων που

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

Τα keywords είναι οι λέξεις-κλειδιά της γλώσσας C. Μια λέξη-

κλειδί είναι ουσιαστικά μια διαταγή που καθορίζει αυτό που μπορεί να

γίνει και τον τρόπο με τον οποίο θα γίνει . Η ANSI-C περιλαμβάνει τις

ακόλουθες 32 λέξεις- κλειδιά :

auto double int struct

break else long switch

case enum register typedef

char extern return union

const float short unsigned

continue for signed void

default goto sizeof volatile

do if static while

12

Page 13: Georgia Dis Intro C

Προκαθορισμένοι Τύποι - Δηλώσεις Δεδομένων

Έχουμε ήδη αναφέρει ότι τα δεδομένα μας περιγράφονται με

τύπους , μεταβλητές και σταθερές . Προσοχή!!! Οι θέσεις μνήμης

καταλαμβάνονται μόνον από μεταβλητές και σταθερές . Οι τύποι ( types)

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

στον compiler πώς να χειριστεί την κάθε μεταβλητή . Οι τύποι ορίζουν

κατηγορίες μεταβλητών με διαφορετικές διαστάσεις - εννοούμε πόσος

χώρος δεσμεύεται στη μνήμη - και ιδιότητες . Έτσι το int απλά λέει

στον compiler ότι οι μεταβλητές που ακολουθούν θα έχουν μέγεθος 2

bytes και θα αντιμετωπιστούν σύμφωνα με τους κανόνες που ισχύουν

για τους ακέραιους . Μ’ αυτόν τον τρόπο κάνουμε οικονομική

διαχείριση στη μνήμη και δεσμεύουμε όσο πρέπει ανάλογα με την

περίπτωση .

Τα δεδομένα στην Τurbo-C μπορούν να ανήκουν σε έναν από

τους παρακάτω προκαθορισμένους τύπους (Predefined Types), ή σε

τύπους που προκύπτουν από συνδυασμούς αυτών των τύπων .

Τύπος /Λέξη-Κλειδί Πλάτος σε bytes Εύρος Τιμών

char 1 -128 έως 127

int 2 -32768 έως 32767

float 4 3.4E-38 έως 3.4 Ε+38

double 8 1.7E-308 έως 1.7Ε+308

void 0 χωρίς τιμές

Οι μεταβλητές τύπου char (χαρακτήρας) χρησιμοποιούνται για

να κρατάνε χαρακτήρες ASCII σαν τους Α , b ή C, ή οποιαδήποτε άλλη

ποσότητα ενός byte. Οι μεταβλητές τύπου int (ακέραιος) μπορούν να

περιέχουν ακέραιες ποσότητες που δεν έχουν ανάγκη από κλασματικό

μέρος . Οι μεταβλητές τύπου float (κινητής υποδιαστολής ή

πραγματικός) χρειάζονται όταν διαχειριζόμαστε πολύ μεγάλους

αριθμούς ή όταν υπάρχουν κλασματικά μέρη . Οι μεταβλητές double

13

Page 14: Georgia Dis Intro C

(πραγματικός διπλής ακρίβειας) στο μόνο που διαφέρουν από τις

float είναι ότι χειρίζονται μικρότερα και μεγαλύτερα νούμερα . Τέλος ο

τύπος void (χωρίς τιμή) χρησιμοποιείται στη βελτίωση του ελέγχου

των τύπων .

Τροποποιητές Τύπων

Όλοι οι τύποι εκτός από τον void, μπορούν να έχουν μπροστά

τους διάφορους τροποποιητές (modifiers), οι οποίοι αλλάζουν τη

σημασία του βασικού τύπου ώστε να ικανοποιεί καλύτερα τις ανάγκες

διάφορων καταστάσεων . Οι τροποποιητές είναι :

ι) short (μικρός) ι ι ι) signed (προσημασμένος)

ι ι) long (μεγάλος) ιv) unsigned (απρόσημος)

Ο τροποποιητής signed δεν μας ενδιαφέρει και πολύ στα

μηχανήματα που τώρα δουλεύουμε γιατί οι εξ’ ορισμού (default)

ρυθμίσεις έχουν τους int και char προσημασμένους , όπως είδαμε και

προηγουμένως από το εύρος των τιμών τους . Όσο για το short για

μεγαλύτερους υπολογιστές προσφέρει μία μικρότερη σε μέγεθος

μνήμης εναλλακτική λύση για ακέραιους .

Οι νέοι τύποι που προκύπτουν λοιπόν είναι :

Τύπος /Λέξη-Κλειδί Πλάτος σε bytes Εύρος Τιμών

unsigned char 1 0 έως 255

unsigned int ή unsigned 2 0 έως 65535

long int ή long 4 -2 δισεκ . έως 2 δισεκ .

unsigned long 4 0 έως 4 δισεκ .

long double 10 3.4E-4392 έως 1.1Ε+4392

Unsigned

Η διαφορά ανάμεσα στους προσημασμένους και στους

απρόσημους , δεν βρίσκεται στη δέσμευση της μνήμης . Όπως βλέπετε οι

14

Page 15: Georgia Dis Intro C

ανάγκες σε μνήμη παραμένουν σε ένα και δύο bytes αντίστοιχα για

τους χαρακτήρες και τους ακέραιους . Βρίσκεται στον τρόπο που ο Η/Υ

ερμηνεύει το υψηλής τάξης bit του ακέραιου . Στους προσημασμένους

χαρακτήρες ή ακέραιους - η εξ’ ορισμού περίπτωση - το bit αυτό

δηλώνει με 0 ότι η μεταβλητή είναι θετική και με 1 ότι είναι αρνητική .

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

συμπληρώματος ως προς 2 . Δηλαδή ο αρνητικός προκύπτει από την

αντιστροφή όλων των bits (εκτός από αυτό που λειτουργεί σαν

πρόσημο) με αύξηση μιας μονάδας .

Παράδειγμα : ο αριθμός k

01111111 11111111

έχει το πιο σπουδαίο bit ίσο με 0. ‘Αρα δηλώνει τον αριθμό 32767. Αν

ο k είχε δηλωθεί ως unsigned, τότε πάλι θα δήλωνε τον ίδιο αριθμό .

Στις αρνητικές όμως τιμές είναι που φαίνεται η διαφορά . Δηλαδή αν

π .χ . o αριθμός m ήταν :

11111111 11111111

Τότε κανονικά (εξ’ ορισμού) ο αριθμός θα ήταν ο αρνητικός

0000000 00000000 = 0 + 1 = 1

δηλαδή m = -1. Σε περίπτωση όμως που είχε δηλωθεί ο m ως unsigned

τότε παύει να έχει νόημα προσήμου το σπουδαίο bit και ο m είναι ο

65535.

Οι απρόσημοι ακέραιοι έχουν όπως είναι φυσικό διπλάσιο εύρος

θετικών τιμών . Όμως ο πραγματικός σκοπός τους δεν είναι να

αυξήσουμε το μέγεθος αυτό , αλλά έχει σχέση με την παροχή άμεσων

διευθύνσεων (direct addressing) στη μνήμη μέσω των δεικτών θέσης

(pointers).

long

O τροποποιητής long επιβάλλει μεγαλύτερη δέσμευση σε μνήμη

και προσφέρει μεγαλύτερο εύρος τιμών . Ειδικά στους ακέραιους είναι

15

Page 16: Georgia Dis Intro C

χρησιμότατος αφού για μη κλασματικά δεδομένα που ξεφεύγουν πάνω

από 33000 προσφέρει ταχύτατες πράξεις σε σχέση με τους αριθμούς

float.

Προσέξτε :

1) Ο πιο βασικός τύπος είναι ο int . Σε πολλές περιπτώσεις , κατά

τις οποίες ένας τύπος δεν έχει καθοριστεί , η C υποθέτει ότι είναι int.

2) Tα strings , δηλαδή οι σειρές χαρακτήρων στην C ορίζονται

σαν πίνακες χαρακτήρων . Με την τελευταία μάλιστα θέση να έχει

πάντα τον χαρακτήρα ‘ \0’. Έτσι η δήλωση char s[11] δημιουργεί

μεταβλητή που μπορεί να αποθηκεύσει μέχρι 10 χαρακτήρες .

3) Η C δεν διαθέτει τύπο boolean για λογικές μεταβλητές , όπως

η Pascal. Έτσι χρησιμοποιεί τους int με τον ακόλουθο τρόπο : αν η

τιμή είναι 0 θεωρείται Ψευδής (False) και αν είναι οτιδήποτε διάφορο

του μηδενός τότε θεωρείται Αληθής (Τrue).

4) O τύπος char στη πραγματικότητα ξεφεύγει από το σύνολο

χαρακτήρων ASCII. Χρησιμοποιείται δηλαδή και κάθε φορά που

χρειαζόμαστε έναν μικρό ακέραιο εύρους -128 έως 127, ενώ ως

unsigned char είναι κάτι αντίστοιχο με τον τύπο byte της Pascal που

υποστηρίζει τις τιμές 0 έως 255. Αυτό συμβαίνει γιατί στην ουσία η C

ακόμα και τους χαρακτήρες τους χειρίζεται εσωτερικά με τον

αντίστοιχο ASCII κωδικό τους .

Παράδειγμα : Μια δήλωση

char c1 ;

μπορεί να ακολουθηθεί από τις εντολές

c1 = 65 ;

c1=c1+4 ;

H μεταβλητή c1 ουσιαστικά αποθηκεύει το νούμερο 69 στο τέλος των

δύο αυτών εντολών . Έτσι μπορούμε να κάνουμε πράξεις μ΄αυτήν αλλά

16

Page 17: Georgia Dis Intro C

μπορούμε ακόμη και να την χειριστούμε ως ASCII χαρακτήρα , οπότε η

c1 έχει τότε την τιμή ‘D’ αφού στον ASCII κώδικα το ‘D’ είναι το

νούμερο 69. Είναι λοιπόν το ίδιο πράγμα είτε πούμε c1 = 65 είτε c1 =

‘A’.

Τι γίνεται με τις σταθερές (constants)

Οι σταθερές του προγράμματος είναι τιμές που πρέπει να μείνουν

αμετάβλητες σε όλη τη διάρκεια εκτέλεσής του . Δεσμεύουν θέσεις

μνήμης που χαρακτηρίζονται RO (Read Only - μόνο διάβασμα). Η

προσπέλαση σε τέτοιες μνήμες είναι γρηγορότερη , γι’ αυτό και

προσπαθούμε όπου μπορούμε να τις χρησιμοποιούμε . Μπορούν να

ανήκουν σε οποιοδήποτε τύπο και ο τρόπος παράστασής τους

εξαρτάται ακριβώς από τον τύπο τους .

α) Οι σταθερές χαρακτήρα τοποθετούνται σε μονά εισαγωγικά .

Π .χ . ‘K’, ‘4’ κλπ .

β) Οι αλφαριθμητικές σταθερές είναι ένα σύνολο χαρακτήρων

μέσα σε διπλά εισαγωγικά . Π .χ . “καλημέρα σας”, “sos βοήθεια” κλπ .

γ) Οι ακέραιες σταθερές είναι αριθμοί χωρίς κλασματικά μέρη .

Μπορεί να ακολουθείται από το L για να δείχνει ότι είναι του τύπου

long. Π .χ . -4500, 5600L κλπ .

δ) Οι πραγματικές σταθερές χρησιμοποιούν είτε την υποδιαστολή

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

γραφή με τις δυνάμεις του 10 (όπου Ε-4 σημαίνει επί 10 - 4 ) . Π .χ .

432.65, 4.1Ε+3 κλπ .

Παρατηρήσεις πάνω στις Σταθερές

i) Στις σταθερές χαρακτήρα , ανήκουν και οι σταθερές ανάποδης

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

χαρακτήρες που προκαλούν ορισμένες ενέργειες . Περικλείονται

κανονικά σε μονά εισαγωγικά και τους χειριζόμαστε σαν

17

Page 18: Georgia Dis Intro C

οποιουσδήποτε χαρακτήρες . Μπορούν φυσικά να αποτελέσουν μέρος

και ενός string (π .χ . “καλά \nείναι”). Οι πιο κοινοί χαρακτήρες

ανάποδης κάθετου είναι :

Κωδικός Σημασία

\n αλλαγή γραμμής

\f αλλαγή σελίδας

\b πίσω ένας χαρακτήρας (backspace)

\ t μετακίνηση σαν το tab

\’ και \” μονά και διπλά εισαγωγικά

\ \ ανάποδη κάθετος (backslash)

\a κουδούνι (bell)

\x δεκεξαδική σταθερά

\Ν οκταδική σταθερά ,

όπου Ν 8-δικός αριθμός .

\0 μηδενικό (end of string)

i i) Στις ακέραιες σταθερές , ανήκουν και οι 16-δικές και 8-δικές

σταθερές . Γνωρίζουμε ότι η σπουδαιότητά τους οφείλεται στο ότι

συνδυάζοντας τα bits ανά τριάδες ή τετράδες δημιουργούμε οκταδικούς

ή δεκαεξαδικούς αριθμούς . Δηλαδή αριθμούς με ψηφία 0 έως 7 ή 0 έως

9 και Α έως F (για τους αριθμούς 10 έως 15). Μια σταθερή σε 16-δική

μορφή πρέπει να αρχίζει από 0x, ενώ μια σε 8-δική αρχίζει με ένα

μηδενικό .

Παράδειγμα : int hex = 0xF /* 15 στο δεκαδικό */

int oct = 013 /* 11 στο δεκαδικό */

i i i) Προσέξτε ότι ‘b’ για παράδειγμα δεν είναι ίδιο με “b”. To

πρώτο είναι χαρακτήρας (δεσμεύει 1 byte), ενώ το δεύτερο είναι string

(δεσμεύει 2 bytes - 1 για το ‘b’ και 1 για τον χαρακτήρα ‘ \0’ του

τέλους ενός string.

18

Page 19: Georgia Dis Intro C

iv) Αν θέλουμε να χρησιμοποιήσουμε κάποιο όνομα για μια

σταθερά , αυτό γίνεται με την οδηγία #define . Όπως και η include που

ήδη συναντήσαμε , η define ανήκει στις εντολές αυτές που ξεκινάνε με

# και δεν τελειώνουν με ; (ερωτηματικό). Είναι οδηγίες (directives)

προς τον προεπεξεργαστή και βρίσκονται στην αρχή μιας συνάρτησης .

Συνήθως διαλέγουμε ονόματα με κεφαλαία γράμματα για να

ξεχωρίζουν οι σταθερές μας μέσα στο πρόγραμμα .

Παράδειγμα :

#define LONGZERO 0L

#define MESSAGE “Καληνύχτα παιδάκια”

Στην ΑNSI-C μια σταθερά δηλώνεται και με την χρήση της const, μαζί

με τον τύπο και την τιμή .

Π .χ . const long k = 4000;

Δηλώσεις Μεταβλητών

Οι μεταβλητές είναι θέσεις μνήμης που κατά την διάρκεια

εκτέλεσης ενός προγράμματος επιτρέπεται να αλλάζουν τιμές .

Ορίζονται κατά τύπους , δηλαδή :

int i , j , k ;

char onoma[30] ;

Επιτρέπεται και είναι καλό , να δίνουμε αρχικές τιμές στις μεταβλητές

μαζί με την δήλωσή τους :

int j=2, k=5 ;

char c= ‘’ ;

Προσδιοριστές Φόρμας για την printf και την scanf

Αν και αργότερα θα εξετάσουμε αυτές της συναρτήσεις Εισόδου

- Εξόδου με λεπτομέρειες , είναι από τώρα χρήσιμο να δούμε λίγα

βασικά πράγματα σε σχέση με τους διάφορους τύπους :

19

Page 20: Georgia Dis Intro C

Κωδικός η printf εμφανίζει η scanf διαβάζει

%c ένα χαρακτήρα ένα χαρακτήρα

%d ή %i δεκαδικό δεκαδικό

%f πραγματικό πραγματικό

%e επιστημονική γραφή πραγματικό

%ld long ακέραιο long ακέραιο

%u απρόσημο -

%s αλφαριθμητικό αλφαριθμητικό

%o oκταδικό οκταδικό

%x δεκαεξαδικό δεκαεξαδικό

Παραδείγματα : η εντολή scanf(“%s”, k);

γεμίζει ένα string k από τον χρήστη , ενώ όταν m=66 η εντολή

printf(“και %c και %d”, m,m);

εμφανίζει

και Β και 66

20

Page 21: Georgia Dis Intro C

Σύμβολα ή Τελεστές (Οperators)

Σε γενικές γραμμές τα προγράμματα που γράφουμε διαβάζουν

κάποια δεδομένα , τα οποία δεδομένα βέβαια επιπλέον ελέγχονται από

αυτά για την ορθότητά τους . Στη συνέχεια με διάφορες πράξεις

(operations) συμβαίνει η κυρίως επεξεργασία των δεδομένων και

παράλληλα υπάρχουν έλεγχοι για ενδεχόμενα υπολογιστικά λάθη .

Τέλος , τα αποτελέσματα πρέπει να εμφανιστούν με κατανοητή μορφή

στους χρήστες . Ονομάζουμε γενικά πράξεις τις μεταβολές των τιμών

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

μεταβλητές και ένας τελεστής που καθορίζει την πράξη . Βέβαια στη C

είναι συνηθισμένο φαινόμενο να βλέπουμε σύνθετες σειρές από πράξεις

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

(Expressions) ή Παραστάσεις και θα τις μελετήσουμε αργότερα . Θα

δούμε τους τελεστές ανάλογα με την κατηγορία τους .

Τελεστής Απόδοσης Τιμής

Είναι το σύμβολο της ισότητας “=“ και ονομάζεται αλλιώς

εκχώρηση (assignment). Π .χ .

a = 12.45; b = “φεύγω \nαμέσως”; c = a ;

Οι εντολές απόδοσης τιμής μπορούν να είναι οπουδήποτε μέσα στο

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

μεταβλητής μπορούμε να της δώσουμε αρχική τιμή . Π .χ .

float FPA = 0.18 ;

Επιτρέπονται και πολλαπλές εκχωρήσεις τιμών :

k = l = m = 0 ;

Μια εντολή τύπου a=b; σημαίνει ότι το περιεχόμενο της μεταβλητής b

θα γεμίσει την μεταβλητή a. Έτσι αν η a είχε τιμή 10 και η b είχε τιμή

20, τότε μετά από μια εντολή a=b; η a περιέχει την τιμή 20 και η b

εξακολουθεί να έχει την αρχική της τιμή 20. Πρέπει δηλαδή να

21

Page 22: Georgia Dis Intro C

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

συμπεριφορά . Ότι βρίσκεται δεξιά μένει ανέπαφο , ενώ ότι βρίσκεται

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

περιεχόμενο με εκείνο της δεξιάς μεριάς .

Παρατήρηση : Η μεταβλητή που δέχεται την τιμή πρέπει να είναι

αρκετά μεγάλη για να την χωρέσει , αλλιώς προκύπτουν σφάλματα αφού

η C δεν κάνει καθόλου ελέγχους σε τέτοια ζητήματα . Αυτό βέβαια

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

καταστροφικές συνέπειες . Γι’ αυτό και η C θεωρείται γλώσσα

ακατάλληλη για εισαγωγή στον προγραμματισμό .

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

Αν εκχωρούμε πραγματικό σε ακέραιο τότε γίνεται αυτόματα

αποκοπή του δεκαδικού μέρους . Δηλαδή

int k ; float fl ; char ch ;

k = 12.78 ;

To k αποκτά την ακέραια τιμή 12.

Αν εκχωρούμε ακέραιο σε πραγματικό ή χαρακτήρα σε ακέραιο ή

ακέραιο σε χαρακτήρα , τότε γίνεται αυτόματη μετατροπή τύπου .

Δηλαδή

f1 = k ; ch = ‘B’ ;

k = ch ;

H f1 παίρνει την πραγματική τιμή 12.0 και η k παίρνει την ακέραια

τιμή 66 αφού ο χαρακτήρας ‘Β’ έχει ASCII τιμή το 66. Μια εντολή ch

= 77 θα έκανε την ch να έχει περιεχόμενο τον χαρακτήρα ‘Μ’ αφού

αυτός ο χαρακτήρας αντιστοιχεί στο 77 του πίνακα ASCII.

Όπως θα δούμε αργότερα επιτρέπεται να χρησιμοποιούμε τον

τελεστή εκχώρησης σε παραστάσεις που περιέχουν και άλλους

τελεστές . Έτσι κατασκευάζουμε σύνθετες εντολές που εκτελούν

παραπάνω από μία ενέργειες .

22

Page 23: Georgia Dis Intro C

Αριθμητικοί Τελεστές

Τελεστής Πράξη

- αφαίρεση , αρνητικό πρόσημο

+ πρόσθεση

* πολλαπλασιασμός

/ διαίρεση

% υπόλοιπο διαίρεσης (modulo)

-- μείωση κατά ένα (αδιαίρετη)

++ αύξηση κατά ένα (αδιαίρετη)

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

a = - b ; /* το a παίρνει την τιμή -b */

/* αν b ήταν 12, τότε το a έχει τιμή -12 */

Ο τελεστής μείον ή αρνητικού προσήμου ‘-’ είναι μοναδιαίος , δηλαδή

δεν συνδέει δύο μεταβλητές αλλά αναφέρεται σε μία πάντοτε . Ο ίδιος

χαρακτήρας ‘-’ αποτελεί και σύμβολο αφαίρεσης . Τότε όμως συνδέει

δύο μεταβλητές . Έτσι

a = b - 31 ;

/* αν το b έχει τιμή 51, τότε το a έχει τιμή 20 */

Με ίδιο τρόπο ερμηνεύουμε τους τελεστές πρόσθεσης ,

πολλαπλασιασμού , διαίρεσης και υπόλοιπου διαίρεσης . Δηλαδή

/* έστω c ήταν 20 και d ήταν 10 */

a = c + d ; /* τότε το a γίνεται 30 */

a = c * d ; /* το a γίνεται 200 */

a = c / d ; /* το a γίνεται 2 */

a = c % d ; /* το a γίνεται 0 */

/* διότι 20 = 10 * 2 + 0, δηλαδή το υπόλοιπο είναι 0 */

23

Page 24: Georgia Dis Intro C

Παρατηρήσεις :

α) Αν οι μεταβλητές είναι ακέραιες , τότε γίνεται ακέραια

διαίρεση . Δηλαδή

int a=50, b=4, k ; float m ;

k = a / b ; m = a / b;

To k παίρνει την ακέραια τιμή 12 (πηλίκο διαίρεσης), ενώ το m την

πραγματική τιμή 12.0. Αν μας ενδιέφερε η πραγματική διαίρεση ,

δηλαδή αν θέλαμε πηλίκο με κλασματικό μέρος (το 12.5), τότε μία

τουλάχιστον από τις a και b έπρεπε να είναι πραγματική μεταβλητή .

Βλέπε αργότερα το casting. Πάντως για τον ίδιο λόγο και όταν

διαιρούμε με σταθερές , προσθέτουμε στη σταθερά το “.0”, για

παράδειγμα

ακέραια διαίρεση a / 4

πραγματική διαίρεση a / 4.0

24

Page 25: Georgia Dis Intro C

Εντολές Ελέγχου Προγράμματος

Κανονικά οι εντολές ενός προγράμματος εκτελούνται με την

σειρά από την αρχή προς το τέλος . Η σειριακή αυτή ροή μεταβάλλεται

από τις εντολές ελέγχου (control structures), που δίνουν την

δυνατότητα σε ένα μέρος του προγράμματος , ανάλογα με την

περίπτωση κάθε φορά , για δύο πράγματα :

α) να μην εκτελεστεί καθόλου

β) να εκτελεστεί πολλές φορές

Έτσι ένα πρόγραμμα είναι ικανό να ρυθμίζει την συμπεριφορά

του ανάλογα με τις τρέχουσες συνθήκες . Ειδικότερα οι εντολές της

κατηγορίας α) ονομάζονται εντολές λήψης αποφάσεων ενώ η κατηγορία

β) περιλαμβάνει τις εντολές ανακύκλωσης (loops) .

ΛΗΨΗ ΑΠΟΦΑΣΕΩΝ

Η εντολή απόφασης if

Μορφή : if (συνθήκη) εντολή1 ;

else εντολή2 ;

Ο όρος else είναι προαιρετικός . Αν η συνθήκη είναι Αλήθεια , δηλαδή

διαφορετική από το μηδέν , τότε θα εκτελεστεί η εντολή1. Αλλιώς , αν

υπάρχει το else, θα εκτελεστεί η εντολή2.

Παράδειγμα 1ο: int k=5, m=0, b;

if (k > 4) printf(“SOS”);

if (m) printf(“KAΛΗΜΕΡΑ”);

else b=2 *k ;

25

Page 26: Georgia Dis Intro C

Η πρώτη if θα εμφανίσει το SOS γιατί το 5 είναι μεγαλύτερο από το 4.

Η δεύτερη if δεν θα εμφανίσει το ΚΑΛΗΜΕΡΑ γιατί το m ως μηδέν

είναι Ψέμα στη C. Αντίθετα , η δεύτερη if γεμίζει το b με την τιμή 10.

Αν στόχος της if είναι ομάδα εντολών , ή στόχος της else είναι

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

Παράδειγμα 2ο: char i=12, m,a;

scanf(“%c”,&m);

if (m==‘Y”)

printf(“KAΛΗΜΕΡΑ”);

i+=20;

else

printf(“XΆΛΙΑ”);

i++ ;

Αν ο χρήστης γεμίσει με Υ την μεταβλητή m, τότε θα εμφανιστεί

ΚΑΛΗΜΕΡΑ και το i θα γεμίσει με την τιμή 32. Με οποιαδήποτε άλλη

τιμή στο m, θα εμφανιστεί ΧΆΛΙΑ και το i θα γεμίσει με την τιμή 13.

Τελεστής υπό συνθήκη ή τριαδικός ( ? : )

Προτάσεις της μορφής

if (συνθήκη) παράσταση1

else παράσταση2

μπορούν να αντικατασταθούν ως

συνθήκη ? παράσταση1 : παράσταση2 ;

Παράδειγμα 3ο : scanf(“%d”,&y);

if (y <0) k = -y ;

26

Page 27: Georgia Dis Intro C

else k = y;

Με τον τελεστή ? , αντί για την if γράφουμε

k = (y < 0) ? -y : y ;

Δηλαδή εδώ το k γεμίζει πάντα με την απόλυτη τιμή του y.

if . . .else if . . .

H “σκάλα” if-else-if προσφέρει σε αρκετές περιπτώσεις

δραστικές μειώσεις στους αριθμούς λογικών πράξεων που πρέπει να

υπολογιστούν για να ληφθούν αποφάσεις . Μελετήστε το παράδειγμα :

Παράδειγμα 4ο :

if (k<100) printf(“KAΛΑ”) ;

if (k >= 100) && (k < 500) printf(“IΣΩΣ”);

if (k >= 500)

b=k ; printf(“ΣΙΓΟΥΡΑ”);

Για να εκτελεστούν οι 3 αυτές if, πρέπει να γίνουν κάθε φορά πέντε

λογικές πράξεις . Με την παρακάτω διάταξη , το πολύ δύο λογικές

πράξεις θα χρειαστούν .

if (k<100) printf(“KAΛΑ”) ;

else if (k<500) printf(“IΣΩΣ”);

else

b=k ; printf(“ΣΙΓΟΥΡΑ”);

Εκμεταλλευτήκαμε δηλαδή την άρνηση που προσφέρει η else και έτσι

γράψαμε μία εντολή ταχύτερη που έχει ισοδύναμα αποτελέσματα με τις

3 if .

H εντολή πολλαπλών επιλογών switch

Εδώ ο υπολογιστής ελέγχει διαδοχικά μια μεταβλητή με μια

λίστα από ακέραιες σταθερές ή σταθερές χαρακτήρα . Μόλις εντοπιστεί

μια σύμπτωση , εκτελείται η ομάδα εντολών που σχετίζεται με αυτή τη

σταθερά . Προσοχή λοιπόν : Η switch ελέγχει ΜΟΝΟΝ ισότητες .

27

Page 28: Georgia Dis Intro C

Μορφή : switch (μεταβλητή)

case σταθερά1 :

σειρά εντολών

break ;

case σταθερά2 :

σειρά εντολών

break ;

. . . . . . . . .

default :

σειρά εντολών

Ο όρος default είναι προαιρετικός . Όταν υπάρχει και όταν δεν έχει

ταιριάξει η μεταβλητή με καμιά σταθερά , τότε εκτελείται η ομάδα

εντολών της . Και τα break είναι προαιρετικά . Τα βάζουμε όταν

θέλουμε να τερματίσουμε τη σειρά των εντολών που σχετίζονται με

κάθε σταθερά . Γιατί αν παραληφθεί μια break η εκτέλεση θα

συνεχιστεί με τις εντολές της επόμενης case μέχρι να φτάσει είτε σε

κάποιο break είτε στο τέλος της switch.

Παράδειγμα 5ο: switch ( ch )

case ‘Κ’ : printf(“SOS”); b-=10; case ‘Λ’ : printf(“GO”); break ;

case ‘M’ :

case ‘N’ : printf(“ΦΕΥΓΩ”);

b *= 3;

default : a= 1000;

Όταν ο χαρακτήρας ch έχει τιμή Κ τότε θα εμφανιστεί SOS, η b θα

ελαττωθεί κατά 10 και θα εμφανιστεί GO. Όταν ο ch είναι Λ τότε απλά

θα εμφανιστεί GO. Όταν ο ch είναι Μ ή Ν τότε θα εμφανιστεί ΦΕΥΓΩ ,

η μεταβλητή b θα τριπλασιαστεί και η a θα πάρει τιμή 1000. Όταν η ch

28

Page 29: Georgia Dis Intro C

δεν είναι ούτε Κ ούτε Λ ούτε Μ ούτε Ν τότε η a θα πάρει την τιμή

1000.

ΑΝΑΚΥΚΛΩΣΗ - ΒΡΟΓΧΟΣ

Η εντολή while

Όταν θέλουμε ένα μέρος του προγράμματος να επαναλαμβάνεται

μόνο αν , και για όσο ισχύει μια συνθήκη , χωρίς να ξέρουμε πόσες

φορές θα γίνει αυτό , χρησιμοποιούμε συνήθως την while.

Μορφή : while (συνθήκη) ή while (συνθήκη)

εντολή ; εντολές

Πρώτα υπολογίζεται αν είναι Αλήθεια ή Ψέμα η συνθήκη . Αν

είναι Αλήθεια , δηλαδή διάφορη του μηδενός , τότε εκτελείται η εντολή

(ή οι εντολές) και αμέσως ξαναελέγχεται η συνθήκη . Όσο η συνθήκη

είναι αληθής διαρκεί αυτή η ανακύκλωση . Μόλις γίνει ψέμα οι εντολές

αγνοούνται και τελειώνει η δουλειά της while. Πρέπει λοιπόν είτε μέσα

στο κορμό της while, είτε μέσα στη συνθήκη -μέσω μιας σύνθετης

παράστασης- να υπάρχει κάτι που αλλάζει τη τιμή της από αλήθεια σε

ψέμα . Γιατί αλλιώς θα βρεθούμε σε άπειρη ανακύκλωση .

Παράδειγμα 6ο: #include <conio.h>

#define STOP ‘*’

main()

char ch;

int count = 0;

while (( ch = getchar()) != STOP)

putchar(ch) ;

count++ ;

printf(“ήταν %d χαρακτήρες”, count) ;

return 0;

29

Page 30: Georgia Dis Intro C

Οι συναρτήσεις διαβάσματος χαρακτήρα getchar() και εμφάνισης

χαρακτήρα putchar(), χρησιμοποιούν το αρχείο-επικεφαλίδα conio.h .

H συνθήκη είναι σύνθετη παράσταση . Για να μπορέσει να καταλήξει ο

υπολογιστής αν είναι αλήθεια ή ψέμα πρέπει να εκτελέσει πρώτα την

getchar(), να καταχωρήσει έπειτα τον χαρακτήρα που έδωσε ο χρήστης

στη μεταβλητή ch και μετά να τον συγκρίνει με την σταθερά STOP.

Όσο δεν δίνει ο χρήστης το *, τόσο εμφανίζεται ο χαρακτήρας και

αυξάνει ο μετρητής count . Μόλις δώσει το αστεράκι , εμφανίζεται το

μήνυμα για παράδειγμα

ήταν 10 χαρακτήρες

αν ο χρήστης έδωσε 10 χαρακτήρες , έναν-έναν , και μετά έδωσε το *.

Η εντολή do . . . while

Εδώ πρώτα εκτελείται η ανακύκλωση και μετά γίνεται ο έλεγχος .

Αντιστοιχεί στην εντολή repeat της Pascal. Με την do . . . while είμαστε

σίγουροι ότι θα εκτελεστεί τουλάχιστον μια ανακύκλωση . Η πιο κοινή

της χρήση είναι σε ρουτίνες επιλογών από μενού , αφού η εμφάνιση των

μηνυμάτων των μενού γίνεται πάντα πριν τις επιλογές του χρήστη .

Μορφή : do ή do

εντολή ; εντολές

while (συνθήκη) ; while (συνθήκη) ;

Η ανακύκλωση , όπως και στη while, εκτελείται όσο η συνθήκη είναι

αληθής , δηλαδή διάφορη του μηδενός .

Παράδειγμα 7ο: #include <stdio.h>

main()

char ch ;

do

printf(“δώστε (Ν)αι / (Ο)χι :”);

ch = getchar();

30

Page 31: Georgia Dis Intro C

while ( ( ch != ‘N’) && (ch != ‘O’));

return 0 ;

Έτσι εξασφαλίζουμε ότι ο χρήστης δίνει απάντηση μέσα σε επιτρεπτά

όρια . Άν δώσει οτιδήποτε άλλο εκτός από Ν και Ο τότε θα εμφανιστεί

ξανά το μήνυμα και θα περιμένει ο υπολογιστής απάντηση . Και αυτό

θα συνεχιστεί έως ότου ο χρήστης δώσει Ν ή Ο .

Η εντολή for

Στη C η εντολή for είναι πάρα πολύ ισχυρή . Συνήθως η

ανακύκλωση ελέγχεται από ένα μετρητή που είναι μια μεταβλητή

ακέραια ή χαρακτήρας .

Μορφή : for (παράσταση1;παράσταση2;παράσταση3)

εντολή ; ή εντολές

Ισοδυναμεί με παράσταση1;

while (παράσταση2)

εντολές ;

παράσταση3;

Δηλαδή η παράσταση1 δίνει αρχικές τιμές σε μεταβλητές , η

παράσταση2 είναι μια συνθήκη που καθορίζει αν θα συνεχιστεί η

επανάληψη και η παράσταση3 είναι το βήμα , δηλαδή ο τρόπος αύξησης

ή μείωσης μεταβλητών (ενημέρωση). Προσοχή !!! Πρώτα από όλα και

για μία φορά , εκτελείται η παράσταση1. Μετά ελέγχεται η αλήθεια της

παράστασης2. Άν είναι αληθής , τότε εκτελούνται οι εντολές του

κορμού της for. Άν όχι , τότε σταματάει η for. Όπως βλέπετε υπάρχει

πιθανότητα , σαν την while , ο κορμός της for να μην εκτελεστεί ούτε

μια φορά .

31

Page 32: Georgia Dis Intro C

Παρατηρήσεις: α)Μπορεί να λείπουν κάποιες από τις παραστάσεις

της for, αλλά είναι υποχρεωτικό να υπάρχουν τα ερωτηματικά .

β) Άν λείπει η παράσταση2 ή αν δεν μεταβάλλονται μεταβλητές

που επιδρούν στην παράσταση2 είναι σίγουρο ότι θα έχουμε infinite

loop (ατέρμονος βρόγχος).

Παραδείγματα : i) for (x=5; x>0; x--)

printf(“%d ”, x);

k+=4;

Εμφανίζονται στην οθόνη οι αριθμοί 5 4 3 2 1 , ενώ η μεταβλητή

k αυξήθηκε συνολικά κατά 5*4=20 μονάδες .

i i) for (ch=‘A’; ch <= ‘C’; ch++)

printf(“%d είναι %c*”,ch,ch);

Εμφανίζει Α είναι 65*Β είναι 66*C είναι 67 . Προσέξτε ότι η

μεταβλητή ελέγχου ch είναι τύπου char.

i i i) for (num=1; num*num*num<216; num+=2)

printf(“%d ”,num);

Εμφανίζει 1 3 5 . Προσέξτε ότι ελέγχουμε κάποια άλλη συνθήκη

εδώ και όχι τον αριθμό των επαναλήψεων .

iv) ans=2;

for (n=3; ans<=25;)

ans*=n; printf(“%d->”,ans);

Εμφανίζει 6->18->54->.

β) Το πηλίκο μιας διαίρεσης είναι αρνητικός αριθμός μόνον όταν

ο διαιρετέος και ο διαιρέτης έχουν διαφορετικά πρόσημα .

32

Page 33: Georgia Dis Intro C

γ) Το σύμβολο % χρησιμοποιείται μόνο με ακέραιες μεταβλητές

ή σταθερές . Και το υπόλοιπο διαίρεσης έχει πάντοτε ίδιο πρόσημο με

αυτό του διαιρετέου .

δ) Η διαίρεση διά μηδέν (0 ή 0.0) και το υπόλοιπο διαίρεσης διά

του μηδενός προκαλεί πάντοτε σοβαρό σφάλμα που σταματάει την

εκτέλεση του προγράμματος .

Αύξηση και Μείωση κατά 1

Οι τελεστές ++ και -- μπορούν να βρίσκονται είτε στην αρχή είτε

στο τέλος μιας μεταβλητής . Όταν χρησιμοποιούνται μόνοι τους τότε

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

μάλιστα είναι προτιμότερο από άποψη ταχύτητας εκτέλεσης , αντί για

k= k+1; και m =m -1 ;

είτε k++; και m--;

είτε ++k ; και --m;

Υπάρχει διαφορά όταν χρησιμοποιούμε αυτούς τους τελεστές σε

μια παράσταση . Όταν προηγείται ο τελεστής , τότε η C πρώτα αυξάνει ή

μειώνει κατά 1 την μεταβλητή και μετά την χρησιμοποιεί . Αντίθετα

όταν ο τελεστής ακολουθεί , τότε πρώτα χρησιμοποιείται η τιμή της

μεταβλητής και μετά ακολουθεί η αύξηση ή μείωση κατά 1.

Παράδειγμα : x = 100 ;

y = ++x;

Πρώτα η x αποκτά την τιμή 101 και μετά και η y φυσικά αποκτά

την τιμή αυτή του 101. Ενώ όταν

x = 100 ;

y = x++;

33

Page 34: Georgia Dis Intro C

τότε πρώτα η y αποκτά την τιμή 100 και μετά η x γίνεται 101.

Προσοχή!!! Και στις δύο περιπτώσεις η x παίρνει την τιμή 101, η

διαφορά είναι πότε την παίρνει .

Προτεραιότητες . . .

Από υψηλότερη προς χαμηλότερη προτεραιότητα , η σειρά είναι :

++ --

-

* / %

+ -

Όταν έχουμε ισοδύναμους τελεστές , ο υπολογιστής δίνει

προτεραιότητα στον τελεστή που βρίσκεται στην έκφραση αριστερά .

Μπορούμε όμως να χρησιμοποιούμε παρενθέσεις για να αλλάξουμε τις

προτεραιότητες . Έτσι

k = m + 7 * 3 ;

δίνει στο k την τιμή 31 όταν το m είναι 10, ενώ

k = ( m + 7 ) * 3;

δίνει στο k την τιμή 51 όταν το m είναι 10.

Συντομογραφία και όχι μόνο

Θα εξηγήσουμε εδώ την χρησιμότητα των ακόλουθων τελεστών :

+= -= *= /= %=

Σε μια πράξη που η ίδια μεταβλητή βρίσκεται και από τις δύο

πλευρές του “=“, είναι προτιμότερο να χρησιμοποιούμε τους πιο πάνω

τελεστές . Δηλαδή αντί για

k= k +10;

δίνουμε k += 10;

αντί για m = m % 7 ;

δίνουμε m %= 7 ;

και γενικά αυτό γίνεται σε όλους τους δυαδικούς τελεστές .

34

Page 35: Georgia Dis Intro C

Συσχετιστικοί Τελεστές

Τελεστής Ενέργεια

> μεγαλύτερος από

< μικρότερος από

>= μεγαλύτερος ή ίσος

<= μικρότερος ή ίσος

== ίσος με

!= όχι ίσος

Η βασική ιδέα εδώ είναι η έννοια της Αλήθειας και του Ψέματος .

Είπαμε ήδη ότι στη C, αληθής είναι οποιαδήποτε τιμή διάφορη του

μηδενός , ενώ ψευδής τιμή είναι το μηδέν . Έτσι οι παραστάσεις που

χρησιμοποιούν συσχετιστικούς τελεστές θα επιστρέψουν το 0 σαν

ψευδή τιμή και το 1 σαν αληθή τιμή .

Παράδειγμα : int x=12;

printf(“%d”, x>10);

Θα εμφανιστεί στην οθόνη το 1.

Προσοχή : α) Όπως στη Pascal τα σύμβολα εκχώρησης ‘ :=‘ και ελέγχου

ισότητας ‘=‘ είναι διαφορετικά , έτσι και στη C άλλο σύμβολο (‘=‘)

χρησιμοποιούμε για απόδοση τιμής και άλλο (‘==‘) χρησιμοποιούμε

για σύγκριση .

β) Το θαυμαστικό ‘!’ έχει όπως θα δούμε και αργότερα την

έννοια της άρνησης , οπότε ‘!=‘ είναι το διάφορο ‘<>‘ της Pascal.

Λογικοί Τελεστές

Τελεστής Ενέργεια

&& ΑΝD

| | OR

! NOT

35

Page 36: Georgia Dis Intro C

Όπως και οι συσχετιστικοί έτσι και οι λογικοί επιστρέφουν το 0

σαν ψευδή τιμή και το 1 σαν αληθή . Οι λογικοί τελεστές υποστηρίζουν

τις βασικές λογικές πράξεις ΚΑΙ , Ή , ΟΧΙ που ορίζονται με τους

ακόλουθους πίνακες αλήθειας , όπου 1 αλήθεια και 0 ψέμα .

p q p && q p | | q !p

0 0 0 0 1

0 1 0 1 1

1 0 0 1 0

1 1 1 1 0

Όσον αφορά τις προτεραιότητες και οι λογικοί και οι

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

Δηλαδή

10 > a + 100;

είναι το ίδιο σαν 10 > (a+100);

Τώρα για τις μεταξύ τους προτεραιότητες , η σειρά από υψηλότερους

προς χαμηλότερους τελεστές είναι

!

> >= <= <

== !=

&&

| |

Η χρησιμότητα των λογικών και συσχετιστικών τελεστών θα

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

συνέχεια .

Μετατροπές ή Προσαρμογές Τύπων (Casting)

36

Page 37: Georgia Dis Intro C

Μπορούμε να υποχρεώσουμε μια παράσταση να είναι κάποιου

συγκεκριμένου τύπου , δίνοντας πριν την πράξη μέσα σε παρένθεση τον

ζητούμενο τύπο . Δηλαδή

int i = 61;

printf(“%7.2f”, (float)i / 5) ;

εμφανίζει 12.20, γιατί ο i προσαρμόστηκε σε πραγματικό και στη C

ισχύει ότι όταν σε μια παράσταση ανακατεύονται σταθερές και

μεταβλητές διάφορων τύπων τότε θα γίνει πράξη προς πράξη

μετατροπή στον τύπο της μεγαλύτερης μεταβλητής ή σταθερής . Έτσι το

(float)i είναι float, το 5 είναι integer και άρα το (float)i / 5 είναι float.

Αν γράφαμε όμως

int i = 61;

printf(“%7.2f”, (float)(i / 5)) ;

τότε i /5 δίνει 12 και απλά θα εμφανιζόταν 12.00 αφού το (float)

αναφέρεται πλέον στον ακέραιο i /5.

Παρατήρηση : Συνηθισμένο πρόβλημα στον πολλαπλασιασμό είναι το

γινόμενο δύο integer μεταβλητών να ξεφεύγει πάνω από το 32767.

Δηλαδή

long k ; int i=2000, m=30 ;

k = i * m ;

δεν θα αποθηκεύσει το αναμενόμενο 60000 στην μεταβλητή k αλλά το -

5536. Ο λόγος είναι ότι i και m είναι integer και έτσι η πράξη i*m

είναι ακέραια . Άρα όταν ξεφεύγει πάνω από τα όρια του integer πέφτει

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

ενός από τους τελεσταίους (μεταβλητές εδώ), δηλαδή

long k ; int i=2000, m=30 ;

k = (long)i * m ;

και το k θα αποθηκεύσει το αναμενόμενο 60000.

37

Page 38: Georgia Dis Intro C

Εισαγωγή στις Συναρτήσεις

Η φιλοσοφία σχεδίασης της γλώσσας C βασίζεται στη χρήση των

συναρτήσεων (functions). Όταν γράφουμε στο πρόγραμμά μας για

παράδειγμα “printf(“SOS”);” δεν κάνουμε τίποτε άλλο από το να

καλούμε την function printf() με συγκεκριμένη παράμετρο . Η

συνάρτηση αυτή έχει οριστεί μέσα στις βιβλιοθήκες συστήματος και

έτσι εμείς το μόνο που χρειάζεται για να χρησιμοποιούμε τέτοιες

συναρτήσεις συστήματος είναι να μάθουμε να τις καλούμε σωστά ,

δηλαδή δίνοντας σωστά τις παραμέτρους που χρειάζονται . Επιπλέον

όμως πρέπει να δημιουργούμε και τις δικές μας συναρτήσεις και να τις

κάνουμε διαθέσιμες στη βασική συνάρτηση main().

Τι είναι όμως συνάρτηση ; Είναι μια αυτοδύναμη μονάδα κώδικα

προγράμματος που μπορεί να παράγει κάποιες λειτουργίες και να

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

χρησιμοποιούνται όποτε κληθούν κατά τη διάρκεια εκτέλεσης του

προγράμματος . Συνήθως χρειάζονται ορισμένες τιμές από έξω για να

λειτουργήσουν ή πρέπει και να τροποποιήσουν κάποιες από τις τιμές

αυτές για να δώσουν τα αποτελέσματά τους . Η μεταφορά τιμών μέσα

και έξω από συναρτήσεις λέγεται πέρασμα παραμέτρων γιατί

χρησιμοποιεί ορισμένες ειδικές μεταβλητές-παραμέτρους . Για να

εκτελέσουν τις εντολές τους οι συναρτήσεις περιέχουν και εσωτερικές

μεταβλητές για ιδιωτική τους χρήση . Οι εσωτερικές αυτές μεταβλητές

λέγονται τοπικές ( local) και δεν καταλαμβάνουν μόνιμο χώρο στη

μνήμη αλλά δημιουργούνται μόνο όταν κληθεί η συνάρτηση ,

χρησιμοποιούνται και καταστρέφονται μόλις η συνάρτηση τελειώσει

την εκτέλεσή της . Είναι αυτές οι δυνατότητες των συναρτήσεων που

χαρακτηρίζουν την C ως γλώσσα δομημένου προγραμματισμού .

Προσοχή!!! Δεν μπορούμε να φτιάξουμε συνάρτηση μέσα σε

συνάρτηση όπως σε άλλες δομημένες γλώσσες .

38

Page 39: Georgia Dis Intro C

Τι κερδίζουμε όμως γράφοντας κώδικα με συναρτήσεις ;

α)Γράφουμε μια φορά ένα μικρό κομμάτι προγράμματος που

επαναλαμβάνεται . Μπορούμε ακόμη να χρησιμοποιήσουμε την ίδια

συνάρτηση και σε διαφορετικά προγράμματα . Έτσι γλιτώνουμε

γράψιμο κώδικα και επομένως αποφεύγουμε ενδεχόμενα λάθη .

β)Έχουμε σε μικρά κομμάτια το πρόγραμμα , σπονδυλωτό , και

άρα πιο εύκολο σε έλεγχο και τροποποιήσεις .

γ)Κερδίζουμε χώρο εκτελέσιμου κώδικα στη μνήμη γιατί δεν

τον επαναλαμβάνουμε .

δ)Κερδίζουμε χώρο μεταβλητών στη μνήμη κατά την εκτέλεση

γιατί ο χώρος των τοπικών μεταβλητών ξαναχρησιμοποιείται .

Για να κατασκευάσουμε μια συνάρτηση πρέπει πρώτα να την

δηλώσουμε και μετά όπου θέλουμε να την καλέσουμε .

Γενική μορφή δήλωσης μιας συνάρτησης

καθοριστικό-τύπου όνομα-συνάρτησης(λίστα-παραμέτρων)

δηλώσεις τύπων παραμέτρων

κορμός της συνάρτησης

Το καθοριστικό-τύπου είναι ο τύπος της τιμής (πχ . f loat) που θα

επιστρέφει η συνάρτηση μέσω της return . Αν δεν καθορίσουμε κάποιο

τύπο τότε ο υπολογιστής υποθέτει ότι θα επιστραφεί ακέραιος ( int).

Μπορούν να επιστρέφονται όλοι οι τύποι εκτός από πίνακες . Όπως

είδαμε ο τύπος void σημαίνει ότι η συνάρτηση δεν επιστρέφει τίποτα .

H λίστα-παραμέτρων είναι μια σειρά μεταβλητών που χωρίζονται

με κόμματα . Από εδώ παραλαμβάνονται οι τιμές των ορισμάτων όταν

καλείται η συνάρτηση . Μπορούμε να μην έχουμε σε συνάρτηση

παραμέτρους . Τότε , αν και η λίστα θα είναι άδεια , πρέπει πάλι να

βάλουμε τις παρενθέσεις .

39

Page 40: Georgia Dis Intro C

ΠΡΟΣΟΧΗ : Στη σύγχρονη μέθοδο δήλωσης οι δηλώσεις τύπων

παραμέτρων γίνονται όχι από κάτω , αλλά μέσα στη λίστα-παραμέτρων .

Δηλαδή γράφουμε

f loat j im(int x, int y, float z)

κορμός συνάρτησης

και όχι όπως στη κλασσική μέθοδο δήλωσης

f loat j im(x,y,z)

int x, y;

float z;

κορμός συνάρτησης

Προσέξτε ότι σε αντίθεση με την δήλωση μεταβλητών , όπου

μεταβλητές κοινού τύπου δηλώνονται όλες μαζί με κόμματα , κάθε

παράμετρος στη λίστα πρέπει να περιλαμβάνει και τον τύπο της .

Επίσης παρατηρήστε ότι η επικεφαλίδα της συνάρτησης ΔΕΝ τελειώνει

με “;”.

Κλήση συνάρτησης

Στη C η κλήση συνάρτησης είναι σαν πράξη και μπορεί να γίνει

οπουδήποτε , στην κύρια συνάρτηση main(), ή μέσα σε κάποια άλλη

συνάρτηση . Και βέβαια οι συναρτήσεις εκτελούνται όταν κληθούν από

κάποια άλλη συνάρτηση που ήδη εκτελείται . Όταν όμως η εκτέλεση

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

δημιουργούνται οι μεταβλητές της συνάρτησης , εκτελούνται οι εντολές

της και όταν τερματίσει , εξαφανίζονται οι τοπικές μεταβλητές της και

η εκτέλεση του προγράμματος συνεχίζεται με την εντολή που

ακολουθεί την κλήση , στο μέρος από όπου κλήθηκε .

Παράδειγμα 1ο : Εδώ η συνάρτηση1 καλεί τη συνάρτηση2 που με τη

σειρά της καλεί την συνάρτηση3.

Συνάρτηση 1

(καλεί) Συνάρτηση 2

40

Page 41: Georgia Dis Intro C

(καλείται και καλεί) Συνάρτηση3

(καλείται)

x *= 3 ; int fun1() void fun2()

y = fun1() ; ==> fun2(); ==> k = x+ m ;

x += y ; <== return 0; <==

Η εντολή return

Έχει δύο χρήσεις :

α)Προκαλεί άμεση έξοδο από τη συνάρτηση που την περιέχει .

Δηλαδή υποχρεώνει το πρόγραμμα να αγνοήσει τις υπόλοιπες εντολές

της συνάρτησης και συνεχίζει από τον κώδικα που πραγματοποίησε τη

κλήση . Εδώ η return δεν έχει δίπλα της κάποια τιμή .

β)Είναι ένας από τους τρόπους που μια συνάρτηση επιστρέφει

αποτέλεσμα εκεί από όπου κλήθηκε . Με την return συγκεκριμένα , μια

συνάρτηση επιστρέφει ΜΙΑ τιμή στο όνομά της . Και τότε , όπως

είδαμε , πρέπει η τιμή να είναι ίδιου τύπου με το καθοριστικό-τύπου

που δώσαμε κατά την δήλωση-ορισμό της .

Παράδειγμα 2ο: . . . . . .

float f ; με float f1()

f = f1(); return 25.678 ;

. . . . . .

Μια συνάρτηση μπορεί να μην περιέχει εντολή return. Τότε

τερματίζει την εκτέλεσή της μόλις εκτελέσει την τελευταία εντολή της

και συναντήσει το “”. Επειδή όμως ΟΛΕΣ οι συναρτήσεις , εκτός από

αυτές που δηλώθηκαν void , επιστρέφουν μια τιμή θεωρείται το μηδέν

ως τιμή επιστροφής .

Μία συνάρτηση επίσης μπορεί να περιέχει πολλές εντολές return,

αν αυτό απλοποιεί τον αλγόριθμο .

Εμβέλεια Μεταβλητών

41

Page 42: Georgia Dis Intro C

Για να καταλάβουμε καλύτερα τους μηχανισμούς επικοινωνίας

των συναρτήσεων , δηλαδή αυτό που ονομάζουμε “πέρασμα

παραμέτρων”, θα πρέπει να έχουμε στο μυαλό μας ότι στη C υπάρχουν

τρεις τύποι μεταβλητών :

α) Τοπικές Μεταβλητές ( local variables)

β) Καθολικές Μεταβλητές (global variables)

γ) Τυπικές Παράμετροι (formal parameters)

Οι τοπικές μεταβλητές στη C δεν περιορίζονται απλά σε επίπεδο

υποπρογράμματος , όπως συμβαίνει σε άλλες γλώσσες . Κάθε τμήμα

κώδικα , δηλαδή εντολές που ξεκινούν με αριστερό άγκιστρο και

τελειώνουν με δεξί άγκιστρο , μπορεί να ορίζει δικές του ιδιωτικές

μεταβλητές που καταστρέφονται έξω από αυτό . Και μάλιστα δεν είναι

υποχρεωτικό να τις δηλώνουμε στην αρχή του κώδικα . Πολλές φορές

τις δηλώνουμε μέσα σε ένα τμήμα με συνθήκη , εξασφαλίζοντας ότι

δέσμευση μνήμης θα γίνει μόνον όταν θα χρειαστεί .

Παράδειγμα 3ο: . . .

if (ch==‘N’)

float s ;

scanf(“%f”, &f) ; . . .

Βλέπουμε ότι θα κρατηθεί χώρος για τη μεταβλητή s μόνον όταν

χρειαστεί , δηλαδή όταν η ch είναι ίση με ‘Ν’.

Η λέξη-κλειδί auto της γλώσσας C υπάρχει για να δηλώνουμε ότι

μια μεταβλητή είναι τοπική . Όμως ΔΕΝ χρησιμοποιείται καθόλου γιατί

όλες οι μη καθολικές μεταβλητές είναι τύπου auto εξ’ ορισμού .

Οι καθολικές μεταβλητές αντίθετα είναι γνωστές σε ολόκληρο

το πρόγραμμα και μπορούν να χρησιμοποιούνται από οποιοδήποτε

τμήμα κώδικα . Η δήλωσή τους γίνεται έξω από όλες τις συναρτήσεις .

Παράδειγμα 4ο: #include <stdio.h>

char ch ; /* η ch είναι καθολική */

main()

42

Page 43: Georgia Dis Intro C

. . .

Παρατηρήσεις :

α)Όταν μια καθολική και μια τοπική μεταβλητή έχουν το ίδιο

όνομα , πχ . count, όλες οι αναφορές στο εσωτερικό της συνάρτησης

όπου δηλώνεται η count, για την μεταβλητή count, αφορούν την τοπική

μεταβλητή και δεν έχουν καμιά επίδραση στην καθολική .

Παράδειγμα 5ο: #include <stdio.h>

int count ;

main()

void f1();

count =50;

f1();

printf(“%d”, count);

return 0;

void f1()

int count ;

for (count=1;count<6;count++)

printf(“*”);

Αυτό που προκύπτει είναι “ *****50 “. Δηλαδή άλλος χώρος στη

μνήμη είναι η count της f1(), που ξεκινάει από το 1 και φτάνει ως το 6,

και άλλος χώρος είναι η count η ολική που περιέχει την τιμή 50.

β)Οι καθολικές μεταβλητές είναι χρήσιμες όταν χρησιμοποιούμε

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

Ωστόσο , αποφεύγουμε τις μη απαραίτητες καθολικές μεταβλητές για

τους ακόλουθους λόγους :

i) Καταλαμβάνουν μνήμη σε όλη τη διάρκεια εκτέλεσης ενός

προγράμματος και όχι μόνον όταν είναι χρήσιμες .

i i) Οδηγούν σε προγραμματιστικά λάθη λόγω άγνωστων και

ανεπιθύμητων παρενεργειών (πχ . κατά λάθος αλλαγή τιμής μιας

μεταβλητής).

43

Page 44: Georgia Dis Intro C

i i i) Κάνουν την συνάρτηση λιγότερο γενική γιατί την

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

Χαλάνε δηλαδή την ζητούμενη αυτοδυναμία και ανεξαρτησία των

υποπρογραμμάτων .

Παράδειγμα 6ο: Προσέξτε τους δύο τρόπους γραφής μιας

συνάρτησης add(), που υπολογίζει το άθροισμα δύο πραγματικών .

Γενική μορφή Ειδική μορφή

f loat add (float x, float y) float x, y ;

return ( x+y ) float add()

return ( x+y )

Το πλεονέκτημα της γενικής ή παραμετροποιημένης συνάρτησης είναι

ότι επιστρέφει το άθροισμα δύο οποιωνδήποτε πραγματικών , ενώ η

ειδική συνάρτηση βρίσκει το άθροισμα ΜΟΝΟΝ των δυο καθολικών

μεταβλητών x και y.

Οι τυπικές παράμετροι είναι αυτές που αποτελούν την λίστα-

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

Η δουλειά τους είναι να δέχονται τις τιμές των ορισμάτων που

μεταβιβάζονται σε μια συνάρτηση . Συμπεριφέρονται κατά τα άλλα σαν

να είναι απλές τοπικές μεταβλητές .

Πέρασμα παραμέτρων κατά αξία (call by value)

Είναι η εξορισμού μέθοδος επικοινωνίας των συναρτήσεων . Όταν

καλούμε μια συνάρτηση με ορίσματα κάποιες μεταβλητές , τότε η τιμή

κάθε ορίσματος αντιγράφεται σε μια τοπική μεταβλητή δηλαδή στην

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

παραμέτρους μιας συνάρτησης δεν έχουν καμιά επίδραση στις

μεταβλητές που χρησιμοποιήσαμε κατά το κάλεσμα της συνάρτησης .

Παράδειγμα 7ο: . . .

44

Page 45: Georgia Dis Intro C

int z=5;

printf(“%d*%d”, tetr(z), z) ;

tetr( int a)

a *= a ;

return a ;

Το πρώτο τμήμα κώδικα έχει μια μεταβλητή z με τιμή 5 που αποτελεί

κατά την κλήση της συνάρτησης tetr(), το όρισμά της . Έτσι

δημιουργείται μια τοπική μεταβλητή a που γεμίζει με την τιμή 5. Αυτή

η a γίνεται 25 και η τιμή αυτή επιστρέφει μέσω της return έξω από την

συνάρτηση και έχοντας όνομα tetr(z). H z που χρησιμοποιήθηκε για το

κάλεσμα της tetr(), εξακολουθεί να περιέχει την τιμή 5. Έτσι το

αποτέλεσμα του παραδείγματος θα είναι “25*5”.

extern - register

Αυτές οι λέξεις-κλειδιά λένε στον μεταγλωττιστή πως να

αποθηκεύσει τη μεταβλητή που ακολουθεί . Λέγονται μετατροπείς

αποθήκευσης και στην ίδια κατηγορία ανήκει η auto που αναφέραμε

προηγουμένως και η static που θα δούμε αργότερα .

Το καθοριστικό extern βοηθάει στη διαχείριση προγραμμάτων

που αναφέρονται σε περισσότερα από ένα αρχεία . Συγκεκριμένα λέει

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

δηλωθεί κάπου αλλού και έτσι δεν δεσμεύεται επιπλέον χώρος . Έτσι το

extern είναι ο τρόπος για να λέμε σε όλα τα αρχεία ενός προγράμματος

ποιες είναι οι καθολικές του μεταβλητές . Μπορούμε λοιπόν να

συνδέουμε ξεχωριστά μεταγλωττισμένα αρχεία , δίνοντάς τα και

επιπλέον κοινές καθολικές μεταβλητές .

Παράδειγμα 8ο: πρώτο αρχείο δεύτερο αρχείο

int a, b; extern int a, b;

f loat k; extern float k;

45

Page 46: Georgia Dis Intro C

int f1(); void f2()

void main()

. . . a = 100 + b;

f1() f3()

a=4000; k = 2.65 ;

. . . . . . .

Για να μην ξαναδεσμευτεί χώρος στη μνήμη για τις καθολικές

μεταβλητές a, b, k του προγράμματος , βάλαμε το extern. Αυτό γίνεται

επειδή οι συναρτήσεις μας (main, f1, f2 και f3) εκτείνονται σε δύο

αρχεία .

Το καθοριστικό register εφαρμόζεται μόνο σε μεταβλητές int

και char . Επίσης ΔΕΝ μπορούμε να το χρησιμοποιήσουμε στις

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

τις τιμές των μεταβλητών που ζητάμε όχι στη μνήμη , όπως είναι το

κανονικό , αλλά σε καταχωρητή του επεξεργαστή . Έτσι φτιάχνουμε

“γρήγορες” μεταβλητές , ιδανικές για εντολές επανάληψης , έλεγχο

ανακυκλώσεων κλπ . Μπορούμε να δηλώνουμε όσες μεταβλητές register

θέλουμε αλλά η ΤURBO-C ανάλογα με τον επεξεργαστή , τις μετατρέπει

σε κανονικές μόλις ο αριθμός τους φθάσει στο επιτρεπόμενο όριο .

Παράδειγμα 9ο: . . .

aker_dinami( int k, register int e)

register int help = 1;

for (;e;e--) help *= k ;

return help ;

Υπολογίζονται ακέραιες δυνάμεις , δηλαδή το ke για ακέραιους . Οι

μεταβλητές e και help είναι τύπου register.

46

Page 47: Georgia Dis Intro C

Προχωρημένα θέματα συναρτήσεων

Μεταβλητές static

H λέξη-κλειδί static , όπως auto, register και extern , είναι ένας

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

μεταβλητή παραμένει όπως είναι . Διαφορετικό αποτέλεσμα έχει πάνω

σε τοπικές μεταβλητές από ότι σε καθολικές . Θα δούμε λοιπόν τις δύο

αυτές περιπτώσεις .

i) Τοπικές static : Οι μεταβλητές αυτές είναι τοπικές , που όμως

διατηρούν την τιμή τους μεταξύ των κλήσεων της συνάρτησης που

ανήκουν . Δηλαδή ο μεταγλωττιστής της C, όταν σε μια συνάρτηση f1()

βλέπει μεταβλητή static δεν την σώζει στον προσωρινό χώρο μνήμης

(stack) της f1(), όπου σώζονται κανονικά οι τοπικές μεταβλητές . Σε

άλλο χώρο , όπου φυλάσσονται μόνιμα τα δεδομένα , αποθηκεύονται οι

static μεταβλητές . Εκεί που βρίσκονται οι καθολικές

μεταβλητές .Υπάρχει όμως διαφορά ανάμεσα σε τοπικές static και

καθολικές μεταβλητές . Μια τοπική static είναι γνωστή μόνο στο τμήμα

του κώδικα που δηλώνεται . Δηλαδή ο compiler εξασφαλίζει ότι μια

τοπική static είναι προσπελάσιμη ΜΟΝΟΝ από εκεί που πρέπει .

Παράδειγμα 1ο: #include <stdio.h>

void main()

void teststat();

int metr ;

for (metr=1; metr <=3; metr++)

printf(“Επανάληψη %d : “, metr);

teststat();

void teststat()

int apli =1;

static int st =1 ;

printf(“apli = %d, st = %d\n”,apli++, st++);

47

Page 48: Georgia Dis Intro C

H έξοδος αυτού του παραδείγματος θα είναι :

Επανάληψη 1 : apli = 1, st = 1

Επανάληψη 2 : apli = 1, st = 2

Επανάληψη 3 : apli = 1, st = 3

Παρατηρήστε ότι η στατική τοπική μεταβλητή st , θυμάται ότι η τιμή

της είχε αυξηθεί κατά 1 στη προηγούμενη κλήση της συνάρτησης

teststat() , ενώ η μεταβλητή apli όχι . Δηλαδή με το static η st έγινε

σαν καθολική . Όμως μπορούμε ΜΟΝΟΝ μέσα στη teststat να την

χρησιμοποιήσουμε . Προσέξτε επίσης και τη διαφορά στην απόδοση

αρχικών τιμών . Το static υποχρεώνει την μεταβλητή st να πάρει μόνο

ΜΙΑ ΦΟΡΑ αρχική τιμή . Αντίθετα η κανονική τοπική μεταβλητή apli

παίρνει αρχική τιμή ΚΑΘΕ ΦΟΡΑ που καλείται η συνάρτηση teststat().

Ένα ακόμη σημείο που πρέπει να προσέξετε στο προηγούμενο

παράδειγμα είναι η δήλωση της συνάρτησης teststat() μέσα στην

main(). Γενικά όταν θέλουμε μέσα σε μια συνάρτηση να

χρησιμοποιήσουμε μια συνάρτηση που ο ορισμός της ακολουθεί , πρέπει

να την δηλώσουμε . Και η δήλωση γίνεται με την πρώτη γραμμή-

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

ΠΡΙΝ την χρησιμοποίησή της , τότε ΔΕΝ χρειάζεται και να την

δηλώσουμε . Δηλαδή στο παράδειγμά μας θα μπορούσαμε να μην

γράφαμε την 3η γραμμή void teststat(); αν οι τελευταίες 5 γραμμές

(ορισμός της teststat) είχαν τοποθετηθεί κάτω από την γραμμή

#include . . .

i i) Kαθολικές static : Όταν εφαρμόζουμε τον μετατροπέα static

σε καθολική μεταβλητή , τότε δημιουργείται καθολική μεταβλητή που

είναι γνωστή μόνον στο αρχείο όπου δηλώθηκε αυτή η μεταβλητή . Έτσι

τα υποπρογράμματα που βρίσκονται σε άλλα αρχεία δεν είναι σε θέση

να αλλάζουν τα περιεχόμενα αυτής της καθολικής μεταβλητής και

48

Page 49: Georgia Dis Intro C

αποφεύγουμε παρενέργειες . Στη πραγματικότητα , αυτή είναι η

εξ’ορισμού ρύθμιση για τις καθολικές μεταβλητές . Μόνο αν βάλουμε

στα άλλα αρχεία το extern, οι καθολικές μεταβλητές είναι γνωστές σ’

αυτά . Γι΄ αυτό και δεν είναι απαραίτητο το static στις καθολικές

μεταβλητές . Το χρησιμοποιούμε όταν θέλουμε να τονίσουμε στο

πρόγραμμα ότι κάποια μεταβλητή ΔΕΝ θα χρησιμοποιηθεί σε άλλο

αρχείο .

Παράδειγμα 2ο: “st1.c” “st2.c”

#include <stdio.h> extern int a ;

#include “st2.c” f2()

int a, b ;

static float k; . . . .

void main()

. . . f3()

void f1()

. . .

. . . .

Αν μεταγλωττίσουμε μαζί τα δύο αρχεία του προγράμματός μας θα

δούμε ότι η μεταβλητή a είναι γνωστή σε ΟΛΕΣ τις συναρτήσεις μας

(main, f1, f2, f3), ενώ η καθολική στατική k είναι γνωστή ΜΟΝΟ στις

main και f1 του 1ου αρχείου . Και η b όμως ΔΕΝ είναι γνωστή στις f2()

και f3() γιατί δεν έχει δήλωση extern στο αρχείο st2.c , ανεξάρτητα

που δεν δηλώθηκε σαν static καθολική στο “st1.c”.

Ξεκίνημα με Δείκτες

Οι δείκτες (pointers) είναι ίσως το ισχυρότερο χαρακτηριστικό

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

49

Page 50: Georgia Dis Intro C

(πχ . χρήση χωρίς αρχικές τιμές ή χωρίς έλεγχο) μπορεί να προκαλέσει

“κρέμασμα” του συστήματος . Μια ακόμα δυσκολία τους είναι ότι δεν

εντοπίζονται εύκολα τα λάθη μας κατά την χρησιμοποίησή τους . Όμως

αξίζει το κόπο να μάθουμε να δουλεύουμε με αυτούς για τρεις λόγους :

α)Προσφέρουν τρόπους ώστε μια συνάρτηση να μπορεί να

τροποποιεί τα ορίσματα με τα οποία καλείται . Είναι η λεγόμενη κλήση

κατά αναφορά (call by reference) που θα δούμε αναλυτικά στη

συνέχεια .

β)Χρησιμοποιούνται στη θέση των πινάκων και των strings,

προσφέροντας μεγαλύτερες δυνατότητες .

γ)Μπορούμε μέσω αυτών , όπως και στη Pascal, να

υποστηρίξουμε την δυναμική κατανομή της μνήμης . Δηλαδή να

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

χρονικό διάστημα .

δ)Επιτρέπουν την πρόσβαση σε συγκεκριμένες θέσεις μνήμης ,

πράγμα που χρειάζεται όταν γράφουμε προγράμματα που κάνουν άμεσο

έλεγχο του hardware.

ε)Χρησιμοποιούνται για κατασκευή πολύπλοκων δομών

δεδομένων , όπως οι λίστες , οι σωροί και τα δέντρα .

Τι είναι όμως οι δείκτες ; H Κεντρική Μνήμη (RAM) είναι

οργανωμένη σαν μια σειρά από bytes (8 bits το καθένα). Τα bytes αυτά

είναι αριθμημένα από 0 ως τον μέγιστο αριθμό που επιτρέπει το

σύστημα . Το κάθε byte της μνήμης αναγνωρίζεται μοναδικά από τον

αριθμό του . Ο αριθμός αυτός είναι η διεύθυνση (address) του byte. Οι

δείκτες στη C είναι μεταβλητές που περιέχουν διευθύνσεις . Λέγονται

έτσι γιατί “δείχνουν” σε κάποια θέση στη μνήμη . Οι δείκτες έχουν όλοι

το ίδιο μέγεθος στο ίδιο μηχάνημα . Έτσι σε μηχανήματα με

επεξεργαστές από 80386 και πάνω οι δείκτες έχουν μέγεθος 4 bytes.

Και αυτό γιατί αυτοί οι 32-bit επεξεργαστές υποστηρίζουν διευθύνσεις

50

Page 51: Georgia Dis Intro C

4-bytes (4x8bits = 32bits). Ονομάζονται και επεξεργαστές με λέξεις

(words) τάξης 4-bytes.

Στις περισσότερες περιπτώσεις , όταν ένας δείκτης δείχνει μια

διεύθυνση , αυτή η διεύθυνση είναι η θέση μιας άλλης μεταβλητής . Και

τότε λέμε ότι η πρώτη μεταβλητή δείχνει την δεύτερη .

Δήλωση Δεικτών

Οι δείκτες δηλώνονται κυρίως , βάζοντας ένα αστεράκι “*” πριν

από το όνομά τους . Φυσικά το καθοριστικό τύπου πρέπει να υπάρχει .

Πχ . int *k, *m;

/* οι k,m είναι δείκτες σε ακέραιες μεταβλητές */

char *s ;

/* η s είναι δείκτης σε μεταβλητή χαρακτήρα */

Σχετικά με τους δείκτες η C ορίζει δύο ειδικούς τελεστές-

σύμβολα:

1) Το “&” που επιστρέφει την διεύθυνση της μεταβλητής που

ακολουθεί . Το έχουμε ήδη συναντήσει στην συνάρτηση scanf().

Λέγεται τελεστής διεύθυνσης (address). Όπως καταλαβαίνετε , οι

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

μορφή είναι &μεταβλητή .

2) Το “*” που επιστρέφει την τιμή της μεταβλητής ΠΟΥ

ΔΕΙΧΝΕΙ ο δείκτης που ακολουθεί . Λέγεται τελεστής έμμεσης

προσπέλασης (dereferencing). H γενική μορφή είναι *δείκτης .

Παράδειγμα 3ο : . . .

f loat i=25.4, *j, *k ;

k = &i ;

*k = 7.75 ;

i = *k + 5;

. . .

51

Page 52: Georgia Dis Intro C

Στη πρώτη γραμμή δηλώνουμε μια πραγματική μεταβλητή i με αρχική

τιμή 25.4 και δύο δείκτες j και k σε πραγματικούς . Στη δεύτερη

γραμμή η διεύθυνση της μεταβλητής i αποθηκεύεται στον δείκτη k .

Δηλαδή ο k δείχνει την μεταβλητή i . Έτσι , αν η μεταβλητή i , καθώς

τρέχει το πρόγραμμα , είναι αποθηκευμένη στη διεύθυνση μνήμης 2000,

τότε η k γεμίζει με την τιμή 2000.

Στη τρίτη γραμμή η τιμή 7.75 αποθηκεύεται ΕΚΕΙ ΠΟΥ

ΔΕΙΧΝΕΙ η k . Και επειδή η k δείχνει την μεταβλητή i , ουσιαστικά το

7.75 αποθηκεύεται στη μεταβλητή ι .

Στη τέταρτη γραμμή η τιμή της μεταβλητής που δείχνει η k ,

δηλαδή η τιμή της μεταβλητής i , αυξημένη κατά 5 θα αποθηκευτεί

στην μεταβλητή i . Έτσι η i θα γεμίσει με την τιμή 12.75

Παρατήρηση: Οι δυο ειδικοί αυτοί τελεστές , “*” και “&”, έχουν

προτεραιότητα μεγαλύτερη από όλους τους αριθμητικούς τελεστές .

Είναι στο ίδιο επίπεδο με το “-” του αρνητικού προσήμου .

Πέρασμα παραμέτρων κατά αναφορά (call by reference)

Είναι η δεύτερη μέθοδος επικοινωνίας των συναρτήσεων .

Θυμηθείτε ότι με την εντολή return μια συνάρτηση μπορούσε να

επιδράσει σε ΜΙΑ μεταβλητή του κώδικα που την κάλεσε , μέσω του

ονόματός της . Είναι η αντίστοιχη περίπτωση με τις functions της

Pascal. Όταν θέλουμε όμως να επιδράσουμε σε παραπάνω από μία

μεταβλητές , αποφεύγοντας τη χρήση καθολικών μεγεθών , τότε

χρειαζόμαστε κάτι ανάλογο των var-παραμέτρων στις

διαδικασίες(procedures) της Pascal. Δηλαδή , θέλουμε η συνάρτηση να

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

πραγματικές παράμετροι κατά την κλήση της . Αυτό γίνεται περνώντας

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

των μεταβλητών . Βέβαια , και κατά τον ορισμό της συνάρτησης , πρέπει

οι παράμετροι να δηλωθούν σαν δείκτες .

52

Page 53: Georgia Dis Intro C

Παράδειγμα 4ο: #include <stdio.h>

void swap ( int *x, int *y)

int temp ;

temp = *x ;

*x = *y ;

*y = temp ;

void main()

int k=50, m=100 ;

swap(&k, &m) ;

printf(“k=%d και m=%d”, k, m) ;

Οι μεταβλητές k και m ξεκινάνε με αντίστοιχες τιμές 50 και 100. Η

συνάρτηση όμως swap(), εναλλάσσει τις τιμές αυτές , έτσι ώστε στο

τέλος η printf να εμφανίζει “k=100 και m=50”. Καλέσαμε την swap με

πραγματικές παραμέτρους &k και &m (διευθύνσεις), για να επιδράσει

η συνάρτηση swap στις k και m της συνάρτησης main. Επιπλέον όμως

προσέξαμε στον ορισμό της swap οι τυπικές παράμετροι να είναι *x

και *y (δείκτες). Προσέξτε τέλος ότι χρησιμοποιήσαμε σαν βοηθητική-

τοπική μεταβλητή την temp που είναι μια απλή ακέραια μεταβλητή . Ο

ρόλος της ήταν να φυλάξει την τιμή που έδειχνε η x. Κατά την κλήση η

παράμετρος &k έκανε την x να δείχνει στην μεταβλητή k. Και έτσι η

temp τελικά φύλαξε την τιμή της μεταβλητής k.

Λίγη Αριθμητική Δεικτών

Οι διευθύνσεις που περιέχονται στους δείκτες είναι ακέραιες

τιμές . Γι’ αυτό μπορούμε να κάνουμε και αριθμητικές πράξεις μ’

αυτές . Οι μόνες πράξεις που επιτρέπονται είναι η αύξηση και η μείωση

53

Page 54: Georgia Dis Intro C

κατά 1 (σύμβολα ++ και --) καθώς και η πρόσθεση και αφαίρεση

(σύμβολα += και -= ).

Προσέξτε όμως . Η πρόσθεση ενός ακέραιου n σε δείκτη κάνει

τον δείκτη να δείχνει n μεταβλητές πιο πέρα και η αφαίρεση τον κάνει

να δείχνει n μεταβλητές πιο μπροστά .

Παράδειγμα 5ο: int *p ;

char *c1, *c2 ;

printf(“&p=%d &c1=%d &c2=%d\n”, p, c1, c2);

c2-- ; /* η c2 δείχνει 1 byte πιο πίσω */

p++ ; /* η p δείχνει 2 bytes πιο πέρα */

c1 += 7 ; /* η c1 δείχνει 7 bytes πιο πέρα */

p -= 3 ; /* η p δείχνει 6 bytes πιο πίσω */

printf(“&p=%d &c1=%d &c2=%d\n”, p, c1, c2);

. . .

Επειδή η c2 είναι δείκτης σε μεταβλητή χαρακτήρα , μεγέθους

ενός byte, στη 4η γραμμή η c2 δείχνει 1 μόνο byte πιο πίσω , δηλαδή

δείχνει την προηγούμενη μεταβλητή . Όμοια και η c1, στην 6η γραμμή ,

δείχνει την 7η κατά σειρά μεταβλητή χαρακτήρα και άρα δείχνει 7

bytes μετά .

Η p όμως είναι δείκτης σε μεταβλητή integer, μεγέθους 2 bytes.

Έτσι στην 5η γραμμή , η p για να δείξει τον επόμενο ακέραιο , δείχνει 2

bytes μετά . Και στην 7η γραμμή , επειδή θέλει να δείξει 3 ακέραιους

πιο πίσω , δείχνει στην μνήμη 6 bytes πιο πίσω .

H έξοδος λοιπόν του παραδείγματος θα είναι :

&p=65500 %c1=36790 &c2=870

&p=65496 &c1=36797 &c2=869

Δείκτες και Πίνακες - Εισαγωγή

54

Page 55: Georgia Dis Intro C

ΟΛΟΙ οι πίνακες είναι δείκτες ΑΠΟ ΜΟΝΟΙ ΤΟΥΣ . Δηλαδή η

δήλωση int k[10]; σημαίνει ότι ο k είναι δείκτης που δείχνει το πρώτο

στοιχείο (k[0]) του πίνακα k των 10 ακέραιων . Έτσι

το k ισοδυναμεί με &k[0] (διεύθυνση του 1ου στοιχείου)

το *k ισοδυναμεί με k[0] (τιμή του 1ου στοιχείου)

το k[i] ισοδυναμεί με *(k+i) (τιμή του i+1-στοιχείου)

το k++ ισοδυναμεί με &k[1] (διεύθ . του 2ου στοιχείου)

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

ακολουθεί .

55

Page 56: Georgia Dis Intro C

Πίνακες και Δείκτες

Πίνακα (array) ονομάζουμε σε κάθε γλώσσα προγραμματισμού

μια σειρά από μεταβλητές ίδιου τύπου , που αναφερόμαστε σε αυτές με

ένα ΚΟΙΝΟ όνομα . Αποτελείται από διαδοχικές θέσεις μνήμης στη C.

H χαμηλότερη διεύθυνση αντιστοιχεί στο πρώτο στοιχείο-μεταβλητή ,

ενώ η υψηλότερη διεύθυνση περιέχει το τελευταίο στοιχείο . Ειδικότερα

τους πίνακες αλφαριθμητικών , το ανάλογο με τα strings στη Borland-

Pascal, θα τους εξετάσουμε χωριστά επειδή έχουν κάποιες διαφορές με

τους υπόλοιπους πίνακες . Έτσι εδώ θα ασχοληθούμε με πίνακες που τα

στοιχεία τους ΔΕΝ είναι χαρακτήρες (char). Θα ξεκινήσουμε με

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

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

καθοριστικό-τύπου όνομα-πίνακα[μέγεθος];

πχ . int kostas[25];

/*ο kostas είναι πίνακας 25 ακέραιων */

Το συνολικό πλήθος των bytes που δεσμεύονται στη μνήμη δίνεται από

τη σχέση :

Δεσμευμένα bytes = sizeof(τύπος) * μέγεθος πίνακα

Δηλαδή για τον πίνακα kostas δεσμεύονται 2*25=50 bytes, αφού κάθε

ακέραιος χρειάζεται 2 bytes.

Είναι εξαιρετικά σημαντικό να γίνει κατανοητό ότι η γλώσσα C

επιτρέπει δύο τρόπους προσπέλασης στα στοιχεία ενός πίνακα :

α)την χρησιμοποίηση δεικτών-θέσης ( indexes) σε πίνακες και

β)την χρησιμοποίηση δεικτών-διευθύνσεων (pointers).

Χρησιμοποιώντας δείκτες-θέσης

56

Page 57: Georgia Dis Intro C

Μ’ αυτόν τον τρόπο , για να αναφερθούμε στο ( i+1)-στοιχείο

ενός πίνακα k, γράφουμε k[i] . Και αυτό γιατί οι πίνακες

χρησιμοποιούν την θέση 0 για το πρώτο στοιχείο τους . Έτσι k[3] είναι

το 4ο στοιχείο , k[6] το 7ο και k[0] τo 1o στοιχείο . Επειδή οι πίνακες

έχουν σταθερό μέγεθος , συνήθως χρησιμοποιούμε εντολές for για τις

εργασίες μαζί τους . Αρκετές φορές όμως και οι εντολές while και

do...while μπορούν να χρησιμοποιηθούν για το “γέμισμα” ή το

διάβασμα τιμών ενός πίνακα βάσει κάποιας συνθήκης .

Παράδειγμα 1ο: Υπολογισμός μέσης τιμής 20 πραγματικών αριθμών .

#include <stdio.h>

void main()

float pin[20], mesi =0 ;

char i;

for (i=0; i<20; )

printf(“δώσε %dο αριθμό : “, i+1);

scanf(“%f”, &pin[i]);

mesi += pin[i++];

printf(“Μέσος όρος : %7.2f\n”, mesi/20);

Προσέξτε ότι στη for , η μεταβλητή i που αποτελεί τον δείκτη-

θέσης του πίνακά μας pin , ξεκινάει από την τιμή 0 και φτάνει ως την

τιμή 19 για να αναφερθούμε στα 20 στοιχεία του πίνακα . Έτσι , μέσα

στην printf κάθε φορά για την i θέση του πίνακα εμφανίζουμε στο

σχόλιο “δώσε . . .” το περιεχόμενο της μεταβλητής i αυξημένο κατά 1.

Επίσης , σημειώστε ότι η i δηλώθηκε char γιατί οι τιμές της δεν

ξεφεύγουν πάνω από το 20, αφού 20 είναι οι θέσεις του πίνακα . Τέλος

προσέξτε ότι στην εντολή for δεν υπάρχει τίποτα μετά το δεύτερο

ερωτηματικό γιατί η ενημέρωση , δηλαδή η αύξηση κατά 1 της

μεταβλητής i γίνεται στην 8η γραμμή με την πράξη ++ .

57

Page 58: Georgia Dis Intro C

Χρησιμοποιώντας δείκτες-διευθύνσεων

Μ’ αυτόν τον τρόπο , για να αναφερθούμε στο ( i+1)-στοιχείο

ενός πίνακα k, γράφουμε *(k+i). Δηλαδή το k[i] είναι ισοδύναμο με το

*(k+i). Αυτό συμβαίνει γιατί κάθε όνομα πίνακα χωρίς δείκτη-

θέσης( index) είναι δείκτης(pointer) στο πρώτο του στοιχείο .

Παράδειγμα 2ο: Υπολογισμός μέσης τιμής 20 πραγματικών αριθμών

με χρήση pointers .

#include <stdio.h>

void main()

float pin[20], mesi =0, *k ;

char i;

k = pin;

for (i=1; i<21; i++)

printf(“δώσε %dο αριθμό : “, I);

scanf(“%f”, k);

/* ή scanf(“%f”, &*k); */

mesi += *k++;

printf(“Μέσος όρος : %7.2f\n”, mesi/20);

Το στοιχείο *k, την πρώτη φορά που εκτελείται η for , είναι το

pin[0], δηλαδή το 1ο στοιχείο του πίνακά μας . Και στην 8η γραμμή του

προγράμματός μας , αφού ο αθροιστής mesi πάρει την τιμή του , με τη

πράξη ++ το στοιχείο *k πλέον είναι το 2ο στοιχείο του πίνακα . Με

αυτήν την τιμή θα εκτελεστεί η 2η επανάληψη της for και αυτό θα

συνεχιστεί για 20 επαναλήψεις . Τις επαναλήψεις τις μετρά η μεταβλητή

ελέγχου i , αλλά τα διάφορα στοιχεία του πίνακα τα διατρέχει ο δείκτης

k .

ΠΡΟΣΟΧΗ !!!

Είναι απαραίτητο να οριστεί ένας δείκτης μεταβλητών float ,

όπως ο k , που θα διατρέχει τις θέσεις του πίνακά μας pin . Και αυτό

γιατί , όπως αναλυτικά θα δούμε και αργότερα , o pin , όπως κάθε όνομα

58

Page 59: Georgia Dis Intro C

πίνακα , είναι ΣΤΑΘΕΡΑ δείκτη που δείχνει το πρώτο του στοιχείο και

που δεν επιτρέπεται λοιπόν να μεταβάλλουμε την τιμή του .

Γιατί με pointer και όχι με index ;

Η μέθοδος αυτή με δείκτες-διεύθυνσης για να χρησιμοποιούμε

τους πίνακες είναι σίγουρα πιο δύσκολη από τους κλασσικούς δείκτες-

θέσης . Όμως σίγουρα είναι πιο γρήγορη σε εκτέλεση . Γιατί η C

χρειάζεται περισσότερο χρόνο για να χρησιμοποιήσει δείκτες-θέσης σε

ένα πίνακα από ότι για να χρησιμοποιήσει τον τελεστή έμμεσης

προσπέλασης “*”. Ουσιαστικά η βασική προσέγγιση των πινάκων στη

C από κατασκευής της , είναι οι pointers . Ο συμβολισμός με “[“ και

“]” μετατρέπεται από τον μεταγλωττιστή στον αντίστοιχο με pointers

και για αυτό γενικά υστερεί σε ταχύτητα . Πάντως σε περιπτώσεις

τυχαίας πρόσβασης σε πίνακα , δηλαδή ΟΧΙ κατά αύξουσα ή φθίνουσα

σειρά , είναι ίσως προτιμότερο να προτιμούμε αυτούς τους “αργούς”

δείκτες μέσα σε τετραγωνισμένες αγκύλες , γιατί κατασκευάζουμε

προγράμματα πιο κατανοητά .

ΠΡΟΣΟΧΗ: Αν pin είναι ένας πίνακας , ή αν είναι pointer ίδιου τύπου

και έχει προηγηθεί η πράξη pin=arr, όπου arr είναι πίνακας , τότε μην

μπερδεύετε τις ακόλουθες παραστάσεις :

*(pin + 3) /* η τιμή του 4ου στοιχείου του πίνακα */

*pin + 3 /* το 3 προστίθεται στη τιμή του 1ου

στοιχείου του πίνακα */

Και αυτό γιατί ο τελεστής * έχει μεγαλύτερη προτεραιότητα από όλες

τις πράξεις .

Βλέπουμε λοιπόν ότι μπορούμε ένα όνομα πίνακα και έναν

δείκτη να τα χρησιμοποιούμε ισοδύναμα για τις εργασίες μας . Υπάρχει

όμως και εξαίρεση . Σε έναν pointer k μπορούμε να κάνουμε πράξεις

που μεταβάλλουν την τιμή του , πχ . k++, γιατί ο pointer είναι

μεταβλητή-δείκτη . Σε έναν πίνακα όμως pin, αντί για pin++ πρέπει να

59

Page 60: Georgia Dis Intro C

χρησιμοποιούμε pin+1 όταν θέλουμε να περάσουμε στο επόμενο

στοιχείο του , γιατί ΔΕΝ ΜΠΟΡΟΥΜΕ να αλλάζουμε την τιμή της

σταθεράς-δείκτη pin .

Ποιος έλεγχος ορίων ;

Τίποτα δεν μας εμποδίζει να ξεπεράσουμε το τέλος ενός πίνακα

γιατί η C δεν εκτελεί έλεγχο ορίων στους πίνακες . Και αυτό γιατί έτσι

κερδίζει η γλώσσα σε ταχύτητα . Στην ουσία η C δεν διαθέτει σχεδόν

κανένα έλεγχο λαθών για να μην επιβραδύνει την εκτέλεση του

προγράμματος . Μόνο έτσι θα μπορούσε να συναγωνιστεί την assembly

σε ταχύτητα . Επομένως περιμένει από εμάς τους προγραμματιστές να

είμαστε προσεκτικοί και να μην ξεφεύγουμε από τα όρια .

Παράδειγμα 3ο: Η C θα μεταγλωττίσει και θα τρέξει το παρακάτω

ΛΑΘΟΣ πρόγραμμα , όπου δεν προσέξαμε και συνεχίζουμε να

αποδίδουμε τιμές και μετά το τέλος του πίνακα problem .

#include <stdio.h>

void main()

int problem[15], i ;

for (i=0 ; i<50; i++)

problem[i]=i;

Όλες οι τιμές που αποδώσαμε από το problem[15] ως το problem[49],

αποτελούν πλέον δεδομένα κάποιων άλλων μεταβλητών και είναι πολύ

πιθανόν να προκαλέσουν “κρέμασμα” στον υπολογιστή μας .

Είναι τελικά λίστες

Οι πίνακες μιας διάστασης είναι λοιπόν στη μνήμη λίστες με

πληροφορίες ίδιου τύπου . Δείτε το παράδειγμα . Υπάρχει η συνάρτηση

emfanisi που σε 3 γραμμές εμφανίζει :

60

Page 61: Georgia Dis Intro C

α) τις διευθύνσεις των ίδιων των μεταβλητών k (pointer) και

h(πίνακας),

β) τα περιεχόμενα των k και h , δηλαδή τις διευθύνσεις των

μεταβλητών /σταθερών που δείχνουν

γ) τις τιμές των μεταβλητών /σταθερών που δείχνονται από τους k

και h .

Παράδειγμα 4ο: #include <stdio.h>

#include <conio.h>

int h[4],i ,*k;

void emfanisi()

printf("\n%u %u \n",&k,&h);

printf("%u %u \n",k,h);

printf("%u %u \n",*k,*h);

printf("******\n");

void main()

clrscr();

emfanisi();

k=h; /* h=k; δεν είναι σωστό */

emfanisi();

for (i=100;i<104;i++)

/* h[i-100]=i; ισοδύναμο με την επόμενη γραμμή*/

*k++=i;

for (i=0;i<4;i++)

printf("%d ",h[i]);

k--;

emfanisi();

k=h+1; /* h++ ; δεν είναι σωστό */

emfanisi();

61

Page 62: Georgia Dis Intro C

printf("%d\n",*k+200);

for (i=0,k=h;i<4;i++)

printf("%d ",*k++);

/* printf("%d ",*h++); δεν είναι σωστό*/

Στη μνήμη ο πίνακας h και ο δείκτης k είναι :

h[0] h[1] h[2] h[3] 100 101 102 103

*h *(h+1) *(h+2) *(h+3)

*k *(k+1) *(k+2) *(k+3)

H πρώτη κλήση της emfanisi παρουσιάζει :

892 896 0 896 0 0 ****** Οι διευθύνσεις λοιπόν μνήμης είναι 892 για την μεταβλητή k και 896

για τον πίνακα h . Κάθε φορά που θα καλέσουμε την emfanisi τις ίδιες

τιμές θα παίρνουμε στη 1η γραμμή , γιατί οι διευθύνσεις ολικών

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

προγράμματος .

Στη 2η γραμμή παρατηρήστε την διαφορά pointer και πίνακα . Η

αρχική τιμή ενός pointer , εξ’ ορισμού για ολικούς δείκτες , είναι το 0 .

Δηλαδή δείχνει ο k στη διεύθυνση 0 (λέγεται και null). Αντίθετα ο

πίνακας h δείχνει εξ’ ορισμού την διεύθυνση 896 που είναι η

διεύθυνσή του , δηλαδή η διεύθυνση του πρώτου του στοιχείου .

Στη 3η γραμμή τα μηδενικά δείχνουν ότι η διεύθυνση 0 περιέχει

τιμή 0 , αλλά το κυριότερο ότι ένας ολικός πίνακας εξ’ ορισμού έχει

μηδενισμένο το πρώτο του στοιχείο . Αν ελέγχαμε και τα υπόλοιπα

στοιχεία θα βλέπαμε ότι και εκείνα είναι μηδενισμένα .

62

Page 63: Georgia Dis Intro C

Με την απόδοση k=h; κάνουμε τον δείκτη k να δείχνει όπου και

ο h . Έτσι η δεύτερη κλήση της emfanisi δίνει :

892 896 896 896 0 0 ******

Το μόνο που άλλαξε είναι ότι η τιμή του k, στη 2η γραμμή , έγινε 896 ,

δηλαδή η διεύθυνση που δείχνει ΣΤΑΘΕΡΑ ο δείκτης h .

Στη συνέχεια η εντολή for γεμίζει τον πίνακα h με τις τιμές 100

ως 103 . Παρατηρήστε τους δύο εναλλακτικούς τρόπους , με index ή

pointer που μπορεί αυτό να γίνει . Η επόμενη for εμφανίζει τα

περιεχόμενα του h, χρησιμοποιώντας δείκτες-θέσης , και δίνει λοιπόν :

100 101 102 103

Ακολουθεί η τρίτη κλήση της emfanisi με έξοδο :

892 896

902 896

103 100

******

Στη δεύτερη γραμμή ο δείκτης k δείχνει την διεύθυνση πια 902

που περιέχει την τιμή 103 . Αυτό έγινε γιατί ο k με την for πέρασε

διαδοχικά τις τιμές 898, 900, 902, 904 και με την πράξη k-- γύρισε στη

διεύθυνση 902 που είναι και η διεύθυνση του τέταρτου στοιχείου του

πίνακα h. Έτσι το *k είναι σε αυτό το σημείο 103 . Επίσης το *h είναι

εδώ το 100 , αφού ο πίνακας h έχει ως πρώτο στοιχείο το 100.

Η πράξη k=h+1; κάνει τον δείκτη k να έχει τιμή 898 και φυσικά

ισχύει *k = 101 , αφού 898 είναι η διεύθυνση του 2ου στοιχείου του

πίνακα . Έτσι η τελευταία κλήση της emfanisi δίνει :

892 896

898 896

101 100

63

Page 64: Georgia Dis Intro C

******

Η εντολή printf που ακολουθεί εμφανίζει 301 γιατί το *k είναι 101 και

του προσθέτουμε 200 μονάδες .

Τέλος η εντολή for εμφανίζει τα περιεχόμενα του πίνακα h

χρησιμοποιώντας τον δείκτη k . Προσέξτε ότι στη παρένθεση της for

έχουμε δύο αρχικές τιμές που χωρίζονται με κόμμα . Πρέπει ο δείκτης k

να δείξει το πρώτο στοιχείο του πίνακα για να μπορέσει να τον

διατρέξει όλον . Εμφανίζεται λοιπόν :

100 101 102 103

Σημείωση : Παρατηρήστε ότι h έχει σταθερά την τιμή 896.

64

Page 65: Georgia Dis Intro C

Προχωρημένα Θέματα Πινάκων - Strings

Αρχικές Τιμές και Κατηγορίες Μνήμης

Όσα έχουμε αναφέρει για τις κατηγορίες μνήμης μεταβλητών ,

δηλαδή για τις λέξεις-κλειδιά extern, static, register και auto , ισχύουν

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

έχουμε υπ’ όψην μας είναι ότι αρχικές τιμές μπορούμε να αποδίδουμε

ΜΟΝΟ σε καθολικούς(global) πίνακες , σε εξωτερικούς(extern) και σε

στατικούς(static), ενώ ΔΕΝ μπορούμε να το κάνουμε αυτό σε πίνακες

καταχωρητή(register) και σε πίνακες καθαρά τοπικούς , δηλαδή

αυτόματους(auto).

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

#include <stdio.h>

int days[12]=31,28,31,30,31,30,31,31,30,31,30,31;

/* days είναι καθολικός πίνακας */

void main()

char k;

static float sos[2]=45.4, -875.976 ;

printf(“%3.2f και %3.2f\n”,sos[0],sos[1]);

for (k=0 ; k <12 ; k++)

printf(“Mήνας %dος :%d μέρες \n”,k+1,days[k]);

Στη 2η και στην 6η γραμμή , βλέπουμε τους δύο πίνακές μας μαζί

με την δήλωσή τους να παίρνουν αρχικές τιμές . Ο ένας είναι καθολικός

και ο άλλος στατικός , οπότε επιτρέπεται η απόδοση αρχικών τιμών .

Όπως καταλαβαίνετε το days[0] περιέχει πλέον 31, το days[1] περιέχει

28 κ .ο .κ . μέχρι το στοιχείο days[11] που πήρε αρχική τιμή 31. Όμοια

στον πίνακα sos το sos[0] πήρε τιμή 45,4 και το sos[1] πήρε τιμή -

875,976. Έτσι η έξοδος του παραδείγματος θα είναι :

45.40 και -875.98

65

Page 66: Georgia Dis Intro C

Μήνας 1ος :31 μέρες

Μήνας 2ος :28 μέρες

. . .

Μήνας 12ος :31 μέρες

Παρατηρήστε ότι μετά το “” των τιμών υπάρχει “;” .

Πίνακες Χωρίς Μέγεθος (Αδιάστατοι)

Αν δεν δώσουμε αρχικές τιμές σε έναν καθολικό ή σε έναν

στατικό πίνακα τότε όλα τα στοιχεία τους παίρνουν την τιμή μηδέν .

Αυτό όμως δεν ισχύει για τους τοπικούς και τους πίνακες καταχωρητή .

Αυτοί παίρνουν οποιαδήποτε τιμή βρουν στη μνήμη(στο stack ή στους

registers).

Χρειάζεται προσοχή όταν αποδίδουμε αρχικές τιμές . Αν δώσουμε

μέσα στα άγκιστρα λιγότερες τιμές από ότι είναι το μέγεθος του

πίνακα , τότε οι επιπλέον θέσεις του πίνακα θα γεμίσουν είτε με

μηδενικά (πίνακες καθολικοί /στατικοί), είτε με απροσδιόριστες τιμές

(πίνακες τοπικοί). Αν όμως δώσουμε περισσότερες τιμές , τότε επειδή

όπως ήδη αναφέραμε η C δεν κάνει έλεγχο ορίων , δημιουργούμε

προϋποθέσεις για σφάλματα δύσκολα να εντοπιστούν , αφού θα

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

μας . Γι’ αυτό το λόγο προτιμούμε κατά την απόδοση τιμών σε ένα

πίνακα , να ΜΗΝ δίνουμε μέγεθος στο πίνακά μας και να αφήνουμε την

ίδια την C να το ορίζει ανάλογα με το πλήθος των τιμών μας μέσα στα

άγκιστρα .

Παράδειγμα 2ο: Το προηγούμενο παράδειγμα Νο .1, θα βγάλει τα ίδια

αποτελέσματα αν αντικαταστήσουμε την 2η γραμμή με

int days[]=31,28,31,30,31,30,31,31,30,31,30,31;

την 6η γραμμή με

static float sos[]=45.4, -875.976 ;

και την 8η γραμμή με

66

Page 67: Georgia Dis Intro C

for (k=0 ; k < sizeof (days)/sizeof(int) ; k++)

Προσέξτε ότι διαιρέσαμε το μέγεθος σε bytes του πίνακα days , με το

μέγεθος σε bytes του τύπου integer που είναι ο τύπος των στοιχείων

του days . Βρίσκουμε έτσι ΠΟΣΑ στοιχεία έχει ο πίνακας days και άρα

η C μόνη της ρυθμίζει τον αριθμό επαναλήψεων . Αντί για sizeof(int)

θα μπορούσαμε να βάλουμε 2 , αλλά τότε θα χρειαζόταν μετατροπές το

πρόγραμμά μας αν μεταφερόταν σε σύστημα με μέγεθος μεταβλητών

integer διαφορετικό από 2 bytes.

Πίνακες Παράμετροι σε Συναρτήσεις

Όταν βάζουμε έναν πίνακα σε μια συνάρτηση σαν παράμετρο ,

τότε η C μεταβιβάζει ΜΟΝΟ τη διεύθυνση του πίνακα και όχι ένα

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

παραμέτρων κατά αναφορά (call by reference). Όταν λοιπόν καλούμε

μια συνάρτηση με το όνομα ενός πίνακα , στην ουσία μεταβιβάζουμε

στη συνάρτηση ΕΝΑΝ ΔΕΙΚΤΗ στο πρώτο στοιχείο του πίνακα . Γιατί ;

Μα γιατί ήδη έχει αναφερθεί ότι :

Κάθε όνομα πίνακα χωρίς δείκτη-θέσης(index) είναι

δείκτης(pointer) στο πρώτο του στοιχείο .

Άρα η δήλωση της παραμέτρου πρέπει να είναι συμβατός τύπος

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

δέχεται ένα δείκτη σε πίνακα :

α) σαν πίνακας απλός πχ . int num[5]

β) σαν αδιάστατος πίνακας πχ . int num[]

γ) σαν δείκτης πχ . int *num

Παράδειγμα 3ο: #include <stdio.h>

void main()

int tel[5] ;

char i ;

for (i=0; i<5; i++)

67

Page 68: Georgia Dis Intro C

tel[i]=i ;

emfanise(tel);

Βλέπουμε ότι στη main() γίνεται κλήση της συνάρτησης emfanise() με

παράμετρο τον πίνακα tel . Δείτε τώρα τις τρεις εκδοχές για την

συνάρτηση emfanise().

α) void emfanise( int num[5])

char k;

for (k=0; k<5; k++)

printf(“%d “, num[k]);

β) Όμως είπαμε ότι η C δεν έχει έλεγχο ορίων . Έτσι το πραγματικό

μέγεθος του num δεν έχει σχέση με την παράμετρο . Μπορούμε λοιπόν

αντί για την 1η γραμμή να δώσουμε :

void emfanise( int num[])

γ) Ο πιο επαγγελματικός τρόπος είναι με δείκτες . Αντί λοιπόν για την

1η γραμμή δίνουμε :

void emfanise( int *num)

Αυτό επιτρέπεται γιατί μπορούμε να αποδίδουμε index σε έναν

pointer χρησιμοποιώντας τα “[]”, σαν να είναι πίνακας ο δείκτης .

Δείτε στο σώμα της emfanise ότι αυτό συμβαίνει .

Επίσης , όποια και από τις 3 εκδοχές και αν χρησιμοποιήσουμε ,

μπορούμε αντί για την 4η γραμμή να δώσουμε

printf(“%d ”, *num++);

ΠΡΟΣΟΧΗ :

1)Ο πίνακας σαν παράμετρος κάνει call by reference, δηλαδή ο

κώδικας που βρίσκεται μέσα στη συνάρτηση θα δουλεύει και θα

τροποποιεί τα πραγματικά περιεχόμενα του πίνακα που θα

χρησιμοποιήσουμε για να καλέσουμε την συνάρτηση . Και μην ξεχνάτε

ότι η εντολή return ΔΕΝ μπορεί να επιστρέφει πίνακες . Οπότε ο μόνος

68

Page 69: Georgia Dis Intro C

δρόμος τροποποίησης τιμών ενός πίνακα μέσα από συνάρτηση είναι το

πέρασμά του σαν παράμετρο της συνάρτησης .

2)Αν pin πίνακας , έστω 5 ακέραιων , τότε μπορούμε να γράψουμε

είτε

scanf(“%d”, &pin[i]); /* με i = 0,1,. .4 */

είτε scanf(“%d”, pin);

Ειδικά η δεύτερη scanf είναι ισοδύναμη με την πρώτη για i=0 .

Πίνακες Χαρακτήρων - Strings

Η C δεν διαθέτει ξεχωριστό τύπο δεδομένων για τα

αλφαριθμητικά(strings). Έτσι όταν θέλουμε να αναφερθούμε στα

προγράμματά μας σε μια ακολουθία χαρακτήρων , κατασκευάζουμε

πίνακες χαρακτήρων μιας διάστασης . Βέβαια έχουμε αναφέρει ότι τον

τύπο char πρέπει να τον βλέπουμε σαν τύπο μικρών ακέραιων . Και

πραγματικά με μια δήλωση

char k[10];

μπορούμε στη μεταβλητή k κρατήσουμε 10 μικρούς ακέραιους .

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

πολύ 9 χαρακτήρες . Γιατί 9 και όχι 10 ; Γιατί η C τοποθετεί τον

χαρακτήρα ‘\0’ (null) που γενικά ισούται με μηδέν , στο τέλος των

χαρακτήρων που περνάμε στο string , σαν σημάδι τέλους των χρήσιμων

χαρακτήρων μέσα στη σειρά θέσεων του πίνακα . Έτσι αν γεμίζαμε το k

με τη λέξη “ναι”, τότε

k[0] είναι το ‘ν’ k[1] είναι το ‘α’ k[2] είναι το ‘ ι’

k[3] είναι το ‘ \0’

και τα περιεχόμενα των k[4] ως k[9] είναι γεμάτα με άχρηστα . Ένας

πίνακας λοιπόν χαρακτήρων που τερματίζεται με μηδενικό , είναι το

string στη C.

Οι σταθερές τύπου string στη C πρέπει να βρίσκονται μέσα σε

ΔΙΠΛΑ εισαγωγικά . Όταν τα χρησιμοποιούμε , τότε ο χαρακτήρας ‘ \0’

69

Page 70: Georgia Dis Intro C

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

τον μεταγλωττιστή . Δηλαδή με τις εντολές :

#define TOP “τώρα”

char onoma[11]=“χρήστος” ;

void main()

. . .

δημιουργήσαμε μια αλφαριθμητική σταθερά TOP με περιεχόμενο τη

λέξη τώρα και μια καθολική αλφαριθμητική μεταβλητή onoma με

δυνατότητες αποθήκευσης strings 10 χαρακτήρων που η αρχική τιμή

του είναι χρήστος . Μπορούμε άνετα να μεταχειριζόμαστε τα στοιχεία

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

Έτσι

TOP[0] είναι ‘τ’, ΤOP[4] είναι ‘ \0’,

onoma[1] είναι ‘ρ’ και onoma[7] είναι ‘ \0’.

Θυμηθείτε ότι μπορούμε να παραλείψουμε την διάσταση κατά

την απόδοση αρχικής τιμής σε πίνακες . Έτσι η μεταβλητή μπορούσε να

δηλωθεί και

char onoma[]=“χρήστος” ;

Με δείκτες ισοδύναμα θα γράφαμε

char *onoma=“χρήστος” ;

Για να αλλάξουμε τιμές στις μεταβλητές string , καθώς

εκτελείται το πρόγραμμα , χρησιμοποιούμε για το πληκτρολόγιο την

συνάρτηση scanf(), με προσδιοριστή φόρμας “%s”. Και ΔΕΝ βάζουμε

τον χαρακτήρα ‘&’ πριν το όνομα του αλφαριθμητικού που

διαβάζουμε . Γιατί το όνομα της μεταβλητής string , όπως και τα

ονόματα όλων των πινάκων , είναι από μόνα τους ΔΙΕΥΘΥΝΣΕΙΣ του

πρώτου τους στοιχείου .

Ένας καλύτερος τρόπος για διάβασμα είναι η συνάρτηση gets()

(get-string) που και αυτή βρίσκεται στο “stdio.h”. Έτσι οι δύο

παρακάτω εντολές είναι ισοδύναμες :

70

Page 71: Georgia Dis Intro C

scanf(“%s”, onoma);

gets(onoma);

Σημειώστε ότι και οι δύο συναρτήσεις ΔΕΝ κάνουν έλεγχο ορίων

(αναμενόμενο) και θέλουν προσοχή .

Για εμφάνιση ενός αλφαριθμητικού η printf() έχει τον ίδιο

προσδιοριστή φόρμας “%s”. Αν θέλουμε να εμφανίσουμε ΜΟΝΟ το

αλφαριθμητικό , τότε μπορούμε να παραλείψουμε τον προσδιοριστή

φόρμας . Υπάρχει επίσης και η puts() (put-string), η οποία μετά την

εμφάνιση του string αλλάζει και γραμμή . Δηλαδή είναι ισοδύναμες οι :

printf(“%s\n”, onoma);

printf(onoma); printf(“\n”);

puts(onoma);

Συναρτήσεις βιβλιοθήκης για αλφαριθμητικά

Η Turbo/Borland C υποστηρίζει τα strings με πολύ ισχυρές

συναρτήσεις συστήματος . Θα δούμε τώρα τις κυριότερες . Απαιτούν την

οδηγία “#include <string.h>“ αφού αυτή είναι η θέση της βιβλιοθήκης

τους .

strlen() Μας δίνει το μήκος του αλφαριθμητικού που περάσαμε σαν

παράμετρο . Μετρά δηλαδή μία προς μία τις θέσεις μέχρι να βρει τον

χαρακτήρα ‘\0’ . Αν λοιπόν το onoma είχε τιμή “σήμερα”, τότε το

strlen(onoma) είναι ίσο με 6. Και αυτό ανεξάρτητα με το πόση μνήμη

δεσμεύεται για το onoma . Το μέγεθος του string καθορίζει την

δέσμευση μνήμης που είναι σταθερή κατά το τρέξιμο ενός

προγράμματος . Ενώ το περιεχόμενο του string , που δεν είναι σταθερό ,

καθορίζει το τρέχον μήκος του που η συνάρτηση strlen() δείχνει .

strcat() Μια κλήση strcat(st1,st2); προσθέτει το st2 στο τέλος του

st1 , χωρίς να αλλάζει το st2 . Έτσι αν το st1 είχε τιμή “με λένε” και το

71

Page 72: Georgia Dis Intro C

st2 είχε τιμή “ Πέτρο”, τότε μετά την κλήση το st1 έχει πιά τιμή “με

λένε Πέτρο” ενώ το st2 δεν άλλαξε καθόλου . Εννοείται ότι το

strlen(st1) είναι πλέον 13.

strcpy() Χρησιμοποιείται πολύ συχνά , κυρίως για να αποδώσουμε

την τιμή που θέλουμε σε ένα string. Η κλήση της έχει μορφή

strcpy(st1, st2); και αντιγράφει τα περιεχόμενα του st2 στο st1. Μία

κλήση λοιπόν strcpy(s, “τέρμα”); αποδίδει στο s την τιμή “τέρμα”.

strcmp() Είναι ο μόνος τρόπος για ΣΥΓΚΡΙΣΕΙΣ δυο strings, γιατί

στη C δεν επιτρέπονται συνθήκες μεταξύ τους με σύμβολα “<“, “==“

κλπ . Η κλήση της έχει μορφή strcmp(st1,st2); και επιστρέφει 0 αν

είναι ίσα τα strings st1 και st2 . Αν st1 είναι από λεξικογραφική άποψη

μεγαλύτερο , τότε επιστρέφει θετικό ακέραιο . Αν τέλος st1 μικρότερο

τότε επιστρέφει αρνητικό .

Παράδειγμα: printf(“%d”, strcmp(“sos”,”help”));

Η παραπάνω εντολή εμφανίζει έναν θετικό ακέραιο .

72

Page 73: Georgia Dis Intro C

Αναδρομή (Recursion)

Όλες οι συναρτήσεις στη C είναι ισοδύναμες . Κάθε μία μπορεί

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

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

ονομάζουμε μια συνάρτηση αναδρομική όταν μία εντολή του κορμού

της , καλεί την ίδια την συνάρτηση . Γενικότερα αναδρομή , είναι μια

διαδικασία όπου ορίζουμε κάτι με την βοήθεια του ίδιου του

οριζόμενου .

Παράδειγμα 1ο: Το πιο κλασσικό παράδειγμα αναδρομής είναι η

κατασκευή μιας συνάρτησης parag() που υπολογίζει το παραγοντικό

ενός ακέραιου . Το παραγοντικό ενός ακέραιου Ν , όπου Ν>0, είναι το

γινόμενο όλων των ακέραιων από 1 ως Ν . Έτσι το 3 παραγοντικό

(συμβολίζεται 3!) είναι 1x2x3 δηλαδή 6. Ισχύει επίσης 0! = 1.

#include <stdio.h>

parag(int n)

int apot ;

if (!n) return 1;

apot = parag(n-1)*n;

return apot;

void main()

int k ;

do

printf("δώσε αριθμό : ");

scanf("%d", &k);

while (k< 0);

printf("%d", parag(k));

73

Page 74: Georgia Dis Intro C

Παρατηρήστε ότι η do . . . while στη main() εξασφαλίζει ότι ο χρήστης

θα δώσει αριθμό 0 ή θετικό . Ακόμη δείτε ότι όταν η συνάρτηση

parag() καλείται με όρισμα 0, τότε επιστρέφει 1. Όταν καλείται όμως

με οποιοδήποτε άλλο όρισμα n τότε επιστρέφει το γινόμενο parag(n-1)

* n . Για να υπολογιστεί αυτή η παράσταση καλείται αναδρομικά η

parag() με n-1 . Αυτή η διαδικασία συνεχίζεται μέχρι το n να γίνει 0

και να αρχίσουν τότε να επιστρέφουν οι κλήσεις προς τη συνάρτηση .

Για να γίνει αυτό το τελευταίο κατανοητό δείτε το ακόλουθο

παράδειγμα .

Παράδειγμα 2ο: #include <stdio.h>

recur(int a)

int apot;

if (a<3)

/* το a αυξάνει τώρα . . . */

printf("*** a=%d\n", a);

apot = recur(a+1)+10;

else apot = 0;

/* Από εδώ το a μειώνεται . . . */

printf("a=%d apot=%d\n", a, apot);

return apot;

#include <conio.h>

void main()

int b;

clrscr();

b = recur(1);

printf("b=%d ", b);

Το αποτέλεσμα του παραδείγματος είναι :

74

Page 75: Georgia Dis Intro C

*** a=1 *** a=2 a=3 apot=0 a=2 apot=10 a=1 apot=20 b=20 Σημειώστε αρχικά ότι κάθε συνάρτηση μπορεί να έχει τις δικές της

προτάσεις “#include.. .” και γενικά να έχει δικές της κάθε είδους

εντολές προεπεξεργαστή . Έτσι η main(), για να μπορεί να καθαρίσει

την οθόνη με την συνάρτηση συστήματος clrscr(), περιέχει include για

την conio.h .

Ας περάσουμε όμως στα θέματα αναδρομής . Στην main()

καλείται για πρώτη φορά η συνάρτηση recur(). Tην καλούμε με όρισμα

την μονάδα και το αποτέλεσμά της θέλουμε να το αποθηκεύσει η

μεταβλητή b , η οποία στη συνέχεια και θα το εμφανίσει . Όπως όμως

βλέπετε πριν την γραμμή “b=20”, εμφανίζονται 5 ακόμη γραμμές .

Φυσικά είναι έργο της συνάρτησης recur() που διαδοχικά καλεί τον

εαυτό της . Συγκεκριμένα: Η α’ κλήση της recur() είδαμε ότι έγινε από

την main() και θα εκτελεστεί μέχρι την 7η γραμμή . Και αυτό γιατί το a

είναι 1, οπότε μέσα στον κορμό της if θα εκτελεστεί η printf που θα

εμφανίσει την γραμμή

*** a=1

και μετά θα γίνει η β’ κλήση της recur() με όρισμα 2. Τι συμβαίνει

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

κατανέμει μνήμη στη στοίβα για νέες τοπικές μεταβλητές και

παραμέτρους και εκτελεί από την αρχή τον κώδικα της συνάρτησης με

αυτές τις νέες μεταβλητές . Άλλη λοιπόν a δημιουργείται , με τιμή 2, και

άλλο apot. Προσοχή όμως . Δεν δημιουργείται ένα νέο αντίγραφο της

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

καινούργια . Στο παράδειγμά μας , επειδή 2<3, και η β’ κλήση της

recur() θα εκτελεστεί μέχρι την 7η γραμμή . Δηλαδή θα προλάβει να

εμφανίσει

75

Page 76: Georgia Dis Intro C

*** a=2

και στη συνέχεια θα κάνει την γ’ κλήση της . Τρίτη λοιπόν μεταβλητή

apot στη στοίβα και τρίτη παράμετρος a με τιμή 3. Τώρα όμως η

εκτέλεση περνάει στο else και η μεταβλητή apot γίνεται 0. Είναι

βασικό σημείο αυτό στη κατασκευή αναδρομικών συναρτήσεων . Πρέπει

κάπου να υπάρχει μια εντολή if για να υποχρεώνουμε την συνάρτηση

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

κλήση . Θα εμφανιστεί λοιπόν η γραμμή

a=3 apot=0

και με την εντολή return ολοκληρώνεται η γ’ κλήση της recur(). Και

τώρα ; Με την επιστροφή κάθε αναδρομικής κλήσης , ο υπολογιστής

αφαιρεί από τη στοίβα τα τρέχουσα τοπικά μεγέθη και συνεχίζει την

εκτέλεση στο σημείο που κλήθηκε η συνάρτηση μέσα στη συνάρτηση .

Δηλαδή με την return ουσιαστικά επιστρέφουμε στην 7η γραμμή της

β’ κλήσης της συνάρτησης . Εκεί λοιπόν είναι recur(3) ίσο με το 0 και

το apot γεμίζει με την τιμή 10. Στη συνέχεια η printf εμφανίζει την

γραμμή

a=2 apot=10

αφού κατά την β’ κλήση η a είναι 2. Η return ολοκληρώνει και την β’

κλήση και έτσι επιστρέφουμε πια στην 7η γραμμή της α’ κλήσης . Έτσι

εκεί που το a είναι 1, οπότε a+1 είναι 2, έχουμε ότι recur(2) είναι ίσο

με 10 και άρα το apot είναι 20. Εμφανίζεται λοιπόν

a=1 apot=20

και με την return τελειώνει και η α’ κλήση , οπότε recur(1) γίνεται ίσο

με 20. Είναι η τιμή που παίρνει και η b , γι’ αυτό και από την main()

εμφανίζεται

b=20

Παρατηρήσεις

1)Πολλές φορές υπάρχουν ισοδύναμες λύσεις προβλημάτων

χωρίς αναδρομικότητα . Είναι οι επονομαζόμενες επαναληπτικές

76

Page 77: Georgia Dis Intro C

εκδοχές των αλγορίθμων . Για παράδειγμα το παραγοντικό υπολογίζεται

και από τον παρακάτω , μη-αναδρομικό αλγόριθμο :

par(int n)

int apot=1;

char m;

for (m=2;m<=n;m++)

apot=apot*m;

return apot;

Το μεγαλύτερο πλεονέκτημα των αναδρομικών λύσεων είναι ότι

δημιουργούν καθαρότερους και απλούστερους αλγορίθμους σε

ορισμένες περιπτώσεις όπως ταξινόμηση (quicksort) και τεχνητή

νοημοσύνη . Όσον αφορά την ταχύτητα εκτέλεσης οι μη-αναδρομικοί

αλγόριθμοι ορισμένες φορές έχουν μικρό πλεονέκτημα . Τέλος προσοχή

χρειάζεται στη χρήση των αναδρομικών συναρτήσεων γιατί υπάρχει

κίνδυνος υπερχείλισης της στοίβας (stack overflow).

2)Και η main() είναι μια συνάρτηση που μπορεί να κληθεί από

οποιαδήποτε άλλη συνάρτηση . Και μάλιστα μπορεί και η ίδια να

καλέσει τον εαυτό της , δηλαδή μπορεί να είναι και η ίδια η main()

αναδρομική συνάρτηση .

77

Page 78: Georgia Dis Intro C

Συνάρτηση Σαν Παράμετρος Συνάρτησης

Η C έχει ένα πολύ ισχυρό χαρακτηριστικό που ονομάζεται

δείκτης σε συνάρτηση . Μια συνάρτηση βέβαια ΔΕΝ είναι μεταβλητή .

Έχει όμως και αυτή μια φυσική θέση , δηλαδή μια διεύθυνση μέσα στη

μνήμη την οποία μπορούμε να αποδώσουμε σε ένα δείκτη . Και έτσι

χρησιμοποιούμε τον δείκτη αυτό αντί για το όνομα της συνάρτησης . Ο

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

σημείου εισόδου της συνάρτησης . Και επιτρέπει κοντά στα άλλα να

μεταβιβάζονται συναρτήσεις σαν ορίσματα σε άλλες συναρτήσεις .

Παράδειγμα 3ο: H συνάρτηση check() ελέγχει για τις δύο πρώτες

παραμέτρους της a και b , αν είναι είτε αλφαβητικά ίσα (strcmp()) είτε

αριθμητικά ίσα (numcmp()). Αυτό γίνεται μέσω της τρίτης της

παραμέτρου που είναι η συνάρτηση σύγκρισης .

#include <stdio.h>

#include <stdlib.h> /* για την atoi() */

#include <ctype.h> /* για την tolower() */

#include <string.h> /* για την strcmp() */

#define N 80

void check( char *a, char *b, int (*cmp) () )

printf("έλεγχος για ισότητα \n");

if ( !(*cmp) (a,b) )

printf(" ίσα ");

else printf(" άνισα ");

numcmp( char *a, char *b )

if ( atoi(a) == atoi(b) ) return 0;

else return 1;

void main()

78

Page 79: Georgia Dis Intro C

char st1[N], st2[N] ;

printf(" Δώσε 2 αλφαριθμητικά\n ");

gets(st1) ; gets(st2) ;

if ( (tolower(*st1) <= 'z' && tolower(*st1) >= 'a')

|| (tolower(*st2) <= 'z' && tolower(*st2) >= 'a'))

printf(“χρήση strcmp()\n”)

check(st1, st2, strcmp);

else printf(“χρήση numcmp()\n”);

check(st1, st2, numcmp);

Χρησιμοποιούμε για αυτό το παράδειγμα , εκτός της γνωστής strcmp(),

δύο συναρτήσεις συστήματος πολύ χρήσιμες . Η tolower() δέχεται

παράμετρο τύπου char και επιστρέφει τον αντίστοιχο πεζό χαρακτήρα .

Αν η παράμετρος δεν είναι κάποιος κεφαλαίος χαρακτήρας τότε δεν τον

αλλάζει καθόλου . Π .χ .

tolower(‘M’) =>‘m’, tolower(‘2’) =>‘2’, tolower(‘a’) =>‘a’.

Σημειώστε ότι στην ίδια βιβλιοθήκη για μεταβλητές τύπου char ,

δηλαδή στην ctype.h , υπάρχει και η συνάρτηση toupper() που κάνει το

αντίστροφο : δίνει τον αντίστοιχο κεφαλαίο χαρακτήρα .

Η συνάρτηση atoi() μετατρέπει το string που δέχεται σαν

παράμετρο σε integer . Στην ίδια βιβλιοθήκη stdlib.h υπάρχουν και οι

atol(), atof() που κάνουν μετατροπές σε long και float αντίστοιχα .

Πως γίνεται η μετατροπή ; Αν ο πρώτος χαρακτήρας δεν είναι ψηφίο ή

πρόσημο , τότε η μετατροπή σταματάει και το αποτέλεσμα είναι 0.

Αλλιώς ένα-ένα τα ψηφία περνούν και η μετατροπή σταματάει στο

πρώτο χαρακτήρα που δεν είναι ψηφίο . Π .χ .

atoi(“ed234”)=>0, atoi(“15x4d”)=>15, atoi(“-3.4qw”)=>-3

Παρατηρήσεις : Στη main() θα μπορούσαμε να δηλώσουμε τα

αλφαριθμητικά χρησιμοποιώντας δείκτες ως εξής :

79

Page 80: Georgia Dis Intro C

char *st1, *st2;

Έτσι δεν θα υπήρχε και ο περιορισμός της διάστασης Ν=80. Θυμηθείτε

ότι τα strings είναι πίνακες και οι πίνακες χειρίζονται αμεσότερα με

δείκτες . Θυμηθείτε ακόμη ότι *st1 είναι το st1[0]. Oυσιαστικά λοιπόν

η περίπλοκη αυτή συνθήκη της if γίνεται ΑΛΗΘΕΙΑ όταν ο πρώτος

χαρακτήρας είτε του πρώτου string st1 είτε του δεύτερου string st2

είναι ένα από τα γράμματα του αγγλικού αλφάβητου , κεφαλαίο ή

μικρό . Την περίπτωση αυτή την ξεχωρίζουμε γιατί είδαμε ότι η atoi()

δίνει 0 και άρα χρειαζόμαστε σύγκριση αλφαβητική μέσω της γνωστής

strcmp(). Αν όμως και τα δύο strings έχουν έστω και ένα στην αρχή

ψηφίο , τότε η atoi() κάνει τις μετατροπές και γίνεται αριθμητική

σύγκριση μέσω της numcmp() που εμείς φτιάχνουμε .

Η numcmp() , όπως και η strcmp(), επιστρέφει 0 όταν οι

παράμετροι είναι ίσοι και 1 όταν είναι άνισοι . Παρατηρήστε τον τρόπο

που περνάμε τα αλφαριθμητικά σαν παραμέτρους συναρτήσεων . Δεν

διαφέρει από όσα αναφέραμε για τους πίνακες άλλων τύπων .

Εξακολουθούμε δηλαδή να προτιμούμε δείκτες (pointers) για τις

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

γραφεί με αδιάστατους πίνακες :

numcmp( char a[], char b[] )

Η check() θέλει προσοχή . Δηλώνει τις δύο πρώτες παραμέτρους

ως δείκτες χαρακτήρων , που είναι ισοδύναμοι με string. Δείτε όμως

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

Υπάρχει ο τύπος int και υποχρεωτικά σε παρένθεση το αστεράκι

ακολουθούμενο από το όνομα που θέλουμε να δώσουμε εμείς στη

παράμετρο συνάρτησης . Είναι λοιπόν (*cmp) και ακολουθείται πάλι

υποχρεωτικά από () γιατί έτσι αναγνωρίζει ο μεταγλωττιστής τα

ονόματα των συναρτήσεων . Πλήρες λοιπόν είναι int (*cmp) ().

80

Page 81: Georgia Dis Intro C

Μέσα στη check() αναφερόμαστε πάλι με (*cmp) αλλά τώρα

ακολουθούν μέσα στις παρενθέσεις και τα ορίσματα a,b που θέλουμε .

Δείτε πράγματι ότι λέμε

if ( ! (*cmp) (a,b) ) . . .

H cmp γίνεται είτε strcmp είτε numcmp από τη κλήση της check στη

main . Ότι όμως και να είναι το αποτέλεσμα 0 σημαίνει ίσα και το

διάφορο του μηδενός άνισα .

ΠΡΟΣΟΧΗ: Αν θέλουμε να δηλώσουμε μια μεταβλητή p σαν δείκτη σε

συνάρτηση πχ . ακέραια , τότε γράφουμε

int (*p) ();

Επίσης η διαδικασία εύρεσης της διεύθυνσης μιας συνάρτησης είναι

παρόμοια με τους πίνακες , που η διεύθυνσή τους βρίσκεται από το

όνομα τους ΧΩΡΙΣ τετραγωνισμένες αγκύλες και δείκτες-θέσεις .

Δηλαδή χρησιμοποιούμε το όνομα της συνάρτησης ΧΩΡΙΣ παρενθέσεις

και ορίσματα . Έτσι αν θελήσουμε η p να δείχνει στη συνάρτηση

strcmp , δίνουμε

p=strcmp;

Στο παράδειγμά μας τότε , μια κλήση

check(st1,st2,p) ;

θα γέμιζε την παράμετρο cmp με την συνάρτηση strcmp .

81

Page 82: Georgia Dis Intro C

Πίνακες Περισσοτέρων Διαστάσεων

Πολυδιάστατοι Πίνακες και Αρχικές Τιμές

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

4x10x3, γράφουμε

int pinakas[4][10][3] ;

Αναφερόμαστε στο σημείο 2,7,1 του πίνακα γράφοντας απλά

pinakas[1][6][0]

επειδή η αρίθμηση των θέσεων αρχίζει από το 0. Η C αποθηκεύει τα

στοιχεία των πινάκων στη μνήμη με τέτοιο τρόπο ώστε πρώτα να

εξαντλούνται οι δεξιότεροι δείκτες και μετά οι αριστερότεροι . E ιδικά

στους δισδιάστατους , ο πρώτος δείκτης δείχνει την γραμμή και ο

δεύτερος την στήλη . Έτσι για έναν 2x3 πίνακα , στη μνήμη

αποθηκεύεται πρώτα το στοιχείο [0][0], μετά το [0][1], μετά το [0][2],

μετά το [1][0] κ .ο .κ . Τελευταίο βέβαια στοιχείο το [1][2], αφού

χρησιμοποιούνται οι μηδενικές θέσεις .

Αυτή η σειρά πρέπει να ληφθεί υπ’ όψην και κατά την απόδοση

αρχικών τιμών . Και βέβαια προτιμούμε να καθορίζουμε όλες τις

διαστάσεις εκτός από την αριστερότερη για να εκτελέσει ο

υπολογιστής τους υπολογισμούς .

Π .χ . int din[][3] = 1,1,1,2,4,8,3,9,27

Ο din σε κάθε γραμμή του έχει μια τριάδα ακεραίων όπου ο 2ος είναι

το τετράγωνο και ο 3ος είναι ο κύβος του 1ου . Πράγματι 22 =4 και 23

=8. Πόσες γραμμές έχει ; Από την αρχική τιμή του έχει τρείς , αφού

υπάρχουν 3 τριάδες . Μπορούμε όμως να αυξήσουμε ή να ελαττώσουμε

το μέγεθός του χωρίς να αλλάξουμε τις διαστάσεις του .

82

Page 83: Georgia Dis Intro C

Πίνακες Αλφαριθμητικών

Χρησιμοποιούμε έναν δισδιάστατο πίνακα χαρακτήρων , όπου το

μέγεθος του αριστερού δείκτη καθορίζει τον αριθμό των strings και το

μέγεθος του δεξιού δείκτη καθορίζει το μέγιστο μήκος , μείον1, για το

κάθε string. Πχ . ένας πίνακας 10 strings με μήκος το πολύ 80

χαρακτήρων δηλώνεται :

char pin_str[10] [81];

Για να διαβάσουμε πχ . το 5ο string του πίνακα , αρκεί να καθορίσουμε

τον αριστερό δείκτη , δηλαδή δίνουμε

gets(pin_str[4]);

Σύνθετοι Τύποι Δεδομένων

Εκτός από τους πίνακες , μπορούμε να δημιουργήσουμε επιπλέον

τύπους δεδομένων όπως δομές (structures), ενώσεις (unions) και

απαριθμήσεις (enumerations).

Δομές

Είναι σύνθετοι τύποι που περιέχουν μεταβλητές διάφορων τύπων .

Αποτελούν ισοδύναμη έννοια με τα records στη γλώσσα Pascal . Είναι

η ιδανική λύση για την αποθήκευση δεδομένων , ΟΧΙ ίδιου τύπου , που

συνδέονται λογικά . Χρησιμοποιούμε τη λέξη-κλειδί struct για τον

ορισμό μιας δομής . Έτσι ο παρακάτω κώδικας

struct atomo

char onoma[35] ;

unsigned etos_gen ;

char filo ;

f loat varos, ipsos ;

petros, kostas ;

83

Page 84: Georgia Dis Intro C

ορίζει μια δομή μέ το όνομα atomo που αποτελείται από 5 επιμέρους

στοιχεία-πεδία . Το πεδίο onoma είναι string 34 χαρακτήρων για το

ονοματεπώνυμο , το etos_gen είναι απρόσημος ακέραιος για το έτος

γέννησης , το filo είναι χαρακτήρας (‘Α’ για αρσενικό και ‘Θ’ για

θυληκό), το varos και το ipsos είναι πραγματικοί αριθμοί για το βάρος

και το ύψος αντίστοιχα . Τα petros και kostas είναι μεταβλητές τύπου

atomo. Mέσα στο πρόγραμμά μας λοιπόν μπορούμε να δουλέυουμε με

τα ονόματα petros και kostas και όχι με το όνομα atomo που είναι

απλά ένα όνομα τύπου δεομένων όπως int , float κλπ .

Θα μπορούσαμε να παραλείψουμε από τον ορισμό της δομής τις

δηλώσεις των μεταβλητών petros , kostas . Σ’ αυτή τη περίπτωση η

δήλωσή τους θα έπρεπε να γίνει τοποθετώντας μέσα στο πρόγραμμα

την παρακάτω πρόταση

struct atomo petros, kostas ;

οπουδήποτε στις δηλώσεις , αλλά μετά φυσικά από τον ορισμό της

δομής atomo .

Eπίσης δεν είναι υποχρεωτικό να δώσουμε όνομα κατά τον

ορισμό μιας δομής . Δηλαδή θα μπορούσε να παραληφθεί η λέξη atomo

και η δομή να οριζόταν με τον παρακάτω τρόπο :

struct

char onoma[35] ;

. . . . .

petros, kostas ;

Τότε όμως οι δηλώσεις των μεταβλητών petros και kostas ,

YΠΟΧΡΕΩΤΙΚΑ θα έπρεπε να υπάρχουν , γιατί μια ανώνυμη δομή δεν

μπορούμε αργότερα να την χρησιμοποιήσουμε για δηλώσεις

μεταβλητών της .

Πεδία στις Δομές - Aρχικές Τιμές

84

Page 85: Georgia Dis Intro C

Ο τελεστής-τελεία χρησιμοποιείται για να αναφερόμαστε στο

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

gets(petros.onoma);

ζητάει από τον χρήστη-πληκτρολόγιο ένα string το οποίο πλέον θα

αποτελεί το περιεχόμενο του πεδίου onoma της μεταβλητής petros .

Παρόμοια , η εντολή

printf(“%3.2f”, kostas.ipsos) ;

εμφανίζει με ακρίβεια 2 δεκαδικών ψηφίων το πεδίο ipsos της

μεταβλητής kostas .

Μπορούμε να δώσουμε σε μια μεταβλητή δομής αρχική τιμή ,

μαζί με την δήλωσή της . Για παράδειγμα η παρακάτω δήλωση

struct atomo nikos =

“Νίκος Αποστόλου”,1966,’Α’,85.5,1.77;

που καταχωρεί στα πεδία της μεταβλητής nikos τις αντίστοιχες τιμές .

Π .χ . βάρος 85,5 κιλά , έτος γέννησης 1966 κλπ .

Πίνακες Δομών

O ι Πίνακες Δομών είναι ο πιο συνηθισμένος τρόπος για να

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

πρέπει πρώτα να ορίσουμε την δομή και μετά να ορίσουμε μια

μεταβλητή-πίνακα αυτού του τύπου . Έτσι γράφουμε

struct atomo pelates[200] ;

και δημιουργούμε 200 μεταβλητές τύπου atomo . Δηλαδή η καθεμία

από αυτές έχει 5 πεδία για να κρατάει τα στοιχεία όνομα , έτος , φύλο ,

βάρος και ύψος . Το γενικό όνομα αυτών των 200 μεταβλητών είναι

pelates .

Όλα όσα έχουμε πεί για τους πίνακες ισχύουν και στους Πίνακες

Δομών . Εξακολουθεί λοιπόν η θέση 0 να είναι η πρώτη θέση ενός

πίνακα . Και έχουμε στη διάθεσή μας είτε τους δείκτες-θέσης είτε τους

85

Page 86: Georgia Dis Intro C

pointers για να μετακινούμαστε στις διάφορες θέσεις των πινάκων των

δομών μας . Για παράδειγμα η παρακάτω εντολή

printf(“%u”, pelates[4].etos_gen);

εμφανίζει το έτος γέννησης του 5ου πελάτη μας , κατά σειρά

αποθήκευσης μέσα στον πίνακα .

Ενώσεις (Unions)

Η ένωση επιτρέπει να αποθηκεύουμε στον ίδιο χώρο μνήμης

πολλές διαφορετικές μεταβλητές , οι οποίες μπορεί να είναι ακόμη και

διαφορετικού τύπου . Ορίζουμε μια ένωση με τον ίδιο τρόπο που το

κάνουμε και στις δομές . Χρησιμοποιούμε όμως τη λέξη-κλειδί union

αντί της struct . Π .χ . union sos

int k ;

char ch ;

float pr;

metr ;

Όσα είπαμε για τις δηλώσεις μεταβλητών στις δομές , ισχύουν και για

τις ενώσεις . Δηλαδή το όνομα της ένωσης sos και το όνομα της

μεταβλητής metr δεν είναι απαραίτητα , αλλά ΔΕΝ μπορούν να λείπουν

ΚΑΙ ΤΑ ΔΥΟ ταυτόχρονα . Γιατί αν δεν βάλουμε το sos , τότε ορίσαμε

έναν ανώνυμο τύπο ένωσης και όσες μεταβλητές αυτού του τύπου

χρειαστούμε θα πρέπει να γραφούν εκεί που βρίσκεται η metr . Αν πάλι

δεν βάλουμε το metr , τότε πρέπει να υπάρχει το όνομα sos για να

μπορούμε να δηλώσουμε μεταβλητές με μια πρόταση όπως η παρακάτω

:

union sos times1, t imes2 ;

Όταν δηλώνουμε μια ένωση , η C δημιουργεί αυτόματα μια

μεταβλητή αρκετά μεγάλη , έτσι ώστε να μπορεί να αποθηκεύει τον

μεγαλύτερο τύπο από τα πεδία της ένωσης . Δηλαδή στο παράδειγμά

μας δεσμεύονται 4 bytes γιατί ο μεγαλύτερος τύπος της ένωσης είναι ο

86

Page 87: Georgia Dis Intro C

float που απαιτεί τόσα ακριβώς . Προσοχή όμως!!! Ένα μόνο από τα

πεδία μπορούμε κάθε φορά να χρησιμοποιήσουμε . Δηλαδή την ίδια

στιγμή μπορούμε στο παράδειγμα της ένωσης sos , να αποθηκεύσουμε

ΕΙΤΕ έναν ακέραιο , ΕΙΤΕ έναν χαρακτήρα , ΕΙΤΕ τέλος έναν

πραγματικό . Και αυτό συμβαίνει γιατί το πρώτο από τα 4 δεσμευμένα

bytes το χρησιμοποιούν και τα 3 πεδία της ένωσης . Το δεύτερο byte

στη συνέχεια το χρειάζονται μόνο τα πεδία k και pr , ενώ τα δύο

τελευταία bytes τα χρειάζεται μόνο το pr . H πρόσβαση στα διάφορα

πεδία μιας ένωσης γίνεται με τον ίδιο τελεστή που χρησιμοποιούν και

οι δομές . Δηλαδή με τον τελεστή-τελεία . Όμως ο τρόπος δέσμευσης-

οικονομίας της μνήμης που ακολουθούν οι unions και που αναφέραμε

παραπάνω μας θέτουν κάποιους περιορισμούς .

Π .χ . t imes1.ch = ‘G’ ;

printf(“%d”,times1.k * 1.18);

O ι γραμμές αυτές κώδικα ΕΙΝΑΙ ΛΑΘΟΣ γιατί αποθηκεύεται σε μια

ένωση times1 ένας χαρακτήρας και στην επόμενη γραμμή

χρησιμοποιείται η ένωση αυτή σαν να περιέχει ακέραιο . Για να γίνει

ακόμη πιο κατανοητό το ζήτημα δείτε και τις επόμενες γραμμές κώδικα

με τα σχόλια τους :

metr.k=1966; /* το 1966 αποθηκεύεται στη metr και

χρησιμοποιήθηκαν 2 bytes */

metr.ch=‘C’; /* το 1966 χάθηκε , αποθηκεύτηκε το ‘C’

και χρησιμοποιήθηκε 1 byte */

metr.pr=85.5; /* το ‘C’ χάθηκε , αποθηκεύτηκε το 85,5

και χρησιμοποιήθηκαν 4 bytes */

H Δήλωση typedef

87

Page 88: Georgia Dis Intro C

Mε την λέξη-κλειδί typedef μπορούμε να ορίσουμε νέα ονόματα

για υπάρχοντες τύπους . Η γενική μορφή είναι

typedef τύπος ΟΝΟΜΑ ;

Π .χ . typedef float REAL;

To REAL είναι πια ένα επιπρόσθετο όνομα για τον τύπο float .

Μπορούμε δηλαδή να δηλώσουμε μια πραγματική μεταβλητή

apotelesma με οποιαδήποτε από τις ακόλουθες γραμμές :

f loat apotelesma; /* είναι ισοδύναμες οι 2 γραμμές */ REAL

apotelesma;

Συνήθως το όνομα το βάζουμε με κεφαλαία γράμματα , όπως και τις

σταθερές , για να υπενθυμίζουν σ’ αυτόν που βλέπει τον κώδικα ότι

πρόκειται για μια συντομογραφία συμβολική .

Η εμβέλεια αυτού του επιπλέον ονόματος εξαρτάται από την

θέση της πρότασης typedef . Aν η θέση της είναι μέσα σε μια

συνάρτηση τότε φυσικά το επιπλέον όνομα ισχύει μόνο εκεί , τοπικά .

Μπορούμε όμως να έχουμε και καθολικής εμβέλειας νέα ονόματα

τύπων .

Οι δηλώσεις typedef χρησιμοποιούνται για την τεκμηρίωση

κυρίως των προγραμμάτων μας : Επιτρέπουν περιγραφικά ονόματα για

τους καθιερωμένους τύπους δεδομένων και έτσι ο κώδικας που

γράφουμε αποκτά κάποιο προσωπικό ύφος .

Επίσης ένας άλλος λόγος για την χρήση της typedef είναι ότι

διευκολύνουν την φορητότητα του κώδικα : Ανάλογα με το μηχάνημα , η

C δεσμεύει διαφορετικό πλήθος bytes για την αποθήκευση των βασικών

της τύπων όπως int , float κλπ . Βάζοντας λοιπόν στα προγράμματά μας

τις μεταβλητές να δηλώνονται με ονόματα τύπων από typedef ,

μπορούμε κάθε φορά που αλλάζουμε μηχάνημα να αλλάζουμε απλά τις

δηλώσεις typedef .

Π .χ . η δήλωση typedef short diobytes;

88

Page 89: Georgia Dis Intro C

προγράμματος σε μηχάνημα με τύπο short των 2 bytes, θα γίνει

typedef int diobytes;

όταν περάσουμε σε μηχάνημα με int των 2 bytes.

Απαριθμήσεις ή Αριθμημένοι Τύποι

Είναι σύνολα από συγκεκριμένες ακέραιες σταθερές . Όταν

δηλώσουμε μια μεταβλητή τύπου απαρίθμησης τότε οι αποδεκτές τιμές

αυτής της μεταβλητής καθορίζονται από το σύνολο αυτό των ακεραίων .

Οι αριθμημένοι τύποι ορίζονται όπως και οι δομές , αλλά

χρησιμοποιούν την λέξη-κλειδί enum .

Π .χ . enum xromata kokino, prasino, mavro, ble xro1;

Eδώ ορίστηκε ο τύπος xromata και δηλώθηκε η μεταβλητή αυτού του

τύπου xro1 . Iσχύουν τα γνωστά περί της ανωνυμίας ή όχι των τύπων ,

που αναφέραμε στις δομές και ενώσεις . Έτσι για παράδειγμα η

παρακάτω πρόταση

enum xromata xro2,xro3;

δηλώνει δυο μεταβλητές xro2 , xro3 του τύπου xromata . Αυτές τώρα οι

μεταβλητές xro1, xro2 και xro3 μπορούν να πάρουν την τιμή είτε

kokino είτε prasino , είτε mavro , είτε ble . Δηλαδή είναι αποδεκτές και

ολόσωστες εντολές όπως :

xro2 = mavro;

if (xro3 == prasino) printf(“είναι πράσινο \n”);

To σημείο που πρέπει να προσεχτεί ιδιαίτερα είναι ότι κάθε τιμή ,

όπως kokino , prasino κλπ . αντιστοιχίζεται σε μια ακέραια τιμή . Γι’

αυτό και μπορούμε να τα χρησιμοποιούμε σε ακέραιες παραστάσεις .

Εκτός και αν δοθούν διαφορετικές αρχικές τιμές , η τιμή του πρώτου

στοιχείου απαρίθμησης είναι μηδέν , η τιμή του δεύτερου 1, κ .ο .κ .

Π .χ . η εντολή printf(“%d %d”, kokino, ble):

θα εμφανίσει το 0 3 στην οθόνη .

89

Page 90: Georgia Dis Intro C

Aν χρησιμοποιήσουμε το σύμβολο της ισότητας στον ορισμό

μιας απαρίθμησης , μπορούμε να αλλάξουμε τις αντιστοιχίες .

Π .χ . enum xromata kokino,prasino=6,mavro,ble=21;

printf(“%d %d %d %d”, kokino,prasino,mavro,ble):

θα εμφανίσει το 0 6 7 21 στην οθόνη .

ΠΡΟΣΟΧΗ

Μην ξεχνάτε ότι τα στοιχεία-σύμβολα όπως kokino , prasino

κλπ . ΔΕΝ ΕΙΝΑΙ αλφαριθμητικά αλλά απλώς είναι ονόματα κάποιων

ακεραίων . Έτσι η είσοδος και η έξοδος δεν γίνεται απευθείας , π .χ .

printf(“%s”, xro1); /* ΛΑΘΟΣ */

αλλά χρησιμοποιούμε συνήθως εντολή switch . Δηλαδή :

switch (xro1)

case kokino : printf(“κόκινο”);

break ;

case prasino : printf(“πράσινο”);

break ;

. . .

90

Page 91: Georgia Dis Intro C

Προχωρημένα Θέματα Δομών

Πίνακες και Δομές μέσα σε Δομές

Ένα στοιχείο δομής μπορεί να είναι απλό ή σύνθετο . Δείτε το

παρακάτω :

struct a

int x[5][5]; /* πίνακας 5x5 */

float y;

help;

Για να αναφερθούμε ας πούμε στον ακέραιο της 1ης γραμμής και 2ης

στήλης , γράφουμε help.x[0][1].

Όταν μάλιστα μια δομή έχει σαν στοιχείο άλλη δομή , όπως

παρακάτω , τότε μιλάμε για ένθετες δομές :

struct addr char name[30];

char street[40];

char city[15]; ;

struct emp struct addr dieft;

f loat misthos;

ergatis ;

Η δομή emp έχει στοιχείο μια δομή τύπου addr . Ισχύουν όλα όσα

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

city του ergatis με την εντολή :

strcpy(ergatis.dieft .city,”Κιλκίς”);

Δομές και Συναρτήσεις

Τα στοιχεία των δομών μεταβιβάζονται μέσω συναρτήσεων

σύμφωνα με όσα αναφέραμε για τους τύπους τους . Δηλαδή αν met1

είναι δομή με πεδίο a ακέραιο , τότε για μια συνάρτηση func :

func(met1.a) ; /* κλήση κατά αξία */

func(&met1.a) ; /* κλήση κατά αναφορά */

91

Page 92: Georgia Dis Intro C

Θυμηθείτε ότι μια συνάρτηση επηρεάζει τα ορίσματά της , μόνο όταν

κάνουμε κλήση κατά αναφορά . Βέβαια αν b ήταν πεδίο πίνακα κάποιου

τύπου , τότε θα γράφαμε

func(met1.b) ; /* κλήση κατά αναφορά */

γιατί έχουμε πει ότι τα ονόματα πινάκων είναι διευθύνσεις από μόνα

τους .

Τι κάνουμε όμως όταν θέλουμε μεταβίβαση ολόκληρων δομών; Η

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

το όνομα μιας δομής ΔΕΝ είναι δείκτης στη διεύθυνσή της . Έτσι στο

παρακάτω παράδειγμα έχουμε κλήση κατά αξία για την δομή par .

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

#include <stdio.h>

struct s_type

int a,b;

char ch;

char m[11];

;

void f1(struct s_type param)

printf("f1:%d\n",param.a);

void f2(int z)

printf("f2:%d\n",z);

void f3(int *z)

*z=500;

void f4 (char *k)

printf("δώσε string: ");

gets(k);

92

Page 93: Georgia Dis Intro C

void main()

struct s_type arg;

arg.a=200;

f1(arg);

f2(arg.a);

f3(&arg.a);

f1(arg);

f4(arg.m);

puts(arg.m);

Το αποτέλεσμα θα είναι

f1:200

f2:200

f1:500

δώσε string:

Δίνοντας εκεί οποιαδήποτε σειρά χαρακτήρων , αυτή θα επαναληφθεί

από κάτω .

Ας δούμε από την αρχή τις συναρτήσεις . Η συνάρτηση f1 με

όρισμα δομή τύπου s_type , θα εμφανίσει f1 :200 , αφού αυτή είναι η

τιμή του πεδίου a της δομής arg . Οι υπόλοιπες συναρτήσεις έχουν

ορίσματα που είναι όχι ολόκληρες δομές , αλλά πεδία δομών . Έτσι η f2

κάνει κλήση κατά αξία στο ακέραιο πεδίο της δομής , ενώ η f3 κάνει

κλήση κατά αναφορά . H f2 είναι ισοδύναμη με την f1 . Γι’ αυτό

εμφανίζει ίδιο αποτέλεσμα f2:200 . Όμως η πρώτη δέχεται παράμετρο

δομή , ενώ η δεύτερη δέχεται ακέραιο . Η f3 είναι παράδειγμα

συνάρτησης που τροποποιεί πεδίο δομής . Δίνει τιμή 500 στο πεδίο a ,

οπότε στη συνέχεια η f1 εμφανίζει f1:500 .

Τέλος , παρατηρήστε ότι η f4 κάνει κλήση κατά αναφορά , χωρίς

να χρειαστεί να βάλει τον χαρακτήρα & . Βέβαια στους ορισμούς των

93

Page 94: Georgia Dis Intro C

συναρτήσεων f3 και f4 , οι παράμετροι είναι δείκτες ( int * και char *

αντίστοιχα), για να επιδράσουν στην δομή arg της main .

Προσοχή: Για κλήση δομής κατά αναφορά πρέπει να χρησιμοποιήσουμε

Δείκτες σε Δομές .

Οι Επιλογές του Προεπεξεργαστή

Έχουμε συναντήσει στα μέχρι τώρα παραδείγματά μας και

κάποιες εντολές που ξεκινούν με το σύμβολο ‘#’ και δεν τελειώνουν σε

‘;’ . Ονομάζονται και οδηγίες προς τον μεταγλωττιστή . Ο

προεπεξεργαστής στη C είναι ένα πολύ χρήσιμο εργαλείο . Βλέπει το

πρόγραμμα πριν από τον επεξεργαστή και ακολουθώντας τις οδηγίες

μας αντικαθιστά τις συμβολικές σταθερές με τις κατάλληλες τιμές .

Επίσης μπορεί να ψάχνει άλλα αρχεία για να συνδυαστούν με το

πρόγραμμά μας ή ακόμη και να αλλάζει τις συνθήκες της

μεταγλώττισής του .

Οδηγία #include

Δίνει εντολή στον μεταγλωττιστή να συμπεριλάβει ένα άλλο

αρχείο κώδικα στο τρέχον αρχείο . Υπάρχουν δύο παραλλαγές :

#include “stdio.h”

#include <stdio.h>

Και οι δύο λένε ότι το αρχείο επικεφαλίδας stdio.h πρέπει να

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

Αν καθορίσουμε και διαδρομή μαζί με το όνομα του αρχείου , πχ .

c:\test\mylib.h, τότε η αναζήτηση θα γίνει μόνο σε αυτή τη διαδρομή .

Αν δεν καθορίσουμε διαδρομή και κλείσουμε το όνομα του αρχείου σε

εισαγωγικά , η αναζήτηση ξεκινάει από τον τρέχοντα κατάλογο . Αν δεν

καθορίσουμε διαδρομή και κλείσουμε το όνομα αρχείου σε γωνιώδεις

παρενθέσεις , τότε η αναζήτηση ξεκινάει από τους καταλόγους που

94

Page 95: Georgia Dis Intro C

έχουμε ορίσει κατά την προσαρμογή του ολοκληρωμένου

περιβάλλοντος στις ανάγκες μας .

Είναι σημαντικό ότι ο μεταγλωττιστής ενσωματώνει στο

πρόγραμμά μας , μόνο όσες από τις πληροφορίες του αρχείου

συμπερίληψης είναι απαραίτητες . ΄Ετσι όταν συμπεριλάβουμε ένα

αρχείο άχρηστο , το πρόγραμμά μας δεν γίνεται μεγαλύτερο .

Τα αρχεία “συμπερίληψης” μπορούν να περιέχουν οδηγίες

#include . Είναι η περίπτωση των ένθετων συμπεριλήψεων . Και μάλιστα

συνήθως η κατάληξη .h χρησιμοποιείται για τα αρχεία επικεφαλίδας ,

δηλαδή για αυτά που έχουν πληροφορίες που πηγαίνουν στην αρχή ενός

προγράμματος όπως είναι οι εντολές προεπεξεργαστή . Φυσικά εκτός

από αυτά που έρχονται μαζί με την γλώσσα , όπως stdio.h, string.h

κλπ . , μπορούμε να δημιουργούμε και δικά μας αρχεία συμπερίληψης .

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

των σχολίων /* και */, οπότε μπορούμε να βάζουμε σχόλια σε αρχεία

μας που θα ενσωματωθούν σε άλλα προγράμματα .

Οδηγία #define

Μέχρι τώρα είδαμε ότι μπορεί να ορίσει συμβολικές σταθερές .

Π .χ .

#define SOS “Καλημέρα”

#define MAX. 1000

#define TELOS ‘@’

Αυτό λέει στον μεταγλωττιστή κάθε φορά που συναντά στον κώδικα το

SOS να το αντικαθιστά με το string Καλημέρα . Παρόμοια το ΜΑΧ και

το TELOS να αντικατασταθούν αντίστοιχα με τον ακέραιο 1000 και

τον χαρακτήρα @. Σημειώστε ότι η οδηγία #define μπορεί να μπεί

οπουδήποτε μέσα στο πρόγραμμα .

Η γενική μορφή της οδηγίας είναι

#define όνομα συμβολοσειρά

95

Page 96: Georgia Dis Intro C

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

ορίζουμε συμβολική σταθερά τύπου string . Συνήθως το όνομα , γνωστό

και ως μακρο-όνομα (macro-name), το δίνουμε με ΚΕΦΑΛΑΙΟΥΣ

χαρακτήρες για να αναγνωρίζεται γρήγορα μέσα σε ένα πρόγραμμα ότι

αποτελεί συμβολική σταθερά . Και βέβαια όταν ορίσουμε ένα μακρο-

όνομα , μπορούμε να το χρησιμοποιήσουμε στους ορισμούς άλλων

μακρο-ονομάτων .

Η διαδικασία αντικατάστασης από τον προεπεξεργαστή των

συμβολικών σταθερών με τις αντίστοιχες τιμές τους , ονομάζεται

μακρο-αντικατάσταση . Δεν είναι παρά μια αντικατάσταση ενός

αναγνωριστικού ονόματος με μια συγκεκριμένη τιμή ακέραια ,

χαρακτήρα κλπ . Όταν η τιμή αυτή , δηλαδή η συμβολοσειρά είναι

μεγαλύτερη από μία γραμμή , μπορούμε να την συνεχίσουμε στην

επόμενη τοποθετώντας ένα \ στο τέλος της γραμμής :

#define BIG “αυτό είναι ένα πολύ μεγάλο \

αλφαριθμητικό “

Το μακρο-όνομα λέγεται και μακροεντολή όταν η συμβολοσειρά

είναι έκφραση της C, όπως στο παρακάτω παράδειγμα :

#define EMFX printf(“Το Χ είναι %d\n”, x)

Μπορούμε μέσα σε μια συνάρτηση να γράψουμε EMFX; και τότε θα

εμφανιστεί το μήνυμα

Το Χ είναι 12

αν είχε την τιμή 12 το x.

Μακροεντολές με Ορίσματα

Η οδηγία #define έχει ένα ισχυρό χαρακτηριστικό : Το μακρο-

όνομα μπορεί να έχει και ορίσματα . Μια τέτοια μακροεντολή με

ορίσματα μοιάζει πάρα πολύ με μια συνάρτηση , γιατί τα ορίσματα

96

Page 97: Georgia Dis Intro C

περικλείονται μέσα σε παρενθέσεις . Έτσι οι μακροεντολές αυτές είναι

γνωστές και ως συναρτήσεις μακροεντολών .

Παράδειγμα 2ο:

#define TETR(x) x*x

#define EM(x) printf(“είναι %d\n”, x)

#define MIN(a,b) (a<b) ? a : b

#include <stdio.h>

void main()

int x=4, k;

k= TETR(x);

EM(k);

k= TETR(3);

EM(k);

printf(“το ελάχιστο είναι %d\n”, MIN(x,k));

Θα εμφανιστεί ως αποτέλεσμα :

είναι 16

είναι 9

το ελάχιστο είναι 4

Η χρήση μακρο-αντικαταστάσεων στη θέση πραγματικών

συναρτήσεων έχει σαν πλεονέκτημα την αύξηση της ταχύτητας του

κώδικα γιατί αποφεύγουμε την κλήση συνάρτησης . Επίσης άλλο ένα

πλεονέκτημα είναι ότι στις μακροεντολές δεν μας απασχολεί ο τύπος

των μεταβλητών , γιατί διαχειρίζονται συμβολοσειρές και όχι

πραγματικές τιμές . Πράγματι , στο παράδειγμά μας , η μακροεντολή

TETR(x) μπορεί άνετα να χρησιμοποιηθεί και με μεταβλητές int και

με f loat . Ωστόσο η χρήση πραγματικών συναρτήσεων κάνει τα

προγράμματά μας να καταλαμβάνουν λιγότερο χώρο , γιατί οι

μακροεντολές αυξάνουν το μέγεθος των προγραμμάτων μας . Έτσι

ανάλογα με τις ανάγκες μας σε μνήμη και ταχύτητα , κάνουμε τις

επιλογές μας .

97

Page 98: Georgia Dis Intro C

Μεταγλώττιση υπό συνθήκη

Ορισμένες οδηγίες προς τον επεξεργαστή μας βοηθούν να

παράγουμε αρχεία που μπορούν να μεταγλωττιστούν με πολλούς

τρόπους . Συγκεκριμένα , μπορούμε να ζητήσουμε από το σύστημα να

“βλέπει” ορισμένα μόνο τμήματα του κώδικά μας , ανάλογα με τη

περίπτωση .

#if, #else, #elif και #endif

Παράδειγμα 3ο:

#include <stdio.h>

#define MAX 50

void main()

#if MAX >49

printf(“έκδοση για πίνακα πάνω από 49\n”);

#endif

Όπως καταλαβαίνετε , η οδηγία #if μοιάζει με την πρόταση if της C .

Δηλαδή ακολουθείται από μια σταθερή έκφραση και θεωρείται ότι

είναι αληθής αν δεν είναι μηδενική . Φυσικά όταν είναι αληθής τότε θα

μεταγλωττιστεί ο κώδικας που βρίσκεται μεταξύ αυτής και της #endif.

Αλλιώς θα αγνοηθεί το τμήμα αυτό του κώδικα . Στο παράδειγμά μας

λοιπόν , θα εμφανιστεί το μύνημα . Αν όμως στην #define βάλουμε αντί

για 50 μια τιμή μικρότερη ή ίση με το 49, τότε ΔΕΝ θα εμφανιστεί το

μήνυμα . Προσέξτε ότι η έκφραση που ακολουθεί την #if , υπολογίζεται

στο χρόνο της μεταγλώττισης . Έτσι , δεν μπορεί να περιέχει

μεταβλητές , ούτε και τον τελεστή sizeof . Επιτρέπεται να περιέχει

αναγνωριστικά (συμβολικά ονόματα) που έχουν οριστεί από πριν και

σταθερές .

98

Page 99: Georgia Dis Intro C

Η #else αποτελεί την εναλλακτική περίπτωση όταν αποτύχει η

#if . Δηλαδή παίζει τον ρόλο της else στην εντολή if . Kαι μάλιστα

υπάρχει και η δυνατότητα κλιμάκωσης των if/else/if αφού η οδηγία

#elif ισοδυναμεί με την else if .

Παράδειγμα 4ο:

#define SYS “IBMPC”

#if SYS == “IBMPC”

#include <ibmpc.h>

#elif SYS == “MAC”

#include <mac.h>

#else

#include “general.h”

#endif

Το αποτέλεσμα είναι να συμπεριληφθεί μόνο το αρχείο “ ibmpc.h”.

Αλλά αν στην οδηγία #define αλλάζουμε την τιμή , μπορούμε κάθε

φορά να επιλέγουμε ποιό αρχείο θα συμπεριλάβουμε . Γίνεται λοιπόν

κατανοητό ότι οι οδηγίες αυτές χρησιμεύουν πολύ στην παραγωγή και

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

#ifdef και #ifndef

Σημαίνουν “ i f defined” και “ i f not defined”. Δηλαδή “αν έχει

οριστεί” και “αν δεν έχει οριστεί” αντίστοιχα . Δίπλα στις οδηγίες

αυτές πρέπει να υπάρχει ένα μακρο-όνομα . Γιατί η οδηγία εξετάζει

ακριβώς αν αυτό έχει οριστεί ή όχι . Τόσο η #ifdef όσο και η #ifndef

μπορούν να χρησιμοποιήσουν μια πρόταση #else , όχι όμως και μια

#elif . Και οι δύο επίσης τελειώνουν με μια πρόταση #endif , όπως

ακριβώς και η #if .

Παράδειγμα 5ο:

#include<stdio.h> #define US 20

99

Page 100: Georgia Dis Intro C

void main() #ifdef US printf(“δολάριο \n”); #else printf(“δεν ορίστηκε χώρα \n”); #endif #ifndef GREECE printf(“δεν ορίστηκε η Ελλάδα \n”); #else printf(“δραχμή \n“); #endif Θα εμφανιστεί

δολάριο δεν ορίστηκε η Ελλάδα γιατί το US είναι ορισμένο , ενώ το GREECE όχι .

Προσοχή

Πριν τον χαρακτήρα # επιτρέπονται μόνο κενοί χαρακτήρες . Και επειδή

τις γραμμές αυτές τις βλέπει μόνο ο προεπεξεργαστής , τις εντολές για

το κάθε τμήμα #if , #else , και #elif τις βάζουμε να ξεκινάνε από νέα

γραμμή .

Λίστες

Γραμμική Λίστα

100

Page 101: Georgia Dis Intro C

Γραμμική λίστα είναι κάθε σύνολο από n στοιχεία - λέγονται

κόμβοι - που έχουν την ιδιότητα ότι κάθε ενδιάμεσο στοιχείο k

ακολουθεί το k-1 και προηγείται του στοιχείου k+1. Έτσι και οι

πίνακες που είδαμε , είναι μια μορφή γραμμικής λίστας .

Ανάλογα με τον τρόπο υλοποίησής τους , τις διακρίνουμε σε

σειριακές(sequential) και συνδεδεμένες(linked) . Οι πρώτες

χρησιμοποιούν συνεχόμενες θέσεις μνήμης για την αποθήκευση των

κόμβων , ενώ οι δεύτερες αποθηκεύουν τους κόμβους σε

απομακρυσμένες θέσεις μνήμης οι οποίες όμως μεταξύ τους είναι

συνδεμένες .

Οι σειριακές λέγονται και στατικές γιατί καθώς

προγραμματίζουμε τις λειτουργίες των λιστών , πρέπει να καθορίσουμε

ακριβώς και την μνήμη που απαιτούν οι λίστες , ανεξάρτητα από τα

τρέχοντα δεδομένα . Έτσι θυμηθείτε δουλεύαμε με τους πίνακες .

Αντίθετα οι συνδεδεμένες , λέγονται επιπλέον στη γλώσσα C

(αλλά και στη Pascal), δυναμικές , γιατί μπορούμε κατά την εκτέλεση

του προγράμματος να δεσμεύσουμε ή να ελευθερώσουμε μνήμη ,

ανάλογα με τις ανάγκες μας .

Όλες οι λίστες μπορούν να κατασκευαστούν τόσο με σειριακό ,

όσο και με συνδεδεμένο τρόπο . Οι σειριακές παρουσιάζουν

προβλήματα ταχύτητας στην εισαγωγή και διαγραφή ενδιάμεσων

κόμβων . Επίσης δεν μπορούν να εκμεταλλευτούν τις δυνατότητες

δυναμικής κατανομής της μνήμης μιας γλώσσας όπως η C. Έτσι , λόγω

οικονομίας μνήμης προτιμούμε τις δυναμικές συνδεδεμένες λίστες , αν

και απαιτούν σημαντικές ικανότητες προγραμματισμού .

Δυναμική Κατανομή Μνήμης

Πρέπει σύντομα να δούμε τι προσφέρει η C σε θέματα δέσμευσης

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

101

Page 102: Georgia Dis Intro C

θα μπορέσουμε να υλοποιήσουμε τις συνδεδεμένες λίστες που μας

ενδιαφέρουν .

Χρησιμοποιούμε κυρίως τις δύο συναρτήσεις malloc() και free()

της βιβλιοθήκης “stdlib.h”. Οι συναρτήσεις αυτές συνεργάζονται για

να χρησιμοποιήσουν την ελεύθερη μνήμη που υπάρχει στο σύστημά

μας και λέγεται σωρός(heap) . Είναι ο χώρος ανάμεσα στη μόνιμη

περιοχή αποθήκευσης(με το πρόγραμμα , τις καθολικές και τις στατικές

μεταβλητές) και στη στοίβα(με τις τοπικές μεταβλητές).

Κάθε φορά που ζητάμε μνήμη με τη malloc(), κατανέμεται ένα

τμήμα της απομένουσας ελεύθερης μνήμης , ενώ κάθε φορά που

καλούμε την free() επιστρέφεται μνήμη στο σύστημα .

Η malloc() παίρνει σαν όρισμα τον αριθμό bytes που θέλουμε να

δεσμεύσουμε . Γι’ αυτό και συνήθως χρησιμοποιούμε την sizeof για τον

προσδιορισμό των απαιτήσεων του κάθε τύπου δεδομένων . Η malloc()

επιστρέφει ένα δείκτη void . Τι σημαίνει αυτό ; Σημαίνει ότι πρέπει με

άμεση προσαρμογή τύπου(casting) να αλλάξουμε τον τύπο του δείκτη

που επιστρέφεται από την malloc() σε ένα δείκτη του τύπου που

θέλουμε .

H free() παίρνει σαν όρισμα έναν δείκτη . Η C θυμάται πόση

μνήμη δεσμεύτηκε με την malloc() , και στη συνέχεια αποδεσμεύει

όλον αυτό τον χώρο .

Παράδειγμα 1ο: Να κατανεμηθεί χώρος αποθήκευσης για 10

ακέραιους , να εμφανιστούν οι τιμές τους και στη συνέχεια να

αποδεσμευτεί η μνήμη και θα επιστραφεί στο σύστημα .

#include <stdlib.h>

#include <stdio.h>

#define M 10

void main()

int *p;

char ch;

102

Page 103: Georgia Dis Intro C

p = (int *) malloc(M*sizeof(int)); /* casting */

if (!p) /* έλεγχος αποτυχίας της malloc */

printf("δεν υπάρχει μνήμη ");

else

for (ch=0;ch<M;ch++)

*(p+ch) = ch+100;

/* ισοδύναμο με p[ch]=ch +100 */

for (ch=0;ch<M;ch++)

printf("%d\n",*(p+ch));

free(p);

Προσέξτε στην 7η γραμμή την προσαρμογή του void τύπου που

επιστρέφει η malloc(), σε int . Επίσης από κάτω υπάρχει έλεγχος για το

αν η κλήση δέσμευσης μνήμης ήταν επιτυχής . Πρέπει πάντα πριν τη

χρήση ενός δείκτη που επιστράφηκε από τη malloc(), να ελέγχουμε

την επιστρεφόμενη τιμή ως προς το μηδέν . Διότι αν η μνήμη δεν είναι

αρκετή η malloc επιστρέφει ένα μηδενικό(null). Μόνο αν δεν υπάρξει

πρόβλημα , επιστρέφει από τον σωρό έναν δείκτη void , που υποδεικνύει

το πρώτο byte της μνήμης που κατανεμήθηκε .

Δείκτες σε Δομές

Έχουμε ήδη αναφέρει ότι η κλήση κατά αναφορά συναρτήσεων

με ορίσματα δομές , μπορεί να γίνει μόνο με χρήση δεικτών . Αλλά και

οι δυναμικές λίστες κατασκευάζονται μόνο με δείκτες δομών . Έτσι το

θέμα αξίζει προσοχής . Ιδιαίτερα προσέξτε τον τελεστή εμμέσου μέλους

“->“ για τη προσπέλαση πεδίων δομών , όταν δείκτης δείχνει τη δομή .

Ακόμη σημειώστε τη χρήση typedef που αν και δεν είναι υποχρεωτική ,

είναι πολύ διαδεδομένη .

Παράδειγμα 2ο:

#include <stdio.h>

103

Page 104: Georgia Dis Intro C

#include <stdlib.h>

void main()

struct vivlio

char *titlos ;

unsigned timi;

*vptr;

typedef struct vivlio VIVLIO ;

vptr = (VIVLIO *) malloc(sizeof(VIVLIO));

vptr->timi=12000; /* (*vptr).t imi =12000; */

vptr->titlos="Mathematica";

printf("%s %u",vptr->titlos,vptr->timi);

Θα εμφανίσει φυσικά “Mathematica 12000”, δηλαδή τα περιεχόμενα

μιας δομής βιβλίου που εμείς γεμίσαμε . Το σημαντικό είναι ότι ΔΕΝ

δηλώσαμε καμιά μεταβλητή βιβλίου . Κατασκευάσαμε δείκτη vptr στη

δομή και μετά δεσμεύσαμε μνήμη ικανή για αποθήκευση των

δεδομένων ενός βιβλίου . Μέσα σε σχόλια φαίνεται η ισοδύναμη -

γνωστή εντολή για προσπέλαση πεδίου χωρίς τον τελεστή ‘->‘.

ΠΡΟΣΟΧΗ:Επειδή δηλώσαμε το string titlos ως δείκτη και όχι ως

πίνακα , μπορούμε να του αποδώσουμε τιμή με μια σταθερά εντός

διπλών εισαγωγικών . Δηλαδή αν αντί για *char δίναμε πχ . char[20],

τότε η απόδοση θα γραφόταν μόνο ως

strcpy(vptr->titlos,”Mathematica”);

Υλοποίηση Δεσμευμένης Λίστας

Κάθε κόμβος της λίστας είναι δομή και για μια δοσμένη λίστα

όλοι οι κόμβοι έχουν τον ίδιο τύπο και μέγεθος . Κάθε κόμβος της

λίστας , εκτός από τον τελευταίο , περιέχει ένα δείκτη που δείχνει στον

επόμενό του κόμβο . Επίσης κάθε κόμβος , εκτός από τον πρώτο ,

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

104

Page 105: Georgia Dis Intro C

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

δείκτη της ουράς περιέχει την τιμή NULL.

Παράδειγμα 3ο: Πρόκειται για κατασκευή μιας διατεταγμένης

λίστας με ακέραιους που δίνονται σε τυχαία σειρά από το

πληκτρολόγιο . Περιλαμβάνει ΕΙΣΑΓΩΓΗ και ΔΙΑΓΡΑΦΗ κόμβου σε

οποιοδήποτε σημείο της λίστας χρειαστεί(κεφαλή , ουρά ή ενδιάμεσα),

έτσι ώστε να συνεχίσει να μένει ταξινομημένη . Για επιτάχυνση των

αναζητήσεων , χρησιμοποιούμε έναν επιπλέον πλασματικό κόμβο -

”σκοπό”, όπου βάζουμε την τιμή που αναζητούμε . Ο δείκτης end

υπάρχει για να διακρίνουμε τον κόμβο αυτό από όλους τους άλλους . Ο

σκοπός βρίσκεται στο τέλος της λίστας . Ο δείκτης start δείχνει την

αρχή της λίστας μας .

#include <stdlib.h>

#include <stdio.h>

#define NULL 0

struct node

int t imi;

struct node *next;

;

typedef struct node TIMH;

TIMH *start, *end, *p, *q;

int x;

TIMH *getnode()

TIMH *p;

p = (TIMH *) malloc(sizeof(TIMH));

if (p==NULL)

printf("μνήμη ανεπαρκής");

exit(1);

return p;

105

Page 106: Georgia Dis Intro C

void emfanisi()

printf("Εμφάνιση \n");

for (p=start; p != end; p=p->next)

printf("%d\n",p->timi);

char anazito()

end->timi=x;

p=start;

while ( p->timi < x)

p=p->next;

if (p->timi == x && p != end)

return 1 ; else return 0 ;

void main()

/* σχηματισμός άδειας λίστας */

start=end=getnode();

printf("Δώσε ακέραιους ή # για τέλος \n");

while ( scanf("%d",&x) > 0)

if (anazito())

printf("υπάρχει ήδη \n");

else

q=getnode();

if ( p == end) end=q;

else *q = *p ;

p->next = q;

p->timi=x;

emfanisi();

while(getchar() == '\n ');

printf(“Διαγραφές \n”);

106

Page 107: Georgia Dis Intro C

printf("Δώσε ακέραιους ή # για τέλος \n");

while ( scanf("%d",&x) > 0)

if (anazito())

q=p->next;

if ( q == end) end=p;

else *p = *q ;

free(q);

emfanisi();

else printf("Δεν υπάρχει \n");

Η getnode() επιστρέφει δείκτη για κόμβο , δηλαδή δεσμεύει

μνήμη όταν όλα πάνε καλά . Αλλιώς με την exit , σταματάει την

εκτέλεση του προγράμματος δίνοντας κωδικό λάθους 1. Η emfanisi()

χρησιμοποιεί δείκτη p για να διατρέξει τη λίστα από την αρχή μέχρι το

τέλος , δηλαδή τον σκοπό .

Η anazito() επιστρέφει 1 όταν η τιμή που ψάχνουμε υπάρχει στη

λίστα και 0 όταν δεν υπάρχει . Χρησιμοποιείται και στις εισαγωγές

αλλά και στις διαγραφές . Στην main(), προσέξτε ότι ελέγχουμε στην

εντολή scanf αν έγινε ή όχι απόδοση τιμής στην μεταβλητή x , για να

καταλάβουμε το σημάδι τέλους των δεδομένων του χρήστη . Τέλος

προσέξτε ότι και στην εισαγωγή και στην διαγραφή υπάρχει εντολή if

που προβλέπει ειδικές περιπτώσεις : Όταν ο νέος κόμβος πρέπει να

είναι ο νέος σκοπός(end) και όταν ο κόμβος που διαγράφεται βρίσκεται

πριν τον σκοπό .

Σωροί ή Στοίβες (Stacks)

Πρόκειται για ειδική περίπτωση λιστών . Εδώ μπορούμε να

παρεμβάλλουμε ή να διαγράφουμε κόμβους μόνο από την κεφαλή .

Παράδειγμα 4ο: Εδώ ο χρήστης δίνει μια σειρά ακέραιων που

τερματίζεται όμοια με το προηγούμενο παράδειγμα . Οι ακέραιοι αυτοί

107

Page 108: Georgia Dis Intro C

σχηματίζουν σωρό με κεφαλή το δείκτη start . Η εισαγωγή κόμβου

γίνεται με τη συνάρτηση push() , η οποία έχει όρισμα την τιμή του νέου

κόμβου . Η emfanisi() εδώ έχει στη for συνθήκη p != NULL , αφού ΔΕΝ

υπάρχει ο κόμβος - σκοπός end . Το πρόγραμμα μετά από την εμφάνιση

του σωρού , διαγράφει την κεφαλή με την συνάρτηση pop και εμφανίζει

τον τροποποιημένο σωρό . H pop δεν έχει όρισμα , αλλά επιστρέφει την

τιμή της κεφαλής .

#include <stdlib.h>

#include <stdio.h>

#define NULL 0

struct node

int t imi;

struct node *next;

;

typedef struct node SOROS;

int x;

SOROS *start=NULL,*p;

SOROS *getnode()

SOROS *p;

p = (SOROS *) malloc(sizeof(SOROS));

if (p==NULL)

printf("ανεπαρκής μνήμη \n");

exit(1);

return p;

void emfanisi()

printf("εμφάνιση \n");

for (p=start; p != NULL; p=p->next)

printf("%d\n",p->timi);

108

Page 109: Georgia Dis Intro C

void push(int x)

p=getnode();

p->next = start;

p->timi = x;

start = p ;

int pop()

int k;

if (start == NULL)

printf("εξαγωγή από άδειο σωρό \n");

exit(2);

k= start->timi;

p=start;

start =start->next;

free(p) ;

return k;

void main()

printf("δώσε ακέραιους , # για τέλος \n");

while ( scanf("%d",&x) > 0)

push(x);

emfanisi();

x=pop();

printf("Η κεφαλή ήταν :%d",x);

emfanisi();

Ουρές(Queues)

109

Page 110: Georgia Dis Intro C

Είναι οι λίστες που η διαχείρισή τους γίνεται ως εξής : Νέοι

κόμβοι εισάγονται από την ουρά της λίστας , η οποία σημαδεύεται από

τον δείκτη new , ενώ παλιοί κόμβοι διαγράφονται από την κεφαλή , η

οποία σημαδεύεται από τον δείκτη old .

Παράδειγμα 5ο: Είναι ολοκληρωμένο παράδειγμα διαχείρισης ουράς .

Εκτελεί επανειλημμένα τις εξής πράξεις :

α) διαβάζει ακέραιο από τον χρήστη που ακολουθεί ένα

θαυμαστικό(‘!’) και τον καταχωρεί σε νέο κόμβο ουράς .

β) αν αντί για θαυμαστικό , βάλουμε ερωτηματικό(‘?’), τότε

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

Με ‘$’ διακόπτεται η λειτουργία του προγράμματος

#include <stdlib.h>

#include <stdio.h>

#define NULL 0

struct node

int timi;

struct node *next;

;

typedef struct node OYRA;

OYRA *new=NULL,*old, *p;

int x; char ch;

OYRA *getnode()

p = (OYRA *) malloc(sizeof(OYRA));

return p;

void emfanisi()

printf("εμφάνιση\n");

for (p=old; p != NULL; p=p->next)

printf("%d\n",p->timi);

void push(int x)

p=getnode();

110

Page 111: Georgia Dis Intro C

if (new == NULL)

old = p;

else new->next = p;

p->next = NULL;

p->timi = x;

new = p ;

int pop()

int k;

k= old->timi;

p=old;

if (old == new) new =NULL ;

old=old->next;

free(p) ;

return k;

void main()

printf("Δώστε κάθε φορά \n\n");

printf(“ ! με ακέραιο για καταχώρηση \n“);

printf(“ ? για την εμφάνιση του πρώτου ακέραιου\n”);

printf(“ της ουράς, και διαγραφή του \n”);

printf(“ $ για τέλος \n”);

while (1)

do ch=getchar();

while (ch != '!' && ch != '?' && ch != '$');

if (ch == '!')

if ( scanf("%d",&x) > 0)

push(x);

if (p==NULL)

printf("ανεπαρκής μνήμη\n");

111

Page 112: Georgia Dis Intro C

printf("δώσε ? ή $ \n");

continue;

emfanisi();

else printf("περιμένω ακέραιο\n");

else

if (ch =='?')

if (new == NULL)

printf("άδεια ουρά\n");

printf("δώσε πρώτα ! ή $ \n");

continue;

x=pop();

printf("H κεφαλή ήταν :%d\n",x);

emfanisi();

else break; /* ch == '$' */

Η εντολή continue, μας επαναφέρει στην αρχή του ατέρμονου

βρόγχου while (1), για νέα πληκτρολόγηση . Αντίθετα η break , μας

βγάζει έξω εντελώς από αυτόν . H do.. . while , μας υποχρεώνει να

ξεκινήσουμε τα δεδομένα με ! , ? ή $.

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

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

διαγραφές τους . Δεν πρέπει μάλιστα να ξεχνάμε τους ελέγχους για

ειδικές περιπτώσεις , όπως άδεια λίστα , ανεπάρκεια μνήμης κλπ .

112

Page 113: Georgia Dis Intro C

Αρχεία Εισόδου/Εξόδου σε Βάθος

Επικοινωνία με Αρχεία

Τα προγράμματα που κατασκευάζουμε πρέπει να έχουν την

δυνατότητα , όποτε το χρειαζόμαστε , να διαβάζουν δεδομένα από ένα

αρχείο ή να τοποθετούν τα αποτελέσματα σε ένα αρχείο . Και βέβαια

σαν αρχείο , θεωρούμε ένα τμήμα μιας περιφερειακής μνήμης όπως ο

σκληρός δίσκος , η δισκέτα κλπ .

Το λειτουργικό σύστημα UNIX έχει μόνο μια μορφή δομής

αρχείων , τα αρχεία κειμένου(text). Ετσι , η C που αναπτύχθηκε αρχικά

σε αυτό το περιβάλλον , έχει σαν εξ ' ορισμού(default) ρύθμιση , την

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

αποθηκεύονται σαν χαρακτήρες με την χρήση του κώδικα ASCII . Θα

εξετάσουμε λοιπόν στην αρχή συναρτήσεις Εισόδου /Εξόδου αρχείων

κειμένου . Αργότερα θα δούμε και ορισμένα πράγματα πάνω στα

δυαδικά αρχεία , δηλαδή στα αρχεία που αποθηκεύουν κώδικα γλώσσας

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

πληροφοριών σε λειτουργικά συστήματα όπως το MS-DOS.

FILE, fopen(), και fclose()

Παράδειγμα 1ο : Θα ζητήσουμε από τον χρήστη το όνομα αρχείου

κειμένου που θα διαβάσουμε και μετά αν υπάρχει αυτό το αρχείο , θα το

διαβάσουμε χαρακτήρα-χαρακτήρα και θα το εμφανίσουμε στην οθόνη .

#include <stdio.h>

void main()

FILE *in;

int ch;

char s[20];

printf("δώσε όνομα αρχείου :");

gets(s);

113

Page 114: Georgia Dis Intro C

i f ( (in = fopen(s,"r")) != NULL)

while ( (ch=fgetc(in)) != EOF)

fputc(ch, stdout);

fclose(in);

else printf("δεν μπόρεσα να ανοίξω το \"%s\"\n",s);

Δηλώνουμε λοιπόν την μεταβλητή αρχείου in , στην 3η γραμμή ,

σαν δείκτη τύπου FILE . Είναι ένας τύπος δομής για αρχεία που

ορίζεται στο stdio.h και χρησιμοποιείται ευρύτατα .

Στη συνέχεια , καλούμε την fopen(). Η συνάρτηση αυτή

επιστρέφει έναν δείκτη τύπου FILE , δηλαδή μια μεταβλητή που δείχνει

σε αρχείο . Γι ' αυτό και στην 8η γραμμή , αποδίδουμε αυτόν τον δείκτη

στη μεταβλητή μας in . Από εδώ και πέρα , in θα είναι το λογικό όνομα

του αρχείου . Αν όμως , δεν μπορέσει για οποιοδήποτε λόγο να ανοίξει

το αρχείο μας , τότε η fopen() επιστρέφει μηδενική τιμή(NULL). Αυτός

ο έλεγχος , πρέπει πάντα να γίνεται και εδώ βλέπουμε στην 12η γραμμή

το else να εμφανίζει κατάλληλο μήνυμα .

Η fopen() δέχεται δύο παραμέτρους . Η πρώτη είναι τύπου string

και αποτελεί το συμβολικό όνομα του αρχείου . Είναι δηλαδή το

πραγματικό όνομά του , έτσι όπως το αναγνωρίζει το λειτουργικό

σύστημα . Σημειώστε ότι όταν το γεμίζει ο χρήστης , όπως εδώ , μπορεί

να δίνει και πλήρη διαδρομή για προσπέλαση αρχείων ακόμη και

διαφορετικών καταλόγων . Η fopen() λοιπόν έχει παρόμοια δράση με

την assign της Pascal . Επιπλέον όμως διαθέτει και δεύτερη παράμετρο

που περιγράφει την χρήση του αρχείου . Οι τρεις βασικές χρήσεις είναι

:

"r" : θα διαβάσουμε ένα αρχείο που υπάρχει

"w" : θα γράψουμε σε αρχείο που τώρα δημιουργείται

"a" : θα προσθέσουμε στο τέλος ενός αρχείου που αν

114

Page 115: Georgia Dis Intro C

δεν υπάρχει , θα δημιουργηθεί τώρα .

Οπως προσέχουμε στον πρώτο κωδικό χρήσης , να ελέγχουμε ότι

πράγματι υπάρχει το αρχείο που θέλουμε να διαβάσουμε , έτσι

προσέχουμε στον κωδικό "w" να ΜΗΝ υπάρχει αρχείο με τέτοιο

όνομα , γιατί τα περιεχόμενά του θα χαθούν . Είναι δηλαδή παρόμοια με

την "καταστροφική" εντολή rewrite της Pascal . Επίσης εύκολα

παρατηρούμε ότι η "r" αντιστοιχεί στην reset και η "a" στην append

των text αρχείων της Pascal .

Το κλείσιμο του αρχείου γίνεται στην 11η γραμμή με την

fclose(). Η μοναδική της παράμετρος είναι το λογικό όνομα του

αρχείου , δηλαδή η μεταβλητή τύπου FILE . Είναι απαραίτητη γιατί οι

fopen() και fclose() λειτουργούν με αρχεία κειμένου ενδιάμεσης

μνήμης . Αυτό σημαίνει ότι τα δεδομένα που εισάγονται ή εξάγονται

από την κεντρική μνήμη προς την περιφερειακή , περνούν από

προσωρινούς χώρους αποθήκευσης για λόγους ασφάλειας αλλά και

ταχύτητας . Η fclose() αδειάζει την ενδιάμεση μνήμη και επιστρέφει 0

αν όλα πήγαν καλά , ενώ επιστρέφει -1 στην αντίθετη περίπτωση .

fgetc() και fputc()

Οι συναρτήσεις αυτές εργάζονται παρόμοια με τις getchar() και

putchar(). Απλά πρέπει να πούμε ποιο αρχείο χρησιμοποιούμε . Στο

παράδειγμα 1 -γραμμή 9- διαβάζουμε χαρακτήρα από το αρχείο in και

στην επόμενη γραμμή γράφουμε χαρακτήρα στο αρχείο stdout . To

αρχείο αυτό , που αποτελεί την δεύτερη παράμετρο της fputc() είναι η

εξ ' ορισμού έξοδος , δηλαδή η οθόνη .

Οπως λοιπόν και στην Pascal, η κονσόλα (πληκτρολόγιο-οθόνη),

αποτελεί αρχείο text . Ετσι με stdin (είσοδος) και stdout (έξοδος),

μπορούμε να αναφερόμαστε στις βασικές περιφερειακές συσκευές

115

Page 116: Georgia Dis Intro C

χρησιμοποιώντας και τις συναρτήσεις που εργάζονται με αρχεία text

περιφερειακής μνήμης .

Σημειώστε τέλος ότι υπάρχουν και οι μακροεντολές getc() και

putc(), που εργάζονται όμοια με τις fgetc() και fputc() αντίστοιχα .

fscanf() και fprintf()

Εργάζονται όπως οι scanf() και printf(), αλλά θέλουν και μια

επιπλέον παράμετρο που να δείχνει το αρχείο που χρησιμοποιούμε . Η

παράμετρος αυτή μπαίνει πρώτη , όπως φαίνεται και στις γραμμές 12

και 13 του παραδείγματος που ακολουθεί .

Παράδειγμα 2ο : Θα ζητήσουμε από τον χρήστη το όνομα αρχείου

κειμένου που θα διαβάσουμε και μετά αν υπάρχει αυτό το αρχείο , θα

ζητήσουμε το όνομα αρχείου εξόδου . Εκεί , χωρίς να καταστραφούν

ενδεχομένως προηγούμενα περιεχόμενα , θα αποθηκεύσουμε τις

ακέραιες τιμές που διαβάσαμε από την είσοδο , σε ξεχωριστές γραμμές .

Κάθε γραμμή θα ξεκινάει με την πρόταση "είναι ".

#include <stdio.h>

void main()

FILE *fin, *fout;

int k;

char s[20] , s2[20];

printf("δώσε όνομα αρχείου εισόδου:");

gets(s);

i f ( (fin = fopen(s,"r")) != NULL)

printf("δώσε όνομα αρχείου εξόδου:");

gets(s2);

fout = fopen(s2,"a");

while ( (fscanf(fin,"%d",&k)) != EOF)

fprintf(fout,"είναι %d\n",k);

116

Page 117: Georgia Dis Intro C

fclose(fin);

fclose(fout);

else printf("δεν μπόρεσα να ανοίξω το \"%s\"\n",s);

Οπως και οι fgetc(), fputc(), οι συναρτήσεις fscanf(), fprintf()

χρησιμοποιούνται αφού η fopen() έχει ανοίξει ένα αρχείο και πριν το

κλείσει η fclose().

fgets() και fputs()

Είναι βέβαια αντίστοιχες με τις gets() και puts(). Διαβάζουν και

γράφουν στα αρχεία χρησιμοποιώντας μεταβλητές string, δηλαδή

πίνακες χαρακτήρων .

Παράδειγμα 3ο : Θα ζητήσουμε από τον χρήστη το όνομα αρχείου

κειμένου που θα διαβάσουμε και μετά αν υπάρχει αυτό το αρχείο , θα

ζητήσουμε το όνομα αρχείου εξόδου που θα δημιουργηθεί . Εκεί θα

αποθηκεύσουμε τα περιεχόμενα του αρχείου εισόδου αποκόπτοντας

τους χαρακτήρες κάθε γραμμής που βρίσκονται πέρα από την στήλη 80.

#include <stdio.h>

#define MAXLIN 81

void main()

FILE *fin, *fout;

char *str[MAXLIN] ;

char s[20] , s2[20];

printf("δώσε όνομα αρχείου εισόδου:");

gets(s);

i f ( (fin = fopen(s,"r")) != NULL)

printf("δώσε όνομα αρχείου εξόδου:");

gets(s2);

fout = fopen(s2,"w");

117

Page 118: Georgia Dis Intro C

while ( (fgets(str,MAXLIN,fin)) != NULL)

fputs(str,fout);

fclose(fin);

fclose(fout);

else printf("δεν μπόρεσα να ανοίξω το \"%s\"\n",s);

Η fgets() δέχεται τρία ορίσματα . Το πρώτο , κατά τα γνωστά ,

είναι η μεταβλητή τύπου string που γεμίζει . Το δεύτερο όρισμα βάζει

ένα όριο στο μήκος της γραμμής που πρόκειται να διαβαστεί . Το τρίτο

όρισμα είναι το αρχείο που διαβάζουμε . Έτσι στην 13η γραμμή , η

fgets() σταματάει όταν διαβάσει τον χαρακτήρα νέας γραμμής ή όταν

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

Και στις δύο περιπτώσεις προστίθεται ο χαρακτήρας ' \0' στο τέλος της

str .

ΠΡΟΣΟΧΗ: α) Ενώ η gets() αντικαθιστά τον χαρακτήρα νέας γραμμής

με ' \0' , η fgets() τον διατηρεί .

β) Η fgets(), όπως και η gets(), επιστρέφουν την τιμή

NULL όταν βρουν το τέλος του αρχείου(EOF) . Γι ' αυτό και στην 13η

γραμμή ελέγχουμε για το τέλος αρχείου , όχι όπως στα προηγούμενα

παραδείγματα με το EOF , αλλά με το NULL .

Τυχαία Προσπέλαση - fseek()

Παράδειγμα 4ο : Είναι ένα πρόγραμμα που θα το καλούμε να

εκτελεστεί δίνοντας ταυτόχρονα και το όνομα του αρχείου κειμένου

που θα διαβάσει . Αν το αρχείο τώρα , περιέχει πχ . τη λέξη "καλή", τότε

εμφανίζεται στην οθόνη "κήαλλαήκ". Δηλαδή εμφανίζεται ο 1ος , ο

τελευταίος , ο δεύτερος , ο προτελευταίος κλπ . χαρακτήρας .

#include <stdio.h>

void main(int number,char *names[] )

FILE *fp;

118

Page 119: Georgia Dis Intro C

long apost =0L;

int ch;

i f (number < 2)

puts("δεν έδωσες όνομα αρχείου \n");

else

i f ( (fp = fopen(names[1] ,"r")) != NULL)

while ( fseek(fp,apost++,0) == 0

&& (ch=getc(fp)) != EOF)

putchar(ch);

i f ( fseek(fp,-(apost+2),2) == 0)

putchar(getc(fp));

fclose(fp);

else printf("δεν μπόρεσα να ανοίξω το \"%s\"\n",names[1]);

Η συνάρτηση fseek() μας επιτρέπει να μεταχειριστούμε ένα

αρχείο που ανοίχτηκε με την fopen() σαν ένα πίνακα και να

μετακινηθούμε κατ ' ευθείαν σε μια συγκεκριμένη θέση του . Επιστρέφει

τιμή 0 αν όλα είναι εντάξει , ενώ επιστρέφει τιμή -1 όταν προσπαθούμε

να μετακινηθούμε έξω από τα όρια του αρχείου . Γι ' αυτό και στις

γραμμές 10 και 13, συγκρίνουμε την fseek() με το μηδέν .

Τρία είναι τα ορίσματά της . Το πρώτο είναι η μεταβλητή αρχείου

που διατρέχουμε . Το δεύτερο καλείται απόσταση (offset) και πρέπει να

είναι τύπου long . Μας λέει πόσο μακριά να μετακινηθούμε από το

σημείο εκκίνησης . Θετική απόσταση σημαίνει κίνηση προς τα εμπρός ,

ενώ αρνητική είναι κίνηση προς τα πίσω .

Το σημείο εκκίνησης προσδιορίζεται από το τρίτο όρισμα .

Καλείται κατάσταση (mode), και ισχύει :

κατάσταση σημείο εκκίνησης

119

Page 120: Georgia Dis Intro C

0 αρχή του αρχείου

1 τρέχουσα θέση

2 τέλος του αρχείου

Στην 10η λοιπόν γραμμή , μεταφερόμαστε στη θέση που απέχει

αρχικά 0 από την αρχή . Δηλαδή διαβάζουμε και εμφανίζουμε τον

πρώτο χαρακτήρα . Μετά , στην 13η γραμμή μεταφερόμαστε στην θέση

που απέχει 2 από το τέλος του αρχείου . Αυτό το +2 υπάρχει για να

αρχίζουμε από τον τελευταίο κανονικό χαρακτήρα . Παραλείπουμε

δηλαδή τον χαρακτήρα ΕΟF(control-Z) και τον χαρακτήρα νέας

γραμμής( ' \n'). Ετσι εμφανίζεται ο τελευταίος κανονικός χαρακτήρας .

Οταν ξαναγυρίσει στην 10η γραμμή το πρόγραμμα θα εμφανίσει τον

χαρακτήρα που απέχει 1 από την αρχή του αρχείου κ .ο .κ .

Ορίσματα γραμμής-διαταγής (παράμετροι στη main())

Ενα άλλο σημαντικό στοιχείο στο προηγούμενο παράδειγμα είναι

οι παράμετροι που μπορούμε να χρησιμοποιούμε στη συνάρτηση

main(). Συνήθως επιτρέπονται δύο ορίσματα . Το πρώτο παριστάνει τον

αριθμό των strings που ακολουθούν το όνομα του εκτελέσιμου

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

argc(argument count-μέτρηση ορισμάτων).

Το δεύτερο όρισμα είναι ένας πίνακας με στοιχεία string , δηλαδή

πίνακας δεικτών τύπου char. Παραδοσιακά καλείται argv(argument

values-τιμές ορισμάτων).

Παράδειγμα 5ο : Εχω ένα πρόγραμμα στο αρχείο test.c και μετά την

μεταγλώττιση παίρνω το εκτελέσιμο αρχείο "test.exe". Αν δώσω στην

προτροπή C: του λειτουργικού συστήματος

test new.dat τώρα

τότε το argc έχει τιμή 2 και για τον πίνακα argv ισχύει

argv[0] δείχνει το "test"

120

Page 121: Georgia Dis Intro C

argv[1] δείχνει το "new.dat"

argv[2] δείχνει το "τώρα"

Γι ' αυτό και στο παράδειγμα 4, το names[1] είναι το όνομα αρχείου

που θέλουμε να διαβάσουμε και που ο χρήστης το δίνει από την

γραμμή διαταγής .

Καταστάσεις "ενημέρωσης" της fopen()

Εκτός από τις 3 πρωταρχικές χρήσεις αρχείων κειμένου που ήδη

αναφέραμε , υπάρχουν και οι ακόλουθες :

"r+" Ανοίγει ένα υπάρχον αρχείο για διάβασμα ή γράψιμο

"w+" Δημιουργεί αρχείο και επιτρέπεται διάβασμα ή γράψιμο

"a+" Επιτρέπεται διάβασμα ή γράψιμο . Αν όμως το αρχείο ήδη

υπάρχει , τότε το γράψιμο γίνεται μόνο στο τέλος του αρχείου .

Μεταξύ των κλήσεων της εισόδου και εξόδου , θα πρέπει να

υπάρχει μία κλήση στη fflush()-φρεσκάρισμα , fseek() ή στην

rewind()-επαναφορά . Γιατί όλες αυτές μεταφέρουν τα περιεχόμενα της

ενδιάμεσης μνήμης και το αρχείο έτσι έχει τα πιο πρόσφατα δεδομένα .

Υποστήριξη δυαδικών αρχείων

Στους 6 κωδικούς χρήσης που ήδη αναφέραμε , αν προσθέσουμε

δίπλα ένα b(binary), τότε η fopen() δημιουργεί /ανοίγει δυαδικό

αρχείο .

Πχ . η πρόταση

in=fopen("test.dat","r+b");

ανοίγει για διάβασμα /γράψιμο το δυαδικό αρχείο "test.dat".

121

Page 122: Georgia Dis Intro C

Αν συναντήσετε δίπλα στους κωδικούς που αναφέρθηκαν αντί για

το γράμμα b το γράμμα t ( text), τότε και πάλι η fopen() υποστηρίζει

αρχεία κειμένου . Οπως όμως αναφέραμε δεν είναι απαραίτητο το t ,

γιατί τα αρχεία κειμένου είναι η εξ ' ορισμού επιλογή .

122