Skip to content

3. Εντολές ελέγχου και επανάληψης

Σύνοψη Η εντολή if, ένθετες if, η if‐else‐if, ξεκρέμαστο if, η εντολή switch, οι εντολές επανάληψης for, while και do ... while, ατέρμονοι βρόχοι, ένθετες επαναλήψεις, οι εντολές break, continue και goto.

Προαπαιτούμενη γνώση Τύποι δεδομένων, είσοδος/έξοδος.

3.1 Εισαγωγή

Η C ανήκει στις λεγόμενες προστακτικές (imperative) γλώσσες προγραμματισμού. Οι προστακτικές γλώσσες προγραμματισμού διαθέτουν εντολές ελέγχου ροής, δηλαδή εντολές επιλογής και εντολές επανάληψης, που επιτρέπουν στους προγραμματιστές να καθορίσουν βήμα προς βήμα τις εντολές που θα εκτελούνται. Ένα επιπλέον χαρακτηριστικό των προστακτικών γλωσσών είναι ότι οι μεταβλητές είναι τροποποιήσιμες (mutable), δηλαδή μπορούν να αλλάζουν τιμές κατά την εκτέλεση του προγράμματος. Με αυτόν τον τρόπο διατηρείται μια κατάσταση προγράμματος (program’s state) που αλλάζει καθώς εκτελούνται οι επιμέρους εντολές. Ο προγραμματιστής πρέπει να σχεδιάζει και να υλοποιεί το πρόγραμμα έτσι ώστε οι διαδοχικές αλλαγές στην κατάσταση του προγράμματος να παράγουν το επιθυμητό τελικό αποτέλεσμα. Υπάρχουν πολλές άλλες γλώσσες που υποστηρίζουν το «προστακτικό παράδειγμα προγραμματισμού» (imperative programming paradigm) όπως η C++, η Java, η Python και η C#.

3.2 Εντολές επιλογής

Η C διαθέτει δύο εντολές επιλογής, την εντολή if και την εντολή switch που παρουσιάζονται στη συνέχεια.

3.2.1 Η εντολή if

Η εντολή if έχει την ακόλουθη σύνταξη.

if (συνθήκη){
    // Μπλοκ κώδικα 1
} else{
    // Μπλοκ κώδικα 2
}

Αν η συνθήκη είναι αληθής, δηλαδή έχει μη μηδενική τιμή, τότε εκτελείται το μπλοκ κώδικα 1, ενώ αν είναι ψευδής, τότε εκτελείται το μπλοκ κώδικα 2. Συνεπώς, τα δύο μπλοκ κώδικα είναι μεταξύ τους αμοιβαία αποκλειόμενα. Το τμήμα else μαζί με το μπλοκ κώδικα 2 είναι προαιρετικό. Αν ένα μπλοκ κώδικα περιέχει μόνο μια εντολή, τότε οι αγκύλες του μπλοκ μπορούν να παραλειφθούν, αν και θεωρείται καλή πρακτική να υπάρχουν αγκύλες ακόμα και για μπλοκ κώδικα με μια εντολή. Το ακόλουθο παράδειγμα (κώδικας 3.1) χρησιμοποιεί την if με τη σύνταξη που δόθηκε και εξετάζει αν ένας αριθμός είναι άρτιος ή περιττός.

Κώδικας 3.1: ch3_p1.c - απλό παράδειγμα με την if.
#include <stdio.h>

int main(void) {
  int x;
  printf("Input an integer number: ");
  scanf("%d", &x);
  if (x % 2 == 0) {
    printf("The number %d is even.\n", x);
  } else {
    printf("The number %d is odd.\n", x);
  }
  return 0;
}
Ακολουθεί ένα παράδειγμα εκτέλεσης του κώδικα, όπου ο χρήστης εισάγει την τιμή 73.

Input an integer number: 73
The number 73 is odd.
Με βάση τις δυνατότητες που αναφέρθηκαν για τον τρόπο γραφής της if, οι γραμμές του κώδικα 7-11 μπορούν να αντικατασταθούν, αφαιρώντας τις αγκύλες με τις ακόλουθες.

if (x%2==0)
    printf("The number %d is even.\n", x);
else
    printf("The number %d is odd.\n", x);
Επίσης, οι ίδιες γραμμές κώδικα (7-11) μπορούν να αντικατασταθούν με τις δύο διαδοχικές if που ακολουθούν.

if (x%2==0)
    printf("The number %d is even.\n", x);
if (x%2==1)
    printf("The number %d is odd.\n", x);

Και στις δύο νέες περιπτώσεις οι κώδικες είναι ισοδύναμοι με τον αρχικό.

Ένθετες if Σε μια εντολή if, στις εντολές που εκτελούνται αν η συνθήκη είναι αληθής ή στις εντολές που εκτελούνται αν η συνθήκη είναι ψευδής, μπορούν να υπάρχουν οποιεσδήποτε εντολές, άρα και νέες εντολές if, οπότε δημιουργούνται οι λεγόμενες ένθετες ή εμφωλευμένες επιλογές (nested ifs). Ένα παράδειγμα ακολουθεί στη συνέχεια στον κώδικα 3.2 όπου εμφανίζεται για έναν αριθμό που δίνει ο χρήστης, το εάν είναι θετικός, αρνητικός ή μηδέν.

Κώδικας 3.2: ch3_p2.c - ένθετη επιλογή.
#include <stdio.h>

int main(void) {
  int x;
  printf("Input an integer number: ");
  scanf("%d", &x);
  if (x > 0) {
    printf("Positive number\n");
  } else {
    if (x < 0) {
      printf("Negative number\n");
    } else {
      printf("Zero\n");
    }
  }
  return 0;
}

Η if-else-if Συχνή είναι η χρήση της if με τη μορφή if‐else‐if. Πρόκειται, επί της ουσίας για μια μορφή ένθετων if. Ο τρόπος γραφής της if‐else‐if είναι ο ακόλουθος:

if (συνθήκη1)
    εντολή_1
else if (συνθήκη2)
    εντολή_2
else if (συνθήκη3)
    εντολή_3
...
else
    εντολή_ν
Στη μορφή αυτή της if, οι συνθήκες αποτιμώνται στη σειρά από πάνω προς τα κάτω μέχρι να βρεθεί μια συνθήκη που να είναι αληθής. Τότε εκτελείται η εντολή ή το μπλοκ εντολών που αντιστοιχεί στην αληθή συνθήκη και η εκτέλεση συνεχίζει με την εντολή που ακολουθεί την εντολή if συνολικά. Αν δεν βρεθεί καμία συνθήκη που να είναι αληθής, τότε εκτελούνται οι εντολές που βρίσκονται στο else τμήμα της εντολής, εφόσον το τμήμα αυτό υπάρχει. Χρησιμοποιώντας την if-else-if, ο κώδικας του προηγούμενου παραδείγματος γράφεται «καθαρότερα» όπως στη συνέχεια στον κώδικα 3.3.

Κώδικας 3.3: ch3_p2b.c - παράδειγμα με if-else-if.
#include <stdio.h>

int main(void) {
  int x;
  printf("Input an integer number: ");
  scanf("%d", &x);
  if (x > 0)
    printf("Positive number\n");
  else if (x < 0)
    printf("Negative number\n");
  else
    printf("Zero\n");
  return 0;
}

Μια ιδιαιτερότητα της if που θα πρέπει να γνωρίζει ο προγραμματιστής της C είναι το λεγόμενο «ξεκρέμαστο else» (dangling else) που αφορά τη συσχέτιση των else με if σε ένθετες επιλογές με πολλούς κλάδους if. Αν υπάρχουν ένθετες if και ένα else όπως στο ακόλουθο παράδειγμα, τότε εκ πρώτης όψης δεν είναι φανερό σε ποιο από τα if αντιστοιχεί το else.

if (συνθήκη1)
    if (συνθήκη2)
        if (συνθήκη3)
            ...
else
    printf("ξεκρέμαστο else\n");
Μπορεί μάλιστα η μορφή γραφής του κώδικα να υποδεικνύει, όπως στο παράδειγμα, ότι το else αντιστοιχεί στο πρώτο if, κάτι που όμως δεν ισχύει. Ο κανόνας που επιλύει αυτό το πρόβλημα είναι ότι «η πρόταση else ανήκει στο πλησιέστερο if που δεν έχει κλείσει». Συνεπώς, στο παράδειγμα η else κλείνει την if με τη συνθήκη3. Ο δε κώδικας θα μπορούσε να στοιχιστεί όπως στη συνέχεια.

if (συνθήκη1)
    if (συνθήκη2)
        if (συνθήκη3)
            ...
        else
            printf("ξεκρέμαστο else\n");
Εντολές if με σύνθετες συνθήκες Μια σύνθετη συνθήκη δημιουργείται συνδυάζοντας με λογικούς τελεστές (&& για λογικό ΚΑΙ, || για λογικό Ή και ! για λογικό ΌΧΙ) απλές συνθήκες. Για παράδειγμα η σύνθετη συνθήκη
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0 συνδυάζει 3 απλές συνθήκες και γίνεται αληθής όταν η τιμή της ακέραιας μεταβλητής year διαιρείται με το 4 και δεν διαιρείται με το 100, εκτός και αν διαιρείται με το 400 οπότε πάλι γίνεται αληθής. Ο έλεγχος αυτός εξετάζει αν ένα έτος είναι δίσεκτο και στη συνέχεια παρουσιάζονται δύο λειτουργικά ισοδύναμοι κώδικες που ο πρώτος χρησιμοποιεί τη σύνθετη συνθήκη ενώ ο δεύτερος χρησιμοποιεί ένθετες if και απλές συνθήκες.

Κώδικας 3.4: ch3_p3.c - έλεγχος για δίσεκτο έτος με σύνθετη συνθήκη.
#include <stdio.h>

int main(void) {
  int year;
  printf("Input a year: ");
  scanf("%d", &year);
  if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
    printf("%d is a leap year\n", year);
  else
    printf("%d is not a leap year\n", year);
  return 0;
}

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

Input a year: 2023
2023 is not a leap year
Μια διαφορετική υλοποίηση για το ίδιο πρόβλημα παρουσιάζεται στη συνέχεια

Κώδικας 3.5: ch3_p3b.c - έλεγχος για δίσεκτο έτος με απλές συνθήκες και ένθετες if.
#include <stdio.h>

int main(void) {
  int year;
  printf("Input a year: ");
  scanf("%d", &year);
  if (year % 4 == 0) {
    if (year % 100 != 0) {
      printf("%d is a leap year.\n", year);
    } else {
      if (year % 400 == 0) {
        printf("%d is a leap year.\n", year);
      } else {
        printf("%d is not a leap year.\n", year);
      }
    }
  } else {
    printf("%d is not a leap year.\n", year);
  }
  return 0;
}
Ο δεύτερος κώδικας (κώδικας 3.5) είναι συνθετότερος από τον πρώτο (κώδικας 3.4). Ο απλούστερος και συντομότερος κώδικας είναι ο λόγος που συνήθως προτιμώνται οι σύνθετες συνθήκες έναντι των ένθετων if. Ωστόσο, σε μερικές περιπτώσεις, ο κώδικας είναι περισσότερο κατανοητός αν χρησιμοποιηθεί συνδυασμός ένθετων if και σύνθετων συνθηκών.

3.2.2 Η εντολή switch

Η switch είναι η δεύτερη εντολή επιλογής της C και συνήθως χρησιμοποιείται λιγότερο συχνά από την if. Η σύνταξή της είναι η ακόλουθη:

switch (έκφραση) {
    case σταθερά1:
        // Κώδικας που θα εκτελεστεί αν η έκφραση αποτιμηθεί στην τιμή σταθερά1
        break;
    case σταθερά2:
        // Κώδικας που θα εκτελεστεί αν η έκφραση αποτιμηθεί στην τιμή σταθερά2
        break;
    // Επιπλέον περιπτώσεις μπορούν να προστεθούν εδώ ανάλογα με τις ανάγκες
    default:
        // Κώδικας που θα εκτελεστεί αν η έκφραση δεν ταιριάξει με καμία από τις
            ↪ ανωτέρω σταθερές τιμές
        break;
}

Η τιμή της έκφρασης (που μπορεί να είναι μόνο ακέραια ή χαρακτήρας) συγκρίνεται από πάνω προς τα κάτω με τις τιμές των σταθερών σε κάθε περίπτωση case. Αν υπάρξει ταύτιση τότε εκτελούνται οι αντίστοιχες εντολές. Αν δεν υπάρξει ταύτιση σε καμία από τις περιπτώσεις τότε εκτελείται το τμήμα default, εφόσον υπάρχει, καθώς είναι προαιρετικό. Παρατηρήστε ότι οι εντολές που ακολουθούν κάθε περίπτωση case δεν τοποθετούνται σε αγκύλες.

Ακολουθεί ένα παράδειγμα χρήσης της switch, στον κώδικα 3.6, στον οποίο ο χρήστης εισάγει έναν αριθμητικό τελεστή από τους +, , *,/ καθώς και δύο τιμές και στη συνέχεια πραγματοποιείται η αριθμητική πράξη που επιλέχθηκε με τελεστέους τις δύο τιμές.

Κώδικας 3.6: ch3_p4.c - αριθμητικές πράξεις.
#include <stdio.h>

int main(void) {
  char operator;
  double num1, num2, result;

  printf("Enter an operator (+, -, *, /): ");
  scanf("%c", &operator);
  printf("Enter two numbers: ");
  scanf("%lf %lf", &num1, &num2);
  switch (operator) {
  case '+':
    result = num1 + num2;
    printf("Result: %.2lf\n", result);
    break;
  case '-':
    result = num1 - num2;
    printf("Result: %.2lf\n", result);
    break;
  case '*':
    result = num1 * num2;
    printf("Result: %.2lf\n", result);
    break;
  case '/':
    if (num2 != 0) {
      result = num1 / num2;
      printf("Result: %.2lf\n", result);
    } else {
      printf("Error: Division by zero!\n");
    }
    break;
  default:
    printf("Error: Invalid operator!\n");
  }
  return 0;
}

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

Enter an operator (+, ‐, *, /): /
Enter two numbers: 100 21
Result: 4.76
Παρατηρήστε ότι μετά από τον κώδικα κάθε περίπτωσης case υπάρχει η εντολή break, που προκαλεί έξοδο από τη switch. Αν δεν τοποθετηθεί break σε μια περίπτωση case τότε ο έλεγχος συνεχίζει στην περίπτωση case που ακολουθεί στη σειρά (follow-through). Αυτό το χαρακτηριστικό μπορεί να χρησιμοποιηθεί έτσι ώστε να προκύψει συντομότερος κώδικας όπως στο ακόλουθο παράδειγμα (κώδικας 3.7) που αναμένει τον χρήστη να εισαγάγει σε μια υποθετική ερώτηση είτε ’Y’ ή ’y’ είτε ’N’ ή ’n’.

Κώδικας 3.7: ch3_p5.c - περιπτώσεις case χωρίς break.
#include <stdio.h>

int main(void) {
  char c;
  printf("Is C a great language? ");
  scanf("%c", &c);
  switch (c) {
  case 'y':
  case 'Y':
    printf("I agree!\n");
    break;
  case 'n':
  case 'N':
    printf("Think again!\n");
    break;
  default:
    printf("I will take that as a yes!\n");
  }
  return 0;
}
Ακολουθεί ένα παράδειγμα εκτέλεσης του κώδικα.
Is C a great language? y
I agree!

3.3 Εντολές επανάληψης

Η C διαθέτει 3 εντολές επανάληψης, την for, την while και την do...while που η λειτουργία τους μπορεί να τροποποιηθεί με τις εντολές continue και break. Επίσης, βρόχοι επανάληψης μπορούν να υλοποιηθούν και με την εντολή goto, αν και αυτό δεν θεωρείται καλή πρακτική προγραμματισμού.

3.3.1 Η εντολή for

Η εντολή for είναι η εντολή επανάληψης που χρησιμοποιείται συχνότερα. Έχει την ακόλουθη σύνταξη.

for (αρχικοποίηση; συνθήκη ελέγχου επανάληψης; ενημέρωση) {
    // Κώδικας που εκτελείται σε κάθε επανάληψη
}
Η δεσμευμένη λέξη for ακολουθείται από 3 τμήματα που χωρίζονται μεταξύ τους με ερωτηματικά. Το πρώτο τμήμα (αρχικοποίηση) εκτελείται μια φορά στην αρχή της επανάληψης. Το δεύτερο τμήμα (συνθήκη ελέγχου επανάληψης) είναι μια συνθήκη που όσο είναι αληθής η εκτέλεση των εντολών επανάληψης συνεχίζεται. Όταν η συνθήκη γίνει ψευδής, το πρόγραμμα συνεχίζει την εκτέλεσή του με την εντολή που ακολουθεί το μπλοκ επανάληψης της for. Το τρίτο τμήμα (ενημέρωση) εκτελείται στο τέλος κάθε επανάληψης και χρησιμοποιείται συνήθως για να ενημερώνει τη μεταβλητή (ή τις μεταβλητές) από την οποία εξαρτάται η συνέχεια ή ο τερματισμός της επανάληψης. Ένα απλό παράδειγμα χρήσης της for είναι το ακόλουθο πρόγραμμα (κώδικας 3.8) που πραγματοποιεί 5 επαναλήψεις.

