C# - Lezione 7 - Gli operatori
CONTENUTI:
Operatori C sharp (unari, binari, ternari).
Cast implicito ed esplicito.
Problemi di precisione e overflow.
Regole di casting del compilatore.
Design time, compile time, run time, codice rientrante.
Opzioni checked e unchecked, eccezioni run time.
Operatore sizeof.
Operatori bitwise.
Operatori booleani.
Operatore trialico.
TABELLA OPERATORI C#
Category
Operators
Primary
(x) x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
Unary
+ - ! ~ ++x --x (T)x
Multiplicative
* / %
Additive
+ -
Shift
<< >>
Relational
< > <= >= is
Equality
== !=
Logical AND
&
Logical XOR
^
Logical OR
|
Conditional AND
&&
Conditional OR
||
Conditional
? :
Assignement
= *= /= %= += -= <<= >>= &= ^= |=
OPERATORI UNARI, BINARI, TERNARI
Operatori unari: richiedono un unico operando.
Operatori binari: richiedono 2 operandi.
Operatori ternari: richiedono 3 operandi.
CAST ESPLICITO
E’ un’operazione di conversione di tipo effettuata esplicitamente dal programmatore.
Esempio:
int x;
float y = 10.5;
// conversione esplicita da int a float
x = (int) y;
Qualsiasi operazione di conversione può dare adito a problemi di precisione o di overflow.
Ad esempio nella conversione effettuata sopra viene persa la parte decimale della variabile y.
CAST IMPLICITO
E’ un’operazione di conversione che viene effettuata dal compilatore (quindi a COMPILE TIME), per consentire operazioni fra tipi diversi di variabili o costanti.
Esempio1:
int x;
float y;
y = y*x;
// questo viene convertito dal compilatore come :
y = y * (float)x ; // 1 cast implicito
Esempio2:
int x;
float y;
x = y*x;
// questo viene convertito in :
x = (int)(y * (float)x); // 2 cast impliciti
Il compilatore non può comunque risolvere tutti i problemi di correttezza e prevenire errori RUN TIME.
Esempio:
Int x;
x = x*x/18
// nessun cast implicito nè esplicito
Il risultato dell’operazione potrebbe causare un overflow, in quanto il compilatore non si preoccupa di possibili overflow. La prevenzione di eventuali overflow è a cura del programmatore. In questo caso si sarebbe potuto scrivere:
x = (long)(x) * x / 18; // 1 cast esplicito
Che il compilatore avrebbe trasformato in:
x = (int)((long)x * (long)x / (long)18); // 3 cast impliciti
Il rischio di overflow sarebbe stato eliminato.
REGOLE DI CASTING DEL COMPILATORE
Nell’effettuare i cast impliciti, il compilatore applica delle regole ben precise. Per gli operatori binari
le regole sono le seguenti, e vengono considerate in successione (il compilatore si ferma dopo averne applicata una).
Se un operando è di tipo decimal, l’altro viene convertito in decimal se non è di tipo float o double. Altrimenti si verifica un errore.
Se un operando è di tipo double, l’altro viene convertito in double.
Se un operando è di tipo float, l’altro viene convertito in float.
Se un operando è di tipo ulong, l’altro viene convertito in ulong se non è di tipo sbyte, short, int o long. Altrimenti si verifica un errore.
Se un operando è di tipo long, l’altro viene convertito in long.
Se un operando è di tipo uint e l’altro è di tipo sbyte, short o int, tutti e 2 vengono convertiti in long.
Se il secondo operando non appartiene a questo insieme di tipi, viene convertito in uint.
Se non è stata applicata nessuna delle regole precedenti, ambedue gli operandi vengono convertiti in int.
DESIGN TIME, COMPILE TIME, RUN TIME.
Il DESIGN TIME è il periodo di tempo nel quale viene scritto il codice sorgente dell’applicazione.
Il COMPILE TIME è il periodo di tempo nel quale il sorgente viene compilato.
Il RUN TIME è il periodo di tempo nel quale il programma compilato è in esecuzione.
I compilatori in uso dagli anni ’70 in poi producono di norma CODICE RIENTRANTE. Un programma in codice rientrante ha la caratteristica di non modificare sè stesso durante l’esecuzione.
A run time possono verificarsi errori che non sono stati segnalati in fase di compilazione. Se nell’esecuzione si verifica un overflow, viene normalmente segnalata un’ ECCEZIONE e il processo che l’ha generata muore.
E’ possibile evitare che a run time vengano generate eccezioni tramite l’opzione UNCHECKED.
Se invece si desidara che ciò avvenga (come è anche per default) si può utilizzare l’opzione CHECKED.
E’ possibile cambiare l’opzione di default.
Esempio:
class Test
{
static int x =1000000;
static int y =1000000;
static int F()
{
return checked(x * y); // genera un’eccezione di overflow
}
static int G()
{
return unchecked(x * y); // restituisce –727379968 e non genera eccezioni
}
static int H()
{
return x * y; // dipende dal default
}
}
OPERATORE SIZEOF
Restituisce la dimensione in byte del tipo o della variabile specificata.
Esempio:
Sizeof(int); // restituisce la dimensione in byte del tipo int
OPERATORI BITWISE
Sono operatori ORIENTATI AL BIT, ovvero vengono applicati sui singoli bit.
Shift a destra/sinistra: >> <<
Not sui singoli bit: ~
And bit a bit: &
Or bit a bit: |
Xor bit a bit: ^
Esempi:
Ubyte x = 2;
// i singoli bit che compongono il valore di x saranno quindi
// 00000010 dal MSB all’LSB.
x << 3; // shifta di 3 posizioni verso sinistra i singoli bit di x
// il risultato sarà 00010000 ovvero 32
x >> 2; // shifta di 2 posizioni verso destra i singoli bit di x
// il risultato sarà 00000100 ovvero 8
Utilizzando opportunamente questi operatori è possibile svolgere operazioni più complesse.
Ad esempio, il codice seguente verifica se il terzo bit della variabile x è un 1 o uno 0.
Ubyte x = 13; // 00001101
If ((x & 4) == 4)
Console.WriteLine (“Il terzo bit è 1”);
else
Console.WriteLine (“Il terzo bit è 0”);
OPERATORI BOOLEANI
Sono operatori che agiscono esclusivamente su operandi booleani.
And: &&
Or: ||
Not: !
Boolean x, y;
if (x && y)
Console.WriteLine (“l’espressione è vera”);
else
Console.WriteLine (“l’espressione è falsa”);
Gli operatori booleani sono CORTOCIRCUITATI: se la prima parte dell’espressione risulta tale da permettere (in funzione degli operatori successivi) di decidere la valutazione dell’espressione allora il test complessivo viene interrotto, e si assegna il valore a tutta l’espressione. Nel caso specifico, se x ha valore TRUE, l’espressione y non viene valutata e si passa direttamente all’esecuzione del “ramo vero” dell’if.
Questa caratteristica viene implementata dal compilatore, che organizza il codice dell’eseguibile in modo che ciò avvenga.
La cortocircuitazione permette di diminuire i tempi di esecuzione nella valutazione delle espressioni. Bisogna tenerne conto nella scrittura del codice.
Ad esempio, se si valuta:
(a && ((b=c)==0)
occorre ricordare che l’assegnamento b=c verrà effettuato solo se, a run time, l’espressione a risulterà vera.
Il discorso è analogo per l’operatore ||. L’espressione complessiva viene considerata vera senza valutarla completamente se la sua prima parte è vera.
OPERATORE TRIATICO
Valuta l’espressione posta prima di ? e ritorna il valore posto prima di : se è vera, il valore posto successivamente se è falsa. I valori possono essere riferimenti a variabili o costanti.
Esempio:
A = b ? x : y;
equivale al costrutto:
if b
A = x;
else
A = y;
|