Δευτέρα, 7 Νοεμβρίου 2011

Άλλοι τελεστές της C++

Τα αναδημοσιεύω από [ΕΔΩ]

 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) . Οι παρενθέσεις μπορούν να επιβάλουν διαφορετική σειρά εκτέλεσης των πράξεων.
Πίνακας: Σχετικές προτεραιότητες (κατά φθίνουσα σειρά) κάποιων τελεστών. Τελεστές στην ίδια θέση του Πίνακα έχουν ίδια προτεραιότητα.
Σχετικές Προτεραιότητες Τελεστών της C++
επιλογή μέλους κλάσης .
επιλογή μέλους δείκτη σε κλάση -->
κλήση συνάρτησης ()
εξαγωγή τιμής από πίνακα []
αύξηση/μείωση (μετά τη μεταβλητή) ++ , --
μέγεθος αντικειμένου sizeof
μέγεθος αντικειμένου ή τύπου sizeof()
αύξηση/μείωση (πριν τη μεταβλητή) ++ , --
bitwise NOT  ∼ 
εξαγωγή διεύθυνσης &
εξαγωγή τιμής από δείκτη *
λογικό NOT !
μοναδιαίο συν/πλην + , -
πολλαπλασιασμός *
διαίρεση (ή πηλίκο) /
υπόλοιπο %
άθροισμα +
διαφορά -
μετατόπιση δεξιά, αριστερά >> , <<
μικρότερο <
μικρότερο ή ίσο <=
μεγαλύτερο >
μεγαλύτερο ή ίσο >=
ίσο ==
άνισο !=
bitwise AND &
bitwise XOR ^
bitwise OR |
λογικό AND &&
λογικό OR ||
τελεστής συνθήκης2.4 ?:
απλή ανάθεση =
πολλαπλασιασμός και ανάθεση *=
διαίρεση και ανάθεση /=
υπόλοιπο και ανάθεση %=
άθροισμα και ανάθεση +=
διαφορά και ανάθεση -=
μετατόπιση αριστερά με ανάθεση <<=
μετατόπιση δεξιά με ανάθεση >>=
bitwise AND με ανάθεση &=
bitwise XOR με ανάθεση ^=
bitwise OR με ανάθεση |=
τελεστής κόμμα ,

Σε εκφράσεις που συμμετέχουν ποσότητες διαφορετικών τύπων γίνονται αυτόματα από τον 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.
Πίνακας: Τελεστές bit της C++.
Τελεστής Όνομα Χρήση
 ∼  bitwise NOT  ∼  expr
<< μετατόπιση αριστερά expr1 << expr2
>> μετατόπιση δεξιά expr1 >> expr2
& bitwise AND expr1 & expr2
^ bitwise XOR expr1 ^ expr2
| bitwise OR expr1 | expr2
<<= μετατόπιση αριστερά με ανάθεση expr1 <<= expr2
>>= μετατόπιση δεξιά με ανάθεση expr1 >>= expr2
&= bitwise AND με ανάθεση expr1 &= expr2
^= bitwise XOR με ανάθεση expr1 ^= expr2
|= bitwise OR με ανάθεση expr1 |= expr2

Ο τελεστής ∼  δρώντας σε ένα ακέραιο, επιστρέφει νέο ακέραιο έχοντας μετατρέψει τα 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).

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου