Επανάληψη Εντολές while, for, do-while Απροσδιόριστη Επανάληψη ή Επανάληψη υπό συνθήκη (while, do-while) Απαριθµητή Επανάληψη (for) Εντολή while while (συνθήκη) εντολή C? ναι όχι S Σηµασιολογία Εάν από την αρχή δεν ευσταθεί η συνθήκη C, η εντολή S δεν θα εκτελεστεί καθόλου. ιαφορετικά η S εκτελείται και αυτό επαναλαµβάνεται µέχρις ότου η συνθήκη C να µην ευσταθεί. 1
Εποµένως για να µην υπάρξει βρόχος άπειρης διάρκειας (infinite loop), η διεργασία της S πρέπει να είναι τέτοια, ούτως ώστε να επέλθει µία κατάσταση στην οποία η C παύει να ευσταθεί, οδηγώντας έτσι σε έξοδο από το βρόχο. /* άπειρος βρόχος */ while (1) printf( \n εν µπορώ να σταµατήσω! ); /* κενή εντολή */ while (0) printf(???? ); while (0); Παράδειγµα: Υπολογισµός του αθροίσµατος 1 + 2 + + n int n, count = 1, sum = 0; printf( \nenter limit: ); scanf( %d, &n); while (count <= n){ sum = sum + count; count = count + 1; printf( \nthe sum is %d, sum); 2
Παράδειγµα: Πρόγραµµα το οποίο παράγει τα ακόλουθα: 0 1 1 2 2 4 3 8 4 16 5 32 6 64 x 2 x #include <stdio.h> #define LIMIT 6 void main ( ){ int count = 0, res = 1; while (count <= LIMIT) { printf( \n%1d%5d, count, res); count = count + 1; res = res * 2; Σύνθετοι Τελεστές Ανάθεσης count = count + 1; res = res * 2; sum = sum + count; x = x 3; µεταβλητή τελεστής= έκφραση; Σηµασιολογία: µεταβλητή = µεταβλητή τελεστής (έκφραση); Σύνθετοι τελεστές: +=, =, *=, /=, %= 3
Απλή Ανάθεση Σύνθετη Ανάθεση count = count + 1; count += 1; res = res * 2; res *= 2; sum = sum + count; sum += count; x = x 3; x = 3; n = n * (x + 1); n *= x + 1; int count = 1, sum = 0; while (count <= n) { sum += sum; count += 1; int count = 0, res = 1; while (count <= LIMIT) { printf( \n%1d%5d, count, res); count += 1; res *= 2; Εντολή for for (έκφραση-αρχικοποίησης ; συνθήκη ; έκφραση-ενηµέρωσης) εντολή 4
Γενική Σηµασιολογία Καταρχήν αποτιµείται η έκφραση-αρχικοποίησης, και στη συνέχεια δοκιµάζεται η συνθήκη. Εάν δεν ευσταθεί ο έλεγχος µεταφέρεται εκτός της for. ιαφορετικά εκτελείται η εντολή, αποτιµείται η έκφρασηενηµέρωσης και ο έλεγχος επιστρέφει στη δοκιµή της συνθήκης. Η εκτέλεση της εντολής και η αποτίµηση της έκφρασηςενηµέρωσης επαναλαµβάνεται µέχρις ώτου η συνθήκη να µην ευσταθεί. Τυπική Εφαρµογή της Εντολής for αρχικοποίηση µεταβλητής ελέγχου συνθήκη? όχι ναι εντολή ενηµέρωση µεταβλητής ελέγχου 5
Η επανάληψη ελέγχεται από µία µεταβλητή η οποία ονοµάζεται µεταβλητή ελέγχου (control variable) ή µεταβλητή απαρίθµησης (counting variable). Η έκφραση-αρχικοποίησης αποτελεί την εντολή βάσει της οποίας καταχωρείται αρχική τιµή στη µεταβλητή ελέγχου. H αποτίµηση της έκφρασης-ενηµέρωσης έχει ως αποτέλεσµα την ενηµέρωση της τιµής της µεταβλητής ελέγχου. Η συνθήκη (επανάληψης) εµπλέκει την µεταβλητή ελέγχου. Εποµένως προς αποφυγή βρόχου άπειρης διάρκειας, η έκφρασηενηµέρωσης πρέπει τελικά να οδηγήσει σε µία κατάσταση, όπου η συνθήκη παύει να υφίσταται. Για να είναι η λογική πιο διαφανής, η τιµή της µεταβλητής ελέγχου θα πρέπει να ενηµερώνεται αποκλειστικά από την έκφραση-ενηµέρωσης. Τέλος η µεταβλητή ελέγχου τείνει να είναι απαριθµητού τύπου. Σε περίπτωση που µεταβλητή τύπου double χρησιµοποιείται ως µεταβλητή ελέγχου, η κοινή πρακτική είναι να έχει µόνο ακέραιο µέρος. /* άπειρος βρόχος */ int n; for (n =1; 1; n += 1) printf( \n εν µπορώ να σταµατήσω! ); for (1; 1; 1) printf( \n εν µπορώ να σταµατήσω! ); for (n =1; n = 10; n += 1) printf( \n εν µπορώ να σταµατήσω! ); 6
/* κενή εντολή */ for (n = 1; n == 10; n += 1) printf(??? ); for (0; 0; 0) printf(??? ); Παράδειγµα: Υπολογισµός του αθροίσµατος 1 + 2 + + n int n, count, sum = 0; printf( \nenter limit: ); scanf( %d, &n); for (count = 1; count <= n; count += 1) sum += count; printf( \nthe sum is %d, sum); Παράδειγµα: Πρόγραµµα το οποίο εκτυπώνει 2 x για x από το 1 µέχρι το 6. #include <stdio.h> #define LIMIT 6 void main ( ){ int count, res = 1; for (count = 1; count <= LIMIT; count += 1) { printf( \n%1d%5d, count, res); res = res * 2; 7
Παράδειγµα: Εναλλακτικοί Ορισµοί για Συναρτήσεις draw_square, draw_tria και draw_trid void draw_square(int size, char fill){ int row, col; for (row = 1; row <= size; row += 1) { putchar( \n ); for (col = 1; col <= size; col += 1) putchar(fill); void draw_tria(int size, char fill){ int row, col; for (row = 1; row <= size; row += 1) { putchar( \n ); for (col = 1; col <= row; col += 1) putchar(fill); for (col = row +1; col <= size; col += 1) putchar( ); void draw_trid(int size, char fill){ int row, col; for (row = size; row > 0; row = 1) { putchar( \n ); for (col = size; col > row; col = 1) putchar( ); for (col = row; col > 0; col = 1) putchar(fill); 8
Παράδειγµα: Εκτύπωση συγκεκριµένου αρχείου Αλγόριθµος 1. Ανοίγω το αρχείο 2. ιαδοχικά διαβάζω και εκτυπώνω κάθε χαρακτήρα µέχρι να φτάσω στο τέλος του αρχείου Σηµείωση: Η σταθερά EOF, η οποία ορίζεται στη βιβλιοθήκη stdio, είναι ένας αρνητικός αριθµός. Χρησιµοποιείται προς επισήµανση του τέλους κάποιου αρχείου. Μετάφραση αλγόριθµου µε χρήση while #include <stdio.h> void main ( ) { char ch; FILE *inp; inp = fopen( mycat.c, r ); while (fscanf(inp, %c, &ch)!= EOF) putchar(ch); mycat.c $ cc o mycat mycat.c $ mycat #include <stdio.h> void main ( ) { char ch; FILE *inp; inp = fopen( mycat.c, r ); while (fscanf(inp, %c, &ch)!= EOF) putchar(ch); 9
Μετάφραση αλγόριθµου µε χρήση for #include <stdio.h> void main ( ) { char ch; FILE *inp; for (inp = fopen( mycat.c, r ); fscanf(inp, %c, &ch)!= EOF; putchar(ch)); $ cc o mycat mycat.c $ mycat #include <stdio.h> mycat.c void main ( ) { char ch; FILE *inp; for (inp = fopen( mycat.c, r ); fscanf(inp, %c, &ch)!= EOF; putchar(ch)); Η εντολή για το άνοιγµα του αρχείου αποτελεί την έκφραση αρχικοποίησης. Όπως και στην εκδοχή µε την εντολή while, και εδώ η συνθήκη επανάληψης κάνει χρήση της τιµής εξόδου της συνάρτησης fscanf, ενώ παράλληλα γίνεται η σάρωση του επόµενου χαρακτήρα Η εντολή για εκτύπωση έκφραση ενηµέρωσης. του χαρακτήρα αποτελεί την Η εντολή for δεν έχει σώµα. 10
Τελεστές για Αύξηση και Μείωση Τιµών (++, ) Increment and Decrement Operators count = count + 1; count += 1; ++count Ο τελεστής αύξησης, ++, λαµβάνει µία µοναδική µεταβλητή ως τον τελεστέο του. Το πλευρικό φαινόµενο της εφαρµογής του, είναι ότι η τιµή του τελεστέου του αυξάνεται κατά ένα. Συνήθως ο τελεστής χρησιµοποιείται απλά για αυτή την παρενέργεια, όπως στο παρακάτω παράδειγµα: for (count = 1; count <= n; ++count) sum += count; Η τιµή µίας έκφρασης στην οποία χρησιµοποιείται ο τελεστής ++, εξαρτάται από τη θέση του τελεστή: Προ-σηµειογραφική αύξηση (prefix increment) ο τελεστής προηγείται του τελεστέου, π.χ. ++count. Η τιµή της έκφρασης είναι η τιµή του τελεστέου µετά την αύξηση. Μετα-σηµειογραφική αύξηση (postfix increment) ο τελεστής ακολουθεί τον τελεστέο, π.χ. count++. Η τιµή της έκφρασης είναι η τιµή του τελεστέου πριν την αύξηση. Παροµοίως, ο τελεστής µείωσης,, λαµβάνει µία µοναδική µεταβλητή ως τελεστέο, η παρενέργεια της εφαρµογής του είναι η µείωση της τιµής του τελεστέου κατά ένα, ενώ παράλληλα επιστρέφεται η τιµή του τελεστέου µετά ή πριν τη µείωση, σύµφωνα µε το εάν χρησιµοποιείται προ- ή µετασηµειογραφική µείωση αντιστοίχως. 11
Παράδειγµα i j 2? j = ++i; προ-σηµειογραφική αύξηση: πρώτα αυξάνεται η i και µετά χρησιµοποιείται η τιµή της j = i++; µετα-σηµειογραφική αύξηση: πρώτα χρησιµοποιείται η τιµή της i και µετά γίνεται η αύξηση i j i j 3 3 3 2 Η χρήση των τελεστών ++ και, πρέπει να αποφεύγεται σε σύνθετες εκφράσεις όπου οι µεταβλητές στις οποίες εφαρµόζονται αυτοί οι τελεστές, εµφανίζονται περισσότερες από µία φορά. Οι µεταγλωττιστές της C εκµεταλλεύονται τις ιδιότητες της αντιµετάθεσης και προσεταιριστικότητας των τελεστών, µε στόχο την παραγωγή αποδοτικού κώδικα. x = 5; i = 2; y = i * x + ++i; Σε αυτό το παράδειγµα, είναι δυνατό να ανατεθεί είτε η τιµή 13 (2 * 5 + 3), είτε η τιµή 18 (3 * 5 + 3), στη µεταβλητή y. Εποµένως η ορθότητα του κώδικα δεν θα πρέπει να εξαρτάται από παρενέργειες οι οποίες ενδεχοµένως να διαφέρουν από υλοποίηση σε υλοποίηση της γλώσσας. 12
Παράδειγµα: Εναλλακτικός oρισµός της factorial int factorial (int x) {int i, res = 1; for (i = x; i > 1; i) res = res * i; return res; Παράδειγµα: Επαναδιατύπωση της is_prime και επέκταση για να καλύπτει και την περίπτωση του 2 int is_prime (int n) {int prime = n % 2!= 0, x = 3; while (prime && x * x <= n) prime = (n % x++)!= 0; return n == 2 prime; Η τιµή της έκφρασης, x++, δηλαδή η τιµή της µεταβλητής x πριν την αύξηση, διοχετεύεται ως δεξιός τελεστέος του τελεστή %. Ως πλευρικό φαινόµενο η τιµή της x αυξάνεται κατά ένα. Η µετα-σηµειογραφική αύξηση είναι αναγκαία σε αυτό τον ορισµό. Συγκεκριµένα η πρόταση prime = (n % x++)!= 0 ερµηνεύεται ως ακολούθως (σηµειώνεται ότι οι παρενθέσεις σε αυτή την πρόταση δεν είναι αναγκαίες): Έστω n x 17 3 17 % 3 2 x x = x + 1 2!= 0 1 4 prime = 1 13
Ορισµός µε χρήση της εντολής for int is_prime (int n) {int prime, x = 3; for (prime = n % 2!= 0; /* αρχικοποίηση */ prime && x * x <= n; /* συνθήκη επανάληψης */ prime = (n % x++)!= 0); /* ενηµέρωση */ /* η for δεν έχει σώµα */ return n == 2 prime; Ολόκληρο το πρόγραµµα για την εκτύπωση δεδοµένου πλήθους πρώτων αριθµών Η µεταβλητή current_prime είναι τοπική της main και η συνάρτηση get_prime έχει µία παράµετρο η οποία είναι δείκτης σε ακέραιο αριθµό. #include <stdio.h> int get_prime (int *cp_add); void main () { int count = 1, total, current_prime = 2; printf( \nhow many primes? ); scanf( %d, &total); while (count <= total){ printf( \n%d, get_prime(¤t_prime)); ++count; int is_prime (int n){ int prime, x = 3; for (prime = n % 2!= 0; prime && x * x <= n; prime = (n % x++)!= 0); return n == 2 prime; 14
int get_prime (int *cp_add) { int res = *cp_add; *cp_add = *cp_add + 1; while (!is_prime(*cp_add)) *cp_add = *cp_add + 1; return res; Επαναδιατύπωση της main µε χρήση της εντολής for void main () { int count, total, current_prime = 2; printf( \nhow many primes? ); scanf( %d, &total); for (count = 1; count <= total; ++count) printf( \n%d, get_prime(¤t_prime)); Επαναδιατύπωση της get_prime µε χρήση του τελεστή ++ (προσηµειογραφική αύξηση) int get_prime (int *cp_add) { int res = *cp_add; ++ *cp_add; while (!is_prime(*cp_add)) ++ *cp_add; return res; Είναι ο ακόλουθος ορισµός στον οποίο χρησιµοποιείται µετασηµειογραφική αύξηση ισοδύναµος; int get_prime (int *cp_add) { int res = *cp_add; *cp_add ++; while (!is_prime(*cp_add)) *cp_add ++; return res; 15
Εκτέλεση προγράµµατος µε βάση τον τελευταίο ορισµό της get_prime $ primes How many primes? 5 2 2 2 2 2 Γιατί συµβαίνει το πιο πάνω; Ποιά είναι η ερµηνεία της έκφρασης *cp_add ++ Για να είναι ορθός ο ορισµός αυτή η έκφραση πρέπει να ερµηνεύεται ως (*cp_add)++ Επειδή όµως ο τελεστής ++ έχει υψηλότερη προτεραιότητα από τον τελεστή * ( ακολούθα το δείκτη ), η έκφραση ερµηνεύεται ως *(cp_add++) cp_add current_prime 2, η διεύθυνση του current_prime (*cp_add)++ *(cp_add++) cp_add current_prime cp_add current_prime 3 +1 2 16
Εποµένως ποτέ δεν τροποποιείται η τιµή της current_prime. Η µεταβλητή cp_add, που αποτελεί την παράµετρο της get_prime, δηµιουργείται τοπικά της get_prime, εκ νέου για κάθε νέα κλήση της get_prime. Έτσι το τι περιγράφεται στο πιο πάνω, δεξιό σχήµα, επαναλαµβάνεται ξανά και ξανά. Αυτό το πρόβληµα δεν υπάρχει σε σχέση µε την έκφραση ++ * cp_add Εδώ υπάρχει µία και µοναδική ερµηνεία, ++(*cp_add) Εναλλακτικός ορισµός της get_prime µε χρήση της εντολής for int get_prime (int *cp_add) { int res = *cp_add; for ((*cp_add)++; /* αρχικοποίηση */!is_prime(*cp_add); /* συνθήκη επανάληψης */ (*cp_add) ++); /* ενηµέρωση */ /* η for δεν έχει σώµα */ return res; 17
Oλόκληρο το πρόγραµµα µε χρήση εντολών for #include <stdio.h> int get_prime (int *cp_add); void main () { int count, total, current_prime = 2; printf( \nhow many primes? ); scanf( %d, &total); for (count = 1; count <= total; ++count) printf( \n%d, get_prime(¤t_prime)); int is_prime (int n){ int prime, x = 3; for (prime = n % 2!= 0; prime && x * x <= n; prime = (n % x++)!= 0); return n == 2 prime; int get_prime (int *cp_add) { int res = *cp_add; for ((*cp_add)++;!is_prime(*cp_add); (*cp_add) ++); return res; Παράδειγµα: Εναλλακτικός ορισµός της sqrt double sqrt (double x) { double y; for (y = x;!good_enough(x,y); y = next_approx(x,y)); return y; 18
Εντολή do-while do εντολή while (συνθήκη) ; do S while (C); S ναι C? όχι Σηµασιολογία Καταρχήν εκτελείται η εντολή S και µετά δοκιµάζεται η συνθήκη C. Ενόσω αυτή επαληθεύεται η εκτέλεση της εντολής S επαναλαµβάνεται. Εποµένως η S εκτελείται τουλάχιστο µία φορά. Προς αποφυγή βρόχου άπειρης διάρκειας, η διεργασία της S πρέπει να είναι τέτοια ούτως ώστε να επέλθει κάποια κατάσταση στην οποία η C να µην υφίσταται πλέον. do printf( \n εν µπορώ να σταµατήσω! ); while (1); do printf( \Θα σταµατήσω αµέσως! ); while (0); printf( \Θα σταµατήσω αµέσως! ); 19
Η εντολή do-while είναι συνήθως η πιο κατάλληλη δοµή για τον έλεγχο εγκυρότητας δεδοµένων: do { printf( \nenter values for A and B where A < B ); scanf( %lf%lf, &A, &B); while (A >= B); Παράδειγµα: Εισδοχή 10 αριθµών από το πεδίο [0.0, 10.0] και υπολογισµός του µέσου όρου αυτών των αριθµών int i; double sum = 0.0, num; for (i=1; i <= 10; ++i) { do scanf( %lf, &num); while (num < 0.0 num > 10.0); sum += num; printf( \nthe average is %f, sum/10); Κοινά Λάθη Σύγχυση ανάµεσα στις εντολές if και while: if (συνθήκη) εντολή while (συνθήκη) εντολή Παράλειψη των παρενθέσεων γύρω από τις συνθήκες, αναφορικά µε τις εντολές while και do-while. Αναφορικά µε την εντολή for, παράλειψη του χαρακτήρα ; µετά την έκφραση αρχικοποίησης και µετά την συνθήκη επανάληψης. 20
Σε κάθε περίπτωση, το σώµα της επανάληψης θεωρείται ότι είναι µία και µοναδική εντολή. Κοινό λάθος είναι η παράλειψη των παρενθέσεων { όταν το τι επαναλαµβάνεται είναι µία ακολουθία εντολών, π.χ. while (x > x_big) x = z; ++x_big; Σε αυτή την εντολή το σώµα της επανάληψης θεωρείται µόνο η εντολή x = z; while (x > x_big) {x = z; ++x_big; Εδώ, η ακολουθία των δύο εντολών αποτελεί το σώµα της επανάληψης. Η χρήση του τελεστή για ανάθεση (=) αντί για του τελεστή για ισότητα (==), π.χ. do {..... while (gain = 1); Εδώ οδηγούµαστε σε βρόχο άπειρης διάρκειας. if (συνθήκη 1 ) do {.... while (συνθήκη 1 ); θα πρέπει να απλοποιηθεί σε while (συνθήκη 1 ) {..... 21
Η χρήση των τελεστών ++,, και σύνθετης ανάθεσης πρέπει να αποφεύγεται σε σχέση µε σύνθετες εκφράσεις. Επίσης η χρήση του τελεστή για µη ισότητα αναφορικά µε µεταβλητές τύπου double πρέπει να αποφεύγεται, π.χ. while (balance!= 0.0)..... Αυτή η συνθήκη πολύ πιθανώς να οδηγήσει σε βρόχο άπειρης διάρκειας, απλά επειδή µπορεί να µην τύχει να καταχωρηθεί επακριβώς η τιµή 0.0 στη µεταβλητή balance. while (balance > 0.0)..... Εδώ αποφεύγεται ο βρόχος, νοουµένου ότι η τιµή της balance δεν παραµένει επί µονίµου βάσεως κάποιος θετικός αριθµός. Παρερµήνευση των σύνθετων τελεστών ανάθεσης Μ τ= Ε σηµαίνει Μ = Μ τ (Ε) δεν σηµαίνει Μ = Μ τ Ε Οι παρενθέσεις δηλαδή γύρω από την έκφραση Ε που αποτελεί τον δεξιό τελεστέο αποτελούν αναγκαίο στοιχείο. Εποµένως a *= b + c; σηµαίνει a = a * (b + c); και όχι a = a * b + c; a = (a * b) + c; εν υπάρχει συντοµογραφία για αυτή την πρόταση. a *= b; a += c; 22
23