Τα αναδημοσιεύω από [ΕΔΩ]
2.7 Αριθμητικοί Τελεστές
Στη C και τη C++ υπάρχουν διάφοροι τελεστές που εκτελούν συγκεκριμένες αριθμητικές πράξεις:- Μοναδιαίοι
+,-
: Δρώντας σε ένα αριθμό a μας δίνουν τον ίδιο αριθμό ή τον αντίθετό του αντίστοιχα. - Δυαδικοί
+,-,*
: Δρώντας μεταξύ δύο αριθμών a,b μας δίνουν το άθροισμα, τη διαφορά και το γινόμενό τους αντίστοιχα. - Δυαδικός
/
μεταξύ πραγματικών a,b: δίνει το λόγο a/b. - Δυαδικοί
/,%
μεταξύ ακεραίων δίνουν το πηλίκο και το υπόλοιπο της διαίρεσης αντίστοιχα.
std::pow()
που περιλαμβάνεται στον header
<cmath>
(Πίνακας 4.1).
Ένας ιδιωματισμός της C++ είναι οι συντμήσεις των παραπάνω τελεστών
με το (
=
): η πράξη
a = a + b;
γράφεται συνήθως ως
a+=b;
. Αντίστοιχα ισχύουν
και για τους άλλους τελεστές
(
+=
,
-=
,
*=
,
/=
,
%=
)
χωρίς κενά μεταξύ του τελεστή και του
=
.
Οι δύο εκφράσεις παραπάνω είναι ισοδύναμες, εκτός από την περίπτωση
που ο υπολογισμός της μεταβλητής a παρουσιάζει
“παρενέργειες” σαν και αυτή που θα δούμε αμέσως παρακάτω.
Άλλος ιδιωματισμός της C++ είναι οι μοναδιαίοι τελεστές
++
και --
(χωρίς κενά)
οι οποίοι δρουν είτε πριν είτε μετά την αριθμητική μεταβλητή.
Αν δρουν πριν, π.χ. όπως στην έκφραση
b = ++a + c;
τότε αυξάνεται κατά 1 η τιμή του a,
αποθηκεύεται στο a
και η νέα τιμή χρησιμοποιείται για να υπολογιστεί η έκφραση.
Αν δρουν μετά, π.χ. όπως στην έκφραση
b = a++ + c;
τότε πρώτα υπολογίζεται η έκφραση και μετά αυξάνεται κατά 1
το a και αποθηκεύεται. Αντίστοιχα (με μειώσεις κατά 1)
ισχύουν για το --
. Άρα το
b = --a + c;
ισοδυναμεί με
a = a-1;
b = a + c;
ενώ το
b = a-- + c;
ισοδυναμεί με
b = a + c;
a = a-1;
Παρατηρήστε ότι οι τελεστές
++
και --
μετά την αριθμητική μεταβλητή χρειάζονται μια προσωρινή ποσότητα
για αποθήκευση κατά την εκτέλεση της αντίστοιχης πράξης τους
και, επομένως, είναι προτιμότερο, αν δεν υπάρχει λόγος,
να γίνεται η αύξηση ή μείωση με τους τελεστές πριν την αριθμητική μεταβλητή.
Στον Πίνακα 2.4 παρατίθενται οι σχετικές προτεραιότητες
κάποιων τελεστών. Για τελεστές ίδιας προτεραιότητας, οι πράξεις
εκτελούνται από αριστερά προς τα δεξιά. Εξαίρεση αποτελούν
οι τελεστές ανάθεσης και οι μοναδιαίοι.
Σημειώστε ότι συνεχόμενα σύμβολα (χωρίς κενά) ομαδοποιούνται από αριστερά
προς τα δεξιά από τον compiler ώστε να σχηματιστεί ο
μακρύτερος σύνθετος τελεστής και δεν αντιμετωπίζονται χωριστά. Π.χ.
η έκφραση
a
--
b
θεωρείται ως
(a
--
)b
(και είναι λάθος)
παρά ως
a-(-b)
.
Οι παρενθέσεις μπορούν να επιβάλουν διαφορετική σειρά εκτέλεσης των πράξεων.
|
Σε εκφράσεις που συμμετέχουν ποσότητες διαφορετικών τύπων γίνονται αυτόματα από τον compiler οι κατάλληλες μετατροπές (αν είναι εφικτές, αλλιώς στη μεταγλώττιση βγαίνει λάθος) ώστε να γίνουν όλες ίδιου τύπου και συγχρόνως να μη χάνεται η ακρίβεια. Έτσι π.χ. σε πράξη μεταξύ
int
και
double
γίνεται μετατροπή του
int
σε
double
και μετά η αντίστοιχη πράξη
μεταξύ ποσοτήτων διπλής ακρίβειας. Ας σημειωθεί ότι ποσότητες τύπου
μικρότερου από
int
(όπως
bool
και
char
)
μετατρέπονται σε
int
και κατόπιν εκτελείται η πράξη,
ακόμα και όταν είναι ίδιες.
Για να είναι κατανοητός ο κώδικας είναι καλό να αποφεύγονται
“αφύσικες” εκφράσεις παρόλο που η γλώσσα προβλέπει
κανόνες μετατροπής: γιατί π.χ. να χρειάζεται να προσθέσω
bool
και
char
;
Οι μετατροπές από ένα θεμελιώδη τύπο σε άλλον, “μεγαλύτερο” (με την έννοια ότι επαρκεί για να αναπαραστήσει την αρχική τιμή) δεν κρύβουν ιδιαίτερες εκπλήξεις. Προσοχή χρειάζεται όταν γίνεται μετατροπή σε “μικρότερο” τύπο, π.χ. στην ανάθεση σε ακέραιο ενός πραγματικού αριθμού. Σε τέτοια περίπτωση γίνεται στρογγυλοποίηση του πραγματικού στον αμέσως μικρότερο ακέραιο και κατόπιν γίνεται η ανάθεση. Επιπλέον, είναι δυνατόν η στρογγυλοποιημένη τιμή να μην είναι μέσα στα όρια τιμών της προς ανάθεση μεταβλητής οπότε η συμπεριφορά του προγράμματος (και όχι μόνο το αποτέλεσμα) είναι απροσδιόριστη.2.5 Έτσι
int a = 3.14; // a is 3
short int b = 12121212121.3; // b = ??
Υπάρχουν περιπτώσεις που ο προγραμματιστής θέλει να καθορίζει
συγκεκριμένη μετατροπή. Όπως αναφέρθηκε, ο τελεστής (
/
)
εκτελεί κάτι διαφορετικό μεταξύ πραγματικών αριθμών από ότι αν είναι
μεταξύ ακεραίων. Στον παρακάτω κώδικα
που υπολογίζει την (πραγματική) μέση τιμή κάποιων ακεραίων
είναι απαραίτητο να επιλεχθεί η δράση του
/
.
Αυτό επιτυγχάνεται με τη ρητή μετατροπή των ακεραίων ορισμάτων του
σε πραγματικούς με την εντολή
static_cast
:
int sum = 2 + 3 + 5;
int N = 3;
// Wrong value
double mean1 = sum / N;
// Correct value
double mean2 = static_cast<double>(sum) / N;
Η σύνταξη του
static_cast
είναι:
static_cast<newtype>(variable);
Σε παλαιότερους κώδικες μπορείτε να δείτε τέτοιες μετατροπές με τη
σύνταξη:
(newtype) variable
.
Καλό είναι να μη χρησιμοποιείτε αυτή τη μορφή.
Μια άλλη περίπτωση που χρειάζεται ρητή μετατροπή σε συγκεκριμένο τύπο
εμφανίζεται κατά την κλήση overloaded συνάρτησης, όταν η
επιλογή της κατάλληλης υλοποίησης δεν είναι μονοσήμαντη. Π.χ. η συνάρτηση
της τετραγωνικής ρίζας,
std::sqrt()
από το
<cmath>
δέχεται όρισμα τύπου
float
,
double
,
long double
αλλά όχι
int
με αποτέλεσμα να χρειάζεται μετατροπή όταν ζητούμε την ρίζα ακέραιου.
Όπως θα δούμε στο §4.6, η μετατροπή που κάνει
ο compiler σύμφωνα με τους κανόνες της C++ δεν είναι
μονοσήμαντη, οπότε πρέπει να γίνει ρητά από τον προγραμματιστή:
#include <cmath>
int
main() {
int p = 8;
double riza = std::sqrt(p);
// Error, ambiguous
double r = std::sqrt(static_cast<double>(p));
// Correct. Calls sqrt(double).
}
Ένα χαρακτηριστικό της C++ είναι ότι μια εντολή ανάθεσης ή μια εντολή σύγκρισης έχει η ίδια κάποια τιμή που μπορεί να ανατεθεί σε κάποια ποσότητα ή να μετέχει σε λογικές συνθήκες, π.χ.
int a;
int b;
b = a = 3;
//First a = 3; then b = 3;
bool cond;
cond = b < 5;
// First check b < 5; true. Then cond = true.
2.7.1 Άλλοι τελεστές
2.7.1.1 Τελεστής sizeof
Ο τελεστής
sizeof
δέχεται ως όρισμα μια ποσότητα ή ένα τύπο
και επιστρέφει το μέγεθός τους σε bytes.2.6
Στο παρακάτω παράδειγμα δίνονται οι τρόποι κλήσης του τελεστή
sizeof
:
int a;
std::cout << sizeof(int);
// parentheses are necessary
std::cout << sizeof(a);
std::cout << sizeof a;
Προσέξτε ότι ο τελεστής ακολουθείται από το όνομα του τύπου
ή της ποσότητας σε παρενθέσεις. Οι παρενθέσεις μπορούν
να παραλείπονται αν το όρισμα είναι το όνομα ποσότητας.
Η δράση του
sizeof
σε πίνακα (array)
επιστρέφει το μέγεθος σε bytes ολόκληρου του πίνακα,
δηλαδή, το πλήθος των στοιχείων επί το μέγεθος ενός στοιχείου.
Έτσι, στον παρακάτω κώδικα
double a[13];
int k = sizeof(a) / sizeof(a[0]); // k == 13
δρώντας κατάλληλα τον τελεστή υπολογίζεται το πλήθος των στοιχείων
του πίνακα.
Ο τελεστής
sizeof
υπολογίζεται κατά τη μεταγλώττιση και
το αποτελεσμά του θεωρείται σταθερή ποσότητα· μπορεί, επομένως,
να χρησιμοποιείται όπου χρειάζεται τέτοια.
Ο επιστρεφόμενος από το
sizeof
τύπος είναι ο
std::size_t
, ένας απρόσημος ακέραιος τύπος
που ορίζεται στο
<cstddef>
. Ποσότητες τέτοιου τύπου
είναι ιδιαίτερα κατάλληλες για διαστάσεις πινάκων καθώς και ως δείκτες
για να προσπελαύνουμε τα στοιχεία τους.
2.7.1.2 Τελεστής κόμμα (,)
Δύο ή περισσότερες εκφράσεις μπορούν να διαχωρίζονται με τον τελεστή (,).
Ο υπολογισμός τους γίνεται από αριστερά προς τα δεξιά και η τιμή της
συνολικής έκφρασης είναι η τιμή της δεξιότερης.
2.7.1.3 Τελεστές bit
Υπάρχουν τελεστές που αντιμετωπίζουν τα ορίσματά τους ως σύνολο bits σε σειρά, δηλαδή ως ακολουθίες από 0 ή 1. Η δράση τους ελέγχει ή θέτει την τιμή του κάθε bit χωριστά. Τα ορίσματά τους είναι μεταβλητές με τύπο ακεραίου (
short int
,
int
,
long int
,
bool
,
char
) και
enum
,
signed
ή
unsigned
.
Οι τελεστές παρουσιάζονται στον Πίνακα 2.5.
Ο τελεστής ∼ δρώντας σε ένα ακέραιο, επιστρέφει νέο ακέραιο έχοντας μετατρέψει τα 0 του αρχικού σε 1 και αντίστροφα.
Οι τελεστές
<<
,
>>
μετατοπίζουν τα bits
του αριστερού τους ορίσματος κατά τόσες θέσεις όσες ορίζει
το δεξί τους όρισμα. Τα επιπλέον bits χάνονται.
Ο τελεστής
<<
γεμίζει τις κενές θέσεις με 0 ενώ
ο
>>
κάνει το ίδιο αν το αριστερό όρισμα
είναι
unsigned
.
Οι τελεστές
&
,
^
,
|
επιστρέφουν ακέραιο με bit pattern που προκύπτει
αν εκτελεστεί το AND, XOR, OR αντίστοιχα
στα ζεύγη bit των ορισμάτων τους.
Η αποθήκευση bit σε ακέραιους και η χρήση τελεστών για το χειρισμό τους είναι σημαντική όταν θέλουμε να καταγράψουμε την κατάσταση (true/false, on/off, ...) ενός πλήθους αντικειμένων. Η C++ έχει εισαγάγει την κλάση
bitset<>
και την εξειδίκευση της κλάσης
std::vector<>
για
bool
που διευκολύνουν πολύ αυτό το σκοπό, και βέβαια είναι προτιμότερες.
Η προφανής εναλλακτική λύση ενός πίνακα με ποσότητες τύπου
bool
, παρόλο που είναι πιο εύχρηστη,
κάνει πολύ μεγάλη σπατάλη μνήμης καθώς για την αποθήκευση
ενός bit δεσμεύει τουλάχιστον
ένα byte, (§2.2.4).
Δεν υπάρχουν σχόλια:
Δημοσίευση σχολίου