Operatori in C e C++
Questa è una lista degli operatori nei linguaggi di programmazione C e C++. Tutti gli operatori elencati di seguito (eccetto typeof) esistono nel linguaggio C++, mentre in C solo quelli indicati nella colonna “Incluso in C”.
C++ include inoltre gli operatori type conversion (): const_cast
, static_cast
, dynamic_cast
, e reinterpret_cast
. La formattazione di questi operatori intende che il livello di precedenza è irrilevante.
Molti degli operatori disponibili in C e C++ sono implementabili anche in altri linguaggi della “C family”, quali C#, D, ma anche Java, Perl e PHP, con le medesime caratteristiche mostrate (precedenze, associatività e semantica).
Overloading degli operatori
C++ possiede molti operatori già predefiniti in grado di operare su vari tipi di dato, perciò si può parlare di “operatori già sovraccaricati”. Per esempio l’operatore +
può essere implementato per sommare sia due variabili di tipo int
(interi), sia due variabili di tipo float
o double
(variabili a virgola mobile).
Oltre a quando detto in precedenza, il programmatore può anche “estendere” l’operatività di ciascuno di essi; per esempio operandi complessi definiti con le classi, si potrebbero trattare anche come se fossero dati semplici.
In C++ è possibile sovraccaricare tutti gli operatori predifiniti, con l’eccezione su accesso al membro (.)
, indirezione di accesso al membro (.*)
e risoluzione di ambito (::)
.
Nel caso degli operatori, il numero degli operandi e la priorità dell’operatore sovraccaricato sono le stesse dell’operatore predefinito, mentre il nuovo operatore può essere definito come una funzione membro oppure come una funzione friend (funzione amica).
Proprietà degli operatori
Ogni operatore possiede delle proprietà, in modo tale da permettere l'interpretazione univoca del significato di ogni singola espressione. Gli esempi che seguono sono tutti applicabili al linguaggio C++ e, a seconda dei casi, anche al C.
Posizione
Un operatore può precedere gli operandi (o argomenti), seguirli oppure né precederli né seguirli. In questi casi parleremo rispettivamente di operatori prefissi, postfissi oppure infissi.
Numero di operandi
Ogni operatore può avere un numero diverso di operandi (arietà).
a[b]
Nel caso riportato, l'operatore di indicizzazione possiede due operandi, ossia le parentesi quadre. L'arietà può essere anche di tre operandi, come nel caso degli operatori condizionali, riportato di seguito:
e1 ? e2 : e3
Precedenza degli operatori
Ogni operatore possiede una propria precedenza, rappresentata attraverso un numero intero.
#include <iostream>
using namespace std;
int main () {
int a = 5;
int b = 6;
int c = 8;
int op = a+b*c;
cout << op << endl; // Stampa a schermo
return 0;
}
Se eseguiamo questo semplice codice attraverso un compilatore, il risultato a schermo sarà 53 e non 19, poiché l'operatore *
possiede la precedenza sull'operatore +
, nonostante esso sia compilato subito dopo.
Tabella delle precedenze
Nella tabella seguente sono elencati tutti gli operatori in base alla loro precedenza. Essi sono sempre implementabili su C++, mentre alcuni non lo sono in C.
Tabelle degli operatori
Nelle tabelle seguenti, a
, b
e c
rappresentano valori validi qualsiasi (literals, valori da variabili, oppure return value) o, in alcuni casi specifici, nomi di oggetti o lvalues. R
, S
e T
indicano qualsiasi tipo, mentre K
indica un tipo di classe o un tipo enum.
Operatori aritmetici
Tutti gli operatori aritmetici esistono sia in C e C++ e possono essere, come già indicato, sovraccaricati solo in C++.
Nome operatore | Sintassi | Esempi di implementazione in C++ | ||
---|---|---|---|---|
Come membro della classe K | Fuori dalla classe | |||
Addizione | a+b
|
R K::operator +(S b);
|
R operator +(K a, S b);
| |
Sottrazione | a-b
|
R K::operator -(S b);
|
R operator -(K a, S b);
| |
Unario più | +a
|
R K::operator +(); | R operator +(K a); | |
Unario meno | -a | R K::operator -(); | R operator -(K a); | |
Moltiplicazione | a * b
|
R K::operator *(S b);
|
R operator *(K a, S b);
| |
Divisione | a / b
|
R K::operator /(S b);
|
R operator /(K a, S b);
| |
Modulo | a % b
|
R K::operator %(S b);
|
R operator %(K a, S b);
| |
Incremento | Prefisso | ++a
|
R& K::operator ++();
|
R& operator ++(K& a);
|
Postfisso | a++
|
R K::operator ++(int);
|
R operator ++(K& a, int);
| |
Il C++ utilizza il parametro fittizio senza nome int per differenziare gli operatori di incremento prefisso e postfisso. | ||||
Decremento | Prefisso | --a
|
R& K::operator --();
|
R& operator --(K& a);
|
Postfisso | a--
|
R K::operator --(int);
|
R operator --(K& a, int);
| |
Il C++ utilizza il parametro fittizio senza nome int per differenziare gli operatori di incremento prefisso e postfisso. |
Operatori relazionali
Tutti gli operatori di confronto possono essere sovraccaricati solo in C++. Dal C++20, l'operatore di disuguaglianza viene generato automaticamente se viene definito operator==
e tutti e quattro gli operatori relazionali vengono generati automaticamente se viene definito operator<=>
.
Nome operatore | Sintassi | Incluso in C | Esempi di implementazione in C++ | |
---|---|---|---|---|
Come membro della classe K | Fuori dalla classe | |||
Uguale | a = b
|
Sì | bool K::operator ==(S const& b) const;
|
bool operator ==(K const& a, S const& b);
|
Diverso | a != b |
Sì | bool K::operator !=(S const& b) const;
|
bool operator !=(K const& a, S const& b);
|
Maggiore di | a > b |
Sì | bool K::operator >(S const& b) const;
|
bool operator >(K const& a, S const& b);
|
Minore di | a < b |
Sì | bool K::operator <(S const& b) const;
|
bool operator <(K const& a, S const& b);
|
Maggiore o uguale a | a >= b |
Sì | bool K::operator >=(S const& b) const;
|
bool operator >=(K const& a, S const& b);
|
Minore o uguale a | a <= b |
Sì | bool K::operator <=(S const& b) const;
|
bool operator <=(K const& a, S const& b);
|
Operatori logici (o booleani)
Tutti gli operatori logici (o booleani[1]) sono esistenti sia in C che in C++ e possono essere chiaramente sovraccaricati solo in quest'ultimo linguaggio di programmazione.
Nonostante la possibilità di sovraccarico in C++, tale operazione è sconsigliata con AND logico e OR logico sia sconsigliato perché, come operatori sovraccaricati, si comporterebbero come normali chiamate di funzione, il che significa che entrambi i loro operandi verrebbero valutati, perdendo di conseguenza la loro importante valutazione di McCarthy. [2]
Nome dell'operatore | Sintassi | Esempi di implementazione in C++ | |
---|---|---|---|
Come membro della classe K | Fuori dalla classe | ||
NOT logico | ! a
|
bool K::operator !();
|
bool operator !(K a);
|
AND logico | a && b
|
bool K::operator &&(S b);
|
bool operator &&(K a, S b);
|
OR logico | a || b
|
bool K::operator ||(S b);
|
bool operator ||(K a, S b);
|
Operatori bit a bit
Tutti gli operatori bit a bit (o di manipolazione dei bit) esistono sia C che C++ e possono essere sovraccaricati soltanto nel linguaggio inventato da Bjarne Stroustrup.
Nome dell'operatore | Sintassi | Esempi di implementazione in C++ | |
---|---|---|---|
Come membro della classe K | Fuori dalla classe | ||
Complemento a uno (inversione di tutti i bit) | ~a
|
R K::operator ~();
|
R operator ~(K a);
|
AND bit a bit | a & b
|
R K::operator &(S b);
|
R operator &(K a, S b);
|
OR inclusivo bit a bit | a | b
|
R K::operator |(S b);
|
R operator |(K a, S b);
|
OR esclusivo bit a bit (OR exclusive, XOR) | a ^ b
|
R K::operator ^(S b);
|
R operator ^(K a, S b);
|
Spostamento di tutti i bit verso sinistra | a << b
|
R K::operator <<(S b);
|
R operator <<(K a, S b);
|
Spostamento di tutti i bit verso destra | a >> b
|
R K::operator >>(S b);
|
R operator >>(K a, S b);
|
Operatori ed espressioni di assegnamento
Tutti gli operatori e le espressioni di assegnamento sono esistenti sia in C che in C++ e possono essere chiaramente sovraccaricate solo in quest'ultimo linguaggio di programmazione.
Per gli operatori indicati, la semantica dell'espressione di assegnazione combinata incorporata a ⊚= b
è equivalente a a = a ⊚ b
, eccetto per il fatto che a
viene valutata una sola volta.
Tipologia assegnamento | Sintassi | Esempi di implementazione in C++ | |
---|---|---|---|
Come membro della classe K | Fuori dalla classe | ||
Espressione semplice di assegnamento | a = b
|
R& K::operator =(S b);
|
N.D. |
Assegnamenti composti | a += b
|
R& K::operator +=(S b);
|
R& operator +=(K& a, S b);
|
a -= b
|
R& K::operator -=(S b);
|
R& operator -=(K& a, S b);
| |
a *= b
|
R& K::operator *=(S b);
|
R& operator *=(K& a, S b);
| |
a /= b
|
R& K::operator /=(S b);
|
R& operator /=(K& a, S b);
| |
a %= b
|
R& K::operator %=(S b);
|
R& operator %=(K& a, S b);
| |
a &= b
|
R& K::operator &=(S b);
|
R& operator &=(K& a, S b);
| |
a |= b
|
R& K::operator |=(S b);
|
R& operator |=(K& a, S b);
| |
a ^= b
|
R& K::operator ^=(S b);
|
R& operator ^=(K& a, S b);
| |
a <<= b
|
R& K::operator <<=(S b);
|
R& operator <<=(K& a, S b);
| |
a >>= b
|
R& K::operator >>=(S b);
|
R& operator >>=(K& a, S b);
|
Operatori membro e puntatore
Nome operatore[3] | Sintassi | Ammette overload (sovraccarico) in C++ | Implementabile in C | Esempi di implementazione in C++ | |
---|---|---|---|---|---|
Come membro della classe K | Fuori dalla classe | ||||
Indicizzazione | a[b]
|
Sì | Sì | R& K::operator [](S b);
|
N.D. |
Deferenziazione | *a
|
Sì | Sì | R& K::operator *(); | R& operator *(K a); |
Indirizzo | &a
|
Sì | Sì | R* K::operator &(); | R* operator &(K a); |
Deferenziazione e selezione | a->b
|
Sì | Sì | R* K::operator ->(); | N.D. |
Selezione (object.member) | a.b
|
No | Sì | N.D. | N.D. |
Deferenziazione e selezione con puntatore a membro | a->*b
|
Sì | No | R& K::operator ->*(S b); | R& operator ->*(K a, S b); |
Selezione con puntatore a membro | a.*b
|
No | No | N.D. | N.D. |
Altri operatori
Nome operatore | Sintassi | Ammette overload (sovraccarico) in C++ | Implementabile in C | Esempi di implementazione in C++ | |
---|---|---|---|---|---|
Come membro della classe K | Fuori dalla classe | ||||
Chiamata di funzione | a(a1, a2)
|
Sì | Sì | R K::operator ()(S a, T b, ...); | |
Virgola | a, b
|
Sì | Sì | R K::operator ,(S b);
|
R operator ,(K a, S b);
|
Espressione condizionale | a ? b : c
|
No | Sì | N.D. | N.D. |
Risolutore di visibilità | a::b
|
No | No | N.D. | N.D. |
Dimensione di oggetto o di tipo (sizeof) | sizeof a
|
No | Sì | N.D. | N.D. |
Conversione di tipo static_cast | static_cast<R>(a)
|
Sì | No | K::operator R();
|
N.D. |
Conversione const const_cast | const_cast<R>(a)
|
No | No | N.D. | N.D. |
Allocazione | new R
|
Sì | No | void* K::operator new(size_t x);
|
void* operator new(size_t x);
|
Allocazione di array | new R[n]
|
Sì | No | void* K::operator new[](size_t a);
|
void* operator new[](size_t a);
|
Deallocazione | delete a
|
Sì | No | void K::operator delete(void* a);
|
void operator delete(void* a);
|
Deallocazione di array | delete[] a
|
Sì | No | void K::operator delete[](void* a);
|
void operator delete[](void* a);
|
Note
- ^ L'accezione booleana è stata coniata in onore del matematico George Boole.
- ^ isocpp.org, https://isocpp.org/wiki/faq/operator-overloading .
- ^ Andrea Domenici e Graziano Frosini, Introduzione alla programmazione ed elementi di strutture dati con il linguaggio C++, collana "Informatica", diretta da A. L. Frisiani, VIII edizione, Milano/Pisa, ISBN 978-8846462022.
- ^ Per le conversioni definite dall'utente, il tipo di ritorno corrisponde implicitamente e necessariamente al nome dell'operatore, a meno che il tipo non sia dedotto. (es.
operator auto(), operator decltype(auto)()
etc.).