Κώδικας 3.8: ch3_p6.c - εκτέλεση του βρόχου επανάληψης 5 φορές
1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 5; i++) {
    printf("Loop %d, variable i = %d\n", i + 1, i);
  }
  return 0;
}
Αρχικά ανατίθεται στη μεταβλητή i η τιμή 0. Μετά εξετάζεται η συνθήκη ελέγχου (i < 5) για τη συνέχεια της επανάληψης. Λόγω του ότι είναι αληθής, εκτελείται το μπλοκ κώδικα που ακολουθεί που αποτελεί και το σώμα του βρόχου. Όταν ολοκληρωθεί η εκτέλεση του σώματος του βρόχου, η εκτέλεση συνεχίζεται με την αύξηση της μεταβλητής i κατά 1 (με την εντολή i++) και τον έλεγχο εκ νέου της συνθήκης της επανάληψης. Όταν το i λάβει την τιμή 5, τότε η συνθήκη της επανάληψης θα γίνει ψευδής και θα τερματιστεί η επανάληψη. Συνεπώς, η έξοδος του προγράμματος θα είναι η ακόλουθη:

Loop 1, variable i = 0
Loop 2, variable i = 1
Loop 3, variable i = 2
Loop 4, variable i = 3
Loop 5, variable i = 4
Καθένα από τα 3 τμήματα μέσα στην παρένθεση αμέσως μετά τη δεσμευμένη λέξη for μπορούν είτε να παραλειφθούν είτε να γίνουν συνθετότερα. Ακολουθεί ένα παράδειγμα (κώδικας 3.9) όπου η αρχικοποίηση και η ενημέρωση αποτελούνται η καθεμία από 2 επιμέρους εντολές που χωρίζονται μεταξύ τους με κόμμα (,) που αποτελεί ειδικό τελεστή για αυτήν την περίπτωση.

Κώδικας 3.9: ch3_p7.c - παράδειγμα με διπλή αρχικοποίηση στη for.
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void) {
  int i, j;
  for (i = 10, j = 30; i < j; i += 7, j += 2) {
    printf("i = %d, j = %d\n", i, j);
  }
  return 0;
}
Η εκτέλεση του προγράμματος θα εμφανίσει τα ακόλουθα αποτελέσματα.

i = 10, j = 30
i = 17, j = 32
i = 24, j = 34
i = 31, j = 36
Το επόμενο παράδειγμα (κώδικας 3.10) δείχνει τη χρήση της εντολής for χωρίς να έχει τμήμα ενημέρωσης τιμών μεταβλητών επανάληψης, καθώς αυτό πραγματοποιείται στο συγκεκριμένο παράδειγμα μέσα στον βρόχο επανάληψης.

Κώδικας 3.10: ch3_p8.c - παράλειψη του τμήματος ενημέρωσης μεταβλητών επανάληψης της for
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 5;) {
    i++;
    printf("Loop %d, variable i = %d\n", i, i);
  }
  return 0;
}

Ακολουθεί η έξοδος του προγράμματος.

Loop 1, variable i = 1
Loop 2, variable i = 2
Loop 3, variable i = 3
Loop 4, variable i = 4
Loop 5, variable i = 5

3.3.2 Η εντολή while

Μια άλλη εντολή επανάληψης της C είναι η εντολή while που επαναλαμβάνει ένα μπλοκ εντολών όσο μια συνθήκη είναι αληθής. Η σύνταξή της είναι η ακόλουθη.

while (συνθήκη) {
    // Κώδικας που εκτελείται σε κάθε επανάληψη
}

Η συνθήκη ελέγχεται στην αρχή κάθε επανάληψης και αν είναι αληθής εκτελείται το μπλοκ εντολών που ακολουθεί. Αν το μπλοκ εντολών περιέχει μία μόνο εντολή, τότε οι αγκύλες του μπλοκ μπορούν να παραλειφθούν. Αν η συνθήκη είναι ψευδής, τότε τερματίζεται ο βρόχος επανάληψης και το πρόγραμμα συνεχίζει την εκτέλεσή του με την εντολή αμέσως μετά το μπλοκ εντολών. Καθώς ο έλεγχος της συνθήκης για τη συνέχεια ή τον τερματισμό της επανάληψης βρίσκεται πριν το μπλοκ εντολών, υπάρχει περίπτωση η συνθήκη να είναι αρχικά ψευδής και να μην γίνει καμία επανάληψη. Στη συνέχεια, στον κώδικα 3.11 παρουσιάζεται ένα παράδειγμα χρήσης της εντολής while που υπολογίζει το μικρότερο πλήθος διαδοχικών ακεραίων που ξεκινούν από το 1 και που το άθροισμά τους ξεπερνά την τιμή 1000.

Κώδικας 3.11: ch3_p9.c - παράδειγμα επανάληψης με τη while.
#include <stdio.h>

int main(void) {
  int n = 1;
  int sum = 0;
  while (sum <= 1000) {
    sum += n;
    n++;
  }
  printf("The smallest number n such that 1+2+...+n > 1000 is: %d\n", n - 1);
  return 0;
}
Ακολουθεί το αποτέλεσμα της εκτέλεσης του κώδικα.

The smallest number n such that 1+2+...+n > 1000 is: 45

