Οδηγός Μελέτης - Εισαγωγή στον Προγραμματισμό

Διάλεξη 6 · 20/10/2025 · Διαφάνειες (PDF) · Σημειώσεις 3 · Εργαστήριο 3

Κεφάλαιο 6: Εντολές και Ροή Ελέγχου

Στόχοι: μετά από αυτό το κεφάλαιο θα μπορείτε να κατατάσσετε κάθε εντολή της C σε μία από πέντε κατηγορίες, να γράφετε if, if-else και αλυσίδες else if, να αποφεύγετε το dangling else, να γράφετε βρόχους while, for και do-while, να προβλέπετε πόσες φορές εκτελείται το σώμα τους και να διαβάζετε το διάγραμμα ροής κάθε εντολής ελέγχου.

Προαπαιτούμενα: Κεφάλαιο 2, Κεφάλαιο 5

Χρόνος μελέτης: ~2 ώρες

Σύνοψη

Η διάλεξη κατατάσσει τις εντολές της C σε πέντε κατηγορίες (κενές, έκφρασης, σύνθετες, συνθήκης, επανάληψης) και περνάει στη ροή ελέγχου: στις εντολές που αποφασίζουν ποια εντολή θα εκτελεστεί και πόσες φορές. Βλέπουμε την if και την if-else, τις εμφωλευμένες if και την παγίδα του dangling else, και τους τρεις βρόχους while, for και do-while, ο καθένας με το διάγραμμα ροής του. Με αυτά γράφουμε προγράμματα που παίρνουν αποφάσεις και επαναλαμβάνουν υπολογισμούς.

Θεωρία

§6.1 Κατηγορίες εντολών

Κάθε εντολή (statement) της C ανήκει σε μία από πέντε κατηγορίες:

# Κατηγορία English Παράδειγμα
1 Κενές εντολές null / empty statements ;
2 Εντολές έκφρασης expression statements x = 4;
3 Σύνθετες εντολές compound statements / blocks { x = 4; y = 7; }
4 Εντολές συνθήκης conditional statements if (x > y) max = x;
5 Εντολές επανάληψης iteration statements / loops while (i < 42) i++;

Το semicolon (;) είναι το διαχωριστικό εντολών: δείχνει πού τελειώνει μια εντολή. Οι κατηγορίες 4 και 5 είναι οι εντολές ροής ελέγχου.

§6.2 Κενή εντολή, εντολή έκφρασης, σύνθετη εντολή

Η κενή εντολή (empty / null statement) είναι ένα σκέτο ; και δεν κάνει τίποτα (no-op)· το ;;;;; είναι πέντε κενές εντολές. Χρησιμεύει (και κρύβει παγίδες) όταν γίνεται σώμα μιας if ή ενός βρόχου.

Εντολή έκφρασης (expression statement) είναι μια έκφραση που τελειώνει με semicolon, π.χ. οι αναθέσεις, οι αυξήσεις και οι κλήσεις συναρτήσεων: x = 4;, z = ++y;.

Σύνθετη εντολή (compound statement) ή block είναι μια σειρά εντολών μέσα σε αγκύλες { }. Συντακτικά ένα block μετράει ως μία εντολή, οπότε όπου η C ζητάει «μια εντολή» (π.χ. ως σώμα μιας if) μπορούμε να βάλουμε όσες θέλουμε μέσα σε αγκύλες. Μετά το } δεν χρειάζεται ;, και το άδειο block { } κάνει ό,τι η κενή εντολή.

§6.3 Ροή ελέγχου

Ροή ελέγχου (control flow) είναι η σειρά με την οποία εκτελούνται οι εντολές ενός προγράμματος. Την καθορίζουν οι εντολές ροής ελέγχου (control flow statements), που παραλείπουν εντολές (συνθήκη) ή τις επαναλαμβάνουν (βρόχοι). Συνήθως την οπτικοποιούμε με ένα διάγραμμα ροής (flowchart): ένας ρόμβος είναι ο έλεγχος μιας συνθήκης με δύο εξόδους, «αληθής» και «ψευδής», και ένα ορθογώνιο είναι μια εντολή.

Μια συνθήκη στην C είναι απλώς μια έκφραση (Κεφάλαιο 5): τιμή μηδέν σημαίνει ψευδής, οποιαδήποτε άλλη τιμή σημαίνει αληθής. Γι’ αυτό γράφουμε while (1) ή if (n).

§6.4 Η εντολή if

Η εντολή if εκτελεί μια εντολή μόνο αν μια λογική συνθήκη είναι αληθής:

if ( συνθήκη )
    εντολή

Οι παρενθέσεις γύρω από τη συνθήκη είναι υποχρεωτικές. Η εντολή μπορεί να είναι οποιασδήποτε κατηγορίας: κενή (if (year > 1) ;, που δεν κάνει τίποτα), εντολή έκφρασης (if (year > 1) year++;) ή block (if (year > 1) { year++; }). Οι δύο τελευταίες κάνουν το ίδιο, αλλά με αγκύλες μια δεύτερη εντολή που θα προσθέσετε αργότερα μπαίνει σίγουρα μέσα στην if.

flowchart TD
  S(["execution"]) --> C{"( συνθήκη )"}
  C -- "αληθής" --> E["εντολή"]
  C -- "ψευδής" --> N(["επόμενη εντολή"])
  E --> N

Σχήμα: διάγραμμα ροής της if.

§6.5 Η εντολή if-else

Η εντολή if-else εκτελεί μια άλλη εντολή όταν η συνθήκη είναι ψευδής. Εκτελείται ακριβώς μία από τις δύο, ποτέ και οι δύο και ποτέ καμία:

if ( συνθήκη )
    εντολή1
else
    εντολή2
flowchart TD
  S(["execution"]) --> C{"( συνθήκη )"}
  C -- "αληθής" --> E1["εντολή1"]
  C -- "ψευδής" --> E2["εντολή2"]
  E1 --> N(["επόμενη εντολή"])
  E2 --> N

Σχήμα: διάγραμμα ροής της if-else.

Το else δεν είναι απαραίτητο (το ίδιο αποτέλεσμα βγαίνει με μια αρχική ανάθεση και μια απλή if), αλλά δείχνει καθαρά ότι οι δύο περιπτώσεις αποκλείονται. Για απλές επιλογές τιμής υπάρχει και ο τελεστής συνθήκης ? : του Κεφαλαίου 5. Βλ. το παράδειγμα Μέγιστο δύο αριθμών.

§6.6 Εμφωλευμένες if και η αλυσίδα else if

Όταν έχουμε πάνω από δύο περιπτώσεις, χρησιμοποιούμε εμφωλευμένες εντολές if (nested if): το σώμα μιας if ή ενός else είναι άλλη if. Το else if δεν είναι ξεχωριστή λέξη-κλειδί· είναι ένα else του οποίου η εντολή είναι μια νέα if. Στο παράδειγμα των διαφανειών:

lab = -1;
if (year < 1)
    { /* empty block */ }
else if (year == 1)
    lab = 20;
else
    lab = 0;

Οι συνθήκες ελέγχονται με τη σειρά και εκτελείται το σώμα της πρώτης αληθούς: για year < 1 το lab μένει -1 (άδειο block), για year == 1 γίνεται 20, αλλιώς 0.

flowchart TD
  S(["execution"]) --> C1{"year #lt; 1"}
  C1 -- "αληθής" --> B0["{ } (τίποτα)"]
  C1 -- "ψευδής" --> C2{"year == 1"}
  C2 -- "αληθής" --> B1["lab = 20;"]
  C2 -- "ψευδής" --> B2["lab = 0;"]
  B0 --> N(["επόμενη εντολή"])
  B1 --> N
  B2 --> N

Σχήμα: η αλυσίδα else if ως δύο εμφωλευμένες if-else.

§6.7 Το πρόβλημα του dangling else

Οι εμφωλευμένες if χωρίς αγκύλες μπορεί να είναι αμφίσημες (ambiguous): σε ποια if ανήκει ένα else; Ο μεταγλωττιστής αγνοεί τη στοίχιση και ακολουθεί έναν κανόνα: κάθε else ανήκει στην πλησιέστερη προηγούμενη if που δεν έχει ήδη else. Έτσι, στο

