Περίληψη
Ένα πρόγραμμα που απλώς παραθέτει εντολές τη μία μετά την άλλη κάνει πάντα το ίδιο, όποια κι αν είναι τα δεδομένα του· για να ανταποκρίνεται σε αυτά, χρειάζεται να αποφασίζει. Το μάθημα αυτό μελετά τις αποφάσεις της C++: τα if και else, που επιλέγουν ανάμεσα σε δύο δρόμους ανάλογα με μια συνθήκη· τις αλυσίδες else if, που επιλέγουν ανάμεσα σε πολλούς, και στις οποίες η σειρά των συνθηκών καθορίζει ποια περίπτωση εξυπηρετεί κάθε κλάδος· τις εμφωλευμένες αποφάσεις και την παγίδα του ξεκρέμαστου else· και το switch με enum class, που επιλέγουν ανάμεσα σε διακριτές τιμές με όνομα, με έναν μεταγλωττιστή που προειδοποιεί αν λείπει μια περίπτωση ή αν μια περίπτωση διολισθαίνει σε εκείνη που ακολουθεί. Η έννοια που τα ενοποιεί όλα είναι η ανάλυση κατά περιπτώσεις: μια σωστή απόφαση διαμερίζει το σύνολο των εισόδων σε ξένες περιπτώσεις που το καλύπτουν πλήρως, και κάθε περίπτωση ελέγχεται με μια δοκιμή στο εσωτερικό της και μια σε καθένα από τα όριά της.
Μαθησιακοί στόχοι
- Να γράφετε απλές, αλυσιδωτές και εμφωλευμένες αποφάσεις που καλύπτουν όλες τις περιπτώσεις μιας προδιαγραφής.
- Να επαληθεύετε ότι οι περιπτώσεις μιας αλυσίδας
ifκαιelse ifείναι ξένες και εξαντλητικές, και να σχεδιάζετε μία είσοδο δοκιμής ανά περίπτωση. - Να χρησιμοποιείτε το
switchμεenum classγια διακριτές τιμές, και να εξηγείτε για τι προειδοποιεί ο μεταγλωττιστής αν λείπει μια περίπτωση. - Να εντοπίζετε το ξεκρέμαστο
elseκαι τη διολίσθηση από περίπτωση σε περίπτωση σε έναswitch, και να τα διορθώνετε.
if και else
Η μορφή μιας απόφασης
Το μάθημα Από το πρόβλημα στον αλγόριθμο παρουσίασε τρεις μορφές με τις οποίες κατασκευάζεται κάθε αλγόριθμος: την ακολουθία, την απόφαση και την επανάληψη. Ως εδώ τα προγράμματα του κύκλου μαθημάτων ήταν ακολουθίες, εκτός από κάποιο if που εμφανίστηκε νωρίτερα στην ενότητα 1· το μάθημα αυτό γράφει στη C++ τη δεύτερη μορφή. Μια απόφαση αποτιμά μια συνθήκη, δηλαδή μια έκφραση τύπου bool όπως εκείνες του μαθήματος Σχέσεις και λογική, και εκτελεί ένα μπλοκ αν η συνθήκη είναι αληθής και ένα άλλο αν είναι ψευδής:
if (συνθήκη) { // εκτελείται αν η συνθήκη είναι αληθής} else { // εκτελείται αν η συνθήκη είναι ψευδής}Η συνθήκη μπαίνει πάντα σε παρενθέσεις. Το τμήμα else είναι προαιρετικό: χωρίς αυτό, αν η συνθήκη είναι ψευδής δεν γίνεται τίποτα και το πρόγραμμα συνεχίζει με την εντολή που ακολουθεί την απόφαση. Και στις δύο περιπτώσεις οι δύο δρόμοι ξανασμίγουν στο τέλος, έτσι ώστε η απόφαση, ιδωμένη απέξω, είναι μία μόνο εντολή με μία είσοδο και μία έξοδο, όπως απαιτούσε το μάθημα 1.1.
Το μάθημα Αριθμητικές πράξεις έδειξε ότι το a % b έχει το πρόσημο του διαιρετέου, έτσι ώστε με και ισούται με , ενώ το ευκλείδειο υπόλοιπο, που δεν είναι ποτέ αρνητικό, ισούται με 1· και προανήγγειλε ότι ο ασφαλής τρόπος να διορθωθεί είναι να προστεθεί το μόνο όταν το υπόλοιπο είναι αρνητικό. Το πρόγραμμα υποθέτει ότι .
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; int r{a % b}; if (r < 0) { r = r + b; } std::println("Ευκλείδειο υπόλοιπο του {} διά {}: {}", a, b, r);}-7 2Ευκλείδειο υπόλοιπο του -7 διά 2: 1Η μεταβλητή r δεν είναι πια σταθερά, γιατί η απόφαση μπορεί να την αλλάξει. Το σχήμα ακολουθεί τους δύο δυνατούς δρόμους.
Απόδειξη
Η εκτέλεση φτάνει στην απόφαση με το
rήδη υπολογισμένο, και το πρώτο που κάνει είναι να αποτιμήσει τη συνθήκηr < 0: ο ρόμβος είναι το μόνο σημείο του προγράμματος όπου οι δρόμοι χωρίζονται.Αν η συνθήκη είναι αληθής, όπως με και 2, εκτελείται το μπλοκ του
if: τοrπερνά από σε 1.Αν είναι ψευδής, όπως με 7 και 2, το μπλοκ παρακάμπτεται ολόκληρο, και το
rδιατηρεί την τιμή του.Οι δύο δρόμοι ξανασμίγουν στο ίδιο σημείο, και από εκεί το πρόγραμμα συνεχίζει με τον ίδιο τρόπο και στις δύο περιπτώσεις: η εντολή που γράφει το αποτέλεσμα εκτελείται πάντα.
Με το else, η απόφαση επιλέγει ανάμεσα σε δύο ενέργειες που αλληλοαποκλείονται. Η συνθήκη του δίσεκτου έτους από το μάθημα Σχέσεις και λογική παράγει ένα bool· μια απόφαση το μετατρέπει σε συμπεριφορά του προγράμματος.
#include <iostream>#include <print>int main(){ int year{}; std::cin >> year; const bool isLeap{(year % 4 == 0 && year % 100 != 0) || year % 400 == 0}; if (isLeap) { std::println("Το {} είναι δίσεκτο: ο Φεβρουάριος έχει 29 ημέρες.", year); } else { std::println("Το {} δεν είναι δίσεκτο: ο Φεβρουάριος έχει 28 ημέρες.", year); }}1900Το 1900 δεν είναι δίσεκτο: ο Φεβρουάριος έχει 28 ημέρες.Το να δοθεί όνομα στη συνθήκη, όπως isLeap, χωρίζει το τι αποφασίζεται από το τι γίνεται με την απόφαση.
Άγκιστρα πάντα
Η γραμματική της C++ επιτρέπει να παραλείπονται τα άγκιστρα όταν το μπλοκ έχει μία μόνο εντολή: το if (r < 0) r = r + b; είναι έγκυρο. Ωστόσο, χωρίς άγκιστρα η απόφαση διέπει μόνο την εντολή που ακολουθεί, και ό,τι ο αναγνώστης πιστεύει ότι διέπεται εξαρτάται από την εσοχή, την οποία ο μεταγλωττιστής αγνοεί. Αν αυτό ισχύει, κάθε μεταγενέστερη τροποποίηση (η προσθήκη μιας δεύτερης εντολής, η μετατροπή της πρώτης σε σχόλιο) μπορεί να αλλάξει τη σημασία χωρίς να το δείχνει το κείμενο. Από αυτό έπεται ο κανόνας του κύκλου μαθημάτων: το μπλοκ ενός if ή ενός else έχει πάντα άγκιστρα, ακόμη κι αν περιέχει μία μόνο εντολή.
Το συντομότερο σφάλμα απ’ όλα επιβεβαιώνει τον κανόνα. Ένα ερωτηματικό μετά τις παρενθέσεις είναι μια κενή εντολή, και η απόφαση διέπει αυτή την κενή εντολή· το μπλοκ που ακολουθεί δεν ανήκει πια στο if και εκτελείται πάντα.
#include <print>int main(){ const int stock{12}; if (stock == 0); { std::println("Το προϊόν εξαντλήθηκε"); } std::println("Διαθέσιμα τεμάχια: {}", stock);}main.cpp: In function ‘int main()’:main.cpp:6:20: warning: suggest braces around empty body in an ‘if’ statement [-Wempty-body] 6 | if (stock == 0); | ^main.cpp:6:5: warning: this ‘if’ clause does not guard... [-Wmisleading-indentation] 6 | if (stock == 0); | ^~main.cpp:7:5: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’ 7 | { | ^Το προϊόν εξαντλήθηκεΔιαθέσιμα τεμάχια: 12Το πρόγραμμα ανακοινώνει ότι το προϊόν εξαντλήθηκε, παρότι απομένουν 12 τεμάχια. Ο GCC δίνει δύο προειδοποιήσεις: το -Wempty-body προτείνει άγκιστρα γύρω από το κενό σώμα, και το -Wmisleading-indentation επισημαίνει ότι το μπλοκ έχει εσοχή σαν να το διείπε το if. Το άλλο κλασικό σφάλμα, if (x = 0) αντί για if (x == 0), εξετάστηκε στο μάθημα Μεταγλώττιση, εκτέλεση και ρύθμιση: η ανάθεση έχει τιμή μηδέν, που μετατρέπεται σε false, και το -Wparentheses προειδοποιεί. Και στις δύο περιπτώσεις το πρόγραμμα μεταγλωττίζεται· χωρίς τις προειδοποιήσεις, το σφάλμα θα περνούσε απαρατήρητο.
Τα άγκιστρα έχουν ακόμη μία συνέπεια: μια μεταβλητή που δηλώνεται μέσα στο μπλοκ ενός if υπάρχει μόνο εκεί. Αν μια τιμή που υπολογίζεται σε έναν κλάδο χρησιμοποιείται μετά την απόφαση, η μεταβλητή δηλώνεται πριν από αυτήν, όπως το r στο παράδειγμα του υπολοίπου· η εμβέλεια θα μελετηθεί στο μάθημα Παράμετροι και εμβέλεια.
Ο τελεστής συνθήκης
Όταν οι δύο κλάδοι μιας απόφασης απλώς επιλέγουν μια τιμή, η C++ προσφέρει μια συντομότερη μορφή: το συνθήκη ? a : b ισούται με a αν η συνθήκη είναι αληθής και με b αν είναι ψευδής, και αποτιμά μόνο το στοιχείο που επιλέγεται. Σε αντίθεση με το if, που είναι εντολή, ο τελεστής συνθήκης είναι έκφραση: έχει τιμή και τύπο, και μπορεί να μπει όπου μπαίνει οποιαδήποτε έκφραση, για παράδειγμα στην αρχικοποίηση μιας σταθεράς, κάτι που ένα if δεν θα επέτρεπε χωρίς να εγκαταλειφθεί το const.
#include <iostream>#include <print>int main(){ int a{}; int b{}; std::cin >> a >> b; const int larger{a > b ? a : b}; std::println("Μεγαλύτερος: {}", larger); std::println("{} {}", b, b == 1 ? "μονάδα" : "μονάδες"); const auto mixed{a > b ? a : 0.5}; std::println("Κοινός τύπος: {:.1f}", mixed); std::println("Χωρίς παρενθέσεις: {}", 10 + a > b ? 1 : 0); std::println("Με παρενθέσεις: {}", 10 + (a > b ? 1 : 0));}7 3Μεγαλύτερος: 73 μονάδεςΚοινός τύπος: 7.0Χωρίς παρενθέσεις: 1Με παρενθέσεις: 11Οι δύο πρώτες γραμμές δείχνουν τη χρήση που διευκρινίζει: επιλογή του μεγαλύτερου και συμφωνία μιας λέξης με τον αριθμό, χωρίς να επαναλαμβάνεται ολόκληρη η εντολή σε δύο κλάδους. Η τρίτη δείχνει ότι η έκφραση έχει έναν μόνο τύπο, όποιος κι αν είναι ο κλάδος που επιλέγεται: αν τα δύο αποτελέσματα είναι διαφορετικών τύπων, μετατρέπονται σε κοινό τύπο με τις συνήθεις αριθμητικές μετατροπές του μαθήματος Αριθμητικές πράξεις· εδώ ένα int και ένα double δίνουν double, και το 7 που επιλέγεται αποθηκεύεται ως 7.0. Οι δύο τελευταίες δείχνουν την προτεραιότητα: ο τελεστής συνθήκης είναι από τους τελεστές με τη χαμηλότερη προτεραιότητα, έτσι ώστε χωρίς παρενθέσεις η συνθήκη είναι 10 + a > b και η τιμή 1, αντί για 11. Όταν ο τελεστής συνθήκης είναι μέρος μιας μεγαλύτερης έκφρασης, μπαίνει σε παρενθέσεις.
Ο τελεστής συσκοτίζει μόλις του ζητηθεί κάτι περισσότερο από την επιλογή μιας τιμής. Εμφωλευμένος, όπως στο a > b ? (a > c ? a : c) : (b > c ? b : c), αναγκάζει να ανασυνθέσει κανείς νοερά μια εμφωλευμένη απόφαση χωρίς τα άγκιστρα που την κάνουν ευανάγνωστη· και όταν χρησιμοποιείται για τις παρενέργειές του, όπως στο ok ? std::println("ναι") : std::println("όχι"), είναι ένα μεταμφιεσμένο if. Ο κανόνας του κύκλου μαθημάτων είναι να χρησιμοποιείται μόνο για την επιλογή ανάμεσα σε δύο σύντομες τιμές, χωρίς εμφώλευση· όλα τα άλλα γράφονται με if.
Φόρτωση του περιεχομένου…
Ολοκληρώθηκε
Συνδεθείτε για να αποθηκεύσετε την πρόοδό σας