C Notes

114
1 Η Γλώσσα Προγραμματισμού C (Μέρος 1 - Εισαγωγή) Σύντομη Ιστορία της C Η γλώσσα προγραμματισμού C δημιουργήθηκε από τον Dennis Ritchie στα Bell Labs το 1972 όταν αυτός και ο Ken Thompson ασχολούνταν με τον σχεδιασμό του λειτουργικού συστήματος Unix. Η C ήταν μια εξέλιξη της γλώσσας Β του Ken Thompson και εκείνη με τη σειρά της ήταν μια εξέλιξη της γλώσσας BCPL και δημιουργήθηκε για να μπορέσει να καλύψει κάποιες αυξημένες ανάγκες στον προγραμματισμό. Αργότερα αναπτύχθηκαν πολλές παραλλαγές της C που ήταν επόμενο να έχουν ασυμφωνίες μεταξύ τους. Έτσι, δημιουργήθηκε μια επιτροπή στις αρχές του καλοκαιριού του 1983 που άρχισε να δουλεύει πάνω στη δημιουργία ενός προτύπου Ansi το οποίο και θα όριζε μια για πάντα τη γλώσσα C. Μια Σύγκριση των Γλωσσών Προγραμματισμού Η C θεωρείται γενικά γλώσσα μέσου επιπέδου και αυτό γιατί συνδυάζει στοιχεία των γλωσσών υψηλού επιπέδου (high level languages), όπως είναι η Cobol και η Pascal και στοιχεία των γλωσσών χαμηλού επιπέδου (low level languages), όπως είναι η Assembly. Για να διευκρινίσουμε, πρέπει να πούμε ότι σαν γλώσσα υψηλού επιπέδου θεωρείται η γλώσσα εκείνη που είναι αρκετά περιγραφική και που είναι έτσι πιο κοντά στην ανθρώπινη γραπτή γλώσσα και σαν γλώσσα χαμηλού επιπέδου θεωρείται εκείνη που είναι πιο κοντά στη μηχανή. Αυτός ο χαρακτηρισμός δεν έχει καμία απολύτως σχέση με τις δυνατότητες της γλώσσας. Σίγουρα, η κάθε γλώσσα προγραμματισμού έχει κάποιες προτεραιότητες να εκπληρώσει. Η Pascal, για παράδειγμα, χρησιμοποιείται κυρίως για τη σωστή διδασκαλία των αρχών του προγραμματισμού, ενώ η Basic δημιουργήθηκε έτσι ώστε να δώσει τη δυνατότητα σε αρχάριους στον προγραμματισμό να κάνουν με άνεση και ευκολία τα πρώτα τους βήματα στον ιδιόμορφο αυτό χώρο. Ο Clipper και η Cobol είναι καθαρά επαγγελματικές γλώσσες προγραμματισμού.

Transcript of C Notes

Page 1: C Notes

1

Η Γλώσσα Προγραµµατισµού

C (Μέρος 1 - Εισαγωγή)

Σύντοµη Ιστορία της C

Η γλώσσα προγραµµατισµού C δηµιουργήθηκε από τον Dennis Ritchie στα Bell Labs το 1972 όταν αυτός και ο Ken Thompson ασχολούνταν µε τον σχεδιασµό του λειτουργικού συστήµατος Unix. Η C ήταν µια εξέλιξη της γλώσσας Β του Ken Thompson και εκείνη µε τη σειρά της ήταν µια εξέλιξη της γλώσσας BCPL και δηµιουργήθηκε για να µπορέσει να καλύψει κάποιες αυξηµένες ανάγκες στον προγραµµατισµό.

Αργότερα αναπτύχθηκαν πολλές παραλλαγές της C που ήταν επόµενο να έχουν ασυµφωνίες µεταξύ τους. Έτσι, δηµιουργήθηκε µια επιτροπή στις αρχές του καλοκαιριού του 1983 που άρχισε να δουλεύει πάνω στη δηµιουργία ενός προτύπου Ansi το οποίο και θα όριζε µια για πάντα τη γλώσσα C.

Μια Σύγκριση των Γλωσσών Προγραµµατισµού

Η C θεωρείται γενικά γλώσσα µέσου επιπέδου και αυτό γιατί συνδυάζει στοιχεία των γλωσσών υψηλού επιπέδου (high level languages), όπως είναι η Cobol και η Pascal και στοιχεία των γλωσσών χαµηλού επιπέδου (low level languages), όπως είναι η Assembly.

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

Σίγουρα, η κάθε γλώσσα προγραµµατισµού έχει κάποιες προτεραιότητες να εκπληρώσει. Η Pascal, για παράδειγµα, χρησιµοποιείται κυρίως για τη σωστή διδασκαλία των αρχών του προγραµµατισµού, ενώ η Basic δηµιουργήθηκε έτσι ώστε να δώσει τη δυνατότητα σε αρχάριους στον προγραµµατισµό να κάνουν µε άνεση και ευκολία τα πρώτα τους βήµατα στον ιδιόµορφο αυτό χώρο. Ο Clipper και η Cobol είναι καθαρά επαγγελµατικές γλώσσες προγραµµατισµού.

Page 2: C Notes

2

Η C, όµως, µπόρεσε να φέρει τον προγραµµατιστή πιο κοντά στο hardware, που µε τις άλλες γνωστές γλώσσες προγραµµατισµού κάτι τέτοιο θα ήταν πολύ δύσκολο να γίνει. Βέβαια, η κάθε γλώσσα προγραµµατισµού κάνει και διαφορετική δουλειά και δεν θα ήταν σωστό να κάνουµε συγκρίσεις, για τον ίδιο λόγο που δεν µπορούµε να συγκρίνουµε ένα αεροπλάνο µ’ ένα ποδήλατο καθώς το καθένα είναι προορισµένο να κάνει διαφορετική δουλειά.

Τα Πλεονεκτήµατα της C

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

• Χαρακτηριστικά Σχεδίασης, Η C έχει µοντέρνες δοµές ελέγχου για να µπορούµε να κάνουµε επαναληπτικές εργασίες και για εύκολη επιλογή εναλλακτικών τρόπων δράσης. Με το πλήθος των δοµών δεδοµένων που διαθέτει, µπορεί να αναπαραστήσει ένα µεγάλο σύνολο από διαφορετικούς τύπους πληροφοριών. Έχει και το µεγάλο πλεονέκτηµα ότι επιβάλλει τη διάσπαση του προγράµµατος σε αυτοδύναµες ενότητες, τις συναρτήσεις.

• Αποτελεσµατική, Η C είναι µια αποτελεσµατική γλώσσα προγραµµατισµού, που είναι τόσο συµπεριεκτική, ώστε να χρησιµοποιούµε σ' αυτήν πολύ λιγότερες λέξεις σε σχέση µε άλλες γλώσσες. Έχει έναν συµπαγή και γρήγορο κώδικα.

• Φορητή Γλώσσα, Η C είναι µια φορητή γλώσσα, δηλ. τα προγράµµατά της µπορούν να τρέξουν µε λίγες ή και µε καθόλου τροποποιήσεις και σε ένα άλλο σύστηµα. Μάλιστα θεωρείται σαν η πιο φορητή γλώσσα.

• Δυναµικότητα και Ευελιξία, Η C είναι δυναµική και ευέλικτη, δύο ιδιότητες που είναι αρκετά δηµοφιλείς στους υπολογιστές. Όπως ξέρουµε, το µεγαλύτερο µέρος του δυναµικού και ευέλικτου λειτουργικού συστήµατος Unix είναι γραµµένο σε C. Αυτό ισχύει και για επεξεργαστές κειµένων, µεταγλωττιστές (compilers) και ερµηνευτές (interpreters) γλωσσών προγραµµατισµού. Η C διαθέτει µερικά από τα χαρακτηριστικά ελέγχου που συνήθως τα συναντάµε στη συµβολική γλώσσα (assembly language).

• Προσανατολισµός προς τον Προγραµµατιστή, Η C είναι προσανατολισµένη προς τις ανάγκες του προγραµµατιστή, ο οποίος και έχει άµεση πρόσβαση στο υλικό. Με τη C έχουµε τη σπουδαία δυνατότητα να µπορούµε να χειριζόµαστε µεµονωµένα τα δυαδικά ψηφία (bits) της µνήµης. Γενικά η C είναι πολύ λιγότερο περιοριστική στο να µας αφήνει να κάνουµε ό,τι θέλουµε σε σχέση µε την Pascal για παράδειγµα.

Αυτή η ελευθερία είναι και πλεονέκτηµα, αλλά είναι και επικίνδυνη όπως είναι φυσικό. Στη C τα πάντα (σχεδόν) επιτρέπονται. Δεν γίνεται έλεγχος των τύπων, άρα µπορεί κανείς να ανακατέψει ό,τι δεδοµένα θέλει, κάτι που είναι πολύ χρήσιµο όταν προγραµµατίζουµε σε επίπεδο συστήµατος. Ακόµη, η C έχει µια τεράστια βιβλιοθήκη από χρήσιµες συναρτήσεις.

Page 3: C Notes

3

Τα Μειονεκτήµατα της C

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

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

Επιπλέον, συχνά είναι πολύ δύσκολο να ανιχνευθούν και τα λογικά λάθη σ’ ένα πρόγραµµα της C. Η C έχει τελικά τόσες πολλές δυνατότητες έκφρασης, ώστε να χρειαστεί πολύς καιρός για να µπορεί να πει κανείς µε βεβαιότητα ότι την έµαθε καλά.

Γράψιµο προγράµµατος σε C

Όπως είπαµε στα προηγούµενα, η C επιβάλλει τον καταµερισµό του προγράµµατος σε ενότητες, που ονοµάζονται συναρτήσεις (functions). Εάν είναι απαραίτητο, οι συναρτήσεις µπορούν να χωριστούν και σε µικρότερες συναρτήσεις. Επίσης, στη C το κύριο πρόγραµµα είναι κι αυτό µια συνάρτηση, που ονοµάζεται main().

Μια µέθοδος για το γράψιµο ενός προγράµµατος στη C είναι να ξεκινήσουµε γράφοντας τη συνάρτηση main(), την ενότητα του πιο πάνω επιπέδου και µετά να ασχοληθούµε µε τις συναρτήσεις των πιο κάτω επιπέδων. Η διαδικασία αυτή ονοµάζεται πάνω-προς-τα-κάτω προγραµµατισµός (top-down programming).

Η αντίστροφη διαδικασία, δηλ. το να ασχοληθούµε πρώτα µε τις συναρτήσεις των κατώτερων επιπέδων και µετά να ανεβαίνουµε προς τα πάνω, ονοµάζεται κάτω-προς-τα-πάνω προγραµµατισµός (bottom-up programming). Ένα πλεονέκτηµα του πάνω-προς-τα-κάτω προγραµµατισµού είναι ότι µπορούµε να χαράξουµε καλύτερα τη ροή του προγράµµατος, µια και δεν ασχολούµαστε από την αρχή µε τις λεπτοµέρειες των επί µέρους συναρτήσεων.

Αντικατάσταση της Assembly από τη C

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

Η γλώσσα Assembly είναι, όµως, δύσκολη στο γράψιµο προγραµµάτων και επειδή δεν είναι καθόλου δοµηµένη, κάνει το τελικό πρόγραµµα να είναι ανεπιθύµητο από τους προγραµµατιστές. Ο ερχοµός της C κάνει περιττή πλέον τη χρήση της Assembly.

Έτσι, µε τη C µπορούµε να γράψουµε προγράµµατα που να έχουν την ίδια αποτελεσµατικότητα µε άλλα ανάλογα προγράµµατα που είναι γραµµένα στη γλώσσα Assembly, αλλά να έχουµε συγχρόνως και τα σπουδαία πλεονεκτήµατα µιας γλώσσας προγραµµατισµού ανωτέρου επιπέδου.

Page 4: C Notes

4

Μεταγλώττιση και Σύνδεση Προγράµµατος

Ο µεταγλωττιστής (compiler) της C µετατρέπει τον πηγαίο κώδικα (source program), δηλ. το πρόγραµµα που γράφουµε σε C, σ’ έναν αντικειµενικό κώδικα (object program) και το πρόγραµµα σύνδεσης (linker) συνδυάζει αυτόν τον κώδικα µε άλλους κώδικες και δηµιουργείται έτσι το εκτελέσιµο αρχείο (executable file). Τα προγράµµατα της C έχουν την επέκταση .c.

Ο ρόλος του προγράµµατος σύνδεσης είναι να ενώσει τον τελικό κώδικα, τον κώδικα εκκίνησης (start-up code) του συστήµατός µας και τον κώδικα βιβλιοθήκης (library code) στο εκτελέσιµο αρχείο. Ο κώδικας εκκίνησης έχει σχέση µε την επικοινωνία µεταξύ του προγράµµατος και του λειτουργικού συστήµατος και ο κώδικας βιβλιοθήκης περιέχει τον τελικό κώδικα για πολλές συναρτήσεις.

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

Ένα Απλό Πρόγραµµα στη C

/* prog01.c – αυτό είναι ένα απλό πρόγραµµα στη γλώσσα C */

#include <stdio.h>

main()

{

int num; /* ορίζεται µια ακέραια µεταβλητή µε το όνοµα num */

num = 10 ; /* καταχώρηση τιµής στη µεταβλητή num */

printf("Ένα πολύ απλό πρόγραµµα σε C.\n"); /* η συνάρτηση printf() */

printf("Ο αγαπηµένος µου αριθµός είναι ο %d.\n", num);

}

Αφού µεταγλωττίσουµε και τρέξετε το παραπάνω πρόγραµµα, τότε θα πρέπει να εµφανισθούν στην οθόνη τα παρακάτω :

Ένα πολύ απλό πρόγραµµα σε C.

Ο αγαπηµένος µου αριθµός είναι ο 10.

Page 5: C Notes

5

Σύντοµη Ανάλυση του Προγράµµατος

Η πρώτη γραµµή του προγράµµατος χρησιµοποιεί τα σύµβολα /* και */ για να συµπεριλάβουµε εκεί κάποια σχόλια (comments), που θα µας βοηθήσουν να κάνουµε το πρόγραµµά µας πιο ευανάγνωστο. Αυτά τα σχόλια αγνοούνται από τον υπολογιστή. Η δεύτερη γραµµή λέει στον υπολογιστή να συµπεριλάβει (include) τις πληροφορίες που υπάρχουν στο αρχείο stdio.h, το οποίο αποτελεί µέρος του πακέτου της γλώσσας C.

Τα προγράµµατα της C αποτελούνται από µία ή περισσότερες συναρτήσεις, που είναι και οι βασικές ενότητες ενός προγράµµατος C. Αυτό το πρόγραµµα αποτελείται από µία µόνο συνάρτηση που καλείται main(). Οι παρενθέσεις υποδηλώνουν ότι το main() είναι ένα όνοµα µιας συνάρτησης.

Η αγκύλη { δηλώνει την αρχή των προτάσεων που αποτελούν τη συνάρτηση και ο ορισµός της συνάρτησης τελειώνει µε την αντίστοιχη αγκύλη }. Οι αγκύλες { και } είναι αντίστοιχες µε τα begin και end της Pascal. Η πρόταση δήλωσης µάς λέει ότι θα χρησιµοποιήσουµε µια µεταβλητή µε το όνοµα num και ότι η num είναι ακέραια µεταβλητή (integer). Η πρόταση καταχώρησης δίνει την τιµή 10 στη µεταβλητή num.

Η επόµενη γραµµή είναι για την εκτύπωση της φράσης που βρίσκεται µεταξύ των εισαγωγικών (" "). Το \n λέει στον υπολογιστή να ξεκινήσει µια νέα γραµµή (σαν να πατούσαµε το πλήκτρο <enter>). Η επόµενη γραµµή είναι για την εκτύπωση της τιµής της num (που είναι το 10) ανάµεσα στη φράση που βρίσκεται µεταξύ των " ". Η εντολή %d λέει στον υπολογιστή πού και µε ποια µορφή να εκτυπώσει την τιµή της num. Το πρόγραµµα τελειώνει µε την αγκύλη }.

Λεπτοµερής Ανάλυση του Προγράµµατος

Τα Αρχεία Επικεφαλίδας #include

Το αρχείο stdio.h αποτελεί µέρος του µεταγλωττιστή της C και περιέχει πληροφορίες σχετικές µε συναρτήσεις εισόδου και εξόδου, όπως είναι η printf(), που χρησιµοποιεί ο µεταγλωττιστής. Το όνοµά του προέρχεται από τις λέξεις standard input/output header. Οι χρήστες της C αναφέρουν σαν επικεφαλίδα µια συλλογή πληροφοριών που βρίσκεται στην αρχή ενός αρχείου. Το αποτέλεσµα της εντολής #include <stdio.h> θα ήταν το ίδιο µε το να αντιγράφαµε όλο το περιεχόµενο του αρχείου stdio.h στο δικό µας αρχείο, στη θέση όπου εµφανίζεται αυτή η γραµµή προγράµµατος.

Στην πραγµατικότητα, αυτή η γραµµή προγράµµατος δεν είναι καν µια πρόταση της γλώσσας C. Το σύµβολο # σηµαίνει ότι τη γραµµή αυτή τη διαχειρίζεται ο προεπεξεργαστής (preprocessor) της C, ο οποίος διαχειρίζεται κάποιες εργασίες πριν από τον µεταγλωττιστή.

Η Συνάρτηση main()

Η εκτέλεση ενός προγράµµατος σε C αρχίζει πάντα µε µια συνάρτηση που αποκαλείται main(). Είµαστε ελεύθεροι να επιλέξουµε τα ονόµατα των άλλων συναρτήσεων που ίσως χρησιµοποιήσουµε, αλλά θα πρέπει υποχρεωτικά να υπάρχει η συνάρτηση main() στην αρχή του προγράµµατος. Οι παρενθέσεις δηλώνουν ότι η main() είναι µια συνάρτηση. Γενικά, οι

Page 6: C Notes

6

παρενθέσεις περιέχουν πληροφορίες (ορίσµατα) που θα περάσουν µέσα στη συνάρτηση. Όταν, βέβαια, δεν υπάρχουν πληροφορίες για να περάσουν (µεταβιβασθούν), τότε οι παρενθέσεις είναι άδειες.

Τα Σχόλια

Όταν στο πρόγραµµά µας έχουµε σχόλια, τότε είναι πολύ ευκολότερο σε κάποιον άλλον, αλλά ακόµα και σε µας, να καταλάβει τι κάνει το πρόγραµµά µας. Ο,τιδήποτε υπάρχει ανάµεσα στο σύµβολο ανοίγµατος /* και στο σύµβολο κλεισίµατος */ των σχολίων αγνοείται από τον υπολογιστή και τα σχόλια στη C µπορούν να τοποθετηθούν οπουδήποτε, ακόµα και σε πολλές συνεχόµενες γραµµές.

Αγκύλες, Σώµατα, Μπλοκ

Οι αγκύλες { και } δηλώνουν την αρχή και το τέλος του σώµατος µιας συνάρτησης και µπορούν ακόµη να χρησιµοποιηθούν για να συµπεριλάβουν µαζί προτάσεις µέσα σε µια οµάδα ή σ’ ένα µπλοκ του προγράµµατος, κάτι δηλαδή παρόµοιο µε τα begin και end της Pascal.

Οι Προτάσεις Δήλωσης

Η πρόταση δήλωσης είναι µια από τις σηµαντικότερες προτάσεις της C. Η δήλωση αυτή σηµαίνει ότι κάπου µέσα στη συνάρτηση χρησιµοποιούµε τη µεταβλητή που δηλώνουµε και ότι ο τύπος της είναι αυτός που δείχνουµε, π.χ. ακέραιος. Η λέξη int είναι µια λέξη-κλειδί της C, δηλ. δεν µπορεί να χρησιµοποιηθεί αλλού µέσα στο πρόγραµµα σαν όνοµα µιας συνάρτησης ή µιας µεταβλητής. Το ερωτηµατικό στο τέλος της γραµµής δηλώνει ότι η γραµµή αυτή αποτελεί µια πρόταση ή εντολή της C.

Οι Τύποι Δεδοµένων και οι Μεταβλητές

Η C έχει διάφορα είδη (τύπους) δεδοµένων : ακέραιους, χαρακτήρες, κινητής υποδιαστολής, αριθµούς κ.ά. Για το όνοµα µιας µεταβλητής µπορούµε να χρησιµοποιήσουµε µικρά γράµµατα, κεφαλαία γράµµατα, ψηφία αριθµών και τον χαρακτήρα της υπογράµµισης (_). Ο πρώτος χαρακτήρας, όµως, πρέπει να είναι πάντα γράµµα.

Πρέπει να είναι όλες οι µεταβλητές του προγράµµατος συγκεντρωµένες µαζί για να είναι ευκολότερο για τον αναγνώστη να καταλάβει τι κάνει το πρόγραµµα και ακόµη πρέπει να υπάρχουν και σχόλια δίπλα στην κάθε µεταβλητή που να εξηγούν την αποστολή της. Ακόµη, η ονοµασία που δίνουµε στις µεταβλητές πρέπει να µας βοηθάει να καταλαβαίνουµε και τη χρήση τους.

Η Εντολή Καταχώρησης

Η πρόταση καταχώρησης είναι µια από τις βασικότερες προτάσεις της C και µε τη χρήση της δίνουµε τιµές στις µεταβλητές του προγράµµατος. Η πρόταση καταχώρησης ολοκληρώνεται µ’ ένα ερωτηµατικό ;.

Η Συνάρτηση printf()

Page 7: C Notes

7

Οι παρενθέσεις δηλώνουν κατ' αρχήν ότι πρόκειται για συνάρτηση. Τα στοιχεία που περιέχονται µεταξύ των παρενθέσεων είναι οι πληροφορίες που περνάµε (µεταβιβάζουµε) από τη συνάρτηση main() στη συνάρτηση printf(). Η πληροφορία που περνάει λέγεται όρισµα της συνάρτησης και η συγκεκριµένη συνάρτηση printf() εξετάζει τι υπάρχει µεταξύ των " " και το απεικονίζει στην οθόνη ενός τερµατικού.

Για να καλέσουµε µια συνάρτηση το µόνο που χρειάζεται να κάνουµε είναι να γράψουµε το όνοµά της και να συµπεριλάβουµε το επιθυµητό όρισµα µέσα σε παρενθέσεις. Όταν το πρόγραµµα φθάσει σ’ αυτή τη γραµµή, τότε ο έλεγχος περνάει στη συνάρτηση και µετά την ενεργοποίησή της, ο έλεγχος επιστρέφει στο αρχικό πρόγραµµα.

Ο χαρακτήρας \n, που δεν εµφανίζεται όταν τρέξει το πρόγραµµα, είναι στην πραγµατικότητα µια εντολή για το ξεκίνηµα µιας νέας γραµµής. Ο συνδυασµός \n αποτελεί έναν απλό χαρακτήρα που καλείται χαρακτήρας νέας γραµµής, δηλ. ενεργεί όπως το πλήκτρο <enter>. Ο χαρακτήρας νέας γραµµής είναι ένα παράδειγµα αυτού που ονοµάζεται ακολουθία διαφυγής και που χρησιµοποιείται για να παριστάνει δύσκολους ή αδύνατον να πληκτρολογηθούν χαρακτήρες. Άλλα παραδείγµατα τέτοιων χαρακτήρων είναι το \t για το πλήκτρο tab και το \b για ένα διάστηµα προς τα πίσω (backspace).

Τι είναι, όµως, το σύµβολο %d;

Δείχνει σε ποιο σηµείο θα εµφανιστεί η τιµής µιας µεταβλητής και ότι η µεταβλητή αυτή είναι ένας ακέραιος αριθµός.

Μια συνάρτηση στη C αποτελείται από µια επικεφαλίδα και ένα σώµα. Η επικεφαλίδα περιέχει τις προτάσεις του προεπεξεργαστή, όπως είναι η #include, και το όνοµα της συνάρτησης. Το σώµα της συνάρτησης βρίσκεται µέσα στις αγκύλες { και } και αποτελείται από µια σειρά προτάσεων, που η κάθε µια τελειώνει µ’ ένα ελληνικό ερωτηµατικό ;. Μέσα στο σώµα της συνάρτησης µπορεί να υπάρχουν προτάσεις δήλωσης, καταχώρησης και κλήσης άλλων συναρτήσεων.

Τεχνικές για πιο Ευανάγνωστα Προγράµµατα

Δύο τέτοιες τεχνικές που είδαµε στα προηγούµενα ήταν η χρήση σχολίων και η σωστή ονοµασία των µεταβλητών. Μια άλλη τεχνική είναι η χρήση κενών γραµµών για να ξεχωρίζουν τα τµήµατα του προγράµµατος. Ακόµη, στη C µπορούµε να τοποθετήσουµε πολλές προτάσεις σε µια γραµµή ή και να χωρίσουµε µια πρόταση σε πολλές γραµµές.

Το ελληνικό ερωτηµατικό λέει στον µεταγλωττιστή πού τελειώνει µια πρόταση και πού αρχίζει η επόµενη. Πιο σωστό είναι πάντως να γράφουµε µια πρόταση ανά γραµµή.

Ένα Ακόµη Παράδειγµα στη C

/* prog02.c – το πρόγραµµα αυτό µετατρέπει τα µέτρα σε εκατοστά */

#include <stdio.h>

Page 8: C Notes

8

main()

{

int cm, metres;

metres = 2;

cm = 100 * metres;

printf("Υπάρχουν %d εκατοστά σε %d µέτρα. \n", cm, metres);

}

Όπως βλέπουµε, το πρώτο σχόλιο του προγράµµατος περιέχει το όνοµά του και το τι ακριβώς κάνει. Ακόµη, το πρόγραµµα δηλώνει δύο ακέραιες µεταβλητές µαζί, τις οποίες και διαχωρίζει µε κόµµα. Χρησιµοποιεί τον τελεστή του πολλαπλασιασµού, που είναι το * και εκτυπώνει πολλές µεταβλητές µαζί στη συνάρτηση printf(). Όταν τρέξει το πρόγραµµα, θα δώσει το εξής αποτέλεσµα :

Υπάρχουν 200 εκατοστά σε 2 µέτρα.

Κλήση Συνάρτησης

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

/* prog03.c – ένα πρόγραµµα που καλεί µια συνάρτηση */

#include <stdio.h>

main()

{

printf("Θα καλέσω τη συνάρτηση της Φλώρινας.\n");

florina();

printf("Η κλήση της συνάρτησης έγινε.\n");

}

florina()

Page 9: C Notes

9

{

printf("Γεια σας από τη Φλώρινα.\n");

}

Το αποτέλεσµα του προγράµµατος θα είναι το παρακάτω :

Θα καλέσω τη συνάρτηση της Φλώρινας.

Γεια σας από τη Φλώρινα.

Η κλήση της συνάρτησης έγινε.

Η συνάρτηση florina) ορίζεται µε τον ίδιο τρόπο, όπως και η main(), µε το σώµα της να βρίσκεται ανάµεσα σε αγκύλες. Η συνάρτηση καλείται δίνοντας απλά το όνοµά της µαζί µε τις παρενθέσεις. Όταν η συνάρτηση florina() τελειώσει τη δουλειά της, το πρόγραµµα προχωράει στην επόµενη πρόταση της main().

Πρέπει να έχουµε υπόψη µας ότι οι συναρτήσεις µπορούν να γραφούν είτε πριν ή µετά από την κύρια συνάρτηση main(), αλλά εκτελούνται µόνο όταν και όπου η main() τις καλεί.

Έλεγχος της Ορθότητας των Προγραµµάτων

Συντακτικά Λάθη

Το συντακτικό λάθος στη C είναι κάτι ανάλογο µε το γραµµατικό λάθος στη γλώσσα που µιλάµε. Συντακτικά λάθη στη C µπορούν να γίνουν και µε τη χρήση επιτρεπτών συµβόλων σε λανθασµένες θέσεις. Παραδείγµατα τέτοιων λαθών µπορεί να είναι η µη σωστή χρήση των αγκυλών { και } ή ακόµη το να ανοίγουµε µια αγκύλη και να µην την κλείνουµε, το να ανοίγουµε κάπου σχόλια και να ξεχνάµε να τα κλείσουµε κοκ.

Όπως ξέρουµε, µέρος της δουλειάς του µεταγλωττιστή είναι και η ανακάλυψη των συντακτικών λαθών του προγράµµατος. Υπάρχουν, όµως, και περιπτώσεις όπου ένα λάθος παράγει, άθελά µας, και άλλα λάθη.

Εννοιολογικά Λάθη

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

Μπορούµε ακόµα να χρησιµοποιούµε επιλεκτικά και τη συνάρτηση printf() µέσα στο πρόγραµµα, ώστε να ελέγχουµε τις τιµές κάποιων µεταβλητών του προγράµµατος. Τις εντολές printf() τις αποµακρύνουµε µετά όταν το πρόγραµµά µας λειτουργήσει κανονικά.

Page 10: C Notes

10

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

Υπάρχουν και ειδικά προγράµµατα που λέγονται αποσφαλµατωτές (debuggers) και που µας επιτρέπουν να βλέπουµε τις τιµές των µεταβλητών του προγράµµατος και ποια γραµµή του προγράµµατος εκτελείται.

Οι Λέξεις-Κλειδιά της ANSI C

Οι λέξεις-κλειδιά ή δεσµευµένες λέξεις (reserved words) αποτελούν το λεξιλόγιο της C και γι’ αυτόν τον λόγο δεν µπορούµε να τις χρησιµοποιούµε για να δηλώσουµε ονόµατα µεταβλητών ή συναρτήσεων στα προγράµµατά µας.

Οι λέξεις αυτές είναι οι εξής :

auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while

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

Ένα Απλό Πρόγραµµα

Ακολουθεί ένα απλό πρόγραµµα σε C.

/* prog04.c – η αξία ενός ποσού σε ευρώ */

#include <stdio.h>

main()

{

float draxmes, euro; /* 2 µεταβλητές κινητής υποδιαστολής */

char beep; /* µια µεταβλητή τύπου χαρακτήρα */

Page 11: C Notes

11

beep = ‘\007’ /* καταχώρηση ενός ειδικού χαρακτήρα */

printf("Θέλετε να µάθετε την αξία των χρηµάτων σας σε δραχµές;\n");

printf("Παρακαλώ δώστε την αξία σε ευρώ \n");

printf("και θα δούµε.\n");

scanf("%f", &euro); /* παίρνουµε δεδοµένα από τον χρήστη */

draxmes = 340.75 * euro; /* ο συντελεστής 340.75 µετατρέπει τις δραχµές σε ευρώ */

printf("%c Η αξία σε δραχµές είναι %.2f%c. \n", beep, draxmes, beep);

}

Αφού τρέξουµε αυτό το πρόγραµµα, θα πάρουµε το εξής αποτέλεσµα :

Θέλετε να µάθετε την αξία των χρηµάτων σας σε δραχµές;

Παρακαλώ δώστε την αξία σε ευρώ και θα δούµε.

100.00

Η αξία σε δραχµές είναι 34075.00

Τι Καινούργια Στοιχεία Υπάρχουν

Πρώτα απ' όλα χρησιµοποιούµε δύο νέα είδη µεταβλητών, µια µεταβλητή κινητής υποδιαστολής (float) και µια µεταβλητή χαρακτήρων (char) και συνεπώς µπορούµε τώρα να διαχειριστούµε περισσότερα δεδοµένα. Για την εµφάνιση στην οθόνη αυτών των νέων ειδών µεταβλητών χρησιµοποιούµε τους κώδικες %f και %c της printf() αντίστοιχα. Η χρήση του .2 σηµαίνει δύο δεκαδικά ψηφία στην εµφάνιση του δεκαδικού αριθµού, άσχετα αν µε τις δραχµές δεν είχαµε δεκαδικά ψηφία.

Για την εισαγωγή δεδοµένων από το πληκτρολόγιο χρησιµοποιούµε τη συνάρτηση scanf(). Ο κώδικας %f καθοδηγεί το πρόγραµµα να διαβάσει έναν αριθµό κινητής υποδιαστολής και το &euro λέει στη συνάρτηση scanf() να καταχωρήσει την τιµή στη µεταβλητή euro. Το σύµβολο & θα αναλυθεί αργότερα.

Οι δύο συναρτήσεις printf() και scanf() επιτυγχάνουν τη διαλογική επικοινωνία µας µε τον υπολογιστή, αφού η µεν scanf() διαβάζει δεδοµένα από το πληκτρολόγιο και τα µεταφέρει για επεξεργασία στο πρόγραµµα και η printf() διαβάζει δεδοµένα από το πρόγραµµα και τα εµφανίζει στην οθόνη. Υπάρχει ακόµα και ο χαρακτήρας beep που παριστάνει το ηχητικό σήµα.

Page 12: C Notes

12

Οι Τύποι Δεδοµένων

Ο τύπος µιας µεταβλητής µπορεί να καθοριστεί σε µια πρόταση δήλωσης. Η C χρησιµοποιεί τις εξής επτά λέξεις-κλειδιά για τους τύπους δεδοµένων της :

int, long, short, unsigned, char, float και double.

Η int δηλώνει τον βασικό τύπο ακεραίων και οι long, short και unsigned χρησιµοποιούνται σε παραλλαγές του βασικού τύπου. Η char χρησιµοποιείται γενικά για τους χαρακτήρες και οι float, double και ο συνδυασµός long double χρησιµοποιούνται για την παράσταση αριθµών κινητής υποδιαστολής.

Οι πέντε πρώτες λέξεις-κλειδιά δηµιουργούν ακέραιους τύπους, ενώ οι δύο τελευταίες τύπους κινητής υποδιαστολής. Οι ακέραιοι αποθηκεύονται σε µορφή ψηφιακών αριθµών, ενώ για τους αριθµούς κινητής υποδιαστολής υπάρχουν πολλοί τρόποι για να τους παραστήσουµε µέσα στον υπολογιστή.

Ο πιο γνωστός είναι µε τη χρήση του συµβόλου Ε, όπου και έχουµε το κλασµατικό και το εκθετικό τµήµα του αριθµού, τα οποία και αποθηκεύονται ξεχωριστά. Έτσι, ο 7 γράφεται σαν 0.7Ε1, που σηµαίνει 0.7 Χ 101 = 7.

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

Οι Ακέραιοι Τύποι Δεδοµένων

Ο τύπος int που ήδη ξέρουµε είναι ένας ακέραιος µε πρόσηµο, δηλ. πρέπει να είναι ένας ολόκληρος ακέραιος και µπορεί να είναι θετικός, αρνητικός ή και µηδέν. Συνήθως χρησιµοποιούνται δύο ψηφιολέξεις (bytes) για να αποθηκευθεί ένας ακέραιος της µορφής αυτής και έτσι το εύρος τιµών του είναι από -32768 έως και +32767. Η λέξη-κλειδί int χρησιµοποιείται για τη δήλωση µεταβλητών αυτού του τύπου. Για να δηλώσουµε περισσότερες µεταβλητές αυτού του τύπου, µπορούµε είτε να τις δηλώσουµε χωριστά, ή να τις δηλώσουµε όλες µαζί, αλλά χωρισµένες µε κόµµα.

Οι δηλώσεις δηµιουργούν µεταβλητές, αλλά δεν καταχωρούν τιµές σ' αυτές. Η καταχώρηση των τιµών γίνεται µέσα στο πρόγραµµα µε τις εντολές καταχώρησης ή και µέσω της συνάρτησης scanf(). Απόδοση αρχικής τιµής σε µια µεταβλητή σηµαίνει να ορίσουµε την τιµή εκκίνησης της µεταβλητής. Στη C αυτό µπορεί να γίνει σε µια πρόταση δήλωσης, όπου µετά το όνοµα της µεταβλητής υπάρχει ο τελεστής καταχώρησης (=) και µετά η αρχική τιµή της µεταβλητής, ως εξής :

int a=10;

int b, cs=20;

Στη C ειδικά προθέµατα δηλώνουν ποια βάση αρίθµησης χρησιµοποιούµε. Το πρόθεµα 0 (µηδέν) σηµαίνει ότι γράφουµε στο οκταδικό και το πρόθεµα 0x ή 0X σηµαίνει ότι ο αριθµός

Page 13: C Notes

13

είναι δεκαεξαδικός. Έτσι, ο δεκαδικός αριθµός 16 γράφεται στο οκταδικό σύστηµα σαν 020 και στο δεκαεξαδικό σύστηµα σαν 0x10 ή 0X10. Σ' όλες τις περιπτώσεις χρησιµοποιείται ο δυαδικός κώδικας από τον υπολογιστή για την αποθήκευση του αριθµού.

Το σύµβολο %d που είδαµε στα προηγούµενα παραδείγµατα αντιστοιχεί σε µια ακέραια τιµή αριθµού, που µπορεί να είναι µια µεταβλητή τύπου int, µια ακέραια σταθερά τύπου int ή και οποιαδήποτε άλλη έκφραση, που έχει όµως µια ακέραια τιµή int. Ακολουθεί ένα απλό πρόγραµµα που επεξηγεί τα παραπάνω :

/* prog05.c – µερικές ιδιότητες της printf() */

#include <stdio.h>

main()

{

int ten = 10;

printf("%d µείον %d ίσον %d\n", ten, 2, ten-2);

}

Το αποτέλεσµα του προγράµµατος θα είναι :

10 µείον 2 ίσον 8

Μπορούµε, όµως, να εκτυπώσουµε τους ακέραιους αριθµούς στο οκταδικό σύστηµα µε το σύµβολο %o και στο δεκαεξαδικό µε το σύµβολο %x. Ακολουθεί ένα απλό παράδειγµα :

/* prog06.c – εκτύπωση του αριθµού 100 σε τρία αριθµητικά συστήµατα */

#include <stdio.h>

main()

{

int x = 100;

printf("δεκαδ = %d; οκταδ = %o; δεκαεξ = %x\n", x, x, x);

}

Το αποτέλεσµα του προγράµµατος θα είναι :

δεκαδ = 100; οκταδ = 144; δεκαεξ = 64

Παρατηρούµε ότι τα προθέµατα 0 και 0x δεν παρουσιάζονται στην έξοδο.

Page 14: C Notes

14

Άλλοι Τύποι Ακεραίων

Η C προσφέρει τρεις τύπους λέξεις-κλειδιά για την τροποποίηση του βασικού τύπου ακεραίου : unsigned, long και short. Ο τύπος short int ή short είναι ένας τύπος µε πρόσηµο και χρησιµοποιεί λιγότερο χώρο αποθήκευσης, απ' ό,τι ο int και έτσι µπορεί να χρησιµοποιηθεί για µικρούς αριθµούς. Ο τύπος long int ή long είναι ένας τύπος µε πρόσηµο που χρησιµοποιεί περισσότερο χώρο αποθήκευσης απ' ό,τι ο int και συνεπώς επιτρέπει τη χρήση µεγάλων ακεραίων. Ο τύπος unsigned επιτρέπει τη χρήση της κλίµακας από το 0 µέχρι το 65535 αντί για την κλίµακα από το -32768 µέχρι το +32767.

Όσον αφορά τώρα τη χρήση αυτών των τύπων ακεραίων, πρέπει να έχουµε υπόψη µας ότι ο τύπος unsigned µπορεί να χρησιµοποιηθεί για µέτρηµα, αφού δεν υπάρχουν σ' αυτόν τον τύπο αρνητικοί αριθµοί. Ο τύπος long χρησιµοποιείται αν έχουµε πολύ µεγάλους αριθµούς, που δεν µπορεί να τους διαχειριστεί ο τύπος int. Δεν πρέπει, όµως, να χρησιµοποιείται ο τύπος long αν αυτό δεν είναι απαραίτητο, γιατί επιβραδύνει τους υπολογισµούς και καταλαµβάνει πολύ µνήµη. Μπορούµε να χρησιµοποιήσουµε τον τύπο short όταν χρησιµοποιούµε µεγάλους πίνακες ακεραίων στο πρόγραµµά µας.

Για να αποθηκευθεί µια σταθερά σαν τύπου long, θα πρέπει να προσθέσουµε την κατάληξη l ή L και οι καταλήξεις αυτές µπορούν να χρησιµοποιηθούν ακόµα και µε τους οκταδικούς ή τους δεκαεξαδικούς ακέραιους. Για την εκτύπωση ενός αριθµού τύπου unsigned χρησιµοποιούµε το σύµβολο %u, ενός αριθµού τύπου long το %ld και ακόµη µπορούµε να συνδυάσουµε το l µε τα o και x. Ακόµη, το %h χρησιµοποιείται για τον τύπο short.

Οι Χαρακτήρες

Όπως ήδη ξέρουµε, ο τύπος char χρησιµοποιείται για την αποθήκευση χαρακτήρων, αν και στην πραγµατικότητα αποθηκεύει ακέραιους. Για τον χειρισµό των χαρακτήρων ο υπολογιστής χρησιµοποιεί έναν αριθµητικό κώδικα, όπου συγκεκριµένοι ακέραιοι παριστάνουν συγκεκριµένους χαρακτήρες. Ο πιο συχνά χρησιµοποιούµενος κώδικας είναι ο ASCII και για παράδειγµα ο ακέραιος 65 παριστάνει το γράµµα Α. Οι µεταβλητές τύπου char δηλώνονται όπως όλες οι µεταβλητές :

char name, city;

Υπάρχουν εκδόσεις της C που έχουν τον τύπο char µε πρόσηµο και µε περιοχή τιµών από -128 έως +127 και άλλες εκδόσεις της C έχουν τον τύπο char χωρίς πρόσηµο και µε περιοχή τιµών από 0 έως 255. Μπορούµε να δώσουµε αρχικές τιµές στις µεταβλητές τύπου char µε δύο τρόπους :

char grade = 65; /* εδώ χρησιµοποιούµε τον ASCII κώδικα του Α */

ή

char grade = 'A'; /* εδώ τα πράγµατα είναι πιο απλά για µας */

Τα απλά εισαγωγικά ' ' δηλώνουν στη C µια σταθερά χαρακτήρα, ενώ τα διπλά εισαγωγικά " " θεωρούνται µια συµβολοσειρά, που θα την δούµε αναλυτικά παρακάτω.

Page 15: C Notes

15

Οι µη Εκτυπούµενοι Χαρακτήρες

Υπάρχουν µερικοί ASCII χαρακτήρες που είναι µη εκτυπούµενοι, όπως το διάστηµα προς τα πίσω (backspace), το enter και το ηχητικό σήµα (beep). Η C µάς παρέχει τρεις τρόπους για την αναπαράσταση τέτοιων χαρακτήρων. Ο πρώτος είναι χρησιµοποιώντας τον κώδικα ASCII του χαρακτήρα, ως εξής :

char beep = 7;

Ο δεύτερος είναι χρησιµοποιώντας µια ειδική µορφή του κώδικα ASCII, όπου τοποθετούµε ανάµεσα σε απλά εισαγωγικά τον οκταδικό κώδικα ASCII µαζί µε µια πλάγια κάθετο \ πριν απ’ αυτόν, ως εξής :

beep = '\007';

Οι αριθµοί ερµηνεύονται σαν οκταδικοί, ακόµη και αν δεν υπάρχει µπροστά ένα 0. Η ANSI C και πολλές νέες εκδόσεις δέχονται µια δεκαεξαδική µορφή για τις σταθερές χαρακτήρα, όπου η πλάγια κάθετος \ ακολουθείται από ένα x ή X και από 1 µέχρι 3 δεκαεξαδικά ψηφία. Έτσι, ο χαρακτήρας Control-P θα µπορεί να παρασταθεί σαν '\x10' ή '\X010'. Ο τρίτος τρόπος είναι να χρησιµοποιήσουµε µια ειδική ακολουθία συµβόλων, που ονοµάζεται ακολουθία διαφυγής (escape sequence) :

\a ειδοποίηση - beep (ANSI C)

\b ένα διάστηµα προς τα πίσω (προσοχή δεν σβήνει τους χαρακτήρες)

\f τροφοδότηση σελίδας

\n νέα γραµµή (enter)

\r επιστροφή στην αρχή της τρέχουσας γραµµής

\t οριζόντια στηλοθέτηση (tab)

\v κάθετη στηλοθέτηση (ANSI C)

\\ πλάγια κάθετος (\)

\' απλά εισαγωγικά (')

\" διπλά εισαγωγικά (") (ANSI C)

Όλοι αυτοί οι χαρακτήρες κλείνονται σε απλά εισαγωγικά όταν καταχωρούνται σε µια µεταβλητή χαρακτήρα :

next = '\n';

Η εκτύπωση της µεταβλητής next προχωράει την εκτύπωση κατά µια γραµµή στην οθόνη ή στον εκτυπωτή. Η συνάρτηση printf() χρησιµοποιεί το %c για να δείξει ότι πρέπει να

Page 16: C Notes

16

τυπωθεί ένας χαρακτήρας. Θα πρέπει να θυµόµαστε ότι οι χαρακτήρες είναι αποθηκευµένοι σαν ακέραιοι, οπότε αν τυπώσουµε την τιµή µιας µεταβλητής τύπου char, θα πάρουµε έναν ακέραιο.

Ο προσδιορισµός %c λέει στην printf() να µετατρέψει τον ακέραιο στον αντίστοιχο χαρακτήρα. Αυτά φαίνονται καθαρά στο παρακάτω πρόγραµµα :

/* prog07.c - εµφανίζει τον κωδικό αριθµό κάποιου χαρακτήρα */

#include <stdio.h>

main()

{

char ch;

printf("Δώστε έναν χαρακτήρα : \n");

scanf("%c", &ch);

printf("Ο κώδικας του %c είναι το %d. \n", ch, ch);

}

Το αποτέλεσµα θα είναι :

Δώστε έναν χαρακτήρα :

C

Ο κώδικας του C είναι το 67.

Η printf() τυπώνει την τιµή της ch δύο φορές, την πρώτη σαν χαρακτήρα (προτρεπόµενη από τον κώδικα %c) και τη δεύτερη σαν ακέραιο (προτρεπόµενη από τον κώδικα %d). Οι προσδιοριστές της printf() αποφασίζουν για το πώς θα εµφανιστούν τα δεδοµένα και όχι για το πώς θα αποθηκευθούν.

Οι Τύποι float και double

Σε προγράµµατα µαθηµατικής φύσης συχνά χρησιµοποιούµε αριθµούς κινητής υποδιαστολής που στη C λέγονται τύπου float και είναι αντίστοιχοι µε τους τύπους real της Pascal. Έτσι, µπορούµε να παραστήσουµε ένα πολύ µεγαλύτερο εύρος αριθµών, µεγάλων και µικρών. Υπάρχουν ακόµα και οι τύποι double (για διπλή ακρίβεια) και long double. Οι µεταβλητές αυτές δηλώνονται όπως και οι άλλες :

float a=10.25e-3;

double b;

Page 17: C Notes

17

long double c;

Ένα πρόθεµα f ή F στην ANSI C σ’ έναν αριθµό κινητής υποδιαστολής τον κάνει τύπου float, π.χ. 2.3f και 9.11E9F, ενώ µια κατάληξη l ή L τον κάνει τύπου long double, π.χ. 54.31l και 4.32e4L.

Η συνάρτηση printf() χρησιµοποιεί τον προσδιοριστή µορφής %f για να τυπώσει τύπου float και double αριθµούς µε δεκαδικό συµβολισµό και τη µορφή %e για να τους τυπώσει σε εκθετικό συµβολισµό. Για τους τύπους long double έχουµε αντίστοιχα τους προσδιοριστές %Lf και %Le.

Ο Τελεστής sizeof

Η C έχει έναν εσωτερικό τελεστή, τον sizeof, που δίνει το µέγεθος κάποιων πραγµάτων σε bytes. Αυτό θα το δούµε καλύτερα µ’ ένα παράδειγµα :

/* prog08.c – τυπώνει τα µεγέθη των τύπων δεδοµένων */

#include <stdio.h>

main()

{

printf("Ο τύπος int έχει µέγεθος %d bytes. \n", sizeof(int));

printf("Ο τύπος char έχει µέγεθος %d bytes. \n", sizeof(char));

printf("Ο τύπος long έχει µέγεθος %d bytes. \n", sizeof(long));

printf("Ο τύπος double έχει µέγεθος %d bytes. \n", sizeof(double));

}

Το αποτέλεσµα θα είναι :

Ο τύπος int έχει µέγεθος 2 bytes.

Ο τύπος char έχει µέγεθος 1 bytes.

Ο τύπος long έχει µέγεθος 4 bytes.

Ο τύπος double έχει µέγεθος 8 bytes.

Page 18: C Notes

18

Εισαγωγή στις Συµβολοσειρές

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

/* prog09.c – ζητούνται στοιχεία χρήστη */

#include <stdio.h>

main()

{

float weight;

int size, letters;

char name[40];

printf("Γεια σου! Πώς λέγεσαι; \n");

scanf("%s", name);

printf("%s, Ποιο είναι το βάρος σου σε κιλά; \n", name);

scanf("%f", &weight);

size = sizeof name;

letters = strlen(name);

printf("Γεια σου, %s, το βάρος σου είναι %2.2f κιλά. \n", name, volume);

printf("Επίσης, το σου όνοµά σου έχει %d γράµµατα, \n", letters);

printf("και χρειάζονται %d bytes για την αποθήκευσή του.\n", size);

}

Τρέχοντας αυτό το πρόγραµµα, παίρνουµε τα εξής αποτελέσµατα :

Γεια σου! Πώς λέγεσαι;

Κώστας

Κώστας, ποιο είναι το βάρος σου σε κιλά;

100

Γεια σου, Κώστας, το βάρος σου είναι 100,00 κιλά.

Page 19: C Notes

19

Επίσης, το όνοµά σου έχει 6 γράµµατα,

και χρειάζονται 40 bytes για για την αποθήκευσή του.

Τα κυριότερα νέα χαρακτηριστικά αυτού του προγράµµατος είναι τα εξής :

1. Έχουµε χρησιµοποιήσει έναν πίνακα που περιέχει µια συµβολοσειρά και συγκεκριµένα κάποιο όνοµα.

2. Χρησιµοποιήσαµε τον προσδιοριστή µετατροπής %s για να χειριστούµε την είσοδο και την έξοδο µιας συµβολοσειράς. Η µεταβλητή name, σε αντίθεση µε την weight, δεν χρησιµοποιεί το πρόθεµα & όταν χρησιµοποιείται µε την scanf().

4. Χρησιµοποιήσαµε τη συνάρτηση της C strlen() για να βρούµε το µήκος µιας συµβολοσειράς.

Τι είναι, όµως, µια συµβολοσειρά;

Μια συµβολοσειρά είναι µια σειρά από έναν ή περισσότερους χαρακτήρες, όπως για παράδειγµα : "Το ΙΕΚ Φλώρινας λειτουργεί από το 1993". Τα διπλά εισαγωγικά δεν είναι µέρος της συµβολοσειράς και υπάρχουν για να οριοθετούν τη συµβολοσειρά, όπως ακριβώς τα απλά εισαγωγικά χρησιµοποιούνται για να οριοθετούν έναν χαρακτήρα. Η C αποθηκεύει τις συµβολοσειρές σ’ έναν πίνακα τύπου char µε κάθε χαρακτήρα της συµβολοσειράς να είναι αποθηκευµένος σε κάθε κελί. Η C χρησιµοποιεί τον χαρακτήρα \0 στην τελευταία θέση του πίνακα για να σηµειώνει το τέλος µιας συµβολοσειράς.

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

char name[40];

απ' όπου καταλαβαίνουµε ότι η µεταβλητή name είναι πίνακας µε µέγεθος 40 και το κάθε στοιχείο της είναι ένας χαρακτήρας.

Η Χρήση των Συµβολοσειρών

Δείτε το παρακάτω πρόγραµµα :

/* prog10.c – πρόγραµµα µε συµβολοσειρές */

#include <stdio.h>

#define PRAISE "Έχεις ένα σπουδαίο όνοµα!"

main()

{

Page 20: C Notes

20

char name[50];

printf("Πώς σε λένε;\n");

scanf("%s", name);

printf("Γεια σου, %s. %s\n", name, PRAISE);

}

Η έξοδος του προγράµµατος θα είναι :

Πώς σε λένε;

Μάκη Παπαδόπουλο

Γεια σου, Μάκη. Έχεις ένα σπουδαίο όνοµα!

Παρατηρούµε ότι η scanf() διαβάζει µόνο το µικρό όνοµα του Παπαδόπουλου. Η συνάρτηση σταµατάει λοιπόν µόλις βρει το πρώτο κενό διάστηµα. Γενικά, η scanf() χρησιµοποιούµενη µε το %s διαβάζει απλές λέξεις και όχι φράσεις σαν συµβολοσειρές. Η C έχει άλλες συναρτήσεις για διάβασµα εισόδου, όπως την gets(), για να χειρίζεται γενικευµένες συµβολοσειρές, που θα τις δούµε αργότερα.

Η συµβολοσειρά "x" δεν είναι το ίδιο µε τον χαρακτήρα 'x' και αυτό γιατί η 'x' είναι ένας βασικός τύπος (char), ενώ η "x" είναι ένας παραγόµενος τύπος, ένας πίνακας από στοιχεία τύπου char. Ακόµη, η "x" αποτελείται από δύο χαρακτήρες, τον 'x' και τον µηδενικό χαρακτήρα. Η συνάρτηση srtlen() µας δίνει το µήκος µιας συµβολοσειράς σε χαρακτήρες, ενώ ο τελεστής sizeof επιστρέφει τον αριθµό των συνολικών κελιών µνήµης που είχαν δηλωθεί αρχικά γι αυτή τη µεταβλητή.

Οι Σταθερές και ο Προεπεξεργαστής της C

Ξέρουµε από τις άλλες γλώσσες προγραµµατισµού τη σηµασία που έχει η δήλωση µιας σταθεράς σ’ ένα πρόγραµµα. Στη C υπάρχουν δύο τρόποι για να δηλωθεί µια σταθερή τιµή. Ένας τρόπος είναι να ορίσουµε µια µεταβλητή και να την εξισώσουµε µε την επιθυµητή σταθερά, ως εξής :

float taxrate;

taxrate = 0.2;

Μ’ αυτόν τον τρόπο η αντικατάσταση της τιµής της µεταβλητής taxrate θα γίνεται όταν το πρόγραµµα τρέχει. Ο προεπεξεργαστής της C διαθέτει έναν καλύτερο τρόπο, όπου απλά προσθέτουµε µια γραµµή στην αρχή του αρχείου που περιέχει το πρόγραµµά µας, ως εξής :

#define TAXRATE 0.2

Page 21: C Notes

21

Όταν το πρόγραµµα µεταγλωττιστεί, η τιµή 0.2 θα αντικατασταθεί παντού όπου έχει χρησιµοποιηθεί η TAXRATE. Έτσι, όταν τρέξουµε το πρόγραµµα, όλες οι αντικαταστάσεις θα έχουν ήδη γίνει. Πρέπει να προσέξουµε τη σύνταξη της #define, στην οποία δεν χρησιµοποιείται το ελληνικό ερωτηµατικό (;), αφού δεν είναι µια πρόταση της C και ακόµη αποτελεί παράδοση στη C να γράφονται όλες οι σταθερές µε κεφαλαία γράµµατα για να τις αναγνωρίζουµε αµέσως και να τις ξεχωρίζουµε έτσι από τις άλλες µεταβλητές.

Ακολουθεί ένα παράδειγµα µε χρήση της #define.

/* prog11.c – χρήση της σταθεράς π = 3.14159 */

#include <stdio.h>

#define PI 3.14159

main()

{

float area, circum, radius;

printf("Δώστε την ακτίνα του κύκλου : \n");

scanf("%f", &radius);

area = PI * radius * radius;

circum = 2.0 * PI * radius;

printf("Τα στοιχεία του κύκλου είναι :\n");

printf("Περίµετρος = %1.2f, εµβαδόν = %1.2f\n", circum, area);

}

Το αποτέλεσµα θα είναι :

Δώστε την ακτίνα του κύκλου :

6.0

Τα στοιχεία του κύκλου είναι :

Περίµετρος = 37.70, εµβαδόν = 113.10

Η εντολή #define µπορεί να χρησιµοποιηθεί τόσο για χαρακτήρες όσο και για σταθερές συµβολοσειρές. Χρησιµοποιούµε απλά εισαγωγικά για την πρώτη περίπτωση και διπλά για τη δεύτερη. Ακολουθούν παραδείγµατα :

#define BEEP '\007'

Page 22: C Notes

22

#define TEE 'T'

#define ESC '\033'

#define OOPS "Τώρα το πέτυχες"

Αν κατά λάθος γράψουµε #define TOES = 20, τότε η TOES θα αντικατασταθεί µε το = 20 και όχι µε το 20.

Οι Δυνατότητες της #define

Μπορούµε να µαζέψουµε όλες τις προτάσεις #define σ’ ένα αρχείο και να το ονοµάσουµε, π.χ. const.h. Μετά, στην αρχή κάθε προγράµµατος µπορούµε να εισάγουµε την πρόταση #include "const.h" και όταν τρέξει το πρόγραµµα, ο προεπεξεργαστής θα διαβάσει το αρχείο const.h και θα χρησιµοποιήσει όλες τις προτάσεις #define που θα βρει εκεί.

Χρησιµοποιούµε την ονοµασία "const.h" για να ψάξει πρώτα στον τρέχοντα κατάλογο και µετά στους καταλόγους του συστήµατος, ενώ η ονοµασία <const.h> σηµαίνει να ψάξει στον κανονικό κατάλογο πρώτα. Μια άλλη σπουδαία δυνατότητα της #define είναι ότι µπορούµε να κάνουµε τέτοιες αντικαταστάσεις, ώστε τα προγράµµατα της C να θυµίζουν Pascal, ως εξής :

#define program main() #define begin { #define end } #define times * Έτσι, όπου ο προεπεξεργαστής βρει τους αριστερούς όρους, τους αντικαθιστά µ’ ό,τι είναι δεξιά, δηλ. µε εντολές της C, αλλά εµείς γράφουµε σαν να είµαστε στην Pascal.

Τυπώνοντας Μεγάλες Συµβολοσειρές Αν θέλουµε να κόψουµε µια συµβολοσειρά που είναι πολύ µεγάλη για να χωρέσει σε µια γραµµή, υπάρχουν τρεις τρόποι, όπως φαίνεται στο παρακάτω παράδειγµα : /* prog12.c – τυπώνοντας µεγάλες συµβολοσειρές */ #include <stdio.h> main() { printf("Να ένας τρόπος να τυπώσετε µια "); printf("µεγάλη συµβολοσειρά.\n"); printf("Να ένας άλλος τρόπος να τυπώσετε µια \ µεγάλη συµβολοσειρά.\n"); printf("Να ο πιο καινούργιος τρόπος να τυπώσετε µια " " µεγάλη συµβολοσειρά.\n"); } Το αποτέλεσµα θα είναι : Να ένας τρόπος να τυπώσετε µια µεγάλη συµβολοσειρά. Να ένας άλλος τρόπος να τυπώσετε µια µεγάλη συµβολοσειρά. Να ο πιο καινούργιος τρόπος να τυπώσετε µια µεγάλη συµβολοσειρά.

Page 23: C Notes

23

Η Γλώσσα Προγραµµατισµού

C (Μέρος 2 - Οι Bασικές Εντολές

της C)

Οι Βασικοί Τελεστές της C

Η C, όπως όλες οι γλώσσες προγραµµστισµού, χρησιµοποιεί τελεστές για να εκτελέσει τις αριθµητικές λειτουργίες. Το = είναι ένας τελεστής καταχώρησης τιµών. Πρέπει να έχουµε υπόψη µας ότι δεν µπορούµε να καταχωρήσουµε τιµή σε µια σταθερά και το µέρος στα αριστερά του συµβόλου = πρέπει να είναι το όνοµα της µεταβλητής και αναφέρεται σε µία θέση αποθήκευσης.

Εφαρµογές της εντολής αυτής είναι οι εξής :

year = 2006;

i = i + 1;

timi01 = timi02 = timi03 = 68;

Στην τελευταία εντολή οι καταχωρήσεις γίνονται από τα δεξιά προς τα αριστερά.

Υπάρχουν ακόµα και οι τελεστές πρόσθεσης +, αφαίρεσης -, πολλαπλασιασµού * και διαίρεσης /. Στη διαίρεση πρέπει να έχουµε υπόψη µας ότι η διαίρεση µε αριθµούς κινητής υποδιαστολής δίνει αποτέλεσµα του ίδιου τύπου, ενώ η διαίρεση µε ακεραίους δίνει µια ακέραια απάντηση.

Έτσι, αν η διαίρεση ακεραίων δεν είναι τέλεια, η C απορρίπτει το δεκαδικό µέρος του πηλίκου χωρίς να το στρογγυλοποιεί. Αυτή η διαδικασία λέγεται αποκοπή. Όταν ανακατεύουµε ακεραίους µε αριθµούς κινητής υποδιαστολής, το αποτέλεσµα είναι αριθµός κινητής υποδιαστολής.

Ακολουθεί ένα ερµηνευτικό παράδειγµα :

/* prog13.c – οι διαιρέσεις στη C */

#include <stdio.h>

Page 24: C Notes

24

main()

{

printf("ακέραια διαίρεση : 5/3 είναι %d \n", 5/3);

printf("ακέραια διαίρεση : 8/4 είναι %d \n", 8/4);

printf("ακέραια διαίρεση : 7/5 είναι %d \n", 7/5);

printf("διαίρεση κινητής υποδιαστολής : 7./4. είναι %1.2f \n", 7./4.);

printf("µικτή διαίρεση : 7./4 είναι %1.2f \n", 7./4);

}

Το αποτέλεσµα θα είναι :

ακέραια διαίρεση : 5/3 είναι 1

ακέραια διαίρεση : 8/4 είναι 2

ακέραια διαίρεση : 7/5 είναι 1

διαίρεση κινητής υποδιαστολής : 7./4. είναι 1.75

µικτή διαίρεση : 7./4 είναι 1.75

Βλέπουµε ότι όσον αφορά τις τρεις πρώτες διαιρέσεις που γίνονται µεταξύ ακεραίων αριθµών, σαν αποτέλεσµα παίρνουµε το πηλίκο της ακέραιας διαίρεσης, κάτι που µας είναι χρήσιµο σε πολλές εφαρµογές, ο αντίστοιχος τελεστής δηλ. του div της Pascal. Βλέπουµε ακόµη ότι από το ανακάτεµα ακεραίων και αριθµών κινητής υποδιαστολής, παίρνουµε αριθµό κινητής υποδιαστολής.

Οι Προτεραιότητες των Τελεστών

Η C τοποθετεί κάθε τελεστή σε κάποιο επίπεδο προτεραιότητας. Ο πολλαπλασιασµός και η διαίρεση ανήκουν σε υψηλότερο επίπεδο προτεραιότητας από την πρόσθεση και την αφαίρεση, γι' αυτό και εκτελούνται πρώτα. Αν, όµως, τελεστές του ίδιου επιπέδου προτεραιότητας επιδρούν στον ίδιο τελεστέο, τότε εκτελούνται µε τη σειρά εµφάνισής τους στην πρόταση. Για τους περισσότερους τελεστές, η σειρά εκτέλεσης είναι από αριστερά προς τα δεξιά και ο τελεστής = αποτελεί εξαίρεση.

Δηλαδή, στην πρόταση :

a = 25.0 + 60.0 * b / c;

γίνεται πρώτα ο πολλαπλασιασµός 60.0 * b, µετά η διαίρεση αυτού µε τη µεταβλητή c και τέλος προστίθεται το 25.0.

Page 25: C Notes

25

Αν, όµως, θελήσουµε να γίνει πρώτα η πρόσθεση και µετά η διαίρεση, τότε πρέπει να βάλουµε παρενθέσεις, ως εξής :

a = (25.0 + 60.0 * b) / c;

Ο Τελεστής sizeof

Ο τελεστής αυτός επιστρέφει το µέγεθος σε bytes του τελεστέου του. Ο τελεστέος µπορεί να είναι το όνοµα µιας µεταβλητής ή µπορεί να είναι ένας τύπος. Αν είναι τύπος ονόµατος, τότε πρέπει ο τελεστέος να µπει µέσα σε παρενθέσεις, αλλιώς οι παρενθέσεις είναι προαιρετικές.

Ακολουθεί ένα παράδειγµα :

/* prog14.c - ο τελεστής sizeof */

#include <stdio.h>

main()

{

int n=10;

printf("Ο n έχει %d bytes, όλοι οι ακέραιοι έχουν %d bytes. \n",

sizeof n, sizeof(int));

}

Το αποτέλεσµα θα είναι :

Ο 10 έχει 2 bytes, όλοι οι ακέραιοι έχουν 2 bytes.

Ο Τελεστής Ακεραίου Υπολοίπου %

Ο τελεστής ακεραίου υπολοίπου (%) χρησιµοποιείται στην αριθµητική των ακεραίων και επιστρέφει το υπόλοιπο της ακέραιας διαίρεσης του ακεραίου στα αριστερά µε τον ακέραιο στα δεξιά. Για παράδειγµα, η πράξη 13 % 5 δίνει σαν αποτέλεσµα την τιµή 3, αφού το 5 χωράει δύο φορές στο 13 και έχει υπόλοιπο 3.

Ακολουθεί ένα παράδειγµα :

/* prog15.c – µετατρέπει τα δευτερόλεπτα σε λεπτά και σε δευτερόλεπτα */

#include <stdio.h>

#define SEC_PER_MIN 60 /* 60 δευτερόλεπτα σ’ ένα λεπτό */

Page 26: C Notes

26

main()

{

int sec, min, left;

printf("Μετατροπή δευτερολέπτων σε λεπτά και δευτερόλεπτα \n");

printf("Δώστε τον αριθµό των δευτερολέπτων : \n");

scanf("%d", &sec); /* διάβασµα του αριθµού των δευτερολέπτων */

min = sec / SEC_PER_MIN; /* αριθµός λεπτών */

left = sec % SEC_PER_MIN; /*αριθµός δευτερολέπτων που έµειναν*/

printf(" %d δευτερόλεπτα είναι %d λεπτά και %d δευτερόλεπτα.\n",

sec, min, left);

}

Το αποτέλεσµα θα είναι :

Μετατροπή δευτερολέπτων σε λεπτά και δευτερόλεπτα

Δώστε τον αριθµό των δευτερολέπτων :

152

152 δευτερόλεπτα είναι 2 λεπτά και 32 δευτερόλεπτα.

Όπως είδαµε στο παραπάνω παράδειγµα, για να υπολογίσουµε πόσα λεπτά υπάρχουν σ’ έναν αριθµό δευτερολέπτων sec, παίρνουµε το πηλίκο της ακέραιας διαίρεσης του sec µε το SEC_PER_MIN, που είναι ουσιαστικά το 60 και για να βρούµε πόσα δευτερόλεπτα περίσσευσαν που δεν χώρεσαν σ’ ένα λεπτό, παίρνουµε το υπόλοιπο της ακέραιας διαίρεσης του sec µε το SEC_ PER_MIN.

Ο Τελεστής Αύξησης ++

Ο τελεστής αύξησης (++) αυξάνει κατά ένα την τιµή του τελεστέου. Ο τελεστής αυτός υπάρχει σε δύο µορφές : Το ++ µπορεί να βρίσκεται πριν από την επηρεαζόµενη µεταβλητή και ονοµάζεται τελεστής προγενέστερης αύξησης ή µετά απ’ αυτήν, οπότε ονοµάζεται τελεστής µεταγενέστερης αύξησης.

Τα δύο είδη αυξήσεων διαφέρουν στον χρόνο που γίνεται η αύξηση. Πρώτα, όµως, θα δούµε τις οµοιότητές τους και αργότερα τις διαφορές.

Ακολουθεί ένα παράδειγµα :

Page 27: C Notes

27

/* prog16.c – χρήση του τελεστή αύξησης ++ */

#include <stdio.h>

main()

{

int a = 0, b = 0;

while (a < 5)

{

a++;

++b;

printf("b = %d, a = %d \n", b, a);

}

}

Το αποτέλεσµα θα είναι :

b = 1, a = 1

b = 2, a = 2

b = 3, a = 3

b = 4, a = 4

b = 5, a = 5

Θα µπορούσαµε να πάρουµε το ίδιο αποτέλεσµα χρησιµοποιώντας τις εξής εντολές :

b = a + 1;

a = a + 1;

Ο λόγος που χρησιµοποιούµε αυτόν τον τελεστή αύξησης είναι ότι έχει κάποια πλεονεκτήµατα που θα φανούν αργότερα. Και για να γίνουµε πιο σαφείς, ακολουθεί ένα παράδειγµα :

euro = 2.0;

while (++euro < 100.00)

{

Page 28: C Notes

28

draxmes = 340.75 * euro;

printf("%10.2f %20.2f \n", euro, draxmes);

}

Εδώ έχουµε συνδυάσει τη διαδικασία αύξησης και σύγκρισης του βρόχου while σε µία µόνο έκφραση, δηλ. η τιµή της µεταβλητής euro πρώτα αυξάνεται κατά 1 και αµέσως µετά συγκρίνεται µε το 100.00. Έτσι, έχουµε τον έλεγχο του βρόχου και την αύξηση της µεταβλητής στο ίδιο µέρος.

Ας δούµε ακόµα ένα παράδειγµα :

/* prog17.c – προγενέστεροι και µεταγενέστεροι */

#include <stdio.h>

main()

{

int a =1, b = 1;

int aplus, plusb;

aplus = a++; /* µεταγενέστερος */

plusb = ++b; /* προγενέστερος */

printf("a aplus b plusb \n");

printf("%1d %5d %5d %5d\n", a, aplus, b, plusb);

}

Το αποτέλεσµα θα είναι :

a aplus b plusb

2 1 2 2

Τόσο η a όσο και η b αυξάνονται κατά ένα. Εποµένως, η aplus έχει την τιµή της a πριν η a αλλάξει και η plusb έχει την τιµή της b µετά την αλλαγή της b.

aplus = a++;

/* µεταγενέστερη : η a άλλαξε µετά τη χρήση της τιµής της */

plusb = ++b;

/* προγενέστερη : η b άλλαξε πριν τη χρήση της τιµής της */

Page 29: C Notes

29

Αν ένας από τους τελεστές αύξησης χρησιµοποιείται µόνος του σε κάποια πρόταση, τότε δεν έχει σηµασία ποια µορφή θα χρησιµοποιήσουµε. Η επιλογή, όµως, έχει σηµασία όταν ο τελεστής και ο τελεστέος αποτελούν µέρη κάποιας έκφρασης, όπως µόλις είδαµε.

Για να συνοψίσουµε, το n++ σηµαίνει :

"κάνε χρήση του n και µετά αύξησέ το"

και το ++n σηµαίνει :

"αύξησε το n και µετά χρησιµοποίησέ το".

Ο Τελεστής Μείωσης - -

Δεν θα ήταν δυνατόν φυσικά να µην υπήρχε και ο αντίστοιχος τελεστής µείωσης, δηλ. αντί για το ++ χρησιµοποιούµε το - -.

- -count; /* τελεστής προγενέστερης µείωσης */

count- -; /* τελεστής µεταγενέστερης µείωσης */

Ακολουθεί κι ένα παράδειγµα µε την χρήση του τελεστή µείωσης :

/* prog18.c – χρήση του τελεστή µείωσης */

#include <stdio.h>

#define MAX 100

main()

{

int count = MAX + 1;

while (- -count > 0)

{

printf("Υπάρχουν %d βιβλία στη βιβλιοθήκη \n", count);

printf("Πάρε ένα βιβλίο, \n");

printf("%d βιβλία έµειναν στη βιβλιοθήκη \n\n", count-1);

}

}

Page 30: C Notes

30

Το αποτέλεσµα θα είναι :

Υπάρχουν 100 βιβλία στη βιβλιοθήκη

Πάρε ένα βιβλίο,

99 βιβλία έµειναν στη βιβλιοθήκη

Υπάρχουν 99 βιβλία στη βιβλιοθήκη

Πάρε ένα βιβλίο,

98 βιβλία έµειναν στη βιβλιοθήκη

...

Οι τελεστές αύξησης και µείωσης έχουν υψηλή προτεραιότητα και µόνο οι παρενθέσεις έχουν υψηλότερη. Πρέπει πάντως να αποκτήσει κανείς αρκετή εµπειρία µε τους τελεστές αύξησης και µείωσης, να µάθει τις ιδιαιτερότητες της έκδοσης της C µε την οποία δουλεύει και µετά να τους χρησιµοποιεί.

Πρέπει να έχουµε υπόψη µας τα εξής :

• Δεν πρέπει να χρησιµοποιούµε τελεστές αύξησης ή µείωσης σε µεταβλητή που αποτελεί µέρος περισσότερων του ενός ορισµάτων ή συναρτήσεων.

• Δεν πρέπει να χρησιµοποιούµε τελεστές αύξησης ή µείωσης σε µεταβλητή που εµφανίζεται περισσότερες από µία φορές σε µια έκφραση.

Δηλαδή, οι παρακάτω εκφράσεις µπορεί να µπερδέψουν τη C και πάντως κανείς δεν εγγυάται για το αποτέλεσµά τους :

printf("%10d %10d\n", num, num*num++);

a = num/2 + 5*(1 + num++);

b = n++ + n++;

Εκφράσεις και Προτάσεις

Μια έκφραση αποτελείται από έναν συνδυασµό τελεστών και τελεστέων (ο τελεστέος είναι αυτό, πάνω στο οποίο δρα ο τελεστής). Η απλούστερη έκφραση είναι ένας µόνο τελεστέος.

Ακολουθούν µερικές εκφράσεις :

4

4 + 21

a*(b + c/d)/20

Page 31: C Notes

31

q = 5*2

x = ++q % 3

q > 3

Οι τελεστέοι µπορεί να είναι σταθερές, µεταβλητές ή συνδυασµοί αυτών των δύο. Κάθε έκφραση στη C έχει µια τιµή και για να βρούµε την τιµή αυτή, εκτελούµε τις πράξεις µε τη σειρά που υποδεικνύεται από την προτεραιότητα των τελεστών. Εκφράσεις σχέσεων, όπως η q > 3, έχουν τιµή 1 αν είναι αληθείς και 0 αν είναι ψευδείς.

Ακολουθούν µερικές εκφράσεις :

Έκφραση Τιµή

-4+6 2

c = 3 + 8 11

5>3 1

6+(c=3+8) 17

Οι προτάσεις είναι τα πρωταρχικά δοµικά στοιχεία ενός προγράµµατος, δηλ. το πρόγραµµα είναι µια σειρά προτάσεων και σηµείων στίξης. Στη C οι προτάσεις κλείνουν µε το σύµβολο ;. Έτσι, η a = 4 είναι µια έκφραση, αλλά η a= 4; είναι µια πρόταση. Στο παράδειγµα : x = 6 + (y=5); η υποέκφραση y=5 είναι µια ολοκληρωµένη εντολή, αλλά απλά είναι µέρος µιας πρότασης. Επειδή συνεπώς µια ολοκληρωµένη εντολή δεν είναι και απαραίτητα µια πρόταση, το σύµβολο (;) χρειάζεται για να χαρακτηρίζει τις εντολές που είναι αληθινές προτάσεις.

Στο επόµενο παράδειγµα χρησιµοποιούµε τέσσερα είδη προτάσεων:

/* prog19.c – τα τέσσερα είδη προτάσεων */

#include <stdio.h>

main() /* βρίσκει το άθροισµα των 20 πρώτων ακεραίων */

{

int count, sum; /* πρόταση δήλωσης */

count = 0; /* πρόταση καταχώρησης */

sum = 0; /* πρόταση καταχώρησης */

while (count++ < 20) /* πρόταση ελέγχου while */

sum = sum + count; /* πρόταση */

printf("άθροισµα = %d\n", sum); /* πρόταση - συνάρτηση */

Page 32: C Notes

32

}

Μια σύνθετη πρόταση αποτελείται από δύο ή περισσότερες προτάσεις που περικλείονται από αγκύλες. Λέγεται επίσης και µπλοκ (block).

Ακολουθούν παραδείγµατα :

/* τµήµα προγράµµατος 1 */

index = 0;

while (index++ < 10)

sum = 10*index + 2;

printf("sum = %d\n", sum);

/* τµήµα προγράµµατος 2 */

index = 0;

while (index++ < 10)

{

sum = 10*index + 2;

printf("sum = %d\n", sum);

}

Στο τµήµα προγράµµατος 1, ο βρόχος while περιλαµβάνει µόνο µια πρόταση αντικατάστασης, δηλ. όταν λείπουν τα { και }, µια πρόταση while τρέχει από το while µέχρι το επόµενο σύµβολο ;.

Στο τµήµα προγράµµατος 2, τα σύµβολα { και } δηλώνουν ότι και οι δύο προτάσεις αποτελούν µέρος του βρόχου while. Ολόκληρη η σύνθετη πρόταση θεωρείται σαν µια απλή πρόταση µε την έννοια της δοµής της πρότασης while.

Οι Μετατροπές Τύπου

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

1. Όταν εµφανίζονται σε εκφράσεις, τόσο ο τύπος char όσο και ο τύπος short, µε πρόσηµο ή χωρίς, αυτόµατα µετατρέπονται σε τύπο int. Επειδή αυτές είναι µετατροπές προς κάποιο µεγαλύτερο τύπο, λέγονται προαγωγές.

Page 33: C Notes

33

2. Σε κάθε πράξη όπου εµπλέκονται δύο τύποι, οι δύο τιµές µετατρέπονται στον τύπο αυτής µε τον "υψηλότερο" βαθµό.

3. Η ιεραρχία των τύπων από τους υψηλότερους προς τους χαµηλότερους είναι η εξής : long double, double, float, unsigned long, long, unsigned int και int.

4. Σε µια πρόταση καταχώρησης, το τελικό αποτέλεσµα των υπολογισµών µετατρέπεται στον τύπο της µεταβλητής στην οποία καταχωρήθηκε η τιµή. Έτσι, όµως, µπορεί µια τιµή να µετατραπεί σε τύπο χαµηλότερου βαθµού, όταν π.χ. καταχωρούµε τύπο float σε τύπο int και γίνεται, όπως είδαµε, στρογγυλοποίηση του αριθµού.

Ακολουθεί ένα παράδειγµα :

/* prog20.c – αυτόµατη µετατροπή τύπων */

#include <stdio.h>

main()

{

char ch;

int i;

float fl;

fl = i = ch = ‘A’;

printf("ch = %c, i = %d, fl = %2.2f \n", ch, i, fl);

ch = ch + 1;

i = fl + 2 * ch;

fl = 2.0 * ch + i;

printf("ch = %c, i = %d, fl = %2.2f \n", ch, i, fl);

}

Το αποτέλεσµα θα είναι :

ch = A, i = 65, fl = 65.00

ch = B, i = 197, fl = 329.00

Page 34: C Notes

34

Ο Τελεστής Εκµαγείο

Όλες οι µετατροπές τύπων που αναφέραµε µέχρι τώρα γίνονται αυτόµατα. Όµως, είναι πιθανό να θέλουµε να δώσουµε εµείς τις οδηγίες για την ακριβή µετατροπή του τύπου που θέλουµε. Η µέθοδος αυτή λέγεται εκµαγείο και συνίσταται στην τοποθέτηση µπροστά από την ποσότητα του ονόµατος, του επιθυµούµενου τύπου µέσα σε παρενθέσεις. Οι παρενθέσεις µαζί µε το όνοµα του τύπου αποτελούν τον τελεστή-εκµαγείο.

Ακολουθούν παραδείγµατα, όπου η µεταβλητή m είναι τύπου int :

m = 1.6 + 1.7;

m = (int) 1.6 + (int) 1.7;

Το πρώτο παράδειγµα κάνει αυτόµατη µετατροπή και η µεταβλητή m παίρνει την τιµή 3. Το δεύτερο παράδειγµα περιέχει δύο εκµαγεία τύπου int και οι µετατροπές σε ακεραίους γίνονται πριν από την πρόσθεση και έτσι η τιµή της µεταβλητής m είναι 2.

Ορίσµατα Συναρτήσεων και Μετατροπές Τύπων

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

/* prog21.c – ορίζει µια συνάρτηση µ’ ένα όρισµα (argument) */

#include <stdio.h>

main()

{

int times = 4;

char ch = ‘!’; /* o ASCII κώδικας είναι 33 */

float f = 5.0;

f(times); /* όρισµα τύπου int */

f(ch); /* char αυτόµατα → int */

f((int) f); /* το εκµαγείο αναγκάζει την f → int */

}

Page 35: C Notes

35

void f(int n) /* η συνάρτηση έχει ένα όρισµα τύπου int */

{

while (n- - > 0)

printf("#");

printf("\n");

}

Το αποτέλεσµα θα είναι :

####

################################

#####

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

Στο πρόγραµµά µας, η κλήση f(times) καταχωρεί την τιµή της µεταβλητής times, δηλ. το 4 στην n. Λέµε ότι η κλήση µιας συνάρτησης περνά (µεταβιβάζει) µια τιµή και αυτή η τιµή λέγεται ενεργό όρισµα ή ενεργή παράµετρος.

Τα ονόµατα µεταβλητών ανήκουν αποκλειστικά στην συνάρτηση, που σηµαίνει ότι ένα όνοµα που ορίστηκε σε µια συνάρτηση δεν έχει σχέση µε το ίδιο όνοµα που ορίστηκε κάπου αλλού. Αυτό σηµαίνει ότι µπορεί να έχουµε δύο µεταβλητές µε το ίδιο όνοµα και το πρόγραµµα να εξακολουθεί να διακρίνει ποια είναι ποια.