if (year <= 1)
    if (year == 1)
        lab = 20;
else
    lab = 0;

το else ανήκει στην εσωτερική if (year == 1), παρόλο που η στοίχιση λέει το αντίθετο. Αυτό είναι το πρόβλημα του dangling else. Η λύση των διαφανειών: αν δεν είμαστε σίγουροι, χρησιμοποιούμε αγκύλες { } για να υποδείξουμε τα όρια των σύνθετων εντολών. Ο gcc με -Wall προειδοποιεί: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]. Βλ. το παράδειγμα Είναι τα δύο προγράμματα ισοδύναμα;.

§6.8 Βρόχοι: η εντολή while

Οι βρόχοι (loops) ή δομές επανάληψης επαναλαμβάνουν μια εντολή, το σώμα του βρόχου· κάθε εκτέλεσή του λέγεται επανάληψη (iteration). Η εντολή while επαναλαμβάνει το σώμα όσο η συνθήκη είναι αληθής:

while ( συνθήκη )
    εντολή

Η συνθήκη ελέγχεται πριν από κάθε επανάληψη, οπότε αν είναι ψευδής από την αρχή το σώμα δεν εκτελείται καμία φορά. Για να τελειώσει ο βρόχος, το σώμα πρέπει να αλλάζει κάτι από το οποίο εξαρτάται η συνθήκη· αλλιώς έχουμε ατέρμονα βρόχο (infinite loop).

flowchart TD
  S(["execution"]) --> C{"( συνθήκη )"}
  C -- "αληθής" --> E["εντολή"]
  E --> C
  C -- "ψευδής" --> N(["επόμενη εντολή"])

Σχήμα: διάγραμμα ροής της while· ο έλεγχος γίνεται πριν από το σώμα.

§6.9 Η εντολή for

Η εντολή for αρχικοποιεί μεταβλητές, επαναλαμβάνει μια εντολή όσο η συνθήκη είναι αληθής, και στο τέλος κάθε επανάληψης εκτελεί το βήμα:

for ( αρχικοποίηση ; συνθήκη ; βήμα )     αρχικοποίηση;
    εντολή                                while ( συνθήκη ) {
                                              εντολή
                                              βήμα;
                                          }

Η αρχικοποίηση εκτελείται μία φορά· μετά ελέγχεται η συνθήκη, εκτελείται το σώμα, εκτελείται το βήμα, και ξανά από τον έλεγχο. Όπως δείχνει η δεξιά στήλη, η for είναι απλώς πιο συμπαγής γραφή της while.

flowchart TD
  S(["execution"]) --> I["αρχικοποίηση"]
  I --> C{"( συνθήκη )"}
  C -- "αληθής" --> E["εντολή"]
  E --> B["βήμα"]
  B --> C
  C -- "ψευδής" --> N(["επόμενη εντολή"])

Σχήμα: διάγραμμα ροής της for.

Και τα τρία μέρη είναι προαιρετικά, αλλά τα δύο ; μένουν πάντα. Συνθήκη που λείπει θεωρείται πάντα αληθής, άρα το for (;;) είναι η κλασική γραφή του ατέρμονα βρόχου. Ο βρόχος for (i = 0; i < N; i++) εκτελείται ακριβώς N φορές, για i = 0, 1, …, N-1.

§6.10 Η εντολή do-while

Η εντολή do-while εκτελεί πρώτα μία φορά την εντολή και μετά ελέγχει τη συνθήκη για το αν χρειάζεται άλλη επανάληψη:

do
    εντολή
while ( συνθήκη );

Επειδή ο έλεγχος γίνεται στο τέλος, το σώμα εκτελείται τουλάχιστον μία φορά (π.χ. ζητάμε μια τιμή από τον χρήστη μέχρι να είναι έγκυρη). Το τελικό ; είναι υποχρεωτικό.