Σε κάθε επανάληψη ελέγχεται η τιμή του sum, που έχει αρχικοποιηθεί στο μηδέν, και αν είναι μικρότερη ή ίση του 1000 εκτελείται ο βρόχος επανάληψης. Σε κάθε επανάληψη η τιμή του sum ενημερώνεται προσθέτοντας στο sum την τρέχουσα τιμή του n, και στη συνέχεια αυξάνεται η τιμή του n κατά 1. Όταν η συνθήκη της επανάληψης γίνει ψευδής, τότε ο βρόχος τερματίζεται. Παρατηρήστε ότι η τιμή που εμφανίζεται για το n στη γραμμή 10, είναι μειωμένη κατά 1, διότι η μεταβλητή n αυξάνεται στον βρόχο μετά την εντολή που προσθέτει την τρέχουσα τιμή του n στο sum, οπότε όταν γίνεται ο έλεγχος στη συνθήκη της επανάληψης (για το εάν sum < 1000), η τελευταία τιμή που έχει προστεθεί στο sum είναι η μικρότερη κατά ένα τιμή του n, σε σχέση με την τρέχουσα τιμή της. Από την άλλη μεριά, αν παραλειφθεί η εντολή sum += n τότε η τιμή του sum δεν θα αλλάζει κάθε φορά που θα εκτελείται ο βρόχος και η επανάληψη θα συνεχίζεται επ’ άπειρο. Ο τερματισμός εκτέλεσης του προγράμματος σε αυτήν την περίπτωση γίνεται με τον συνδυασμό πλήκτρων Ctrl + C και είναι ευθύνη του προγραμματιστή να γράφει κώδικα με τέτοιον τρόπο ώστε να μη συμβαίνουν άπειροι ή όπως αλλιώς ονομάζονται ατέρμονοι βρόχοι (infinite loop).

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

Παρόμοια με την εντολή while είναι και η εντολή do...while, με τη διαφορά ότι ο έλεγχος συνέχειας ή τερματισμού της επανάληψης γίνεται μετά το μπλοκ εντολών και όχι πριν όπως στη while. Η σύνταξη της do...while είναι η ακόλουθη:

do {
    // Κώδικας που εκτελείται σε κάθε επανάληψη
} while (συνθήκη);

Οι εντολές του βρόχου επανάληψης εκτελούνται αρχικά και στη συνέχεια εξετάζεται η συνθήκη της επανάληψης. Αν η συνθήκη είναι αληθής τότε οι εντολές του βρόχου εκτελούνται ξανά. Αν όμως η συνθήκη είναι ψευδής, τότε η επανάληψη σταματά και το πρόγραμμα συνεχίζει την εκτέλεσή του με την εντολή που ακολουθεί τη while. H do..while είναι χρήσιμη σε καταστάσεις που επιθυμούμε να διασφαλίσουμε ότι ο βρόχος επανάληψης θα εκτελεστεί τουλάχιστον μια φορά, ανεξάρτητα από τη συνθήκη της επανάληψης. Στο ακόλουθο παράδειγμα (κώδικας 3.12) το πρόγραμμα εμφανίζει ένα μενού με 3 επιλογές και προτρέπει τον χρήστη να εισάγει την επιλογή του έτσι ώστε να εκτυπωθεί ένα μήνυμα ή να τερματίσει η επανάληψη. Η επιλογή της do...while φαίνεται να είναι καταλληλότερη, διότι επιθυμούμε το μενού να εμφανιστεί τουλάχιστον μία φορά, ανεξάρτητα από την τιμή της μεταβλητής choice.

Κώδικας 3.12: ch3_p10.c - παράδειγμα επανάληψης με τη do...while.
#include <stdio.h>

int main(void) {
  int choice;
  do {
    printf("Select an option:\n");
    printf("1. Print 'Hello'\n");
    printf("2. Print 'Goodbye'\n");
    printf("3. Quit\n");
    printf("Enter your choice: ");
    scanf("%d", &choice);
    switch (choice) {
    case 1:
      printf("Hello\n");
      break;
    case 2:
      printf("Goodbye\n");
      break;
    case 3:
      printf("Quitting the program...\n");
      break;
    default:
      printf("Invalid choice. Please try again.\n");
      break;
    }
  } while (choice != 3);
  return 0;
}
Ακολουθεί ένα παράδειγμα εκτέλεσης του κώδικα.

Select an option:
1. Print 'Hello'
2. Print 'Goodbye '
3. Quit
Enter your choice: 4
Invalid choice. Please try again.
Select an option:
1. Print 'Hello'
2. Print 'Goodbye '
3. Quit
Enter your choice: 1
Hello
Select an option:
1. Print 'Hello'
2. Print 'Goodbye '
3. Quit
Enter your choice: 3
Quitting the program...

3.3.4 Οι εντολές break και continue

Οι εντολές break και continue μπορούν να χρησιμοποιηθούν στις εντολές επανάληψης για να παρακάμψουν τον προκαθορισμένο τρόπο λειτουργίας τους. Η εντολή break προκαλεί πρόωρο τερματισμό της επανάληψης, ενώ η εντολή continue προκαλεί μετάβαση στον έλεγχο της συνθήκης της επανάληψης, όπου και καθορίζεται αν η επανάληψη θα συνεχιστεί ή θα τερματιστεί.

Παραδείγματα με την εντολή break Στο πρώτο παράδειγμα με τη break, στον κώδικα 3.13, ο χρήστης εισάγει 5 ακέραιες τιμές και το πρόγραμμα υπολογίζει και εμφανίζει το άθροισμα των τιμών που εισήχθησαν. Αν όμως ο χρήστης εισάγει την τιμή -1, τότε η επανάληψη τερματίζει και εμφανίζεται το άθροισμα από τις τιμές που έχουν εισαχθεί μέχρι εκείνο το σημείο.

Κώδικας 3.13: ch3_p11.c - παράδειγμα με την εντολή break.
#include <stdio.h>

int main(void) {
  int i, x, sum = 0;
  for (i = 0; i < 5; i++) {
    printf("Enter a value: ");
    scanf("%d", &x);
    if (x == -1)
      break;
    sum += x;
  }
  printf("The sum is %d\n", sum);
  return 0;
}
Ακολουθεί ένα παράδειγμα εκτέλεσης.

Enter a value: 7
Enter a value: 11
Enter a value: ‐1
The sum is 18
Η εντολή break μπορεί να χρησιμοποιηθεί με ατέρμονους βρόχους επανάληψης όπως αυτοί που ακολουθούν, δίνοντας τη δυνατότητα να τερματιστεί η επανάληψη.

for(;;){                    | while (1){               |  do {
    // βρόχος επανάληψης    |   // βρόχος επανάληψης   |   // βρόχος επανάληψης
}                           | }                        |  } while (1);

Στο δεύτερο παράδειγμα με τη break, στον κώδικα 3.14, ο χρήστης εισάγει τιμές μέχρι να εισαχθεί η τιμή -1. Ο κώδικας χρησιμοποιεί την εντολή break για να «βγει» από τον ατέρμονα βρόχο.

Κώδικας 3.14: ch3_p12.c - παράδειγμα με «ατέρμονα βρόχο» και την break.
#include <stdio.h>

int main(void) {
  for (;;) {
    int x;
    printf("Enter a number (-1 to exit): ");
    scanf("%d", &x);
    if (x == -1)
      break;
    printf("You entered: %d\n", x);
  }
  printf("Loop terminated.\n\n");
  return 0;
}

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

Enter a number (‐1 to exit): 42
You entered: 42
Enter a number (‐1 to exit): 73
You entered: 73
Enter a number (‐1 to exit): ‐1
Loop terminated.

Παράδειγμα με την εντολή continue Στο παράδειγμα του κώδικα 3.15 εκτελούνται 10 επαναλήψεις με την εντολή for, με τη μεταβλητή επανάληψης i να λαμβάνει τιμές από 0 μέχρι και 9. Σε κάθε επανάληψη εμφανίζεται η τιμή της μεταβλητής επανάληψης, εκτός από τις περιττές τιμές δείκτη που είναι μεγαλύτερες του 5. Η παράλειψη εκτύπωσης της τιμής της μεταβλητής επανάληψης επιτυγχάνεται τοποθετώντας την εντολή continue στην κατάλληλη θέση.

Κώδικας 3.15: ch3_p13.c - παράδειγμα με την continue.
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 10; i++) {
    if (i > 5 && i % 2 == 1)
      continue;
    printf("%d ", i);
  }
  return 0;
}

Το αποτέλεσμα της εκτέλεσης είναι: 0 1 2 3 4 5 6 8.

3.3.5 Ένθετοι βρόχοι

Ένας ένθετος βρόχος είναι ένας βρόχος που περικλείεται σε έναν άλλο βρόχο. Ακολουθεί ένα απλό παράδειγμα ένθετων βρόχων με την εντολή for (κώδικας 3.16). Ο εξωτερικός βρόχος εκτελείται 5 φορές και για κάθε επανάληψή του ο ένθετος βρόχος εκτελείται 2 φορές. Άρα συνολικά η εντολή printf θα εκτελεστεί 10 φορές.

Κώδικας 3.16: ch3_p14.c - ένθετοι βρόχοι.
1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 5; i++)
    for (int j = 0; j < 2; j++)
      printf("i=%d, j=%d\n", i, j);
  return 0;
}
Συνεπώς, η εκτέλεση του κώδικα θα δώσει τα ακόλουθα αποτελέσματα:

i=0, j=0
i=0, j=1
i=1, j=0
i=1, j=1
i=2, j=0
i=2, j=1
i=3, j=0
i=3, j=1
i=4, j=0
i=4, j=1

Οι ένθετοι βρόχοι μπορεί να είναι συνθετότεροι. Στο επόμενο παράδειγμα (κώδικας 3.17), το πλήθος των επαναλήψεων του ένθετου βρόχου αλλάζει από επανάληψη σε επανάληψη.

Κώδικας 3.17: ch3_p15.c - ένθετοι βρόχοι.
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 5; i++) {
    for (int j = i + 1; j > 0; j--) {
      printf("%d ", j);
    }
    printf("\n");
  }
  return 0;
}

Ακολουθεί το αποτέλεσμα της εκτέλεσης.

1
2 1
3 2 1
4 3 2 1
5 4 3 2 1

3.3.6 Η εντολή goto

Η goto είναι μια εντολή αλλαγής της ροής του προγράμματος που προκαλεί μετάβαση σε ένα σημείο του προγράμματος που προσδιορίζεται από μια ετικέτα. Η μετάβαση γίνεται χωρίς να εξαρτάται από κάποια συνθήκη και συνεπώς πραγματοποιείται απευθείας αλλαγή της ροής εκτέλεσης του προγράμματος. Ακολουθεί ένα παράδειγμα χρήσης της goto, στον κώδικα 3.18, που δημιουργεί έναν βρόχο επανάληψης που τερματίζεται όταν ο χρήστης εισάγει την τιμή -1. Το όνομα της ετικέτας στο παράδειγμα αυτό είναι loop.

Κώδικας 3.18: ch3_p16.c - παράδειγμα με τη goto.
#include <stdio.h>

int main(void) {
  int num;
loop:
  printf("Enter a number (-1 to exit): ");
  scanf("%d", &num);
  if (num != -1) {
    printf("You entered: %d\n", num);
    goto loop;
  }
  printf("Loop terminated.\n");
  return 0;
}

Ο κώδικας είναι λειτουργικά ισοδύναμος με το παράδειγμα με την εντολή break του κώδικα 3.14 και συνεπώς παράγει τα ίδια αποτελέσματα.