Η τελευταία κλήση f((int) f) χρησιµοποιεί ένα εκµαγείο τύπου για να µετατρέψει το f στον κατάλληλο τύπο γι' αυτό το όρισµα. Αν δεν χρησιµοποιούσαµε αυτήν την µετατροπή, τότε η συνάρτηση θα περίµενε να βρει τιµή τύπου int και θα διάβαζε έτσι µόνο τα 2 bytes από τα 8 bytes της τιµής τύπου float. Έτσι, προσαρµόσαµε τον τύπο της µεταβλητής µε τον τύπο του ορίσµατος της συνάρτησης.

Η Εντολή While

Η γενική µορφή (σύνταξη) της εντολής while είναι η εξής :

while (έκφραση)

πρόταση

Page 36: C Notes

36

Η έκφραση είναι µια σύγκριση τιµών και έχει γενικά ένα λογικό αποτέλεσµα. Η πρόταση µπορεί να είναι µια απλή πρόταση µε το σύµβολο ; στο τέλος της ή µια σύνθετη πρόταση κλεισµένη ανάµεσα στις αγκύλες { και }. Αν η έκφραση είναι αληθής (δηλ. γενικότερα µη µηδενική), η πρόταση εκτελείται µία φορά και συνεχίζει µέχρι η έκφραση να γίνει ψευδής (δηλ. γενικότερα µηδενική).

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

Ακολουθεί ένα παράδειγµα µε µια εντολή while :

while (scanf(“%d“, &num) == 1)

; /* παραλείπει την είσοδο που είναι ακέραιος */

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

Ένα Παράδειγµα µε την While

Θα δούµε τώρα τον βρόχο while σ’ ένα πρόγραµµα που αθροίζει ακέραιες τιµές που εισάγονται από το πληκτρολόγιο.

/* prog22.c – αθροίζει ακεραίους που δίνονται διαλογικά */

#include <stdio.h>

main()