flowchart TD
  S(["execution"]) --> E["εντολή"]
  E --> C{"( συνθήκη )"}
  C -- "αληθής" --> E
  C -- "ψευδής" --> N(["επόμενη εντολή"])

Σχήμα: διάγραμμα ροής της do-while· το σώμα προηγείται του ελέγχου.

§6.11 Σύγκριση των τριών βρόχων

  while for do-while
Έλεγχος συνθήκης πριν από κάθε επανάληψη πριν από κάθε επανάληψη μετά από κάθε επανάληψη
Ελάχιστες εκτελέσεις σώματος 0 0 1
Αρχικοποίηση / βήμα χωριστά στην επικεφαλίδα χωριστά
; μετά τη συνθήκη λάθος (κενό σώμα) λάθος (κενό σώμα) υποχρεωτικό
Ατέρμονας βρόχος while (1) for (;;) do … while (1);

Μετρητής με γνωστό εύρος: for· «όσο ισχύει κάτι»: while· «τουλάχιστον μία φορά»: do-while. Οι switch, break, continue έρχονται στο Κεφάλαιο 8.

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

§6.12 Ιχνηλάτηση εντολών έκφρασης

Εφαρμόζει: εντολή έκφρασης, ++/-- από το Κεφάλαιο 5. Ποια η τιμή των x, y, z μετά από αυτές τις εντολές;

x = 4;
y = 7;
z = ++y;
y = z - (x++);
z = x - (--y);

Το ++y αλλάζει πρώτα και δίνει τη νέα τιμή· το x++ δίνει την παλιά και αλλάζει μετά:

Εντολή x y z
x = 4; y = 7; 4 7 ?
z = ++y; 4 8 8
y = z - (x++); 5 4 8
z = x - (--y); 5 3 2

§6.13 Μέγιστο δύο αριθμών

Εφαρμόζει: if-else. Οι διαφάνειες ρωτούν αν μπορούμε χωρίς else, αν υπάρχει άλλος τρόπος, και τι κάνουμε με πάνω από δύο συνθήκες. Τρεις ισοδύναμες γραφές:

if (x > y) max = x; else max = y;   /* 1: if-else */
max = y; if (x > y) max = x;        /* 2: χωρίς else */
max = (x > y) ? x : y;              /* 3: τελεστής συνθήκης */

Για περισσότερες από δύο συνθήκες: εμφωλευμένες if.

§6.14 Είναι τα δύο προγράμματα ισοδύναμα;

Εφαρμόζει: εμφωλευμένες if, dangling else. Το πρώτο είναι η αλυσίδα else if της Θεωρίας· το δεύτερο είναι:

lab = -1;
if (year <= 1)
    if (year == 1)
        lab = 20;
else
    lab = 0;

Αν το else ανήκε στην εξωτερική if, όπως υπονοεί η στοίχιση, θα ήταν ισοδύναμα. Ανήκει όμως στην εσωτερική, άρα:

year 1ο πρόγραμμα 2ο πρόγραμμα
0 (ή αρνητικό) lab = -1 lab = 0
1 lab = 20 lab = 20
2 (ή μεγαλύτερο) lab = 0 lab = -1

Δεν είναι ισοδύναμα. Για να ανήκει το else στην εξωτερική if, κλείστε την εσωτερική if σε αγκύλες: if (year <= 1) { if (year == 1) lab = 20; } else lab = 0;.

§6.15 Άθροισμα με while

Εφαρμόζει: while. Τι κάνει το παρακάτω πρόγραμμα;

#include <stdio.h>

int main() {
    int i = 0;
    int sum = 0;
    while (i < 42) {
        sum += i;
        i++;
    }
    printf("%d %d\n", i, sum);
    return 0;
}

Ο βρόχος σταματάει όταν i == 42, αφού έχει προσθέσει \(0 + \dots + 41 = 861\):

$ ./a.out
42 861

Μετά τον βρόχο το i έχει την τιμή που έκανε τη συνθήκη ψευδή (42), όχι την τελευταία για την οποία εκτελέστηκε το σώμα (41).

§6.16 Ατέρμονες βρόχοι: while (1) και for (;;)