Γενικά, προτείνεται η αποφυγή της εντολής goto διότι ο κώδικας που προκύπτει είναι πιθανό να είναι δυσκολότερο να αναγνωσθεί και να κατανοηθεί 1. Ο κώδικας με πολλά goto συχνά αναφέρεται και ως spaghetti κώδικας και όπως παραστατικά περιγράφεται στο 2 σε συνδυασμό με άλλους παράγοντες μπορεί να οδηγήσει σταδιακά στη μεταμόρφωση του λογισμικού σε “big ball of mud” (μεγάλη σφαίρα λάσπης), με μηδενικές δυνατότητες επέκτασης και διόρθωσης. Ωστόσο, σε ορισμένες περιπτώσεις 3 η goto μπορεί να χρησιμοποιηθεί αποδοτικά έτσι ώστε να απλοποιήσει τον έλεγχο ροής εκτέλεσης του προγράμματος ή να χειριστεί σφάλματα. Ακολουθούν δύο παραδείγματα που η goto δίνει εύκολο στην κατανόηση κώδικα. Στο πρώτο παράδειγμα (κώδικας 3.19) η goto προκαλεί την έξοδο από μια τριπλά ένθετη επανάληψη με for, ενώ στο δεύτερο παράδειγμα (κώδικας 3.21) απομονώνει τον χειρισμό εισαγωγής τιμών που οδηγούν σε σφάλματα.

Κώδικας 3.19: ch3_p17.c - έξοδος από ένθετες επαναλήψεις με την goto.
#include <stdio.h>

int main(void) {
  for (int i = 0; i < 10; i++)
    for (int j = 0; j < 10; j++)
      for (int k = 0; k < 10; k++) {
        printf("i=%d, j=%d, k=%d\n", i, j, k);
        if (i == 0 && j == 1 && k == 2)
          goto finish;
      }
finish:
  printf("Exit from the nested loops!\n");
  return 0;
}
Η εκτέλεση του προγράμματος θα εμφανίσει τα ακόλουθα αποτελέσματα, καθώς όταν οι μεταβλητές i, j και k λάβουν τιμές που θα προκαλέσουν την εκτέλεση της goto θα γίνει απευθείας έξοδος και από τις 3 επαναλήψεις.

i=0, j=0, k=0
i=0, j=0, k=1
i=0, j=0, k=2
i=0, j=0, k=3
i=0, j=0, k=4
i=0, j=1, k=0
i=0, j=1, k=1
i=0, j=1, k=2
Exit from the nested loops!
Ο κώδικας 3.20 δείχνει πώς θα μπορούσε να επιτευχθεί το ίδιο αποτέλεσμα, χωρίς goto, αλλά συνθετότερα και λιγότερο αποδοτικά.

Κώδικας 3.20: ch3_p17b.c - έξοδος από ένθετες επαναλήψεις.
#include <stdbool.h>
#include <stdio.h>

int main(void) {
  bool flag = true;
  for (int i = 0; i < 10 && flag; i++)
    for (int j = 0; j < 10 && flag; j++)
      for (int k = 0; k < 5 && flag; k++) {
        printf("i=%d, j=%d, k=%d\n", i, j, k);
        if (i == 0 && j == 1 && k == 2)
          flag = false;
      }
  if (!flag) {
    printf("Exit from the nested loops!\n");
  }
  return 0;
}

Στο επόμενο παράδειγμα (κώδικας 3.21) παρουσιάζεται ένας συνηθισμένος τρόπος χειρισμού λαθών με την goto. Στο παράδειγμα ζητείται η είσοδος δύο ακεραίων τιμών από τον χρήστη και ο υπολογισμός του πηλίκου του πρώτου με τον δεύτερο, καθώς και ο υπολογισμός της τετραγωνικής ρίζας του πρώτου αριθμού. Τα σημεία του κώδικα που προκαλούν σφάλματα (διαίρεση με το μηδέν, τετραγωνική ρίζα με αρνητικό υπόριζο) κατευθύνουν τη ροή εκτέλεσης του προγράμματος με την goto σε συγκεκριμένο τμήμα του κώδικα (γραμμές 17-19) που εμφανίζει μήνυμα σφάλματος και τερματίζει την εκτέλεση του προγράμματος.

Κώδικας 3.21: ch3_p18.c - χειρισμός λαθών με την goto.
#include <math.h>
#include <stdio.h>

int main(void) {
  int a, b;
  printf("Enter two numbers: ");
  scanf("%d %d", &a, &b);
  if (b == 0) {
    goto error_handling;
  }
  if (a < 0) {
    goto error_handling;
  }
  printf("a=%d, b=%d, a/b=%.2f, sqrt(a)=%.2f\n", a, b, (float)a / b, sqrt(a));
  return 0;

error_handling:
  printf("Error: because math!\n");
  return 1;
}

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

Enter two numbers: ‐1 2
Error: because math!

Συμπερασματικά, η υπερβολική χρήση της goto ή η χρήση της goto στη θέση άλλων δομών που θα έδιναν απλούστερο κώδικα, μπορεί να οδηγήσει σε φτωχά δομημένο κώδικα που είναι δύσκολο να συντηρηθεί. Η γενική σύσταση είναι να προτιμώνται οι δομές επιλογής και επανάληψης όπως οι if, switch, for και while, όπου αυτό είναι δυνατό.

3.4 Ασκήσεις

Άσκηση 1
Γράψτε ένα πρόγραμμα που να ελέγχεται από ένα μενού επιλογών με τις επιλογές 1) ημίτονο, 2) συνημίτονο, 3) εφαπτομένη. Ανάλογα με την επιλογή του χρήστη να εμφανίζει τις τιμές της τριγωνομετρικής συνάρτησης που επιλέγεται για όλες τις τιμές γωνιών από 1 μέχρι και 360 μοίρες. Χρησιμοποιήστε τις συναρτήσεις sin() (ημίτονο), cos() (συνημίτονο) και tan() (εφαπτομένη) από την τυπική βιβλιοθήκη της C που ορίζονται στο math.h. Προσέξτε ότι οι συναρτήσεις αυτές λειτουργούν με τιμές σε ακτίνια, οπότε πρέπει πρώτα οι μοίρες να μετατραπούν σε ακτίνια. Η μετατροπή μιας γωνίας από μοίρες σε ακτίνια γίνεται πολλαπλασιάζοντας τις μοίρες με το (𝜋/180).

Λύση άσκησης 1
#include <math.h>
#include <stdio.h>

int main(void) {
  int choice;
  const char *function_name;

  printf("Trigonometric Functions\n");
  printf("=======================\n");
  printf("1. Sine\n");
  printf("2. Cosine\n");
  printf("3. Tangent\n");
  printf("Enter your choice (1-3): ");
  scanf("%d", &choice);

  switch (choice) {
  case 1:
    function_name = "Sine";
    break;
  case 2:
    function_name = "Cosine";
    break;
  case 3:
    function_name = "Tangent";
    break;
  default:
    printf("Invalid choice.\n");
    return 1;
  }

  printf("\n");
  printf("D       %-12s   D       %-12s   D       %-12s\n", function_name,
         function_name, function_name);
  printf("====================================================================="
         "===\n");

  for (int angle = 1; angle <= 360; angle++) {
    double radians = angle * (M_PI / 180.0);
    double value;

    switch (choice) {
    case 1:
      value = sin(radians);
      break;
    case 2:
      value = cos(radians);
      break;
    case 3:
      if (angle % 90 == 0) {
        value = NAN;
      } else {
        value = tan(radians);
      }
      break;
    }

    printf("%-6d   %-12.4f   ", angle, value);

    if (angle % 3 == 0)
      printf("\n");
  }
  return 0;
}

Άσκηση 2
Γράψτε ένα πρόγραμμα που να ελέγχει αν δύο αριθμοί που εισάγει ο χρήστης είναι φίλιοι αριθμοί. Δύο αριθμοί λέγεται ότι είναι φίλιοι αν το άθροισμα των διαιρετών του καθένα από τους δύο αριθμούς (εξαιρώντας τον ίδιο τον αριθμό) είναι ίσο με τον άλλο αριθμό. Για παράδειγμα οι αριθμοί 220 και 284 είναι φίλιοι αριθμοί καθώς

284 = 1 + 2 + 4 + 5 + 10 + 11 + 20 + 22 + 44 + 55 + 110 (οι διαιρέτες του 220)

και

220 = 1 + 2 + 4 + 71 + 142 (οι διαιρέτες του 284).

Λύση άσκησης 2
#include <stdio.h>

int main(void) {
  int num1, num2;
  int sum1 = 0, sum2 = 0;

  printf("Enter the first number: ");
  scanf("%d", &num1);
  printf("Enter the second number: ");
  scanf("%d", &num2);

  for (int i = 1; i <= num1 / 2; i++) {
    if (num1 % i == 0) {
      sum1 += i;
    }
  }

  for (int i = 1; i <= num2 / 2; i++) {
    if (num2 % i == 0) {
      sum2 += i;
    }
  }

  if (sum1 == num2 && sum2 == num1) {
    printf("%d and %d are friends!\n", num1, num2);
  } else {
    printf("%d and %d are not friends.\n", num1, num2);
  }

  return 0;
}

Άσκηση 3
Με βάση τη λύση που δώσατε στην προηγούμενη άσκηση, υπολογίστε το πλήθος των ζυγών φίλιων αριθμών στο διάστημα από 1 μέχρι 10000.

Λύση άσκησης 3
#include <stdio.h>
#define N 10000

int main(void) {
  for (int num1 = 1; num1 <= N; num1++) {
    int sum1 = 0;
    for (int i = 1; i <= num1 / 2; i++) {
      if (num1 % i == 0) {
        sum1 += i;
      }
    }
    for (int num2 = num1 + 1; num2 <= N; num2++) {
      int sum2 = 0;
      for (int i = 1; i <= num2 / 2; i++) {
        if (num2 % i == 0) {
          sum2 += i;
        }
      }
      if (sum1 == num2 && sum2 == num1) {
        printf("%d and %d are friends!\n", num1, num2);
      }
    }
  }
  return 0;
}

Άσκηση 4
Η φόρμουλα του Wallis(1), από το 1652,

  1. https://en.wikipedia.org/wiki/Wallis_product

$$ \frac{\pi}{2} = \left(\frac{2}{1} \times \frac{2}{3}\right) \times \left(\frac{4}{3} \times \frac{4}{5}\right) \times \left(\frac{6}{5} \times \frac{6}{7}\right) \times \left(\frac{8}{7} \times \frac{8}{9}\right) \times \dots $$ μπορεί να χρησιμοποιηθεί για τον υπολογισμό μία προσέγγιση του 𝜋. Γράψτε ένα πρόγραμμα που χρησιμοποιώντας τη φόρμουλα να υπολογίζει την προσέγγιση του 𝜋. Ο υπολογισμός να συνεχίζεται για ένα πλήθος όρων του γινομένου που θα δίνεται από τον χρήστη.

Λύση άσκησης 4
#include <stdio.h>

int main(void) {
  int terms;
  double pi = 1.0;
  printf("Enter number of terms that will be used to approximate pi: ");
  scanf("%d", &terms);
  for (int i = 1; i <= terms; i++) {
    double numerator = (2. * i) * (2. * i);
    double denominator = ((2. * i) - 1) * ((2. * i) + 1);
    double term = numerator / denominator;
    pi *= term;
  }
  pi *= 2;
  printf("Approximation of Pi: %f\n", pi);
  return 0;
}

  1. Edsger W Dijkstra. “Letters to the editor: go to statement considered harmful”. Στο: Communications of the ACM 11.3 (1968), σσ. 147–148. 

  2. Brian Foote και Joseph Yoder. “Big ball of mud”. Στο: Pattern languages of program design 4 (1997), σσ. 654–692. 

  3. Delroy A. Brinkerhoff. Object Oriented Proggramming using C++. Supplemental: Legitimate Uses For goto. https://icarus.cs.weber.edu/~dab/cs1410/textbook/3.Control/goto.html. Accessed: 2023-06-01.