{

long num;

long sum = 0L; /* αρχική τιµή της sum = 0 */

int status;

printf("Δώστε έναν ακέραιο. ");

printf("Δώστε q για να σταµατήσετε. \n ");

status = scanf(" %ld", &num);

while (status == 1) /* το == σηµαίνει ‘ίσο µε‘ */

{

sum = sum + num;

Page 37: C Notes

37

printf("Δώστε τον επόµενο ακέραιο. ");

printf("Δώστε q για να σταµατήσετε. \n");

status = scanf(“ %ld“, &num);

}

printf("Οι ακέραιοι αριθµοί έχουν άθροισµα %ld. \n“, sum);

}

Το αποτέλεσµα θα είναι :

Δώστε έναν ακέραιο. Δώστε q για να σταµατήσετε. 20

Δώστε τον επόµενον ακέραιο. Δώστε q για να σταµατήσετε. 5

Δώστε τον επόµενον ακέραιο. Δώστε q για να σταµατήσετε. 30

Δώστε τον επόµενον ακέραιο. Δώστε q για να σταµατήσετε. Q

Οι ακέραιοι αριθµοί έχουν άθροισµα 55.

Ας δούµε λίγες ενδιαφέρουσες λεπτοµέρειες του προγράµµατος. Κατ’ αρχήν, ο τελεστής == είναι ο τελεστής ισότητας της C, ενώ η εντολή status=1 θα καταχωρούσε το 1 στη µεταβλητή status. Το πρόγραµµα τελειώνει όταν η status πάρει µια τιµή διαφορετική από το 1, οπότε σταµατάει ο βρόχος και το πρόγραµµα εµφανίζει την τελική τιµή του αθροίσµατος.

Βλέπουµε, όµως, ότι µε την χρήση της scanf() πετύχαµε δύο πράγµατα : διαβάζουµε µια καινούργια τιµή για την µεταβλητή num και ταυτόχρονα κάνουµε χρήση της επιστρεφόµενης τιµής της scanf(). Η scanf() επιστρέφει τον αριθµό των στοιχείων που διάβασε µε επιτυχία.

Αν, δηλ., διαβάσει έναν ακέραιο, τότε επιστρέφει την τιµή 1, η οποία και καταχωρείται στην µεταβλητή status, αλλιώς αν δώσουµε µια µη αριθµητική είσοδο, όπως το q, τότε κανένα στοιχείο δεν διαβάζεται και η επιστρεφόµενη τιµή της status γίνεται ίση µε 0.

Έτσι, µε την χρήση της scanf() πετύχαµε µαζί µε το διάβασµα µιας τιµής και τον έλεγχο της εξόδου από τον βρόχο. Βλέπουµε ακόµη ότι έχουµε µια εντολή scanf() πριν µπούµε στον βρόχο while, για να µπορέσει έτσι να γίνει ο αρχικός έλεγχος της συνθήκης και επίσης µια εντολή scanf() στο τέλος του βρόχου για να µπορούν να συνεχίζονται οι επαναλήψεις.

Θα µπορούσαµε να γράψουµε την εντολή while και ως εξής :

while (scanf(“%ld”, &num)==1)

Page 38: C Notes

38

Οι Αληθείς και οι Ψευδείς Τιµές

Ακολουθεί ένα παράδειγµα :

/* prog23.c – οι αληθείς και οι ψευδείς τιµές στη C */

#include <stdio.h>

main()

{

int true, false;

true = (5>2); /* τιµή µιας αληθούς σχέσης */

false = (5==2); /* τιµή µιας ψευδούς σχέσης */

printf("αληθής = %d; ψευδής = %d \n", true, false);

}

Το αποτέλεσµα του προγράµµατος θα είναι :

αληθής = 1; ψευδής = 0

Δηλαδή, στη C µια αληθής έκφραση έχει τιµή 1 και µια ψευδής έκφραση έχει τιµή 0.

Ο παρακάτω βρόχος δεν σταµατάει ποτέ :

while (1)

{

...

}

Από το επόµενο παράδειγµα συµπεραίνουµε ότι στη C αληθείς θεωρούνται όλες οι µη µηδενικές τιµές και µόνο το 0 αναγνωρίζεται σαν ψευδές. Έτσι, ουσιαστικά στη C γίνεται αριθµητικός έλεγχος και όχι έλεγχος αληθούς/ψευδούς.

/* prog24.c – οι αληθείς και οι ψευδείς τιµές */

#include <stdio.h>

main()

{

int n = 3;

Page 39: C Notes

39

while (n)

printf(“%d\n”, n- -);

n = -3;

while (n)

printf(“%2d\n”, n++);

}

Το αποτέλεσµα θα είναι :

3

2

1

-3

-2

-1

Έτσι, στη C η έκφραση while (a!=0) θα µπορεί να γραφεί και ως εξής :

while (a)

Ο Βρόχος For

Ο βρόχος for αποφεύγει τα τρία βήµατα ενός βρόχου while, δηλ. την απόδοση αρχική τιµής, τον έλεγχο της συνθήκης τερµατισµού και την αλλαγή της τιµής, όταν, όπως ξέρουµε από τις άλλες γλώσσες προγραµµατισµού, οι επαναλήψεις που πρέπει να κάνουµε είναι ορισµένες σε αριθµό. Στη C, όµως, αυτό δεν είναι υποχρεωτικό. Η εντολή for χρησιµοποιεί τρεις εκφράσεις ελέγχου, χωρισµένες µε αγγλικές άνω-τελείες (;), για να ελέγχουν τη διαδικασία της ανακύκλωσης. Η πρόταση αρχικών τιµών εκτελείται µόνο µία φορά πριν κάποια από τις προτάσεις του βρόχου εκτελεστεί.

Αν η έκφραση ελέγχου είναι αληθής (ή µη-µηδενική), ο βρόχος εκτελείται µία φορά. Μετά, υπολογίζεται η έκφραση ανανέωσης και η έκφραση ελέγχου ελέγχεται µια φορά ακόµα. Η πρόταση for είναι ένας βρόχος συνθήκης εισόδου, που σηµαίνει, όπως αναφέρθηκε προηγουµένως, ότι η απόφαση για µια ακόµα εκτέλεση του βρόχου, παίρνεται πριν την εκτέλεση αυτή. Έτσι, είναι πιθανό ο βρόχος να µην εκτελεστεί ποτέ. Το µέρος των προτάσεων µπορεί να αποτελείται από µια απλή πρόταση ή από µια σύνθετη πρόταση.

Page 40: C Notes

40

Η σύνταξη της εντολής for είναι ως εξής :

for (αρχική τιµή; Έλεγχος; Ανανέωση)

πρόταση

Ο βρόχος επαναλαµβάνεται µέχρι ο έλεγχος να γίνει ψευδής ή µηδέν.

Ακολουθεί ένα παράδειγµα :

/* prog25.c – ένας βρόχος µε χρήση της for */

#include <stdio.h>

#define NUMBER 10

main()

{

int count;

for (count=1; count<=NUMBER; count++)

printf("Florina per sempre!\n");

}

Οι παρενθέσεις που ακολουθούν τη λέξη-κλειδί for περιέχουν τρεις εκφράσεις χωρισµένες µε το σύµβολο ;. Η πρώτη έκφραση δίνει αρχική τιµή και εκτελείται µία φορά µόνο στην αρχή. Η δεύτερη έκφραση είναι µια συνθήκη ελέγχου και υπολογίζεται πριν από κάθε δυναµική εκτέλεση του βρόχου. Ο βρόχος τελειώνει όταν η έκφραση γίνει ψευδής. Η τρίτη έκφραση υπολογίζεται στο τέλος κάθε βρόχου. Η πρόταση for ακολουθείται από µια απλή ή σύνθετη πρόταση.

Μπορούµε να καθυστερήσουµε για λίγο το τρέξιµο ενός προγράµµατος, ως εξής :

for (n=1; N<=10000; N++)

;

Η ANSI C διαθέτει και τη συνάρτηση clock(), που µπορεί να χρησιµοποιηθεί για να δηµιουργήσουµε χρονικές καθυστέρησης.

Page 41: C Notes

41

Ευέλικτες Χρήσεις της Εντολής For

• Ο ατέρµονας βρόχος µε την for γίνεται ως εξής : for ( ; ; ). • Η εντολή for της C µπορεί να χρησιµοποιήσει και αύξηση διάφορη του 1 :

for (n=0; N<60; N=n+10)

• Ακόµα, µπορούµε να µετράµε µε χαρακτήρες αντί για αριθµούς :

for (ch=’a’; ch<=’z’; ch++)

• Μπορούµε ακόµα να ελέγχουµε κάποιες άλλες συνθήκες, εκτός από τον γνωστό µας αριθµό των επαναλήψεων :

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

• Μπορούµε ακόµα να κάνουµε πράξεις µε την ποσότητα ελέγχου :

for (d=100.0; d<150.0; d=d*1.1)

• Μπορούµε γενικά να χρησιµοποιήσουµε οποιαδήποτε νόµιµη έκφραση για την τρίτη έκφραση :

for (x=1; Y<=75; Y=++x*5+50)

• Η πρώτη έκφραση δεν χρειάζεται να δίνει πάντα αρχική τιµή σε µια µεταβλητή :

for (printf(…); num!=6; scanf(“%d”, &num))

• Μπορούµε να έχουµε περισσότερους προσδιορισµούς αρχικής τιµής ή ανανεώσεις, χωρισµένες µε κόµµα, µέσα στον βρόχο for :

for (x=1, y=2; X<=16; X++, y=y+3)

Συµπεραίνουµε ότι πρόταση for δεν χρησιµοποιείται µόνο για ένα αυστηρά συγκεκριµένο πλήθος επαναλήψεων, αλλά µπορεί να κάνει πολλά περισσότερα πράγµατα.

Περισσότεροι Τελεστές Καταχώρησης

Έχουµε ήδη διαπιστώσει ότι η C έχει αρκετούς τελεστές κατχώρησης. Ο πιο βασικός είναι ο =, ο οποίος απλά καταχωρεί την τιµή της έκφρασης που βρίσκεται στα δεξιά του στην µεταβλητή που βρίσκεται στα αριστερά του. Οι άλλοι τελεστές καταχώρησης ανανεώνουν µεταβλητές : +=, –=, *=, /=, %=.

Κάθε ένας από αυτούς τους τελεστές χρησιµοποιείται µ’ ένα όνοµα µεταβλητής στα αριστερά του και µια έκφραση στα δεξιά του. Στην µεταβλητή καταχωρείται µια νέα τιµή ανάλογα µε την τιµή της έκφρασης στα δεξιά. Η ακριβής ρύθµιση εξαρτάται από τον τελεστή. Για παράδειγµα :

Page 42: C Notes

42

a += 20 είναι το ίδιο όπως a = a + 20

b –= 2 είναι το ίδιο όπως b = b – 2

c *= 2 είναι το ίδιο όπως c = c * 2

d /= 2.5 είναι το ίδιο όπως d = d / 2.5

e %= 3 είναι το ίδιο όπως e = e % 3

x *= 3 * y +12 είναι το ίδιο όπως x = x * (3 * y + 12)

Ο Βρόχος Do While

Είδαµε ότι ο βρόχος while και ο βρόχος for είναι και οι δύο βρόχοι συνθήκης εισόδου. Η C έχει και έναν βρόχο συνθήκης εξόδου, στον οποίο η συνθήκη ελέγχεται µετά από κάθε επανάληψη του βρόχου. Αυτός είναι ο βρόχος do while.

Η πρόταση do while δηµιουργεί έναν βρόχο που επαναλαµβάνεται µέχρι η συνθήκη ελέγχου να γίνει ψευδής ή µηδέν. Ο βρόχος do while είναι ένας βρόχος συνθήκης εξόδου, δηλ. η απόφαση για µια ακόµα επανάληψη παίρνεται µετά από κάθε εκτέλεση του βρόχου.

Έτσι, ο βρόχος πρέπει να εκτελεστεί τουλάχιστον µία φορά. Το µέρος των προτάσεων µπορεί να αποτελείται είτε από µία απλή πρόταση ή από µία σύνθετη.

do

scanf(“%d”, &number)

while (number != 20);

Ακολουθεί ένα παράδειγµα :

/* prog26.c – βρόχος συνθήκης εξόδου */

#include <stdio.h>

main()

{

char ch;

do

{

scanf("%c", &ch);

Page 43: C Notes

43

printf("%c", ch);

} while (ch!=’#’);

}

Βλέπουµε ότι το πρόγραµµα διαβάζει χαρακτήρες και τους τυπώνει µέχρι να εµφανιστεί ο χαρακτήρας #. Θα τυπώσει, όµως, και τον χαρακτήρα #, όταν αυτός εµφανιστεί. Για να αποφύγουµε αυτή την περίπτωση, µπορούµε να χρησιµοποιήσουµε την εντολή if, ως εξής :

if (ch<>’#’)

printf("%c", ch);

Η γενική µορφή του βρόχου do while είναι η εξής :

do

πρόταση

while (έκφραση);

Το µέρος πρόταση επαναλαµβάνεται µέχρι η έκφραση να γίνει ψευδής ή µηδέν.

Ένας βρόχος do while εκτελείται πάντα τουλάχιστον µία φορά, αφού ο έλεγχος γίνεται µετά την εκτέλεση του σώµατος του βρόχου. Από την άλλη µεριά, ένας βρόχος for ή ένας βρόχος while µπορεί να µην εκτελεστεί καµία φορά, αφού ο έλεγχος γίνεται πριν από την εκτέλεση της εντολής.

Το φαινοµενικό αυτό ελάττωµα που έχει ο βρόχος do while, ότι δηλ. εκτελείται τουλάχιστον µία φορά και ίσως και όταν δεν χρειάζεται, είδαµε ότι πολύ εύκολα µπορεί να διορθωθεί µε χρήση της εντολής if µέσα στον βρόχο.

Οι Εσωτερικοί Βρόχοι Εσωτερικός βρόχος λέγεται αυτός που βρίσκεται στο εσωτερικό ενός άλλου βρόχου. Μια συνήθης χρήση των εσωτερικών βρόχων είναι για την απεικόνιση δεδοµένων σε γραµµές και στήλες. Ο ένας βρόχος χειρίζεται όλες τις στήλες σε µια γραµµή και ο άλλος όλες τις γραµµές.

Ακολουθεί ένα παράδειγµα :

/* prog27.c – χρήση εσωτερικών βρόχων */

#include <stdio.h>

#define GRAMMES 5

#define STILES 5

Page 44: C Notes

44

main()

{

int row;

char ch;

for (row=0; row<GRAMMES; row++)

{

for (ch=’A’; ch<’A’+STILES; ch++)

printf(" %c", ch);

printf("\n");

}

}

Το αποτέλεσµα θα είναι :

ABCDE

ABCDE

ABCDE

ABCDE

ABCDE

Ο πρώτος βρόχος for λέγεται έξω βρόχος και ο άλλος βρόχος for λέγεται µέσα βρόχος. Ο έξω βρόχος αρχίζει µε τη row να έχει τιµή 0 και τελειώνει όταν η row γίνει ίση και µε το 4. Άρα, κάνει 5 επαναλήψεις. Η πρώτη πρόταση σε κάθε επανάληψη είναι ο άλλος βρόχος for. Αυτός ο βρόχος κάνει επίσης 5 κύκλους, τυπώνοντας τους χαρακτήρες από το Α µέχρι το E στην ίδια γραµµή.

Μόλις τελειώσει ο µέσα βρόχος, ακολουθεί µια πρόταση printf("\n"), που όπως ξέρουµε αλλάζει γραµµή. Βλέπουµε συνεπώς, ότι ο µέσα βρόχος τυπώνει 5 χαρακτήρες σε µια γραµµή και ο έξω βρόχος δηµιουργεί 5 γραµµές. Μπορεί, όµως, ο µέσα βρόχος να εξαρτάται από τον έξω βρόχο και να συµπεριφέρεται έτσι διαφορετικά σε κάθε επανάληψη.

Ακολουθεί το προηγούµενο παράδειγµα τροποποιηµένο :

/* prog28.c – χρήση εξαρτηµένων εσωτερικών βρόχων */

#include <stdio.h>

#define GRAMMES 5

Page 45: C Notes

45

#define STILES 5

main()

{

int row;

char ch;

for (row=0; row<GRAMMES; row++)

{

for (ch=’A’+row; ch<’A’+STILES; ch++)

printf(" %c", ch);

printf("\n");

}

}

Το αποτέλεσµα θα είναι :

ABCDE

BCDE

CDE

DE

E

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

Οι Πίνακες

Γνωρίζουµε την µεγάλη σηµασία που έχουν οι πίνακες για τον προγραµµατισµό. Μπορούµε να αποθηκεύουµε αρκετά στοιχεία για παρόµοιες πληροφορίες µ’ έναν πολύ βολικό τρόπο. Ένας πίνακας είναι µια σειρά δεδοµένων του ίδιου τύπου, όπως π.χ. 15 στοιχεία τύπου char ή 10 στοιχεία τύπου int, αποθηκευµένα ακολουθιακά. Ολόκληρος ο πίνακας έχει ένα απλό όνοµα και τα δεδοµένα του, που λέγονται στοιχεία του πίνακα, µπορούν να προσπελαστούν µε χρήση µιας κατάστασης ακεραίων.

Για παράδειγµα, η παρακάτω δήλωση : float pin[20];, δηλώνει ότι ο pin είναι ένας πίνακας µε 20 στοιχεία, µε πρώτο στοιχείο το pin[0], δεύτερο στοιχείο το pin[1], κοκ µέχρι και το

Page 46: C Notes

46

pin[19]. Βλέπουµε ότι η αρίθµηση των στοιχείων του πίνακα αρχίζει από το 0 και όχι από το 1. Σε κάθε στοιχείο του παραπάνω πίνακα µπορούµε να καταχωρήσουµε µια τιµή τύπου float, ως εξής :

pin[5] = 20.16;

pin[6] = 1.3e+12;

Μπορούµε να ορίσουµε και πίνακες άλλων τύπων :

int numbers[10];

char alpha[26];

long bignumbers[100];

Οι αριθµοί που χρησιµοποιούνται για να χαρακτηρίζουν τα στοιχεία του πίνακα καλούνται δείκτες του πίνακα, οι οποίοι πρέπει να είναι ακέραιοι και να αρχίζουν από το 0. Τα στοιχεία του πίνακα είναι αποθηκευµένα στην µνήµη, το ένα δίπλα στο άλλο.

Ακολουθεί ένα πρόγραµµα µε πίνακες και µε χρήση της εντολής for, το οποίο διαβάζει 10 βαθµούς, τους εµφανίζει στην οθόνη και µετά υπολογίζει το άθροισµά τους και τον µέσο όρο τους :

/* prog29.c – χρήση βρόχων για επεξεργασία πινάκων */

#include <stdio.h>

#define SIZE 10

main()

{

int i, sum, b[SIZE];

float average;

printf("Δώστε %d βαθµούς \n", SIZE);

for (i = 0; i < SIZE; i++)

scanf("%d", &b[i]); /* διάβασµα των 10 βαθµών */

printf("Οι βαθµοί που διαβάστηκαν είναι οι εξής : \n");

for (i = 0; i < SIZE; i++)

printf("%5d", b[i]); /* επιβεβαίωση της καταχώρησης */

printf("\n");

Page 47: C Notes

47

for (i = 0; i < SIZE; i++)

sum += b[i]; /* άθροιση των βαθµών */

average = (float) sum / SIZE; /* υπολογισµός µέσους όρου */

printf("Άθροισµα βαθµών = %d, Μέσος Όρος = %.2f \n",

sum, average);

}

Βλέπουµε ότι ο βρόχος for µάς δίνει την δυνατότητα για έναν απλό, εύκολο και ευθύ τρόπο χειρισµού των δεικτών του πίνακα. Η παρακάτω εντολή είναι πολύ βολική για την επεξεργασία των στοιχείων ενός πίνακα µεγέθους SIZE :

for (i=0; i<SIZE; i++)

Βρόχοι και Συναρτήσεις

Ακολουθεί ένα πρόγραµµα :

/* prog30.c – ύψωση αριθµού σε δύναµη */

#include <stdio.h>

main()

{

double x, xpow;

double power(); /* δήλωση της συνάρτησης */

int n;

printf("Δώστε έναν αριθµό και µια ακέραια δύναµη ");

printf("Δώστε q για να σταµατήσετε.");

while (scanf("%lf%d", &x, &n) == 2)

{

xpow = power(x, n); /* κλήση της συνάρτησης */

printf("Το %.3e εις την %d είναι %.3e \n", x, n, xpow);

}

Page 48: C Notes

48

}

double power(a, b) /* ορισµός της συνάρτησης */

double a;

int b;

{

double pow = 1;

int i;

for (i = 1; i <= b; i++)

pow *= a;

return pow; /* επιστρέφει την τιµή της pow */

}

Εδώ υπολογίζουµε το αποτέλεσµα της ύψωσης ενός αριθµού σε µια ακέραια δύναµη µε τη χρήση µιας συνάρτησης. Η συνάρτηση δέχεται δύο τιµές και επιστρέφει µία. Χρησιµοποιούµε ένα όρισµα τύπου double και ένα όρισµα τύπου int, που καθορίζουν ποιος αριθµός θα υψωθεί σε ποια δύναµη.

Δηλώνουµε την συνάρτηση µέσα στην main() µε τον τύπο της τιµής που επιστρέφει και µε την χρήση της λέξης-κλειδί return δείχνουµε την τιµή που θα επιστρέψει η συνάρτηση. Τη συνάρτηση την καλούµε µέσα από το πρόγραµµα µε την εξής εντολή :

xpow = power(x,n);.

Η Εντολή If

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

Η γενική µορφή (σύνταξη) της εντολής if είναι η εξής :

if (έκφραση)

πρόταση

Εάν η έκφραση είναι αληθής (δηλ. όχι µηδέν), τότε εκτελείται η πρόταση. Αλλιώς, αγνοείται. Η πρόταση µπορεί να είναι µια απλή πρόταση ή µια σύνθετη πρόταση.

Page 49: C Notes

49

Η C δίνει επίσης την δυνατότητα επιλογής µεταξύ δύο προτάσεων µε την χρήση της δοµής if else. Αν η έκφραση είναι αληθής, τότε εκτελείται ό,τι ακολουθεί την if, αλλιώς εκτελείται ό,τι ακολουθεί την else.

Η γενική µορφή (σύνταξη) της εντολής if else είναι η εξής :

if (έκφραση)

πρόταση1

else

πρόταση2

Ένα else συνδυάζεται µε το πιο κοντινό του if, εκτός κι αν υπάρχουν αγκύλες, που δηλώνουν κάτι άλλο. Υπάρχει η δυνατότητα να χρησιµοποιήσουµε και την εντολή else if, ως εξής :

if (έκφραση1)

πρόταση1

else if (έκφραση2)

πρόταση2

else πρόταση3

Αν η έκφραση1 είναι αληθής, τότε εκτελείται η πρόταση1. Αν η έκφραση1 είναι ψευδής, αλλά η έκφραση2 είναι αληθής, τότε εκτελείται η πρόταση2. Τέλος, αν και οι δύο εκφράσεις είναι ψευδείς, τότε εκτελείται η πρόταση3.

Οι Συναρτήσεις getchar() και putchar()

Ας δούµε τώρα κάτι άλλο. Η C έχει ένα ζευγάρι συναρτήσεων, ειδικά σχεδιασµένων για την είσοδο/έξοδο χαρακτήρων, τις getchar() και putchar(). Η συνάρτηση getchar() δεν έχει ορίσµατα και επιστρέφει τον επόµενο χαρακτήρα που δίνεται ως δεδοµένο, ως εξής :

ch = getchar();

Η συνάρτηση putchar() εκτυπώνει το όρισµά της, ως εξής :

putchar(ch);

Ακολουθούν δύο παραδείγµατα :

/* prog31.c – µετατροπή εισόδου, διατηρώντας τα κενά */

#include <stdio.h>

Page 50: C Notes

50

#define SPACE ‘ ‘

main()

{

char ch;

ch = getchar(); /* διάβασε έναν χαρακτήρα */

while (ch != ‘\n’) /* αν δεν είναι το τέλος µιας γραµµής */

{

if (ch == SPACE) /* άφησε το κενό */

putchar(ch); /* αµετάβλητος χαρακτήρας */

else

putchar(ch + 1); /* άλλαξε τους άλλους χαρακτήρες*/

ch = getchar(); /* επόµενος χαρακτήρας */

}

}

/* prog32.c – µετατροπή εισόδου, διατηρώντας τα κενά */

#include <stdio.h>

#define SPACE ‘ ‘

main()

{

char ch;

while ((ch = getchar()) != ‘\n’) /* αν δεν είναι το τέλος µιας γραµµής */

{

if (ch == SPACE) /* άφησε το κενό */

putchar(ch); /* αµετάβλητος χαρακτήρας */

Page 51: C Notes

51

else

putchar(ch + 1); /* άλλαξε τους άλλους χαρακτήρες*/

}

}

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

FLORINA PER SEMPRE.

GMPSJOB QFS TFNQSF/

Η ευελιξία της C µάς επιτρέπει να συνδυάσουµε την ανάγνωση και τον έλεγχο των δεδοµένων εισόδου σε µία µόνο έκφραση. Και αυτό το βλέπουµε στο δεύτερο πρόγραµµα και στην εντολή while ((ch=getchar())!='\n’), όπου έχουµε σε µια έκφραση την καταχώρηση µιας τιµής στη ch και σύγκριση αυτής της τιµής µε τον χαρακτήρα νέας γραµµής \n. Αν δεν βάζαµε τις παρενθέσεις, τότε η πρώτη έκφραση που θα εκτελείτο, θα ήταν η getchar()!='\n'.

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

Στο παρακάτω παράδειγµα συνδυάζουµε τρεις σχεσιακές εκφράσεις µε τον λογικό τελεστή &&, που είναι το γνωστό µας AND στην C. Το πρόγραµµα µετρά τους µη-λευκούς χαρακτήρες, δηλ. τους χαρακτήρες που δεν είναι κενό, enter και tab.

/* prog33.c – µετρά τους µη-λευκούς χαρακτήρες */

#include <stdio.h>

#define PERIOD ‘.’

main()

{

int ch;

int charcount = 0;

while ((ch = getchar()) != PERIOD)

if (ch != ‘ ‘ && ch != ‘\n’ && ch != ‘\t’)

charcount++;

Page 52: C Notes

52

printf("Υπάρχουν %d µη-λευκοί χαρακτήρες. \n", charcount);

}

Οι λογικοί τελεστές έχουν µικρότερη προτεραιότητα από τους σχεσιακούς τελεστές και υπάρχουν τρεις απ' αυτούς στην C :

Τελεστής Σηµασία

&& And (και)

|| Or (ή)

! Not (όχι)

Ο τελεστής ! έχει πολύ µεγάλη προτεραιότητα, µεγαλύτερη και από εκείνη του πολλαπλασιασµού, ίδια µε την προτεραιότητα των σχεσιακών τελεστών και µικρότερη από εκείνη των παρενθέσεων. Ο τελεστής && έχει µεγαλύτερη προτεραιότητα από τον ||, αλλά και οι δύο βρίσκονται κάτω από τους σχεσιακούς τελεστές και πάνω από τους τελεστές καταχώρησης. Ακόµη, οι λογικές εκφράσεις στην C υπολογίζονται από αριστερά προς τα δεξιά.

Ο Τελεστής υπό Συνθήκη ?

Ένας άλλος τρόπος για να εκφράσουµε την πρόταση if else ονοµάζεται έκφραση υπό συνθήκη και χρησιµοποιεί τον τελεστή υπό συνθήκη ? : που έχει τρεις τελεστέους. Το παρακάτω παράδειγµα βρίσκει την απόλυτη τιµή ενός αριθµού :

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

Η πρόταση λέει τα εξής : «αν το y είναι µικρότερο από το 0, τότε x=-y, αλλιώς x=y».

Η γενική µορφή (σύνταξη) της έκφρασης υπό συνθήκη είναι η εξής :

έκφραση1 ? έκφραση2 : έκφραση3

Αν η έκφραση1 είναι αληθής (δηλ. όχι µηδέν), τότε ολόκληρη η έκφραση υπό συνθήκη έχει την τιµή της έκφρασης2, αν όµως η έκφραση1 είναι ψευδής (µηδέν), τότε ολόκληρη η έκφραση υπό συνθήκη έχει την τιµή της έκφρασης3.

Πώς βρίσκουµε τον µέγιστο από δύο αριθµούς :

max = (a>b) ? a : b

Ακολουθούν παραδείγµατα :

(5>2) ? 1 : 2 έχει τιµή 1

(3>5) ? 1 : 2 έχει τιµή 2

(a>b) ? a : b έχει την τιµή του µεγαλύτερου µεταξύ των a και b

Page 53: C Notes

53

Η Εντολή Switch

Η εντολή switch είναι η πλέον κατάλληλη όταν έχουµε να επιλέξουµε από πολλές εναλλακτικές περιπτώσεις, όπου η χρήση διαδοχικών if else δυσκολεύει πολύ τον κώδικα του προγράµµατος.

Ακολουθεί ένα πρόγραµµα, στο οποίο διαβάζουµε ένα γράµµα και µετά µας δίνει το όνοµα µιας πόλης που ν' αρχίζει µ' αυτό το γράµµα.

/* prog34.c – χρήση της εντολής switch */

#include <stdio.h>

main()

{

char ch;

printf("Δώστε ένα γράµµα του αλφαβήτου.");

printf("Πατήστε # για να τελειώσει το πρόγραµµα : \n");

while ((ch = getchar()) != ‘#’)

{

if (ch >= ‘a’ && ch <= ‘z’) /* δέχεται µόνο */

switch (ch) /* µικρά γράµµατα */

{

case ‘a’ :

printf("Αθήνα \n");

break;

case ‘b’ :

printf("Βόλος \n");

break;

case ‘c’ :

printf("Γρεβενά \n");

break;

Page 54: C Notes

54

case ‘d’ :

printf("Δράµα \n");

break;

case ‘e’ :

printf("Έδεσσα \n");

break;

default :

printf("Δεν υπάρχει πόλη\n");

} /* τέλος της switch */

else

printf("Αναγνωρίζω µόνο µικρά γράµµατα \n");

while (getchar() != ‘\n’)

; /* παρέλειψε το υπόλοιπο της γραµµής εισόδου */

printf("Παρακαλώ, δώστε ένα άλλο γράµµα ή #.\n");

} /* τέλος βρόχου while */

}

Η εντολή switch δουλεύει ως εξής :

Πρώτα, υπολογίζεται η έκφραση µέσα στις παρενθέσεις, που ακολουθεί την switch. Μετά, το πρόγραµµα σαρώνει τον κατάλογο µε τις ετικέττες case µέχρι να βρει µια που να ταιριάζει µ’ αυτήν την τιµή. Τότε, το πρόγραµµα πηγαίνει σ' αυτήν την γραµµή και εκτελεί τις εντολές που περιέχονται σ' αυτήν. Αν δεν ταιριάζει µε καµία επιλογή, τότε το πρόγραµµα πηγαίνει στην γραµµή µε την ετικέττα default, αν υπάρχει αυτή βέβαια. Αν δεν υπάρχει η επιλογή default, τότε το πρόγραµµα συνεχίζει κανονικά στην επόµενη εντολή µετά την switch.

Αν δεν υπήρχε η πρόταση break, τότε το πρόγραµµα θα εκτελούσε κάθε πρόταση από την ετικέττα ταύτισης µέχρι και το τέλος της πρότασης switch. Δεν µπορούµε να χρησιµοποιήσουµε µια µεταβλητή για ετικέττα στην πρόταση switch, αλλά µόνο εκφράσεις σταθερών τύπου int ή char.

Page 55: C Notes

55

Η δοµή της εντολής switch είναι η εξής :

switch (ακέραια έκφραση)

{

case (σταθερά 1 :

προτάσεις; (προεραιτικά)

case (σταθερά 2 :

προτάσεις; (προεραιτικά)

...

default : (προεραιτικά)

προτάσεις; (προεραιτικά)

}

Η Εντολή Break

Η εντολή αυτή µπορεί να χρησιµοποιηθεί µε τη switch, όπως είδαµε νωρίτερα, αλλά και µε τις υπόλοιπες τρεις δοµές βρόχου και έχει ως αποτέλεσµα την διακοπή της εκτέλεσης των εντολών switch, for, while και do while και την µετάβαση στο επόµενο στάδιο του προγράµµατος. Ακολουθεί ένα κοµµάτι προγράµµατος που σταµατά έναν βρόχο όταν διαβαστεί είτε ένα πλήκτρο enter ή ένα πλήκτρο tab :

while ((ch=getchar()) != '\n')

{

if (ch == '\t')

break;

putchar(ch);

}

Το παραπάνω κοµµάτι προγράµµατος θα µπορούσε να δουλέψει και ως εξής :

while ((ch=getchar()) != '\n' && ch != '\t')

putchar(ch);

Page 56: C Notes

56

Η Εντολή Continue

Αυτή η εντολή µπορεί να χρησιµοποιηθεί και στα τρία είδη βρόχων, αλλά όχι µε την πρόταση switch. Και η continue διακόπτει την ροή ενός προγράµµατος, αλλά αντί να τερµατίζει ολόκληρο τον βρόχο, έχει ως αποτέλεσµα την µη εκτέλεση του υπόλοιπου τµήµατος του βρόχου και την επανάληψή του από την αρχή.

Σ’ έναν βρόχο while ή for, ξαναρχίζει ο επόµενος κύκλος του βρόχου, ενώ σ’ έναν βρόχο do while ελέγχεται η συνθήκη εξόδου και µετά, αν χρειαστεί, αρχίζει ο επόµενος κύκλος του βρόχου. Τα δύο παραπάνω κοµµάτια προγράµµατος θα µπορούσαν να γραφούν τώρα ως εξής :

while ((ch=getchar()) != '\n') { if (ch == '\t') continue; putchar(ch); } Δηλαδή ο βρόχος σταµατάει τώρα µόνο µε το πλήκτρο enter και όχι και µε το tab. Το παραπάνω κοµµάτι προγράµµατος θα µπορούσε να δουλέψει κι έτσι : while ((ch=getchar()) != '\n') if (ch != '\t') putchar(ch);

Αποφυγή Χαρακτήρων Κατά το Διάβασµα

Επειδή υπάρχει πάντα ο κίνδυνος ο χαρακτήρας νέας γραµµής <enter> να διαβαστεί ως ξεχωριστός χαρακτήρας και να δηµιουργηθούν έτσι προβλήµατα σε διαλογικά προγράµµατα, µια λύση στο πρόβληµα αυτό είναι µε τη χρήση της εντολής while :

while (getchar() != '\n') /* αγνοεί το υπόλοιπο της γραµµής εισόδου */

;

Πρέπει να έχουµε υπόψη µας ότι η getchar() διαβάζει κάθε χαρακτήρα, συµπεριλαµβανοµένων και των χαρακτήρων κενού διαστήµατος (space), στηλοθέτησης (tab) και νέας γραµµής (enter), ενώ η scanf(), όταν διαβάζει αριθµούς, αγνοεί αυτούς τους χαρακτήρες. Μόνο όταν διαβάζουµε χαρακτήρες χρησιµοποιώντας τον προσδιοριστή %c, η scanf() συµπεριφέρεται όπως η getchar().

Για να αποφύγουµε τυχόν προβλήµατα µε το διάβασµα των χαρακτήρων που προαναφέραµε, µπορούµε να χρησιµοποιήσουµε την εξής εντολή if µέσα στο πρόγραµµά µας :

if (ch != '\n' && ch != ' ' && ch != '\t')

για να αποφύγουµε έτσι αυτούς τους χαρακτήρες.

Page 57: C Notes

57

Η Γλώσσα Προγραµµατισµού C

(Μέρος 3 - Συναρτήσεις, Πίνακες και Δείκτες)

Εισαγωγή στις Συναρτήσεις

Η φιλοσοφία σχεδίασης της C βασίζεται στη χρήση των συναρτήσεων. Έχουµε ήδη δει και χρησιµοποιήσει πολλές συναρτήσεις που έχει το σύστηµα, όπως είναι οι printf(), scanf(), strlen() και άλλες, αλλά έχουµε δηµιουργήσει και δικές µας συναρτήσεις. Οι πιο πολλές είχαν το όνοµα main(). Τα προγράµµατα της C πάντα αρχίζουν µε την εκτέλεση των εντολών της συνάρτησης main() και µετά η main() καλεί άλλες συναρτήσεις. Ήρθε βέβαια ο καιρός για να δούµε τις συναρτήσεις αναλυτικότερα.

Συνάρτηση αποκαλείται µια αυτοδύναµη µονάδα κώδικα προγράµµατος που έχει σχεδιασθεί για να εκτελεί µια συγκεκριµένη εργασία. Μια συνάρτηση της C κάνει την ίδια δουλειά µε τις συναρτήσεις, τις υπορουτίνες και τις διαδικασίες των άλλων γλωσσών. Μερικές συναρτήσεις, όπως η printf(), έχουν σαν αποτέλεσµα να γίνει µια ενέργεια. Μερικές άλλες συναρτήσεις υπολογίζουν µια τιµή, που χρησιµοποιείται από το πρόγραµµα, όπως η strlen(). Γενικά, µια συνάρτηση µπορεί να παράγει ενέργειες ή και να παράγει τιµές.

Χρησιµοποιούµε τις συναρτήσεις για να γλυτώσουµε από τον επαναλαµβανόµενο προγραµµατισµό. Δηλαδή, αν πρέπει να γίνει µια συγκεκριµένη εργασία πολλές φορές µέσα σ’ ένα πρόγραµµα, τότε γράφουµε µια φορά µια κατάλληλη συνάρτηση και µετά την χρησιµοποιούµε από το πρόγραµµα όταν και όπου την χρειαζόµαστε.

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

Page 58: C Notes

58

#include <stdio.h>

#define SIZE 50

main()

{

float list[SIZE];

readlist(list, SIZE);

sort(list, SIZE);

average(list, SIZE);

bargraph(list, SIZE);

}

Όταν χρησιµοποιούµε περιγραφικά ονόµατα για τις συναρτήσεις, είναι ευκολότερο να καταλάβουµε τι κάνει ένα πρόγραµµα και πώς αυτό οργανώνεται. Μπορούµε ακόµα να επεξεργαστούµε ξέχωρα την κάθε συνάρτηση µέχρι να πετύχουµε την σωστή εκτέλεση µιας εργασίας. Ένα ακόµη όφελος είναι ότι αν δηµιουργήσουµε τις συναρτήσεις κατά γενικό τρόπο, θα µπορούµε τότε να τις χρησιµοποιήσουµε και σ’ άλλα προγράµµατα.

Οι συναρτήσεις έχουν σαν σκοπό να µας βοηθήσουν να επικεντρώνουµε την προσοχή µας στην συνολική σχεδίαση ενός προγράµµατος και όχι στις λεπτοµέρειές του. Πριν να γράψουµε τον κώδικα µιας συνάρτησης, πρέπει να σκεφτούµε πρώτα τι δουλειά πρέπει να κάνει αυτή η συνάρτηση και πώς θα συσχετίζεται µε τ’ όλο πρόγραµµα.

Δηµιουργία και Χρήση µιας Απλής Συνάρτησης

Ακολουθεί ένα πρόγραµµα που χρησιµοποιεί τις συναρτήσεις main() και starbar(). Το πρόγραµµα τυπώνει µια επικεφαλίδα από γράµµατα και δύο σειρές από 65 αστεράκια, µία πριν και µία µετά την εκτύπωση των γραµµάτων. Η συνάρτηση main(), δηλ. το κυρίως πρόγραµµα τυπώνει την επικεφαλίδα και η συνάρτηση starbar() έχει αναλάβει τη δουλειά που γίνεται δύο φορές, δηλ. την εκτύπωση της σειράς µε τα 65 αστεράκια.

/* prog35.c – διαµόρφωση επικεφαλίδας */

#include <stdio.h>

#define NAME "Florina pe sempre "

#define ADDRESS "Florina"

#define PLACE "West Macedonia"

Page 59: C Notes

59

#define LIMIT 65

void starbar(); /* δήλωση της συνάρτησης */

main()

{

starbar();

printf("%s \n", NAME);

printf("%s \n", ADDRESS);

printf("%s \n", PLACE);

starbar();

}

void starbar() /* ορισµός της συνάρτησης starbar() */

{

int count;

for (count = 1; count <= LIMIT; count++)

putchar(‘*’);

putchar(‘\n’);

}

Το αποτέλεσµα του προγράµµατος θα είναι :

*****************************************************************

Florina per sempre

Florina

West Macedonia

*****************************************************************

Ας δώσουµε λίγη προσοχή στα παρακάτω σηµεία του προγράµµατος :

Page 60: C Notes

60

1. Η συνάρτηση starbar() καλείται από τη συνάρτηση main() χρησιµοποιώντας απλά τ’ όνοµά της. Μετά το όνοµα της συνάρτησης και τις παρενθέ-σεις πρέπει να ακολουθεί το σύµβολο (;), ως εξής : starbar();

Όταν το πρόγραµµα καθώς εκτελείται φθάσει σε µια κλήση συνάρτησης, αναζητά τη συνάρτηση αυτή και ακολουθεί τις εντολές που βρίσκονται σ' αυτήν. Όταν τελειώσει, επιστρέφει στην επόµενη γραµµή του καλούντος προγράµµατος.

2. Όταν γράφουµε µια συνάρτηση ακολουθούµε την ίδια µορφή που ξέρουµε από τη main(), δηλ. πρώτα το όνοµα, µετά το σύµβολο {, µετά η δήλωση των µεταβλητών, µετά οι προτάσεις ορισµού της συνάρτησης και τέλος το σύµβολο }.

3. Οι παρενθέσεις στη starbar() λένε στον µεταγλωττιστή ότι η starbar() είναι µια συνάρτηση. Η starbar() δεν ακολουθείται από το σύµβολο ;, πράγµα που σηµαίνει ότι η συνάρτηση αυτή ορίζεται παρά ότι χρησιµοποιείται.

4. Η µεταβλητή count στη starbar() είναι µια τοπική µεταβλητή, δηλ. µια µεταβλητή γνωστή µόνο για τη starbar(). Μπορούµε να χρησιµοποιήσουµε το όνοµα count και σ’ άλλες συναρτήσεις χωρίς κανένα πρόβληµα, ακόµα και µέσα στην ίδια την main().

5. Οι συναρτήσεις έχουν κι αυτές τύπους όπως οι µεταβλητές. Ο τύπος void χρησιµοποιείται για συναρτήσεις χωρίς τιµές επιστροφής και ο τύπος αυτός εµφανίζεται στον ορισµό της συνάρτησης.

6. Προγράµµατα που χρησιµοποιούν µια συνάρτηση πρέπει να περιέχουν τη δήλωση του τύπου πριν η συνάρτηση χρησιµοποιηθεί. Γι' αυτόν τον λόγο, η main() περιέχει την εξής δήλωση :

void starbar();

Έτσι δηλώνουµε ότι το πρόγραµµα χρησιµοποιεί µια συνάρτηση τύπου void που ονοµάζεται starbar() και ότι ο µεταγλωττιστής θα βρει τον ορισµό αυτής της συνάρτησης κάπου αλλού.

7. Η συνάρτηση starbar() δεν έχει είσοδο, αφού δεν χρειάζεται καµία πληροφορία από το καλούν πρόγραµµα.

Ορίσµατα Συνάρτησης

Ας δούµε τώρα πώς θα τροποποιηθεί η συνάρτηση starbar() που χρησιµοποιήσαµε προηγουµένως για να κεντράρει αυτή τη φορά όλους τους τίτλους. Η νέα µας συνάρτηση ονοµάζεται n_char() και θα χρησιµοποιήσουµε τώρα ορίσµατα γι' αυτή τη συνάρτηση, όπου το ένα θα είναι ο εκτυπούµενος χαρακτήρας και το άλλο ο αριθµός των επαναλήψεων αυτού του χαρακτήρα.

Η γραµµή µε τους αστερίσκους έχει πλάτος 65 διαστήµατα, άρα η συνάρτηση στην περίπτωση αυτή καλείται σαν n_char('*', 65). Η φράση Florina per sempre έχει πλάτος 18 διαστηµάτων, άρα για να την κεντράρουµε θα πρέπει να αφήσουµε 23 κενά διαστήµατα από δεξιά και από αριστερά της, δηλ. καλούµε την n_char(' ', 23).

Page 61: C Notes

61

/* prog36.c – κεντράρισµα επικεφαλίδας */

#include <stdio.h>

#define NAME "Florina pe sempre "

#define ADDRESS "Florina"

#define PLACE "West Macedonia"

#define LIMIT 65

#define SPACE ‘ ‘

void n_char(char ch, int num);

main()

{

int spaces;

n_char(‘*’, LIMIT); /* χρήση σταθερών ως ορίσµατα */

putchar(‘\n’);

n_char(SPACE, 23);

printf("%s \n", NAME);

spaces = (65 - strlen(ADDRESS))/2; /* υπολογίζει τα κενά διαστήµατα που θα αφήσει */

n_char(SPACE, spaces); /* µια µεταβλητή ως όρισµα */

printf("%s \n", ADDRESS);

n_char(SPACE, (65 - strlen(PLACE))/2); /* µια έκφραση ως όρισµα */

printf("%s \n", PLACE);

n_char(‘*’, LIMIT);

putchar(‘\n’);

}

void n_char(char ch, int num) /* ορισµός της συνάρτησης */

{

Page 62: C Notes

62

int count;

for (count=1; count<=num; count++)

putchar(ch);

}

Το αποτέλεσµα του προγράµµατος θα είναι :

*****************************************************************

Florina per sempre

Florina

West Macedonia

*****************************************************************

Ας δούµε τον ορισµό της παραπάνω συνάρτησης µε ορίσµατα αυτή τη φορά :

void n_char(ch, num)

char ch;

int num;

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

Για να δώσουµε τιµές στα τυπικά ορίσµατα ch και num χρησιµοποιούµε πραγµατικά ορίσµατα στην κλήση της συνάρτησης. Το τυπικό όρισµα είναι µια µεταβλητή στο πρόγραµµα που καλείται και το πραγµατικό όρισµα είναι µια συγκεκριµένη τιµή, που καταχωρείται στη µεταβλητή αυτή από το πρόγραµµα που καλεί.

Το πραγµατικό όρισµα µπορεί να είναι µια σταθερά, µια µεταβλητή ή ακόµη και µια πιο πολύπλοκη έκφραση. Ο,τιδήποτε κι αν είναι, το πραγµατικό όρισµα υπολογίζεται και η τιµή του µεταδίδεται (περνάει) στη συνάρτηση στα αντίστοιχα τυπικά ορίσµατα. Η συνάρτηση δεν ενδιαφέρεται, βέβαια, αν αυτή η τιµή που της περνιέται προέρχεται από µια σταθερά, µια µεταβλητή ή µια πιο γενική έκφραση.

Page 63: C Notes

63

Η Χρήση του return

Ας δούµε τώρα και την περίπτωση που µια συνάρτηση επιστρέφει µια τιµή στο πρόγραµµα που την κάλεσε. Η επόµενη συνάρτηση imin() επιστρέφει σαν τιµή το µικρότερο από τα δύο ορίσµατά της.

Ακολουθεί ένα πρόγραµµα :

/* prog37.c – βρίσκει τη µικρότερη από δύο τιµές */

#include <stdio.h>

main()

{

int num1, num2;

int imin();

while (scanf("%d %d", &num1, &num2) == 2)

printf("Το µικρότερο µεταξύ του %d και %d είναι το %d. \n",

num1, num2, imin(num1, num2));

}

int imin(int n, int m)

{

int min;

if (n < m)

min = n;

else

min = m;

return min;

}

Page 64: C Notes

64

Η λέξη-κλειδί return έχει ως αποτέλεσµα η τιµή οποιασδήποτε έκφρασης που την ακολουθεί, να είναι η τιµή επιστροφής της συνάρτησης. Στη συγκεκριµένη βέβαια περίπτωση, η τιµή επιστροφής της συνάρτησης είναι η τιµή της τοπικής µεταβλητής min. Η συνάρτηση imin() είναι τύπου int, όπως είναι και η µεταβλητή min. Η τιµή επιστροφής µιας συνάρτησης µπορεί να καταχωρηθεί σε µια µεταβλητή ή να χρησιµοποιηθεί σαν µέρος µιας έκφρασης.

Ακολουθεί και µια άλλη έκδοση της συνάρτησης imin() :

/* δεύτερη έκδοση της συνάρτησης ελάχιστης τιµής */

imin(int n, int m)

{

return (n<m) ? n : m;

}

Η χρήση της return έχει και το αποτέλεσµα ότι τερµατίζει τη συνάρτηση και ο έλεγχος επιστρέφει στην επόµενη πρόταση του προγράµµατος από εκείνη που την κάλεσε. Αυτό συµβαίνει ακόµα και αν η πρόταση return δεν είναι η τελευταία της συνάρτησης.

Ακολουθεί και µια τρίτη έκδοση της συνάρτησης imin() :

/* τρίτη έκδοση της συνάρτησης ελάχιστης τιµής */

imin(int n, int m)

{

if (n<m)

return (n);

else

return(m);

}

Η απλή πρόταση return; προκαλεί τον τερµατισµό της συνάρτησης και την επιστροφή του ελέγχου στην καλούσα συνάρτηση. Αυτή η µορφή χρησιµοποιείται σε συναρτήσεις του τύπου void γιατί δεν επιστρέφει καµία τιµή.

Τύποι Συναρτήσεων

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

Page 65: C Notes

65

int. Η δήλωση του τύπου είναι µέρος του ορισµού της συνάρτησης και αναφέρεται στην τιµή επιστροφής και όχι στα ορίσµατα της συνάρτησης. Μια δήλωση της συνάρτησης λέει στο πρόγραµµα τι τύπου είναι η συνάρτηση, ενώ ο ορισµός της συνάρτησης παρέχει τον πραγµατικό κώδικά της. Η δήλωση µιας συνάρτησης πρέπει να γίνεται πριν από το σηµείο όπου χρησιµοποιείται η συνάρτηση.

Στην ANSI C, µπορούµε στη δήλωση µιας συνάρτησης να δηλώνουµε και τον τύπο των µεταβλητών της. Το αποτέλεσµα είναι ένα πρωτότυπο συνάρτησης, δηλ. µια δήλωση συνάρτησης όπου καθορίζονται ο τύπος της τιµής επιστροφής, ο αριθµός των ορισµάτων και ο τύπος των ορισµάτων της συνάρτησης.

Ακολουθούν παραδείγµατα :

int imax(int, int);

int imax(int a, int b);

Ο Τελεστής &

Μερικές συναρτήσεις, όπως η scanf(), καταχωρούν τιµές σε µεταβλητές του καλούντος προγράµµατος και χρησιµοποιούν τον τελεστή διεύθυνσης & στα ορίσµατα. Ο τελεστής & µάς δίνει τη διεύθυνση στην οποία αποθηκεύεται µια µεταβλητή. Για παράδειγµα, αν p είναι το όνοµα µιας µεταβλητής, τότε &p είναι η διεύθυνση της µεταβλητής p στη µνήµη. Αν η µεταβλητή p έχει τιµή 100 και είναι αποθηκευµένη στη διεύθυνση 15879, τότε η παρακάτω πρόταση :

printf("%d %u\n", p, &p);

θα δώσει το εξής αποτέλεσµα :

100 15879

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

/* prog38.c – έλεγχος για το πού βρίσκονται αποθηκευµένες µεταβλητές */

#include <stdio.h>

void myfunction(); /* δήλωση συνάρτησης */

main()

{

int p=2, b=5;

Page 66: C Notes

66

printf("Στη main(), p = %d και &p = %p \n", p, &p);

printf("Στη main(), b = %d και &b = %p \n", b, &b);

myfunction(p);

}

void myfunction(int b); /* ορισµός συνάρτησης */

{

int p = 10;

printf("Στη mikado(), p = %d και &p = %p \n", p, &p);

printf("Στη mikado(), b = %d και &b = %p \n", b, &b);

}

Χρησιµοποιήσαµε εδώ τη µορφή %p για την εκτύπωση των διευθύνσεων· αν, όµως, το σύστηµά µας δεν διαθέτει αυτή τη µορφή, τότε µπορούµε να δοκιµάσουµε µε τη µορφή %u.

Το αποτέλεσµα του προηγούµενου προγράµµατος θα είναι το εξής :

Στη main(), p = 2 και &p = FFD8

Στη main(), b = 5 και &b = FFDA

Στη myfunction(), p = 10 και &p = FFD0

Στη myfunction(), b = 2 και &b = FFD6

Οι διευθύνσεις εµφανίζονται εδώ σε 16δική µορφή. Από το αποτέλεσµα βλέπουµε ότι οι δύο µεταβλητές p έχουν διαφορετικές διευθύνσεις, όπως και οι δύο µεταβλητές b. Δηλαδή, ο υπολογιστής τις θεωρεί σαν τέσσερις ξεχωριστές µεταβλητές.

Αλλαγή των Μεταβλητών στο Πρόγραµµα που Καλεί Μερικές φορές θέλουµε µια συνάρτηση να προκαλεί αλλαγές στις µεταβλητές µιας άλλης συνάρτησης. Ας πάρουµε το δηµοφιλές παράδειγµα της ανταλλαγής των τιµών δύο µεταβλητών x και y.

Ξέρουµε ότι αυτό γίνεται µε τη χρήση µιας τρίτης βοηθητικής µεταβλητής temp, ως εξής :

temp = x;

Page 67: C Notes

67

x = y;

y = temp;

Χρησιµοποιούµε τα ονόµατα x και y για τις µεταβλητές της main() και u και v για τις µεταβλητές της συνάρτησης interchange().

/* prog39.c – ανταλλαγή τιµών µεταβλητών */

#include <stdio.h>

void interchange();

main()

{

int x = 5, y = 10;

printf("Αρχικά x = %d και y = %d. \n", x, y);

interchange(x, y);

printf("Τώρα x = %d και y = %d. \n", x, y);

}

void interchange(int u, int v)

{

int temp;

printf("Αρχικά u = %d και v = %d. \n", u, v);

temp = u;

u = v;

v = temp;

printf("Τώρα u = %d και v = %d. \n", u, v);

}

Το αποτέλεσµα του προγράµµατος θα είναι το εξής :

Αρχικά x = 5 και y = 10

Page 68: C Notes

68

Αρχικά u = 5 και v = 10

Τώρα u = 10 και v = 5

Τώρα x = 5 και y = 10

Βλέπουµε ότι η συνάρτηση interchange() λειτουργεί σωστά, αφού ανταλλάσσει τις τιµές των u και v. Εκείνο που µας ενδιαφέρει, όµως, εµάς είναι η µετάδοση των τιµών αυτών πίσω στη main(). Όπως ξέρουµε, µε την πρόταση return µπορούµε να µεταδώσουµε πίσω στο καλούν πρόγραµµα µόνο µία τιµή.

Τι µπορούµε, όµως, να κάνουµε;

Για να µεταδώσουµε δύο τιµές µιας συνάρτησης στη συνάρτηση που την κάλεσε, πρέπει να χρησιµοποιήσουµε τους δείκτες, που είναι συµβολικές παραστάσεις διευθύνσεων.

Εισαγωγή στους Δείκτες (Pointers)

Χρησιµοποιήσαµε προηγουµένως τον τελεστή & για να βρούµε τη διεύθυνση της µεταβλητής p. Η &p δηλαδή είναι ένας δείκτης της p. Η πραγµατική διεύθυνση είναι ένας 16δικός αριθµός και η συµβολική παράσταση &p είναι µια σταθερά δείκτη. Η µεταβλητή p δεν πρόκειται να αλλάξει διεύθυνση µέσα στο πρόγραµµα, αν και µπορεί να αλλάξει τιµή. Η C έχει και µεταβλητές δείκτη που έχουν µια διεύθυνση ως τιµή.

Ακολουθούν παραδείγµατα :

ptr = &p; /* καταχωρεί τη διεύθυνση της p στην ptr */

ptr = &b; /* ο δείκτης ptr δείχνει τώρα στη b αντί για την p */

Η ptr δηλαδή είναι µια µεταβλητή και η &p είναι µια σταθερά.

Για να δηλώσουµε τον τύπο µιας µεταβλητής δείκτη, πρέπει να χρησιµοποιήσουµε τον τελεστή έµµεσης αναφοράς (*).

val = *ptr /* βρίσκει την τιµή που δείχνει ο δείκτης ptr */

Πώς δηλώνουµε, όµως, τους δείκτες;

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

Οι δείκτες δηλώνονται ως εξής :

int *pi; /* pi είναι ένας δείκτης σε µια ακέραια µεταβλητή */

char *pc; /* pc είναι ένας δείκτης σε µια µεταβλητή χαρακτήρα */

float *pf, *pg; /* pf, pg είναι δείκτες σε µεταβλητές float */

Page 69: C Notes

69

Από τα παραπάνω συµπεραίνουµε ότι pi είναι ο δείκτης και ότι η *pi είναι τύπου int. Παρόµοια, η τιµή *pc την οποία δείχνει ο pc είναι τύπου char.

Οι Δείκτες και οι Συναρτήσεις

Ακολουθεί ένα πρόγραµµα όπου γίνεται επιστροφή τιµών στη συνάρτηση που καλεί.

/* prog40.c – χρήση δεικτών για την ανταλλαγή τιµών */

#include <sdtio.h>

void interchange();

main()

{

int x = 5, y = 10;

printf("Αρχικά x = %d και y = %d. \n", x, y);

interchange(&x, &y); /* στείλε τις διευθύνσεις στη συνάρτηση*/

printf("Τώρα x = %d και y = %d. \n", x, y);

}

void interchange(int *u, int *v) /* οι u και v είναι τώρα δείκτες */

{

int temp;

temp = *u; /* η temp παίρνει την τιµή που δείχνει η u */

*u = *v;

*v = temp;

}

Το αποτέλεσµα θα είναι :

Αρχικά x = 5 και y = 10

Τώρα x = 10 και y = 5

Page 70: C Notes

70

Ας προσέξουµε τα εξής σηµεία :

1. Η κλήση της συνάρτησης γίνεται ως εξής : interchange(&x, &y);, δηλαδή, αντί να στέλνονται οι τιµές των x και y, στέλνονται οι διευθύνσεις τους. Έτσι, τα τυπικά ορίσµατα u και v της συνάρτησης interchange() έχουν διευθύνσεις ως τιµές και πρέπει να δηλωθούν ως δείκτες.

2. Εφόσον οι x και y είναι ακέραιοι, δηλώνουµε τις u και v ως δείκτες σε ακέραιους.

3. Θυµηθείτε ότι η u έχει την τιµή &x, έτσι η u δείχνει τη x, δηλαδή η *u µάς δίνει την τιµή του x.

4. Δεν πρέπει να γράψουµε temp = u;, γιατί έτσι θα αποθηκευτεί η διεύθυνση της x και όχι η τιµή της.

Θέλουµε µια συνάρτηση να ανταλλάσσει τις τιµές δύο µεταβλητών. Δίνοντας στη συνάρτηση τις διευθύνσεις των µεταβλητών αυτών, αποκτάµε πρόσβαση σ' αυτές τις µεταβλητές. Χρησιµοποιώντας δείκτες και τον τελεστή *, η συνάρτηση µπορεί να εξετάσει τις τιµές που βρίσκονται σ' αυτές τις θέσεις και να τις αλλάξει.

Με τους δείκτες δηλαδή µπορούµε να µπούµε στη main() και να αλλάξουµε ό,τι είναι αποθηκευµένο εκεί.

Εισαγωγή στους Πίνακες

Ένας πίνακας αποτελείται από µια σειρά στοιχείων ενός τύπου δεδοµένων. Το πρόγραµµα πρέπει να γνωρίζει από πόσα στοιχεία αποτελείται ένας πίνακας καθώς και τον τύπο των στοιχείων αυτών. Ο τύπος των στοιχείων ενός πίνακα µπορεί να είναι ένας από τους τύπους µεταβλητών.

Ακολουθούν µερικές δηλώσεις πινάκων :

main()

{

float times[365]; /* πίνακας µε 365 στοιχεία τύπου float */

char cities[12]; /* πίνακας µε 12 στοιχεία τύπου char */

int numbers[50]; /* πίνακας µε 50 στοιχεία τύπου int */

}

Οι αγκύλες [ και ] µάς λένε ότι πρόκειται για πίνακα και ο αριθµός που είναι µέσα στις αγκύλες µάς δείχνει τον αριθµό των στοιχείων του πίνακα. Για να αναφερθούµε σ’ ένα συγκεκριµένο στοιχείο του πίνακα, χρησιµοποιούµε έναν αριθµό που ονοµάζεται δείκτης πίνακα και που η αρίθµησή του αρχίζει από το 0.

Page 71: C Notes

71

Ο πίνακας δηλ. είναι µια µεταβλητή µε δείκτη και για να αναφερθούµε πλήρως σ’ ένα στοιχείο του, χρειαζόµαστε το όνοµα του πίνακα και την τιµή του δείκτη του πίνακα. Έτσι, π.χ. το times[0] είναι το πρώτο στοιχείο του πίνακα times και το times[364] είναι το 365ο και τελευταίο στοιχείο του.

Αυτόµατες, Εξωτερικές και Στατικές Μεταβλητές

Μια αυτόµατη µεταβλητή ή ένας αυτόµατος πίνακας είναι µια µεταβλητή ή ένας πίνακας που ορίζεται µέσα σε µια συνάρτηση. Όπως ήδη ξέρουµε, µια µεταβλητή που ορίζεται µέσα σε µια συνάρτηση (τοπική µεταβλητή), ανήκει σ' αυτή τη συνάρτηση και το όνοµά της µπορεί να ξαναχρησιµοποιηθεί και αλλού. Όταν τελειώσει η κλήση της συνάρτησης, τότε απελευθερώνεται ο χώρος της µνήµης που χρησιµοποιήθηκε για τις τοπικές της µεταβλητές.

Μπορούµε να δώσουµε αρχικές τιµές σ’ αυτόµατους πίνακες ως εξής :

main()

{

int powers[8] = {1, 2, 4, 6, 8, 16, 32, 64};

}

Επειδή ο πίνακας ορίζεται µέσα στη main(), είναι ένας αυτόµατος πίνακας και η απόδοση αρχικών τιµών σ' αυτόν γίνεται µε τη χρήση µιας λίστας τιµών που χωρίζονται µε κόµµα και που είναι κλεισµένες σε αγκύλες. Μια εξωτερική µεταβλητή ή ένας εξωτερικός πίνακας είναι µια µεταβλητή ή ένας πίνακας που ορίζεται έξω από µια συνάρτηση.

Ακολουθεί ένα παράδειγµα :

int report;

int numbers[5] = {12, 10, 8, 9, 6};

main()

{

...

}

int feed(int n)

{

Page 72: C Notes

72

...

}

Οι εξωτερικές µεταβλητές διαφέρουν από τις αυτόµατες στο ότι µπορούν να χρησιµοποιηθούν απ' όλες τις συναρτήσεις ενός προγράµµατος, παραµένουν για όσο χρόνο εκτελείται ένα πρόγραµµα και δεν χάνονται όταν τελειώνει µια συγκεκριµένη συνάρτηση. Ακόµη, έχουν αρχική τιµή µηδέν όταν δεν ορίζεται κάτι άλλο. Έτσι, η µεταβλητή report στο παραπάνω πρόγραµµα έχει αρχική τιµή ίση µε 0.

Μια στατική µεταβλητή ή ένας στατικός πίνακας ορίζονται µέσα σε µια συνάρτηση µε τη λέξη-κλειδί static :

int account(int n, int m)

{

static int beans[2] = {343, 332};

...

}

Μια µεταβλητή που δηλώνεται µε τη λέξη-κλειδί static µπορεί να είναι τοπική σε µια συνάρτηση, αλλά διατηρεί τις τιµές της µεταξύ των κλήσεων µιας συνάρτησης και της αποδίδεται αρχική τιµή ίση µε 0, όταν βέβαια δεν δηλώνεται κάτι άλλο.

Απόδοση Αρχικών Τιµών σε Πίνακες

Ακολουθεί ένα πρόγραµµα που καταχωρεί τον αριθµό των ηµερών ανά µήνα σ’ έναν πίνακα µε απόδοση αρχικών τιµών και µετά τους εκτυπώνει.

/* prog41.c – απόδοση αρχικών τιµών στις ηµέρες κάθε µήνα */

#include <stdio.h>

#define MONTHS 12

int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

main()

{

int index;

for (index=0; index<MONTHS; index++)

Page 73: C Notes

73

printf ("Ο µήνας %d έχει %d ηµέρες.\n", index+1, days[index]);

}

Το αποτέλεσµα θα είναι ως εξής :

Ο µήνας 1 έχει 31 ηµέρες.

Ο µήνας 2 έχει 28 ηµέρες.

Ο µήνας 3 έχει 31 ηµέρες.

Ο µήνας 4 έχει 30 ηµέρες.

Ο µήνας 5 έχει 31 ηµέρες.

Ο µήνας 6 έχει 30 ηµέρες.

Ο µήνας 7 έχει 31 ηµέρες.

Ο µήνας 8 έχει 31 ηµέρες.

Ο µήνας 9 έχει 30 ηµέρες.

Ο µήνας 10 έχει 31 ηµέρες.

Ο µήνας 11 έχει 30 ηµέρες.

Ο µήνας 12 έχει 31 ηµέρες.

Βλέπουµε στο παραπάνω παράδειγµα ότι ο αριθµός των στοιχείων της λίστας πρέπει να είναι ίδιος µε το µέγεθος του πίνακα. Αν δώσουµε περισσότερα στοιχεία σ’ έναν πίνακα απ' ό,τι είναι το δηλωµένο µέγεθός του, τότε αυτό θεωρείται λάθος.

Καταχώρηση Τιµών σε Πίνακες

Ακολουθεί ένα πρόγραµµα που καταχωρεί άρτιους αριθµούς σ’ έναν αυτόµατο πίνακα :

#include <stdio.h>

#define SIZE 20

main()

{

int counter, evens[SIZE];

Page 74: C Notes

74

for (counter=0; counter<SIZE; counter++)

evens[counter] = 2 * counter;

}

Η C, όµως, δεν επιτρέπει την καταχώρηση τιµών από έναν πίνακα σ’ έναν άλλον, ούτε µπορούµε να χρησιµοποιήσουµε τη µορφή της λίστας στοιχείων µέσα σε αγκύλες, εκτός κι αν πρόκειται για απόδοση αρχικών τιµών.

Ακολουθεί ένα πρόγραµµα που µας λέει τι δεν µπορούµε να κάνουµε µε τους πίνακες στη C :

#include <stdio.h>

#define SIZE 5

main()

{

int pina[SIZE] = {5, 3, 2, 8}; /* όλα εντάξει */

int pinb[SIZE];

pinb = pina; /* δεν επιτρέπεται */

pinb[SIZE] = pina[SIZE]; /* ανεπίτρεπτο */

pinb[SIZE] = {5, 3, 2, 8}; /* δεν δουλεύει εδώ */

}

Δείκτες σε Πίνακες

Είδαµε στα προηγούµενα ότι οι δείκτες (pointers) αποτελούν έναν συµβολικό τρόπο για να χρησιµοποιούµε διευθύνσεις µεταβλητών. Στην πραγµατικότητα, ο συµβολισµός των πινάκων είναι µια µεταµφιεσµένη χρήση των δεικτών.

Το όνοµα ενός πίνακα είναι και ένας δείκτης, που δείχνει στο πρώτο στοιχείο του. Δηλαδή, αν pina είναι ένας πίνακας, τότε µε :

pina == &pina[0]

παριστάνεται και η διεύθυνση µνήµης του πρώτου στοιχείου του πίνακα.

Ακολουθεί ένα παράδειγµα που κάνει πράξεις µε τις τιµές ενός δείκτη :

Page 75: C Notes

75

/* prog42.c – πρόσθεση δείκτη */

#include <stdio.h>

#define SIZE 4

main()

{

int pina[SIZE], *pti, index;

float pinb[SIZE], *ptf;

pti = pina; /* καταχωρεί τη διεύθυνση του πίνακα σε δείκτη */

ptf = pinb;

for (index=0; index<SIZE; index++)

printf("δείκτες + %d : %10u %10u\n", index, pti+index,

ptf+index);

}

Το αποτέλεσµα θα είναι ως εξής :

δείκτες + 0 : 56014 56026

δείκτες + 1 : 56016 56030

δείκτες + 2 : 56018 56034

δείκτες + 3 : 56020 56038

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

Για να δούµε τι γίνεται από κοντά :

56014 + 1 = 56016 ;

56026 + 1 = 56030 ;

Ξέρουµε ότι ο τύπος int χρησιµοποιεί 2 bytes για αποθήκευση και ο τύπος float 4 bytes. Όταν λοιπόν προσθέτουµε 1 στον αντίστοιχο δείκτη, τότε η C προσθέτει µια µονάδα

Page 76: C Notes

76

αποθήκευσης. Για τους πίνακες, όµως, αυτό σηµαίνει ότι η διεύθυνση αυξάνεται στη διεύθυνση του επόµενου στοιχείου και όχι στο επόµενο byte.

Προσθέτοντας έναν ακέραιο σ' έναν δείκτη ή αυξάνοντας έναν δείκτη, αλλάζει η τιµή του δείκτη κατά τον αριθµό των bytes του στοιχείου που δείχνει ο δείκτης.

Ακολουθούν µερικές ενδιαφέρουσες εκφράσεις :

dates + 2 == &dates[2] /* ίδια διεύθυνση */

*(dates + 2) == dates[2] /* ίδια τιµή */

Αυτές οι ισότητες δείχνουν τη στενή σχέση µεταξύ πινάκων και δεικτών. Αυτό σηµαίνει ότι µπορούµε να χρησιµοποιήσουµε ένα δείκτη για να προσδιορίσουµε ένα συγκεκριµένο στοιχείο ενός πίνακα και να πάρουµε την τιµή του.

Δεν πρέπει, όµως, να συγχέουµε την τιµή της *(dates+2) µε την τιµή *dates+2, γιατί :

*(dates + 2) /* είναι η τιµή του 3ου στοιχείου του πίνακα dates */

*dates + 2 /* το 2 προστίθεται στην τιµή του 1ου στοιχείου */

Μπορούµε συνεπώς να χρησιµοποιούµε και δείκτες για να δουλέψουµε µε τους πίνακες.

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

/* prog43.c – χρησιµοποιεί δείκτες αντί για πίνακες */

#include <stdio.h>

#define MONTHS 12

int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

main()

{

int index;

for (index=0; index<MONTHS; index++)

printf ("Ο µήνας %d έχει %d ηµέρες.\n", index+1,

*(days+index));

}

Page 77: C Notes

77

Συναρτήσεις, Πίνακες και Δείκτες

Η C δεν επιτρέπει ολόκληροι πίνακες να είναι ορίσµατα µιας συνάρτησης, αλλά για να κάνουµε τη δουλειά µας µπορούµε να περάσουµε (µεταβιβάσουµε) τη διεύθυνση ενός πίνακα σε µια συνάρτηση. Η συνάρτηση µπορεί µετά να χρησιµοποιήσει αυτήν την τιµή για τον χειρισµό του πρωτότυπου πίνακα.

Είναι ένα κλασσικό παράδειγµα όπου πρέπει να χρησιµοποιήσουµε δείκτες για τον χειρισµό ενός πίνακα. Μπορούµε να χρησιµοποιήσουµε είτε συµβολισµό πίνακα είτε συµβολισµό δείκτη µέσα στη συνάρτηση.

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

/* prog44.c – άθροισµα στοιχείων ενός πίνακα */

#include <stdio.h>

#define SIZE 10

long sum();

main()

{

static int pina[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};

long answer;

answer = sum(pina, SIZE);

printf("Το άθροισµα των στοιχείων είναι %d.\n", answer);

}

long sum(int *ar, int n) /* ο ar είναι ένας δείκτης */

{

int i;

long total = 0;

for (i=0; i<n; i++)

{

Page 78: C Notes

78

total += *ar; /* πρόσθεση τιµής στο total */

ar++; /* ο δείκτης στο επόµενο στοιχείο */

}

return total;

}

Ο ar ξεκινάει δείχνοντας το πρώτο στοιχείο του πίνακα pina και µε την πρόταση καταχώρησης total += *ar προστίθεται η τιµή του πρώτου στοιχείου, δηλ. η 20, στη µεταβλητή total. Μετά, η έκφραση ar++ αυξάνει τη µεταβλητή δείκτη ar έτσι ώστε αυτή να δείχνει στο επόµενο στοιχείο του πίνακα.

Μπορούµε να γράψουµε τις προηγούµενες δύο εντολές και έτσι :

total += *ar++;

Δηλαδή, αυξάνεται ο δείκτης κατά ένα και όχι η τιµή που δείχνει και µάλιστα αυξάνεται µετά τη χρήση της τιµής του. Εάν χρησιµοποιήσουµε *++ar, τότε η σειρά θα ήταν : αύξηση του δείκτη και µετά χρήση της τιµής που δείχνει. Αλλά αν χρησιµοποιήσουµε την έκφραση (*ar)++, τότε το πρόγραµµα θα κάνει χρήση της τιµής που δείχνει η ar και µετά θα αυξήσει την τιµή αυτή κατά ένα και όχι τον δείκτη.

Οι Λειτουργίες των Δεικτών

Η C έχει πέντε βασικές λειτουργίες δεικτών. Για να δούµε από κοντά τα αποτελέσµατα της κάθε λειτουργίας, τυπώνουµε την τιµή του δείκτη, δηλ. τη διεύθυνση που δείχνει, την τιµή που είναι αποθηκευµένη στη διεύθυνση που δείχνει ο δείκτης και ακόµη και τη διεύθυνση του ίδιου του δείκτη.

Ακολουθεί ένα παράδειγµα :

/* prog45.c – λειτουργίες δεικτών */

#include <stdio.h>

main()

{

static int pina[3] = {100, 200, 300};

int *ptr1, *ptr2;

ptr1 = pina;/* καταχώρηση της διεύθυνσης του πίνακα σ' έναν δείκτη */

ptr2 = &pina[2]; /* καταχώρηση της διεύθυνσης του 3ου στοιχείου */

Page 79: C Notes

79

printf("ptr1 = %u, *ptr1 = %d, &ptr1 = %u\n", ptr1, *ptr1, &ptr1);

ptr1++; /* αυξάνουµε τον δείκτη */

printf("ptr1 = %u, *ptr1 = %d, &ptr1 = %u\n", ptr1, *ptr1, &ptr1);

printf("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);

++ptr2; /* περνάµε τα όρια του πίνακα */

printf("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);

printf("ptr2 - ptr1 = %u\n", ptr2 - ptr1);

/* τυπώνουµε τη διαφορά των δεικτών */

}

Το αποτέλεσµα θα είναι ως εξής :

ptr1 = 234, *ptr1 = 100, &ptr1 = 3606

ptr1 = 236, *ptr1 = 200, &ptr1 = 3606

ptr2 = 238, *ptr2 = 300, &ptr2 = 3604

ptr2 = 240, *ptr2 = 1910, &ptr2 = 3604

ptr2 – ptr1 = 2

Ας δούµε από κοντά τις πέντε βασικές λειτουργίες που µπορούµε να κάνουµε µε τις µεταβλητές δείκτη :

1. Καταχώρηση. Μπορούµε να καταχωρήσουµε µια διεύθυνση σ' έναν δείκτη, είτε χρησιµοποιώντας ένα όνοµα πίνακα ή χρησιµοποιώντας τον τελεστή διεύθυνσης & και την τιµή µιας µεταβλητής.

2. Εύρεση τιµής. Ο τελεστής * µάς δίνει την τιµή που βρίσκεται αποθηκευµένη στη θέση που δείχνεται από τον δείκτη.

3. Απόκτηση της διεύθυνσης ενός δείκτη. Και οι µεταβλητές δείκτη έχουν µια διεύθυνση και µια τιµή.

4. Αύξηση-µείωση ενός δείκτη. Με την αύξησή του ένας δείκτης µετακινείται στο επόµενο στοιχείο του πίνακα. Φυσικά, µε ανάλογο τρόπο µπορούµε και να µειώσουµε έναν δείκτη. Η λειτουργία, όµως, ++ptr2 στο προηγούµενο παράδειγµα είχε σαν αποτέλεσµα ο ptr2 να

Page 80: C Notes

80

µετακινηθεί κατά δύο bytes και να δείχνει ο,τιδήποτε µπορεί να αποθηκευθεί µετά τον πίνακα. Επίσης, πρέπει να θυµόµαστε ότι µπορούµε να χρησιµοποιούµε τους τελεστές αύξησης ή µείωσης µε τις µεταβλητές δείκτη, αλλά όχι µε σταθερές δείκτη. Δηλαδή, το ptr2 = urn++; δεν επιτρέπεται, όπως δεν επιτρέπεται και το 3++.

5. Διαφορά. Μπορούµε να βρούµε τη διαφορά µεταξύ δύο δεικτών.

Οι Πίνακες Πολλών Διαστάσεων

Ένας µετεωρολόγος θέλει να αναλύσει τα δεδοµένα της µηνιαίας βροχόπτωσης σε µια διάρκεια 5 ετών, δηλ. θα χρειαστεί συνολικά 5 Χ 12 = 60 µεταβλητές. Για να παραστήσει αυτά τα δεδοµένα µπορεί να χρησιµοποιήσει 60 ανεξάρτητες µεταβλητές, µία για κάθε στοιχείο δεδοµένων, κάτι φυσικά πολύ άβολο. Κάτι καλύτερο θα ήταν ένας πίνακας µε 60 στοιχεία, αλλά ακόµα καλύτερο θα ήταν αν µπορούσε να κρατήσει ξεχωριστά τα 12 δεδοµένα για τον κάθε χρόνο.

Μπορεί δηλαδή να χρησιµοποιήσει 5 πίνακες, έναν για τα 12 στοιχεία του κάθε έτους, αλλά τι γίνεται αν τα έτη αυξηθούν και γίνουν 50; Μια καλή λύση είναι να χρησιµοποιήσουµε έναν πίνακα από πίνακες. Δηλ., ο κύριος πίνακας θα έχει 5 στοιχεία και το κάθε στοιχείο του θα είναι ένας πίνακας από 12 στοιχεία. Μιλάµε δηλαδή για πίνακα δύο διαστάσεων.

Αυτό γίνεται ως εξής :

static float rain[5][12];

Ο πίνακας rain είναι δισδιάστατος και αποτελείται από 5 γραµµές και από 12 στήλες. Αν αλλάξουµε τον δεύτερο δείκτη, κινούµαστε σε µια γραµµή και αν αλλάξουµε τον πρώτο δείκτη, κινούµαστε κατά µήκος µιας στήλης. Για το συγκεκριµένο παράδειγµα, ο δεύτερος δείκτης δείχνει τους µήνες και ο πρώτος τα χρόνια.

Ας δούµε κι ένα πρόγραµµα µ’ αυτόν τον δισδιάστατο πίνακα. Ο στόχος του προγράµµατος είναι να βρεθεί η συνολική βροχόπτωση για κάθε χρόνο, η µέση ετήσια βροχόπτωση και η µέση µηνιαία βροχόπτωση.

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

Σταθερές Συµβολοσειράς Χαρακτήρα

Όταν ο µεταγλωττιστής της C συναντά κάτι ανάµεσα σε διπλά εισαγωγικά " ", το θεωρεί σαν σταθερά συµβολοσειράς και οι χαρακτήρες µέσα στα εισαγωγικά αυτά µαζί µε τον χαρακτήρα '\0' αποθηκεύονται σε γειτονικές θέσεις της µνήµης.

Εάν, όµως, θέλουµε να χρησιµοποιήσουµε µέσα σε µια συµβολοσειρά διπλά εισαγωγικά, τότε πρέπει να βάλουµε πριν απ’ αυτά µια πλάγια κάθετο, ως εξής :

Page 81: C Notes

81

printf("\"Τρέξε, Μαρία!\" είπε η Βάσω.\n");

Το αποτέλεσµα θα είναι το εξής :

"Τρέξε, Μαρία!" είπε η Βάσω.

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

Πίνακες Συµβολοσειράς Χαρακτήρα

Μπορούµε να δώσουµε αρχικές τιµές σε µια συµβολοσειρά χαρακτήρα ως εξής:

char line[ ] = "Περιορίσου σε µια γραµµή";

Θα µπορούσαµε να κάνουµε το ίδιο και µε τον γνωστό τρόπο απόδοσης αρχικών τιµών σ’ έναν πίνακα ως εξής :

char line[ ] = {'Π', 'ε', 'ρ', 'ι', 'ο', 'ρ', 'ί', 'σ', 'ο', 'υ', ' ', 'σ', 'ε', ' ', 'µ', 'ι', 'α', ' ', 'γ', 'ρ', 'α', 'µ', 'µ', 'ή', '\0'};

H χρήση του µηδενικού χαρακτήρα '\0' εξηγεί ότι πρόκειται για συµβολοσειρά και όχι για πίνακα χαρακτήρων. Και στις δύο µορφές, ο µεταγλωττιστής µετράει τους χαρακτήρες και δίνει στον πίνακα το αντίστοιχο µέγεθος. Όπως συµβαίνει και µε τους άλλους πίνακες, το όνοµα του πίνακα line είναι ένας δείκτης στο πρώτο στοιχείο του πίνακα :

line == &line[0]

*line == 'Π'

*(line+1) == line[1] == 'ε'

Μπορούµε συνεπώς να χρησιµοποιήσουµε τον συµβολισµό δείκτη για να δηµιουργήσουµε µια συµβολοσειρά :

char *Florina = "\nFlorina per sempre";

που ισοδυναµεί µε το :

static char Florina[ ] = "\nFlorina per sempre";

Είσοδος Συµβολοσειράς

Για το διάβασµα µιας συµβολοσειράς σ' ένα πρόγραµµα, πρέπει να κάνουµε δύο πράγµατα : να δεσµεύσουµε τον χώρο αποθήκευσης γι αυτή τη συµβολοσειρά και να χρησιµοποιήσουµε

Page 82: C Notes

82

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

char name[81];

Αφού έχει δηµιουργηθεί χώρος στη µνήµη για τη συµβολοσειρά, µπορούµε πλέον να τη διαβάσουµε. Θα δούµε τις συναρτήσεις διαβάσµατος συµβολοσειρών gets() και scanf().

Η Συνάρτηση gets()

Η συνάρτηση αυτή διαβάζει χαρακτήρες µέχρι να συναντήσει τον χαρακτήρα enter '\n'. Η συνάρτηση αυτή δέχεται όλους τους χαρακτήρες πριν από τον χαρακτήρα '\n', προσθέτει τον µηδενικό χαρακτήρα '\0' και δίνει τη συµβολοσειρά στο καλούν πρόγραµµα. Ο χαρακτήρας νέας γραµµής διαβάζεται και αγνοείται.

Ακολουθεί ένα απλό παράδειγµα χρήσης της συνάρτησης gets() :

/* prog46.c – διάβασµα ενός ονόµατος */

#include <stdio.h>

#define MAX 81

main()

{

char name[MAX]; /* δέσµευση χώρου */

printf("Γεια σου, ποιο είναι το όνοµά σου ;\n");

gets(name);

printf("Γεια σου, %s.\n", name);

}

Αν όλα δουλέψουν σωστά, η gets() επιστρέφει τη διεύθυνση της συµβολοσειράς που διάβασε, αν όµως κάτι πάει στραβά ή αν η gets() συναντήσει EOF, επιστρέφει µια µηδενική διεύθυνση ή τίποτα.

Η κενή διεύθυνση καλείται δείκτης κενού, παριστάνεται στο αρχείο <stdio.h> µε τη σταθερά NULL και δεν έχει φυσικά καµία σχέση µε τον χαρακτήρα του κενού διαστήµατος. Έτσι, µε τη gets() µπορούµε να ελέγχουµε για πιθανά λάθη ως εξής :

while (gets(name) != NULL)

Page 83: C Notes

83

Η Συνάρτηση scanf()

Η βασική διαφορά των δύο συναρτήσεων είναι στον τρόπο που αποφασίζουν ότι έχουν φθάσει στο τέλος της συµβολοσειράς. Μπορούµε να πούµε ότι η scanf() είναι περισσότερο µια συνάρτηση "απόκτησης λέξης" παρά "απόκτησης συµβολοσειράς".

Η συνάρτηση scanf() έχει δύο επιλογές. Ξεκινά πάντα µε τον πρώτο µη-λευκό χαρακτήρα που συναντάει. Αν χρησιµοποιούµε τον προσδιοριστή %s, η συµβολοσειρά φθάνει µέχρι τον επόµενο λευκό χαρακτήρα χωρίς να τον περιλαµβάνει. Αν καθορίσουµε ένα εύρος πεδίου, π.χ. %10s, τότε η scanf() λαµβάνει υπόψη της µέχρι 10 χαρακτήρες ή µέχρι τον πρώτο λευκό χαρακτήρα.

Όπως είδαµε και στα προηγούµενα, η τιµής επιστροφής της scanf() είναι µια ακέραια τιµή, που είναι ίση µε το αριθµό των στοιχείων που διαβάστηκαν µε επιτυχία ή µε τον χαρακτήρα EOF αν συναντήσει το τέλος αρχείου.

Ακολουθεί ένα παράδειγµα :

/* prog47. c – χρήση της scanf() */

#include <stdio.h>

main()

{

static char name1[11], name2[11];

int count;

printf("Παρακαλώ δώστε 2 ονόµατα. \n");

count = scanf("%5s %10s", name1, name2);

printf("Διάβασα τα %d ονόµατα %s και %s. \n", count, name1, name2);

}

Το αποτέλεσµα θα είναι :

Παρακαλώ δώστε 2 ονόµατα.

Τάκης Αντώνης

Διάβασα τα 2 ονόµατα Τάκης και Αντώνης.

ή

Παρακαλώ δώστε 2 ονόµατα.

Page 84: C Notes

84

Λένα Παπαδηµητρίου

Διάβασα τα 2 ονόµατα Λένα και Παπαδηµητρ.

Αν έχουµε να δώσουµε µόνο κείµενο από το πληκτρολόγιο, τότε είναι καλύτερα να χρησιµοποιήσουµε τη συνάρτηση gets(), ενώ η scanf() συνιστάται για την είσοδο µικτών τύπων σε µια τυποποιηµένη µορφή, όπως π.χ. αν κάθε γραµµή εισόδου περιέχει το όνοµα ενός εργαλείου, τον κωδικό του αριθµού και την τιµή του.

Έξοδος Συµβολοσειράς

Θα δούµε δύο συναρτήσεις εξόδου, τις puts() και printf().

Η Συνάρτηση puts()

Η συνάρτηση puts() είναι αρκετά εύκολη στη χρήση και απλά δίνουµε ένα όρισµα που είναι ένας δείκτης σε µια συµβολοσειρά.

Ακολουθεί ένα παράδειγµα :

/* prog48.c – χρήση της puts */

#include <stdio.h>

#define DEF "Είµαι µια συµβολοσειρά που έχει ορισθεί."

main()

{

static char str1[ ] = "Ένας πίνακας µού απέδωσε αρχικές τιµές.";

char *str2 = "Ένας δείκτης µού απέδωσε αρχικές τιµές.";

puts("Είµαι ένα όρισµα στην puts().");

puts(DEF);

puts(str1);

puts(str2);

puts(&str1[4]);

puts(str2+4);

Page 85: C Notes

85

}

Το αποτέλεσµα θα είναι :

Είµαι ένα όρισµα στην puts().

Είµαι µια συµβολοσειρά που έχει ορισθεί.

Ένας πίνακας µού απέδωσε αρχικές τιµές.

Ένας δείκτης µού απέδωσε αρχικές τιµές.

πίνακας µού απέδωσε αρχικές τιµές.

δείκτης µού απέδωσε αρχικές τιµές.

Προσέχουµε στα δύο τελευταία παραδείγµατα από πού αρχίζει να τυπώνει η puts. Κάθε συµβολοσειρά µε την puts() εµφανίζεται σε µια νέα γραµµή.

Η Συνάρτηση printf()

Όπως ήδη γνωρίζουµε, η printf() δεν τοποθετεί αυτόµατα κάθε συµβολοσειρά σε µια νέα γραµµή, αλλά εµείς πρέπει να δείξουµε πού θέλουµε να ξεκινούν οι νέες γραµµές. Γενικά, µε την printf() πρέπει να γράψουµε περισσότερα, αλλά µ’ αυτήν µπορούµε να συνδυάσουµε σε µια γραµµή πολλές συµβολοσειρές.

Η Συνάρτηση strlen()

Η συνάρτηση αυτή βρίσκει το µήκος µιας συµβολοσειράς. Ακολουθεί ένα πρόγραµµα όπου µπορούµε να µικρύνουµε µια µεγάλη συµβολοσειρά σε µέγιστο µήκος χαρακτήρων ίσο µε size :

/* prog49.c – προκρούστεια συνάρτηση */

void match(string, size)

char *string;

int size;

{

if (strlen(string) > size)

*(string + size) = '\0';

}

Page 86: C Notes

86

Η Συνάρτηση strcat()

Με τη συνάρτηση strcat() µπορούµε να ενώσουµε συµβολοσειρές :

/* prog50.c – ένωση δύο συµβολοσειρών */

#include <stdio.h>

#include <string.h>

#define SIZE 80

main()

{

static char color[SIZE];

static char addon[ ] = ", πολύ ωραίο χρώµα";

puts("Ποιο είναι το αγαπηµένο σου χρώµα;");

gets(color);

strcat(color, addon);

puts(color);

puts(addon);

}

Το αποτέλεσµα θα είναι :

Ποιο είναι το αγαπηµένο σου χρώµα;

Κόκκινο

Κόκκινο, πολύ ωραίοα χρώµα

, πολύ ωραίο χρώµα

Όπως βλέπουµε, η συνάρτηση strcat() δέχεται δύο συµβολοσειρές ως ορίσµατα. Ένα αντίγραφο της δεύτερης συµβολοσειράς τοποθετείται στο τέλος της πρώτης και οι δύο µαζί παίρνουν τη θέση της πρώτης, ενώ η δεύτερη συµβολοσειρά δεν αλλάζει. Πρέπει, όµως, να κάνουµε και έλεγχο του µεγέθους της πρώτης συµβολοσειράς για να µην έχουµε αποκοπές χαρακτήρων.

Page 87: C Notes

87

Η Συνάρτηση strcmp()

Η συνάρτηση strcmp() συγκρίνει τα περιεχόµενα δύο συµβολοσειρών και όχι τις διευθύνσεις τους.

Ακολουθεί ένα πρόγραµµα :

/* prog51.c – σύγκριση δύο συµβολοσειρών */

#include <stdio.h>

#include <string.h>

#define ANSWER "Βέροια"

#define MAX 40

main()

{

char try[MAX];

puts("Ποια είναι η πρωτεύουσα της Ηµαθίας;");

gets(try);

while ((strcmp(try, ANSWER) != 0))

{

puts("Λάθος. Προσπάθησε ξανά.");

gets(try);

}

puts("Μπράβο! Σωστή απάντηση!");

}

Η συνάρτηση strcmp() δέχεται δύο δείκτες συµβολοσειράς σαν ορίσµατα και επιστρέφει µια τιµή 0 αν οι δύο συµβολοσειρές είναι ίδιες. Η strcmp() επιστρέφει ακόµη έναν αρνητικό αριθµό εάν η πρώτη συµβολοσειρά προηγείται αλφαβητικά της δεύτερης και έναν θετικό αριθµό στην αντίθετη περίπτωση.

Page 88: C Notes

88

Η Συνάρτηση strcpy()

Η συνάρτηση αυτή αντιγράφει συµβολοσειρές.

Ακολουθεί ένα παράδειγµα :

/* prog52.c – επίδειξη της strcpy() */

#include <stdio.h>

#include <string.h>

#define MOTO "Florina per sempre"

#define SIZE 20

main()

{

static char *second = MOTO;

static char first[SIZE] = "Florina";

puts(second);

puts(first);

strcpy(first, second);

puts(second);

puts(first);

}

Το αποτέλεσµα θα είναι :

Florina per sempre

Florina

Florina per sempre

Florina per sempre

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

Page 89: C Notes

89

Η Συνάρτηση sprintf()

Αυτή δηλώνεται στο αρχείο stdio.h και όχι στο string.h και δουλεύει όπως η printf(), αλλά µε τη διαφορά ότι γράφει σε µια συµβολοσειρά αντί να εµφανίζει στην οθόνη, δηλ. µας δίνει έναν τρόπο για τον συνδυασµό διαφόρων συµβολοσειρών σε µια συµβολοσειρά.

Το πρώτο όρισµα της sprintf() είναι η διεύθυνση της τελικής συµβολοσειράς και τα υπόλοιπα ορίσµατα είναι τα ίδια όπως και για την printf() :

char first = "Florina";

char second = " per sempre";

char pina[20];

sprintf(pina, "%s %s", first, second);

Με τις παραπάνω εντολές αντιγράφεται η συµβολοσειρά "Florina per sempre" στον πίνακα pina.

Οι Συναρτήσεις του Αρχείου ctype.h

Το αρχείο επικεφαλίδων ctype.h περιέχει µια οικογένεια από συναρτήσεις χαρακτήρα, που είναι οι εξής :

Όνοµα Αληθής (True) αν το Όρισµα είναι :

isalnum() Αλφαριθµητικό

isalpha() Αλφαβητικό

iprgtrl() Ένας χαρακτήρας ελέγχου, π.χ. Control-B

isdigit() Ένα ψηφίο

isgraph() Οποιοσδήποτε εκτυπούµενος µη µηδενικός χαρακτήρας

islower() Ένα µικρό γράµµα

isprint() Ένας εκτυπούµενος χαρακτήρας

ispunct() Ένας χαρακτήρας σηµείου στίξης

isspace() Ένας λευκός χαρακτήρας

isupper() Ένα κεφαλαίο γράµµα

isxdigit() Ένας χαρακτήρας σε 16δική µορφή

Page 90: C Notes

90

Υπάρχουν δύο ακόµη συναρτήσεις, η toupper() που µετατρέπει πεζά γράµµατα σε κεφαλαία και η tolower() που κάνει το αντίστροφο.

Ακολουθεί ένα πρόγραµµα που δέχεται ως είσοδο µια γραµµή και αντιστρέφει τα γράµµατα από κεφαλαία σε πεζά και από πεζά σε κεφαλαία.

/* prog53.c – τροποποίηση µιας συµβολοσειράς */

#include <stdio.h>

#include <string.h>

#include <ctype.h>

#define LIMIT 80

char line[LIMIT];

void modify(char *str);

main()

{

puts("Παρακαλώ, εισάγετε µια γραµµή:");

gets(line);

modify(line);

puts(line);

}

void modify(char *str)

{

while (*str != '\0')

{

if (isupper(*str))

*str = tolower(*str);

else if (islower(*str))

*str = toupper(*str);

Page 91: C Notes

91

srt++;

}

}

Μετατροπή Συµβολοσειράς σε Αριθµό

Για να µετατρέψουµε µια συµβολοσειρά σε αριθµό, µπορούµε να χρησιµοποιήσουµε τη συνάρτηση atoi() που δέχεται µια συµβολοσειρά ως όρισµα και επιστρέφει την αντίστοιχη ακέραια τιµή. Για να δουλέψει αυτή η συνάρτηση πρέπει να συµπεριλάβουµε το αρχείο stdlib.h.

Αυτό το αρχείο περιλαµβάνει δηλώσεις και για τις συναρτήσεις atof() και atol(), όπου η µεν πρώτη µετατρέπει µια συµβολοσειρά σε µια τιµή τύπου double και η δεύτερη µετατρέπει µια συµβολοσειρά σε µια τιµή τύπου long.

Page 92: C Notes

92

Η Γλώσσα Προγραµµατισµού C

(Μέρος 4 - Προχωρηµένα Θέµατα)

Κατηγορίες Μνήµης και Εµβέλεια

Οι κατηγορίες µνήµης της C µάς επιτρέπουν να καθορίζουµε ποιες συναρτήσεις θα γνωρίζουν ποιες µεταβλητές και πόσο µια µεταβλητή θα υπάρχει σ’ ένα πρόγραµµα. Μπορούµε να κάνουµε µια µεταβλητή να είναι εξωτερική, ορίζοντάς την έξω από οποιοδήποτε ορισµό συνάρτησης και µετά, µέσα στις συναρτήσεις που χρησιµοποιούν αυτή τη µεταβλητή, δηλώνουµε τη µεταβλητή γράφοντας πριν από τον τύπο της τη λέξη-κλειδί extern. Αυτή η λέξη-κλειδί πληροφορεί τον υπολογιστή ότι ο ορισµός αυτής της µεταβλητής βρίσκεται έξω από τη συνάρτηση.

Αν δεν χρησιµοποιήσουµε την extern και ορίσουµε µια άλλη µεταβλητή µε το ίδιο όνοµα µέσα στη main(), τότε θα έχουµε δύο µεταβλητές µε το ίδιο όνοµα, πράγµα µεν αποδεκτό, γιατί ανήκουν αλλού, αλλά σίγουρα επικίνδυνο για λάθη. Αν δεν δηλώσουµε τίποτα για µια µεταβλητή µέσα στη main(), τότε η main() χρησιµοποιεί αυτόµατα τον εξωτερικό ορισµό της µεταβλητής.

Για την περιγραφή των κατηγοριών µνήµης χρησιµοποιούνται οι εξής 4 λέξεις-κλειδιά : extern (για εξωτερική), auto (για αυτόµατη), static και register. Οι µεταβλητές που δηλώνονται µέσα σε µια συνάρτηση θεωρούνται ότι είναι κατηγορίας auto, εκτός κι αν δηλωθεί κάτι άλλο. Η κατηγορία µνήµης καθορίζει δύο πράγµατα : Πρώτον, ελέγχει ποιες συναρτήσεις έχουν πρόσβαση σε µια µεταβλητή. Η έκταση στην οποία είναι διαθέσιµη µια µεταβλητή ονοµάζεται εµβέλεια. Δεύτερον, η κατηγορία µνήµης καθορίζει για πόσο θα υπάρχει η µεταβλητή στη µνήµη. Θα δούµε αναλυτικά τις ιδιότητες του κάθε τύπου.

Page 93: C Notes

93

Οι Αυτόµατες Μεταβλητές

Εξ ορισµού, οι µεταβλητές που δηλώνονται µέσα σε µια συνάρτηση είναι αυτόµατες. Μπορούµε, όµως, να δηλώσουµε και τη λέξη-κλειδί auto, ως εξής :

main()

{

auto int a;

...

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

Μια αυτόµατη µεταβλητή έχει τιµή για όσο καιρό καλείται η συνάρτηση που την περιέχει από κάποια άλλη συνάρτηση και χάνεται όταν τελειώσει η κλήση της συνάρτησής της.

Οι Εξωτερικές Μεταβλητές

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

int num1;

char namel;

double times[100];

main()

{

extern int num1;

extern char name;

extern double times[100];

...

Μπορούµε να παραλείψουµε τη δήλωση extern αν οι αρχικοί ορισµοί υπάρχουν στο ίδιο αρχείο και πριν από τη συνάρτηση που τις χρησιµοποιεί. Αν, όµως, δηλώσουµε µέσα σε µια

Page 94: C Notes

94

συνάρτηση µια µεταβλητή µε το ίδιο όνοµα, τότε αυτή θα θεωρηθεί σαν αυτόµατη (τοπική) µεταβλητή και η εξωτερική µεταβλητή θα αγνοηθεί.

Οι εξωτερικές µεταβλητές παίρνουν αυτόµατα σαν αρχική τιµή 0 όταν δεν δηλωθεί κάτι άλλο, πράγµα που δεν ισχύει για τις αυτόµατες µεταβλητές και δεν µπορούµε να δώσουµε αρχικές τιµές µε τον παρακάτω τρόπο :

extern char name = 'Y'; /* λάθος */

Οι Στατικές Μεταβλητές

Αυτές οι µεταβλητές έχουν την ίδια εµβέλεια µε τις αυτόµατες µεταβλητές, αλλά δεν χάνονται µόλις τελειώσει το έργο της η συνάρτηση που τις περιέχει. Οι στατικές µεταβλητές, όπως οι εξωτερικές µεταβλητές, παίρνουν ως αρχικές τιµές 0, αν δεν δώσουµε εµείς κάτι άλλο. Ακολουθεί ένα πρόγραµµα που χρησιµοποιεί µια στατική µεταβλητή, όπου φαίνεται η διαφορά της από µια απλή τοπική µεταβλητή.

/* prog59.c – χρήση µιας στατικής µεταβλητής */

#include <stdio.h>

void try();

main()

{

int count;

for (count=1; count<=3; count++)

{

printf("Εδώ είναι η επανάληψη %d: \n", count);

try();

}

}

void try()

{

int a=1;

static int b=1;

Page 95: C Notes

95

printf("a = %d και b = %d\n", a++, b++);

}

Το αποτέλεσµα θα είναι :

Εδώ είναι η επανάληψη 1 :

a = 1 και b = 1

Εδώ είναι η επανάληψη 2 :

a = 1 και b = 2

Εδώ είναι η επανάληψη 3 :

a = 1 και b = 3

Βλέπουµε ότι η a, που είναι καθαρά τοπική µεταβλητή, παίρνει την ίδια τιµή = 1 κάθε φορά που καλείται η συνάρτηση try() που την περιέχει, ενώ η στατική µεταβλητή b διατηρεί την αρχική τιµή της = 1 και κάθε φορά αυξάνει κατά ένα.

Δοµές και Άλλοι Τύποι Δεδοµένων

Οι δοµές της C είναι κάτι ανάλογο µε τα records (εγγραφές) της Pascal. Ακολουθεί ένα απλό παράδειγµα µιας δοµής που περιέχει τον τίτλο, τον συγγραφέα και την τιµή ενός βιβλίου. Το πρόγραµµα θα επεκταθεί αργότερα και για πολλά βιβλία.

/* prog60. c – στοιχεία βιβλίων */

#include <stdio.h>

#define MAXTITLE 41 /* µέγιστο µήκος του τίτλου */

#define MAXAUTHOR 31 /* µέγιστο µήκος του ονόµατος του συγγραφέα */

struct book { /* η µορφοποίηση της δοµής */

char title[MAXTITLE];

char author[MAXAUTHOR];

float value;

}; /* τέλος µορφοποίησης δοµής */

main()

Page 96: C Notes

96

{

struct book libry; /* η libry είναι µεταβλητή τύπου book */

printf("Δώστε τον τίτλο του βιβλίου.\n");

gets(libry.title);

printf("Δώστε τον συγγραφέα του βιβλίου : \n");

gets(libry.author);

printf("Δώστε την τιµή του βιβλίου : \n");

scanf("%f", &libry.value);

printf("%s by %s : $%.2f\n", libry.title, libry.author, libry.value);

printf("%s : \"%s\" \($%.2f\)\n", libry.author, libry.title, libry.value);

}

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

Δηµιουργία µιας Δοµής

Η µορφοποίηση µιας δοµής περιγράφει το πώς δηµιουργείται η δοµή. Ας δούµε πάλι τη δοµή που είχαµε στο προηγούµενο παράδειγµα :

struct book {

char title[MAXTITLE];

char author[MAXAUTHOR];

float value;

};

Εδώ έχουµε µια δοµή από δύο πίνακες char και µια µεταβλητή float. Η λέξη-κλειδί struct δηλώνει ότι ακολουθεί µια δοµή. Μετά ακολουθεί µια προαιρετική ετικέττα, η λέξη book, που χρησιµοποιείται για να αναφερόµαστε στη δοµή.

Έτσι, πιο κάτω έχουµε τη δήλωση :

struct book libry;

Page 97: C Notes

97

που δηλώνει ότι η µεταβλητή libry είναι µια δοµή τύπου book.

Τα µέλη της δοµής περικλείονται από τις αγκύλες { και } και κάθε µέλος περιγράφεται από τη δική του δήλωση. Τα µέλη µπορεί να είναι οποιουδήποτε τύπου δεδοµένων, ακόµα και άλλες δοµές. Υπάρχουν δοµές τοπικές και καθολικές.

Η µεταβλητή δοµής είναι η libry και ο υπολογιστής κρατάει χώρο στη µνήµη µόνο γι' αυτήν και όχι και για τη book. Μπορούµε ακόµη να δηλώσουµε δύο µεταβλητές τύπου struct book ή ακόµη και έναν δείκτη αυτού του τύπου δοµής :

struct book book1, book2, *pointerbook;

Μπορούµε, όµως, να συνδυάσουµε τη διαδικασία ορισµού µορφοποίησης µιας δοµής και τη διαδικασία ορισµού µιας µεταβλητής δοµής µαζί, οπότε και δεν είναι απαραίτητη η χρήση µιας ετικέττας (book) :

struct {

char title[MAXTIT];

char author[MAXAUT];

float value;

} libry;

Η χρήση της ετικέττας είναι βέβαια χρήσιµη όταν χρησιµοποιούµε την ίδια µορφοποίηση δοµής περισσότερες από µία φορά.

Απόδοση Αρχικών Τιµών σε µια Δοµή

Η απόδοση αρχικών τιµών σε µια δοµή γίνεται ως εξής :

static struct book libry = {

"Μάθετε για τη Φλώρινα", "Παπαδόπουλος Αντ.", 10.32

};

Ανάµεσα στα πεδία πρέπει να βάζουµε κόµµατα για να ξεχωρίζουν οι τµές.

Πρόσβαση στα Πεδία µιας Δοµής

Μπορούµε να έχουµε πρόσβαση στα µέλη µιας δοµής µε τη χρήση του τελστή µέλους δοµής (.). Δηλαδή, το libry.value είναι το τµήµα value της libry. Στην ουσία, οι εκφράσεις .title, .author και .value παίζουν τον ρόλο των δεικτών αρίθµησης της δοµής book. Η libry.value είναι µια τιµή τύπου float και και µπορεί να χρησιµοποιηθεί όπως κάθε άλλος τύπος float.

Page 98: C Notes

98

Γενικά για το Δυαδικό Σύστηµα

Η γλώσσα C έχει την ικανότητα να χειρίζεται µεµονωµένα δυαδικά ψηφία σ’ ένα byte. Για παράδειγµα, µπορεί να ελέγχεται µια συσκευή του hardware στέλνοντάς της ένα byte, του οποίου κάθε δυαδικό ψηφίο έχει µια ειδική σµασία για τη συσκευή.

Το δυαδικό σύστηµα (σύστηµα µε βάση το 2) είναι ένα φυσικό σύστηµα για τον υπολογιστή και χρησιµοποιεί δυνάµεις του 2 αντί για δυνάµεις του 10. Ένας δυαδικός αριθµός, όπως ο 1101, σηµαίνει :

1 Χ 23 + 1Χ 22 + 0 Χ 21 + 1 Χ20

το οποίο δίνει το αποτέλεσµα 13 στο δεκαδικό σύστηµα.

Ένα byte περιέχει 8 δυαδικά ψηφία που είναι αριθµηµένα από το 0 µέχρι το 7 και το δυαδικό ψηφίο 7 ονοµάζεται δυαδικό ψηφίο υψηλής τάξης, ενώ το δυαδικό ψηφίο 0 ονοµάζεται δυαδικό ψηφίο χαµηλής τάξης. Η αρίθµηση των δυαδικών ψηφίων αντιστοιχεί σε δυνάµεις του 2, αρχίζοντας από το 20 µέχρι το 27.

Ο µεγαλύτερος δυαδικός αριθµός που µπορεί να κρατήσει ένα byte είναι ο 11111111 = 255 και ο µικρότερος είναι ο 00000000 = 0, έτσι ένα byte µπορεί να αποθηκεύσει αριθµούς από το 0 – 255 ή από το -128 έως +127, δηλ. σύνλο 256 ξεχωριστές τιµές.

Λογικοί Τελεστές Κατά Δυαδικό Ψηφίο

Η C έχει λογικούς τελεστές κατά δυαδικό ψηφίο και τελεστές ολίσθησης κατά δυαδικό ψηφίο. Στα παραδείγµατα που ακολουθούν, οι αριθµοί θα είναι γραµµένοι σε δυαδικό συµβολισµό. Στα προγράµµατα, όµως, θα πρέπει να χρησιµοποιούµε ακέραιες µεταβλητές, δηλ. αντί του 00011001, θα µπορούµε να χρησιµοποιούµε το 25 σε δεκαδική µορφή, το 031 σε οκταδική ή το 0x19 σε δεκαεξαδική.

Στα παραδείγµατά µας, θα χρησιµοποιήσουµε αριθµούς 8 ψηφίων, µε τα δυαδικά ψηφία αριθµηµένα από 7 έως 0, από αριστερά προς τα δεξιά.

Συµπλήρωµα του 1 ή Άρνηση κατά Δυαδικό Ψηφίο : ˜

Αυτός ο µοναδικός τελεστής αλλάζει κάθε 1 σε 0 και κάθε 0 σε 1, ως εξής :

˜(10011010) == (01100101)

Αν υποθέσουµε ότι η µεταβλητή val είναι του τύπου unsigned int µε τιµή 2, τότε στο δυαδικό σύστηµα αυτός ο αριθµός είναι ο 00000010 και το

˜val = 11111101 = 253

Page 99: C Notes

99

Η νέα τιµή (˜val) που προκύπτει µπορεί να καταχωρηθεί κάπου αλλού ή και στην ίδια την val, ως εξής :

newval = ˜val;

printf("%d", ˜val);

val = ˜val;

Το AND κατά Δυαδικό Ψηφίο : &

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

(10010011) & (00111101) == (00010001)

Το OR κατά Δυαδικό Ψηφίο : | Αυτός ο δυαδικός τελεστής παράγει µια νέα τιµή κάνοντας µια σύγκριση δυαδικού ψηφίου ανά δυαδικό ψηφίο µεταξύ των δύο τελεστέων. Για κάθε θέση το αποτέλεσµα είναι 1 αν ένα από τα αντίστοιχα δυαδικά ψηφία των τελεστέων είναι 1, δηλ. αληθές :

(10010011) | (00111101) == (10111111)

Το EXOR κατά Δυαδικό Ψηφίο : ∧ Αυτός ο δυαδικός τελεστής παράγει µια νέα τιµή κάνοντας µια σύγκριση δυαδικού ψηφίου ανά δυαδικό ψηφίο µεταξύ των δύο τελεστέων. Για κάθε θέση το αποτέλεσµα είναι 1 αν µόνο το ένα από τα αντίστοιχα δυαδικά ψηφία των τελεστέων είναι 1, δηλ. αληθές :

(10010011) ∧ (00111101) == (10101110)

Τελεστές Ολίσθησης Κατά Δυαδικό Ψηφίο

Ολίσθηση προς τα Αριστερά : <<

Αυτός ο τελεστής ολισθαίνει τα δυαδικά ψηφία της τιµής του αριστερού τελεστέου προς τα αριστερά κατά τόσες θέσεις όσες προσδιορίζονται από τον δεξιό τελεστέο. Οι θέσεις που αδειάζουν γεµίζουν µε 0 και τα δυαδικά ψηφία που µετακινούνται πέραν του τέλους του αριστερού τελεστέου χάνονται.

Page 100: C Notes

100

Στο παράδειγµα :

(10001010) << 2 == (00101000)

κάθε δυαδικό ψηφίο µετακινείται δύο θέσεις προς τα αριστερά.

Ολίσθηση προς τα Δεξιά : >>

Αυτός ο τελεστής ολισθαίνει τα δυαδικά ψηφία της τιµής του αριστερού τελεστέου προς τα δεξιά κατά τόσες θέσεις όσες προσδιορίζονται από τον δεξιό τελεστέο. Οι θέσεις που αδειάζουν γεµίζουν µε 0 και τα δυαδικά ψηφία που µετακινούνται πέραν του δεξιού τέλους του αριστερού τελεστέου χάνονται.

Στο παράδειγµα :

(10001010) >> 2 == (00100010)

κάθε δυαδικό ψηφίο µετακινείται δύο θέσεις προς τα δεξιά.

Συµβολικές Σταθερές µε την #define

Ο προεπεξεργαστής της C εξετάζει το πρόγραµµα πριν από τον επεξεργαστή και σύµφωνα µε τις οδηγίες που του δίνουµε, αντικαθιστά τις συµβολικές σταθερές του προγράµµατός µας.

Στο επόµενο πρόγραµµα δείχνονται µερικές από τις δυνατότητες και ιδιότητες της οδηγίας #define :

/* prog61.c – επίδειξη της #define */

#include <sdtio.h>

#define TWO 2

#define MSG "Florina per sempre"

#define FOUR TWO*TWO

#define PX printf("Το X is %d\n", x)

#define FMT "Το X είναι %d\n"

main()

{

int x = TWO;

Page 101: C Notes

101

PX;

x = FOUR;

printf(FMT, x);

printf("%s\n", MSG);

printf("TWO: MSG\n");

}

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

Το υπόλοιπο της γραµµής καλείται το σώµα.

Όταν ο προεπεξεργαστής βρει µια από τις µακροεντολές µέσα στο πρόγραµµα, σχεδόν πάντα την αντικαθιστά µε το σώµα. Η διαδικασία αυτή ονοµάζεται επέκταση µακροεντολής.

Το αποτέλεσµα του προγράµµατος της προηγούµενης σελίδας είναι το εξής :

Το Χ είναι 2

Το Χ είναι 4

Florina per sempre

TWO: MSG

Ας δούµε από κοντά τι γίνεται.

Η εντολή int x = TWO; γίνεται τώρα int x = 2; και η πρόταση ΡΧ γίνεται printf("Το X είναι %d\n", x); Βλέπουµε συνεπώς ότι µια µακροεντολή µπορεί εκτός από σταθερές να εκφράσει και οποιαδήποτε συµβολοσειρά, ακόµη και µια ολόκληρη έκφραση της C. Γενικά, όπου ο προεπεξεργαστής βρει µια µακροεντολή στο πρόγραµµα, την αντικαθιστά µε την ισοδύναµη συµβολοσειρά αντικατάστασης.

Εάν αυτή η συµβολοσειρά περιέχει µακροεντολές, τότε αντικαθίστανται κι αυτές. Η µόνη εξαίρεση γίνεται για µια µακροεντολή που βρίσκεται µεταξύ διπλών εισαγωγικών, όπως : Η printf("TWO: MSG\n"); εκτυπώνει απλά το κείµενο TWO: MSG

Προσάρτηση Αρχείου µε την #include

Όταν ο προεπεξεργαστής της C συναντήσει µια οδηγία #include, κοιτάζει το όνοµα αρχείου που ακολουθεί και συµπεριλαµβάνει το κείµενο του αρχείου µέσα στο τρέχον πρόγραµµα.

Page 102: C Notes

102

Τα αρχεία τα προσαρτούµε γιατί περιέχουν πληροφορίες που χρειάζεται ο µεταγλωττιστής. Η επέκταση .h χρησιµοποιείται συµβατικά για τα αρχεία επικεφαλίδων.

Η οδηγία #include υπάρχει σε δύο παραλλαγές :

#include <stdio.h>

και

#include "mystuff.h"

Οι γωνιακές παρενθέσεις < και > λένε στον προεπεξεργαστή να ψάξει για το αρχείο σ’ έναν ή περισσότερους από τους τυποποιηµένους καταλόγους αρχείων του συστήµατος, ενώ τα διπλά εισαγωγικά λένε στον προεπεξεργαστή να ψάξει πρώτα τον δικό µας κατάλογο αρχείων (ή σε κάποιον άλλον που εµείς έχουµε καθορίσει) και µετά να ψάξει στα τυποποιηµένα µέρη.

Ακολουθούν παραδείγµατα :

#include <stdio.h> Ψάχνει τους καταλόγους του συστήµατος #include "mycatalog.h" Ψάχνει τον τρέχοντα κατάλογο εργασίας #include "/usr/biff/mywork.h" Ψάχνει τον κατάλογο /usr/biff

Η Οδηγία #undef Καταργεί µια δεδοµένη µακροεντολή. Δηλαδή, αν έχουµε τον ορισµό : #define LIMIT 400 Τότε η οδηγία : #undef LIMIT καταργεί αυτόν τον ορισµό. Μπορούµε, ακόµα, να επαναορίσουµε τη LIMIT για να έχει µια νέα τιµή.

Οι Τύποι Απαρίθµησης Ο τύπος απαρίθµησης µάς επιτρέπει να ορίζουµε µε συµβολικά ονόµατα ακέραιες σταθερές και µε τη λέξη-κλειδί enum να δηµιουργούµε έναν νέο "τύπο" και να καθορίζουµε τις τιµές που µπορεί να πάρει. Ο σκοπός των δηλώσεων απαρίθµησης είναι να κάνουν τα προγράµµατα πιο ευανάγνωστα. Ακολουθούν µερικές δηλώσεις : enum car {fiat, citroen, bmw, skoda, opel, ford}; enum car voiture; Η πρώτη δήλωση καθορίζει το car ως όνοµα τύπου και η δεύτερη κάνει την voiture µεταβλητή αυτού του τύπου. Μέσα στις αγκύλες υπάρχουν οι πιθανές τιµές που µια µεταβλητή car µπορεί να πάρει. Μπορούµε, δηλαδή, να χρησιµοποιήσουµε τις παρακάτω προτάσεις : color = fiat; if (color == skoda) Η εντολή printf("fiat = %d, citroen = %d\n", fiat, citroen); θα δώσει το εξής αποτέλεσµα : fiat = 0, citroen = 1

Page 103: C Notes

103

Οι τιµές, δηλαδή, που παίρνουν αυτές οι µεταβλητές είναι ακέραιες και ξεκινάνε από το 0, 1, 2 κλπ. Μπορούµε, όµως, να επιλέξουµε εµείς τις τιµές που θέλουµε να έχουν οι σταθερές, ως εξής : enum levels {low = 100, medium = 500, high = 2000}; Επειδή η C τις µεταβλητές απαρίθµησης τις θεωρεί µεταβλητές τύπου int, αυτές οι µεταβλητές µπορούν να χρησιµοποιηθούν σε εκφράσεις µε τον ίδιο τρόπο όπως και οι µεταβλητές int.

Page 104: C Notes

104

ΟΙ ΣΗΜΑΝΤΙΚΟΤΕΡΕΣ

ΕΝΤΟΛΕΣ ΚΑΙ ΣΥΝΑΡΤΗΣΕΙΣ ΤΗΣ

ΓΛΩΣΣΑΣ C

Τα σχόλια στη C γράφονται ανάµεσα στα σύµβολα /* και */ και µπορούν να καταλαµβάνουν και περισσότερες από µία γραµµές.

Ένα πρόγραµµα στη C πρέπει να ξεκινά µε τις δηλώσεις #include <stdio.h> και main() και µετά όλο το πρόγραµµα περικλείεται στις αγκύλες { και }. Με τις αγκύλες αυτές δηλώνουµε γενικότερα µια οµάδα (µπλοκ) εντολών, κάτι αντίστοιχο δηλ. των δηλώσεων begin και end της Pascal.

#include <stdio.h>

main

{

/* κύριο πρόγραµµα */

}

Οι τύποι δεδοµένων πρέπει να δηλώνονται στην αρχή του προγράµµατος :

int i; /* ακέραιος από -32767 έως 32768 */

char ch; /* χαρακτήρας ascii από 0-255 */

char name[20]; /* string 40 χαρακτήρων */

long misthos; /* µεγάλος ακέραιος */

Page 105: C Notes

105

float b; /* πραγµατικός αριθµός */

double a; /* πραγµατικός αριθµός µεγαλύτερης ακρίβειας */

Τα απλά εισαγωγικά (‘’) περικλείουν έναν και µόνον έναν χαρακτήρα (‘x’), ενώ τα διπλά εισαγωγικά ("xyz") µπορούν να περικλείουν οσουσδήποτε χαρακτήρες, αλλά στο τέλος της σειράς των χαρακτήρων προστίθεται ο χαρακτήρας που έχει ascii κωδικό ίσο µε 0 και ο οποίος γράφεται ως εξής : ‘\0’.

‘x’ /* είναι µόνο ο χαρακτήρας x */

"x" /* είναι ίσο µε x\0 */

Η εντολή εκτύπωσης συντάσσεται ως εξής :

printf("Ο αριθµός είναι %d και ο χαρακτήρας είναι %c \n", i, ch);

/* τα %d και %c είναι προσδιοριστές ακεραίων και χαρακτήρων αντίστοιχα*/

printf("Ο αριθµός είναι %ld και το όνοµα είναι %s \n", mi, name);

/* τα %ld και %s είναι προσδιοριστές µεγάλων ακεραίων και strings αντίστοιχα */

printf("Ο αριθµός είναι %10.2f \n", a);

/* το %f είναι προσδιοριστής πραγµατικών αριθµών και εδώ ορίζουµε 10 θέσεις µε 2 δεκαδικά στην εκτύπωση */

Η χρήση του χαρακτήρα \n αλλάζει γραµµή στην οθόνη. Αν δεν τον χρησιµοποιήσουµε, τότε η επόµενη εντολή printf() θα συνεχίσει από εκεί που σταµάτησε η προηγούµενη.

Μπορούµε να δώσουµε αρχικές τιµές µαζί µε τη δήλωση των µεταβλητών ως εξής :

int i = 0;

char ch = ‘A’;

char p = 65; /* η ascii τιµή του Α είναι 65 */

Page 106: C Notes

106

Μια µεταβλητή που δηλώθηκε σαν χαρακτήρας µε το char, µπορεί να εκτυπωθεί και µε ακέραιο προσδιοριστή και µπορεί να πάρει µέρος και σε αριθµητικές πράξεις.

char ch = 65; /* ch = A */

ch = ch + 1; /* η ch γίνεται τώρα ίση µε Β */

printf("η τιµή της c είναι αριθµός %d και χαρακτήρας %c", ch, ch);

/* θα τυπώσει τις τιµές 66 και c */

Οι σταθερές τιµές στη C δηλώνονται µε δύο τρόπους :

#define PI 3.1415926

const float PI = 3.1415926;

Η εντολή define δηλώνεται στην αρχή του προγράµµατος και πριν από τη main(), ενώ η const δηλώνεται µαζί µε τις άλλες δηλώσεις µεταβλητών.

Η εντολή καταχώρησης δεδοµένων συντάσσεται ως εξής :

scanf("%d", &i);

scanf("%ld", &misthos);

scanf("%c", &ch);

scanf("%s", name); /* το name είναι string και δεν θέλει το & */

Η C δεν έχει λογικούς τύπους δεδοµένων, αλλά αν το αποτέλεσµα µιας σύγκρισης είναι αληθές, τότε επιστρέφει αποτέλεσµα 1, ενώ αν είναι ψευδές, τότε επιστρέφει αποτέλεσµα 0.

a = 5 > 3; /* το a γίνεται ίσο µε 1 */

Page 107: C Notes

107

b = 4 > 10; /* το b γίνεται ίσο µε 0 */

Οι τελεστές σύγκρισης είναι όπως και στις άλλες γλώσσες, µε τη διαφορά ότι για τη σύγκριση της ισότητας έχουµε το == και για την ανισότητα ένα από τα : <>, != και #.

if (a == 1) /* Έλεγχος ισότητας */

if (a = 1) /* Προσοχή! Καταχωρεί το 1 στο a και το αποτέλεσµα είναι αληθές (1) */

if (a != 1) /* έλεγχος ανισότητας */

Οι αριθµητικοί τελεστές της C είναι όπως και στις άλλες γλώσσες προγραµµατισµού, αλλά έχει επιπλέον και τους εξής :

i++ /* αυξάνει το i κατά ένα µετά τη χρήση του i */

++i /* αυξάνει το i κατά ένα πριν τη χρήση του i */

i- - /* µειώνει το i κατά ένα µετά τη χρήση του i */

- -i /* µειώνει το i κατά ένα πριν τη χρήση του i */

Όταν διαιρούµε δύο ακεραίους, τότε µε τον τελεστή / παίρνουµε το πηλίκο της ακέραιας διαίρεσης και µε τον τελεστή % το υπόλοιπο.

p = a / 3;

ypol = a % 3;

Για να µην κάνουµε ακέραια διαίρεση, αλλά να προκύψει δεκαδικός αριθµός, πρέπει ο ένας τουλάχιστον τελεστέος να είναι δεκαδικός :

p = a / 3.0;

Υπάρχει και ο τελεστής-εκµαγείο για την αυτόµατη µετατροπή των τύπων δεδοµένων.

Page 108: C Notes

108

a = (int) 3.2 + (int) 4.8 /* το αποτέλεσµα είναι 3 + 4 = 7 */

b = (float) 3 / 1 /* το αποτέλεσµα είναι 3.0 */

Με τον τελεστή sizeof µπορούµε να βρούµε από πόσα bytes αποτελείται ένας τύπος δεδοµένων ή µια µεταβλητή.

char ch;

a = sizeof ch; /* οι χαρακτήρες πιάνουν 1 byte */

b = sizeof(int); /* οι ακέραιοι χρειάζονται 2 bytes */

c = sizeof(long); /* ο τύπος long θέλει 4 bytes */

Η εντολή if στη C συντάσσεται ως εξής :

if (i < 10)

printf("Ο αριθµός %d είναι µικρότερος του 10", i);

else

{

printf("Ο αριθµός %d είναι µεγαλύτερος του 10", i);

printf("Δώσε έναν άλλον αριθµό : ");

scanf("%d", &i);

}

/* η συνθήκη της if περικλείεται σε παρενθέσεις και αν οι εντολές που ακολουθούν την if ή την else είναι παραπάνω από µία, τότε περικλείονται σε αγκύλες. Η χρήση της else δεν είναι υποχρεωτική πάντα */

Η C έχει τον τελεστή υπό συνθήκη () ? :, που µπορεί να αντικαταστήσει την εντολή if else :

max = (a > b) ? a : b;

Η παραπάνω εντολή είναι ισοδύναµη µε την εξής if else :

if (a > b)

Page 109: C Notes

109

max = a;

else

max = b;

Η εντολή case στη C συντάσσεται ως εξής :

switch (a)

{

case 1 :

case 2 :

printf("για τις τιµές 1 και 2 εκτελείται αυτή η εντολή \n");

break;

case 3 :

printf("για την τιµή 3 εκτελείται αυτή η εντολή \n");

break;

case 4 :

printf("για την τιµή 4 εκτελείται αυτή η εντολή \n");

printf("η εντολή break µάς βγάζει εκτός της switch \n");

break;

default :

printf("εκτελείται όταν δεν ισχύει καµία προηγούµενη \n");

}

Η εντολή while στη C συντάσσεται ως εξής :

i = 1;

while (i <= 10)

Page 110: C Notes

110

{

printf("Δώσε έναν αριθµό : ");

scanf("%d", &a);

i = i + 1;

}

/* ο βρόχος εκτελείται όσο ισχύει η συνθήκη : i < 10 */

i = 1;

while (i++ <= 10)

scanf("%d", &a);

/* ο βρόχος αυτός κάνει ό,τι και ο προηγούµενος, µε τη διαφορά ότι ενσωµατώσαµε δύο εντολές σε µία, δηλ. την αύξηση του i κατά ένα και τη σύγκριση */

Για τον βρόχο repeat ... until, η C έχει την εξής εντολή :

i = 1;

do

{

printf("Δώσε έναν χαρακτήρα : ");

scanf("%d", &ch);

i = i + 1;

} while (i < 10);

/* ο βρόχος εκτελείται τουλάχιστον µία φορά και όσο ισχύει η συνθήκη : i<10. Διαφέρει από το repeat ... until της Pascal στο ότι ο βρόχος συνεχίζεται όσο η συνθήκη είναι αληθής, δεν φεύγουµε δηλ. όταν η συνθήκη γίνει αληθής */

Η εντολή επανάληψης for στη C συντάσσεται ως εξής :

for (i=1; i<=10; i++)

Page 111: C Notes

111

{

printf("Το %dο στοιχείο του πίνακα είναι %d : ", i, a[i]);

sum = sum + a[i];

}

/* ξεκινάει µε αρχική τιµή i=1, åëέγχει αν i<=10 και κάθε φορά αυξάνει το i κατά ένα */

Η C έχει δύο πολύ χρήσιµες εντολές, την break και την continue, για την έξοδο από έναν βρόχο ή την επιστροφή στην αρχή του βρόχου αντίστοιχα.

while (1) /* öáίνεται σαν ατελείωτος βρόχος, αλλά δεν είναι */

{

scanf("%d", &i);

if (i == 99)

break; /* έξοδος από τον βρόχο */

else

continue; /* επιστροφή στην αρχή του βρόχου */

}

Ένας πίνακας (array) στη C δηλώνεται ως εξής :

int a[20]; /* πίνακας ακεραίων µίας διάστασης */

int b[10][10]; /* πίνακας ακεραίων δύο διαστάσεων */

char ch[20]; /* πίνακας χαρακτήρων µίας διάστασης */

Η επεξεργασία των πινάκων γίνεται πολύ εύκολα µε την εντολή for :

sum = 0;

Page 112: C Notes

112

for (i=0; i<=19; i++)

{

scanf("%d", &a[i]);

sum = sum + a[i];

}

/* το 1ο στοιχείο ενός πίνακα είναι στη θέση 0 και όχι στην 1 */

Από τις συναρτήσεις συµβολοσειρών, χρήσιµες είναι οι εξής :

len = strlen(a); /* επιστρέφει το πλήθος των χαρακτήρων της a */

strcat(s1, s2); /* προσαρτά την s2 στην s1 */

strcpy(s1, s2); /* αντιγράφει την s2 στην s1 */

strcmp(s1, s2); /* συγκρίνει τις δύο συµβολοσειρές */

Η συνάρτηση getchar() διαβάζει έναν µόνον χαρακτήρα από το πληκτρολόγιο και η putchar() τυπώνει έναν µόνον χαρακτήρα.

ch = getchar();

putchar(ch);

Η συνάρτηση gets() διαβάζει χαρακτήρες µέχρι να συναντήσει τον χαρακτήρα <enter> και η συνάρτηση puts() εµφανίζει συµβολοσειρές χαρακτήρων στην οθόνη.

gets(name);

puts(name);

Page 113: C Notes

113

Οι δείκτες (pointers) είναι πολύ χρήσιµοι στη C, αλλά είναι λίγο πονοκέφαλος για τους νεοεισερχόµενους στη γλώσσα. Στην ουσία, είναι ένας τύπος δεδοµένων που δείχνει σε κάποια µεταβλητή, περιέχει δηλ. τη διεύθυνση στη µνήµη που έχει µια µεταβλητή. Για να βρούµε τη διεύθυνση µιας µεταβλητής, µπορούµε να χρησιµοποιήσουµε και τον τελεστή &. Οι δείκτες χρησιµοποιούνται κυρίως για να µπορεί να αλλάξει µια συνάρτηση τιµές στο πρόγραµµα που την κάλεσε (κλήση µε αναφορά).

int *p; /* ο p είναι δείκτης ακεραίων */

int i, j; /* ο i και ο j είναι απλοί ακέραιοι */

p = &i; /* ο δείκτης p δείχνει τώρα στη µεταβλητή i */

j = *p; /* o j έχει την τιµή που δείχνει ο p, δηλ. την τιµή του i */

printf("η διεύθυνση του i είναι : %p", &i);

Οι συναρτήσεις (functions) στη C είναι δύο ειδών : εκείνες που δεν επιστρέφουν κάποια τιµή στο όνοµά τους ή επιστρέφουν δύο ή και παραπάνω τιµές και δηλώνονται µε τη λέξη void και εκείνες που επιστρέφουν µία και µόνο µία τιµή στο όνοµά τους και δηλώνονται µε τον τύπο δεδοµένων της τιµής που επιστρέφουν, δηλ. int, char, long κ.ά., πριν από το όνοµα της συνάρτησης.

int max(int a, int b)

{

int max;

if (a > b)

max = a;

else

max = b;

return max;

}

/* η συνάρτηση αυτή έχει δύο ακέραια ορίσµατα, τα a και b, και µια τοπική µεταβλητή, τη max. Επιστρέφει µια ακέραια τιµή, τη max, µε τη χρήση της εντολής return. Προσέξτε πού δηλώνεται η κάθε µεταβλητή. Η εντολή που κα-λεί τη συνάρτηση max(), µπορεί να την καλεί µε ακέραια ορίσµατα σταθερών τιµών, π.χ. megistos = max(5, 3);, ή και µε ακέραια ορίσµατα µεταβλητών, π.χ. megistos = max(i, j); */

Page 114: C Notes

114

void blank_lines()

{

int i, j;

printf("Δώσε έναν ακέραιο : ");

scanf("%d", &j);

for (i=1; i<=j; i++)

printf("\n");

}

/* αυτή η συνάρτηση είναι του τύπου void, δεν δέχεται ούτε επιστρέφει κάποια τιµή στη συνάρτηση που την καλεί και απλά εµφανίζει κενές γραµµές στην οθόνη όσες φορές της πούµε. Καλείται ως εξής : blank_lines(); */

void exch_values(int *a, int *b)

{

int temp; /* τοπική µεταβλητή για προσωρινή θέση αποθήκευσης */

temp = *a;

*a = *b;

*b = temp;

}

/* αυτή η συνάρτηση δέχεται δύο ακέραιες τιµές από το πρόγραµµα που την καλεί και ανταλλάσσει τις τιµές τους. Επιστρέφει, δηλ. ουσιαστικά δύο τιµές στο καλούν πρόγραµµα και για να γίνει αυτό, χρησιµοποιεί δείκτες σε ακεραίους και οι τιµές που δέχεται σαν είσοδο είναι οι διευθύνσεις των ακέραιων τιµών που θα ανταλλαγούν. Καλείται, δηλ., ως εξής : exch_values(&m, &n); */