Εφαρμόζει: κενή εντολή, while, for.

while (1);
for (;;) ;

Ατέρμονες βρόχοι με κενό σώμα (στις διαφάνειες το for (;;) δεν έχει σώμα· εδώ πήρε την κενή εντολή): το πρόγραμμα «κολλάει» μέχρι το Ctrl-C. Μια πάντα αληθής συνθήκη έχει νόημα σε προγράμματα που τρέχουν συνεχώς (ένας server, ένα παιχνίδι) ή που τερματίζουν από μέσα, π.χ. με return ή με την break μιας επόμενης διάλεξης.

§6.17 Το ερωτηματικό μετά το while

Εφαρμόζει: κενή εντολή, while. Τι κάνει το παρακάτω;

i = 0;
while (i < 42);
    printf("%d\n", i++);

Το ; μετά το while (i < 42) είναι κενή εντολή και αυτή είναι το σώμα. Το i δεν αλλάζει ποτέ και το πρόγραμμα κολλάει χωρίς έξοδο· η printf βρίσκεται μετά τον βρόχο και δεν εκτελείται ποτέ. Ο κώδικας μεταγλωττίζεται, αφού είναι συντακτικά σωστός.

§6.18 Μέτρηση με for

Εφαρμόζει: for. Ο πρώτος βρόχος τυπώνει 100 φορές Hello world· ο δεύτερος είναι το γενικό παράδειγμα των διαφανειών:

int i;
for ( i = 0 ; i < 100 ; i++ )
    printf("Hello world\n");

for ( i = 0 ; i < N ; i++ ) {
    printf("%d\n", i);
}

§6.19 do-while μέχρι το 42

Εφαρμόζει: do-while. Τι κάνει το παρακάτω;

i = 0;
do
    i++;
while (i < 42);
printf("%d\n", i);

Το i αυξάνεται και μετά ελέγχεται· όταν γίνει 42 ο έλεγχος αποτυγχάνει και τυπώνεται 42. Με i = 100; στην αρχή, η do-while εκτελεί το σώμα μία φορά και τυπώνει 101, ενώ ένα while (i < 42) i++; δεν θα το εκτελούσε καθόλου και θα έμενε 100.

§6.20 Γρήγορος έλεγχος της λύσης με pipes και time

Συμβουλή για την άσκηση aliquot της hw0. Αντί να πληκτρολογείτε κάθε φορά την είσοδο, στείλτε τη στο πρόγραμμα με pipe (Κεφάλαιο 1):

echo -e "138\n0\nf\n" | ./aliquot

Η echo -e ερμηνεύει κάθε \n ως αλλαγή γραμμής, οπότε το ./aliquot διαβάζει τρεις γραμμές: 138, 0 και f. Με time μετράτε και τον χρόνο εκτέλεσης, και με ttyplot (ίσως χρειαστεί να το εγκαταστήσετε) σχεδιάζετε την έξοδο στο τερματικό:

echo -e "138\n0\nf\n" | time ./aliquot
echo -e "2856\n0\nf\n" | ./aliquot | ttyplot

Κύρια σημεία

  1. Κάθε εντολή της C είναι κενή, έκφρασης, σύνθετη, συνθήκης ή επανάληψης.
  2. Η κενή εντολή ; δεν κάνει τίποτα· μετράει όταν γίνεται σώμα if ή βρόχου.
  3. Μια έκφραση που τελειώνει σε ; είναι εντολή έκφρασης.
  4. Ένα block { } μετράει ως μία εντολή και μπορεί να γίνει σώμα κάθε εντολής ελέγχου.
  5. Ροή ελέγχου είναι η σειρά εκτέλεσης των εντολών· τη δείχνει ένα διάγραμμα ροής.
  6. Η if εκτελεί μια εντολή μόνο αν η συνθήκη είναι μη μηδενική· οι παρενθέσεις είναι υποχρεωτικές.
  7. Η if-else εκτελεί ακριβώς μία από δύο εντολές.
  8. Για πάνω από δύο περιπτώσεις γράφουμε εμφωλευμένες if, συνήθως ως αλυσίδα else if.
  9. Κάθε else ανήκει στην πλησιέστερη προηγούμενη if χωρίς else· η στοίχιση δεν μετράει.
  10. Αν δεν είμαστε σίγουροι, χρησιμοποιούμε αγκύλες { } για τα όρια των σύνθετων εντολών.
  11. Η while ελέγχει πριν από κάθε επανάληψη, οπότε το σώμα της μπορεί να μην εκτελεστεί.
  12. Ένα ; αμέσως μετά από while (...) ή for (...) γίνεται το σώμα του βρόχου.
  13. Οι while (1) και for (;;) χρησιμεύουν σε προγράμματα που τρέχουν συνεχώς.
  14. Η for ισοδυναμεί με μια while· το for (i = 0; i < N; i++) εκτελείται N φορές.
  15. Η do-while εκτελεί το σώμα τουλάχιστον μία φορά· το ; στο τέλος είναι υποχρεωτικό.
  16. Με echo -e "..." | ./prog δοκιμάζετε γρήγορα ένα πρόγραμμα, και με time το χρονομετράτε.

Ορολογία

Ελληνικά English Σύντομος ορισμός
εντολή statement Η μονάδα εκτέλεσης ενός προγράμματος C
κενή εντολή empty / null statement Το σκέτο ;, που δεν κάνει τίποτα (no-op)
εντολή έκφρασης expression statement Μια έκφραση που τελειώνει με ;
σύνθετη εντολή compound statement / block Εντολές μέσα σε { }, που μετράνε ως μία
ροή ελέγχου control flow Η σειρά με την οποία εκτελούνται οι εντολές
διάγραμμα ροής flowchart Σχήμα με ρόμβους (συνθήκες) και ορθογώνια (εντολές)
εμφωλευμένες if nested if if μέσα στο σώμα άλλης if ή else
κρεμασμένο else dangling else Αμφισημία για το σε ποια if ανήκει ένα else
βρόχος loop Εντολή που επαναλαμβάνει ένα σώμα
επανάληψη iteration Μία εκτέλεση του σώματος του βρόχου
ατέρμονας βρόχος infinite loop Βρόχος που δεν τερματίζει ποτέ
αρχικοποίηση / βήμα initialization / step Το πρώτο και το τρίτο μέρος της for

Διάβασμα

Συχνά λάθη

Τι δυσκόλεψε την τάξη

Από τα Kahoot των διαλέξεων: οι ερωτήσεις όπου μια λάθος απάντηση μάζεψε πολλές ψήφους, με το ποσοστό σωστών απαντήσεων.

Ερωτήσεις κατανόησης

Kahoot από το αμφιθέατρο (Κ6.1–Κ6.9)

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

Ασκήσεις

Ζέσταμα: από τις διαφάνειες (Α6.1–Α6.9)

Εργαστήριο (Α6.10–Α6.13)

Εργασίες (Α6.14–Α6.15)

Θέματα εξετάσεων (Α6.16–Α6.25)

Σχετικές ασκήσεις από άλλα κεφάλαια

  1. Πέντε κενές εντολές· καμία δεν κάνει τίποτα (no-op). ↩

  2. Η if δέχεται ως σώμα μία εντολή. Ένα block είναι συντακτικά μία (σύνθετη) εντολή, ενώ δύο σκέτες εντολές είναι δύο: μόνο η πρώτη ανήκει στην if. ↩

  3. Στην πλησιέστερη προηγούμενη if που δεν έχει ήδη else, ανεξάρτητα από τη στοίχιση. ↩

  4. Η while ελέγχει πριν από το σώμα (0 ή περισσότερες εκτελέσεις), η do-while μετά (τουλάχιστον μία). Με i = 100, το do i++; while (i < 42); αφήνει i = 101, ενώ το while (i < 42) i++; αφήνει i = 100. ↩

  5. i = 0; while (i < 100) { printf("Hello world\n"); i++; } ↩

  6. N + 1 φορές (για i = 0, …, N), αν N >= 0. ↩

Κατεβάστε το κεφάλαιο: PDF · Markdown · GitHub