26.1 Operatori primari
Gli operatori specificano cio' che deve essere fatto sulle variabili. Il linguaggio C prevede 8 categorie di operatori: operatori primari, operatori di assegnamento, operatori aritmetici, operatori relazionali, operatori logici, operatori di incremento/decremento, operatori bitwise e operatore virgola. Gli operatori possono essere unari binari o ternari in base al numero degli operandi che prevedono. Gli operatori unari prevedono un solo operando, gli operatori binari ne prevedono 2 e gli operatori ternari ne prevendono 3. Gli operatori primari servono per modificare o qualificare gli identificatori e sono le parentesi tonde e quadre, il punto ed il simbolo '->'. Le parentesi tonde identificano una funzione e contengono i parametri passati, ma servono anche per modificare l'ordine di valutazione nelle espressioni. Le parentesi tonde sono utilizzate anche per rappresentare l'operatore di cast. Le parentesi quadre sono utilizzate per rappresentare i vettori. Il punto ed il simbolo '->' sono operatori di riferimento e sono usati nelle strutture. L'uso delle parentesi tonde nelle funzioni e' gia' stato visto: una funzione e' identificata da un nome (identificatore) e da una coppia di parentesi tonde, ad esempio funz(). L'ordine di valutazione di una espressione puo' essere modificato dalle parentesi tonde, ad esempio:
poiche' l'operatore '*' ha precedenza rispetto all'operatore ' ', supponendo che la variabile b contenga 5, la variabile c contenga 2 e la variabile d contenga 3, nella prima espressione si avrebbe: a = 5 2 * 3. Valutando tale espressione il risultato assegnato ad a corrisponderebbe a 30. Nella seconda espressione al contrario, l'ordine di valutazione viene modificato dalle parentesi tonde, percio' viene eseguita prima l'espressione delimitata dalle parentesi (la somma) e poi la moltiplicazione. In quest'ultimo caso infatti, il risultato assegnato ad a corrisponde a 21. L'operatore di cast viene utilizzato per 'forzare' il tipo di una espressione. Ad esempio: int a; char b; b = 2; a = (int) b; l'operatore di cast '(int)' costringe il compilatore a considerare la variabile b di tipo int (pur essendo stata dichiarata di tipo char). L'uso delle parentesi quadre nei vettori e' gia' stato visto mentre l'uso degli operatori '.' e '->' verra' illustrato in seguito. 26.2 Operatori di assegnamento
Esiste un sono operatore di assegnamento 'puro': '='. Occorre prestare attenzione alla differenza tra l'operatore di assegnamento '=' e l'operatore di uguaglianza '=='. L'operatore di assegnamento e' un operatore binario percio' necessita di due operandi. L'operando di sinistra e' una variabile mentre l'operando di destra puo' essere anche una espressione. L'associativita' e' da destra a sinistra, pertanto viene prima valutata l'espressione a destra dell'operando ed il risultato viene assegnato alla variabile alla sinistra dell'operando. L'operatore di assegnamento ha quasi la priorita' piu' bassa in assoluto: solo l'operatore virgola ',' ha una priorita' ancora piu' bassa. Esistono inoltre gli operatori di assegnamento che definirei di tipo 'ibrido' in quanto rappresentano l'unione dell'operatore di assegnamento puro e di un secondo operatore. Ad esempio l'operatore ' =' rappresenta l'operatore di assegnamento puro '=' e l'operatore aritmetico ' ' (N.B.: il K.R. non fa distinzioni di sorta e li considera tutti come normali operatori di assegnamento, si tratta pertanto di una puntualizzazione strettamente personale). Gli operatori che possono venire affiancati alla sinistra dell'operatore '=' sono: ' ', '-'. '*', '/', '%', '<<', '>>', '&', '^' e '|', dove:
negli esempi appena visti le parentesi evidenziano il fatto che viene valutata l'intera espressione nel suo insieme, pertanto:
26.3 Operatori aritmeticiGli operatori aritmetici sono ' ', '-', '*', '/' e '%'. I primi quattro corrispondono alle 4 operazioni (somma, sottrazione, moltiplicazione e divisione) e l'ultimo e' l'operatore modulo. Gli operatori aritmetici sono tutti operatori binari. La moltiplicazione, la divisione ed il modulo hanno stessa priorita' che pero' e' piu' alta della somma e la differenza. La somma la differenza hanno stessa priorita'. L'operatore modulo effettua una divisione e ritorna il resto, ad esempio 4 % 2 ritorna 0. Gli operatori aritmetici sono tutti associativi da sinistra a destra ed hanno priorita' inferiore rispetto agli operatori primari. 26.4 Operatori relazionali e di uguaglianzaGli operatori relazionali sono '>' (maggiore di), '>=' (maggiore o uguale di), '<' (minore di) e '<=' (minore od uguale di). Gli operatori di uguaglianza sono '==' (uguale a) e '!=' (diverso da). Gli operatori relazionali hanno priorita' piu' alta rispetto agli operatori di uguaglianza. Gli operatori relazionali e di uguaglianza hanno priorita' inferiore rispetto agli operatori aritmetici. 26.5 Operatori logici
Gli operatori logici sono '&&' (AND) e '||' (OR). Le espressioni che contengono questi operatori vengono valutate da sinistra a destra e la valutazione si blocca non appena si determina la verita' o la falsita' dell'intera espressione. Per definizione il valore numerico di una espressione che contiene operatori relazionali o logici e' 1 se la condizione e' vera oppure e' 0 se la condizione risulta falsa. Tuttavia l'operatore unario di negazione '!' converte un operando nullo in 1 e viceversa un operando non nullo in 0. Ad esempio, se la variabile 'a' contiene il valore 7, allora:
nella prima if la valutazione dell'espressione tra parentesi tonde produce il valore 1, cioe' vero, in quanto e' vera la condizione 'a > 6'. Quindi viene eseguita la funzione printf() poiche' la condizione risulta vera. Nella seconda if al contrario, la condizione 'a < 6' risulta falsa, pertanto valutando l'espressione si otterrebbe il valore numerico 0 e la funzione printf() non verrebbe eseguita. Poiche' pero' e' presente l'operatore unario '!' il valore dell'espressione viene convertito da 0 a 1 (cioe' vero), la condizione risulta soddisfatta e la funzione printf() viene eseguita ugualmente. Le istruzioni if (!valido) e if (valido == 0) sono equivalenti. Alla luce di quanto detto fino a qui, osserviamo il codice seguente:
poiche' la valutazione dell'espressione termina non appena risulta vera o falsa l'espressione stessa, l'istruzione c=getchar() non viene piu' eseguita non appena risulta falsa la condizione i <= 5. Poiche' gli operatori di relazione e di uguaglianza hanno priorita' superiore rispetto agli operatori logici, in questo caso non sono necessarie parentesi. Infine poiche' gli operatori di uguaglianza hanno priorita' superiore a quello di assegnamento, e' necessario usare le parentesi in (c = getchar()) != EOF. Senza le parentesi verrebbe valutato prima != e successivamente verrebbe assegnato il risultato (0 se falsa la condizione e 1 se vera) alla variabile c ma ovviamente questo non e' cio' che si vuole ottenere. 26.6 Operatori di incremento e decremento
Il C prevede un operatore di incremento ' ' ed un operatore di decremento '--'. Il primo somma 1 al suo operando mentre il secondo sottrae 1. Gli operatori di incremento e di decremento possono essere prefissi o postfissi, cioe', data la variabile var, ' var' e 'var ' ne incrementano di una unita' il valore. La differenza tra prefisso e postfisso e' importante: nella notazione prefissa prima viene incrementata (o decrementata) la variabile e poi valutata l'espressione, mentre nella notazione postfissa prima viene valutata l'espressione e poi incrementata (o decrementata) la variabile. Percio' dato:
nella prima espressione x vale 2 mentre nella seconda espressione x vale 1. La variabile y invece vale comunque 2. In genere e' da notare che un istruzione come ' x' risulta piu' efficiente di un espressione del tipo 'x = x 1'. 26.7 Operatore ternario ?:
un'espressione del tipo:
mostra la sintassi dell'operatore ternario '?:'. Tale operatore funziona in questo modo: viene valutata l'espressione 'espressione1' la quale se risulta vera provoca la valutazione dell'espressione 'espressione2' in caso contrario, cioe' se risulta falsa, provoca la valutazione dell'espressione 'espressione3'. Ad esempio l'espressione: 'z = (a > b) ? a : b' valorizza la variabile z con il valore della variabile a se la condizione e' vera oppure con il valore della variabile b se la condizione e' falsa; in ogni caso z conterra' il valore maggiore tra quello contenuto in a e quello contenuto in b. Le espressioni condizionali possono essere usate per scrivere codice piu' compatto e piu' elegante come in questo esempio: printf ("questo vettore contiene %d element%s", n, n == 1 ? "o" : "i"); 26.8 Operatori bitwise
Gli operatori orientati ai bit o bitwise sono: '&' (cioe' AND), '|' (cioe' OR), '^' (cioe' XOR), '~' (cioe' NOT), '<<' (cioe' SHIFT a sinistra) e '>>' (cioe' SHIFT a destra). Sono tutti operatori binari tranne l'operatore di complemento a uno '~'. Gli operatori bitwise agiscono con dei valori interi, ma occorre tener presente che operano bit a bit, percio' occorre sempre ragionare in termini di valori binari (0 e 1). In altre parole, l'uso degli operatori bit a bit permette di considerare una variabile come un vettore, dove ogni elemento contiene un singolo bit. Ad esempio l'operazione di AND: '1 & 127' corrisponde all'espressione '00000001' 6 '01111111'. In tale espressione viene effettuato l'AND tra i vari bit:
come agisce l'operatore '&'? Semplicemente producendo un valore che e' il risultato dell'operazione di AND tra i due operandi, seguendo questa regola: il bit risultante e' '1' se entrambi i bit degli operandi sono '1', in caso contrario e' '0'. Percio' partendo da sinistra il primo bit sulla prima riga viene comparato al primo bit della seconda riga: poiche' sono entrambi '1' il risultato sara' '1'. Il secondo bit della prima riga e' '0' mentre il secondo bit della seconda riga e' '1', percio' il risultato sara' '0'. L'ottavo bit, e' '0' in entrambe le righe, percio' il risultato sara' '0'. L'operatore AND puo' essere letto in questo modo: il risultato e' vero (1) se solo se entrambi gli operandi sono veri. L'operatore bitwise '&' e' simile all'operatore logico '&&' ma non e' da confondere con quest'ultimo: infatti il primo agisce tra due condizioni il secondo tra due bit. L'operatore '|' corrisponde all' OR inclusivo mentre l'operatore '^' corrisponde all' OR esclusivo o XOR (eXclusive OR). Con l'operatore | il risultato e' 1 per i bit che sono a 1 nel primo operando oppure nel secondo includendo il caso in cui sono ad 1 entrambi gli operandi. Con L'XOR invece viene escluso il caso in cui i bit di entrambi gli operandi vale 1. L'operatore di complemento a uno '~' e' un operatore di tipo unario (agisce su un solo operando) e produce come risultato 1 se il bit analizzato vale 0 oppure produce 0 se il bit di partenza vale 1 (in pratica corrisponde al NOT). Ecco le tabelle degli operatori bit a bit:
che come si puo' vedere corrisponde ai 4 bit piu' a destra. Quindi se volessi copiare i 4 bit bassi di una variabile sarebbe sufficiente effettuare un'operazione di AND con la costante 15: var_destinazione = var_partenza & 15. Dopo tale operazione i 4 bit bassi della variabile var_partenza verranno copiati nella variabile var_destinazione. Gli operatori bit a bit possono venir combinati all'interno di un'espressione: 'x = x & ~077' permette di azzerare gli ultimi sei bit di x. Infatti poiche' 077 (77 ottale) corrisponde alla cifra binaria 00111111, effettuando il complemento ad uno si ottiene 11000000. Poiche' l'AND produce sempre zero tranne quando entrambi gli operandi valgono 1, l'operazione 'x = x & 11000000', azzera sicuramente i 6 bit piu' a destra di x. 26.9 Ordine di valutazione
Nel C l'ordine di valutazione degli operandi di un qualsiasi operatore non e' conosciuto a priori (ad eccezione degli operatori '&&', '||' '?:' e ','). Questo significa che data l'espressione a = f1() f2(), non e' possibile sapere a priori se verra' valutata prima la funzione f1() e poi la funzione f2() o viceversa. Pertanto se la funzione f1() altera il valore di una variabile utilizzata dall'altra funzione f2() o viceversa, il risultato dipendera' dall'ordine di valutazione delle due funzioni. Non e' conosciuto a priori nemmeno l'ordine di valutazione degli argomenti passati ad una funzione, pertanto una chiamata a funzione del tipo: 'funz(a, a)' produce un risultato dipendente dall'implementazione del compilatore usato. Situazione analoga si puo' avere in una chiamata a funzione del tipo: 'funz (funz2(a) - funz2(b))' se il valore ritornato da funz2() puo' risultare diverso tra la prima e la seconda chiamata. In questi casi si puo' porre il valore ritornato da una delle due funzioni in una variabile temporanea ed usare tale variabile nella chiamata. Ad esempio, consideriamo una struttura di tipo LIFO (Last In First Out). Una struttura di questo tipo viene anche definita pila. Per capire come funziona una pila si puo' pensare ad un cilindro contenente degli oggetti (ad esempio un tubetto di cartone contenente caramelle di cioccolato del tipo 'smarties') che vengono introdotti in una sequenza qualsiasi ma devono uscire con una sequenza ben precisa: l'ultimo che viene introdotto e' l'ultimo che esce. Non e' possibile prelevare il primo oggetto che e' entrato senza prima prelevare tutti gli altri. Una struttura di questo tipo viene definita anche stack. La funzione che introduce gli oggetti nello stack si chiama push mentre la funzione che li preleva viene definita pop. Ad esempio:
Ora lo stack contiene le variabili a, b e c, valorizzate rispettivamente con 1, 2 e 3. L'ultimo valore che e' entrato e' 3 che e' anche il primo valore che deve uscire:
la funzione pop() prelevera' prima il valore 3, poi 2 e poi 1. In questo contesto una chiamata a funzione del tipo:
puo' avere dei risultati imprevedibili, in quanto non e' possibile sapere a priori quale delle due funzioni pop() venga eseguita per prima. Qualora venisse eseguita per prima la seconda chiamata si avrebbe una situazione di questo tipo:
che non e' quanto ci si poteva aspettare. Per ovviare al problema occorre usare una variabile temporanea:
Alla luce di quanto detto occorre prestare attenzione anche ad altre situazioni ambigue potenzialmente pericolose, come ad esempio:
La domanda e': esaminando l'espressione, quale ordine di valutazione verra' usato dal compilatore? Poiche' il linguaggio C non specifica alcun ordine di valutazione degli operandi di un qualsiasi operatore, non e' possibile sapere se verra' valutato prima l'operando alla sinistra dell'operatore di assegnamento '=' e poi quello alla sua destra o viceversa. In queste situazioni l'ordine di valutazione degli operandi dipende dall'implementazione. Una situazione analoga alla precedente puo' essere:
in tale espressione infatti non si conosce a priori l'ordine di valutazione degli operandi dell'operatore aritmetico ' ': il compilatore potrebbe valutare prima l'operando a sinistra (cioe' a[0]) e poi quello di destra (cioe' b[1]) oppure viceversa prima quello di destra (cioe' b[1]) e poi quello di sinistra (cioe' a[1]). Un'espressione puo' contenere operatori che hanno la stessa priorita', in questi casi nel valutare l'espressione il compilatore considera l'associativita' degli operatori usati. Ad esempio, l'operatore di assegnamento '=' e' associativo da destra a sinistra, pertanto grazie a questa proprieta' e' possibile scrivere un'espressione di questo tipo:
infatti poiche' in questo caso l'espressione viene valutata associando gli operatori da destra a sinistra, la variabile c conterra' 0, valore che sara' assegnato alla variabile b; successivamente il valore di b (0) verra' assegnato alla variabile a. Se per ipotesi l'ordine di associazione dell'operatore '=' fosse da sinistra a destra, il primo assegnamento fallirebbe subito in quanto non sarebbe possibile assegnare alla variabile b alcun valore (la variabile a non e' stata ancora inizializzata). In sintesi l'associativita' determina l'ordine secondo il quale gli operatori aventi stessa priorita' vengono associati all'interno di una espressione. Il seguente esempio rendera' piu' chiaro il concetto:
la variabile a conterra' il valore 1 oppure 5? In altre parole l'espressione verra' considerata come 'a=(5-2)-2' oppure come a=5-(2-2)? La risposta e': poiche' gli operatori aritmetici sono associativi da sinistra a destra, l'espressione verra' valutata partendo da destra, quindi verra' prima sottratto 2 a 5 ottenendo 3, poi verra' sottratto 2 a 3 ottenendo 1. Quindi l'espressione appena vista equivale a: a=(5-2)-2. L'operatore '=' viene valutato per ultimo in quanto ha una precedenza piu' bassa rispetto all'operatore '-'. Segue una tabella delle precedenze e delle associativita' degli operatori, ordinati con priorita' decrescente (la prima riga rappresenta la precedenza massima e l'ultima quella minima; l'associativita' e' sempre da sinistra a destra tranne quando indicata a lato): Inizio della guida Le funzioni Indice Costrutti di controllo Copyright (c) 2002-2003 Maurizio Silvestri |