Introduzione alle Espressioni in Linguaggio C

Una delle caratteristiche fondamentali del linguaggio C è l'enfasi posta sulle espressioni piuttosto che sulle istruzioni.

Un'espressione è una formula che combina variabili, costanti e operatori per produrre un valore. Ad esempio, l'espressione a + b combina le variabili a e b con l'operatore + per produrre un valore.

Le espressioni più semplici sono composte da singole variabili o singole costanti. Espressioni più complesse applicano operatori ad operandi per produrre valori più complessi. A loro volta, gli operandi possono essere altre espressioni.

Gli operatori, quindi, sono gli strumenti di base per costruire le espressioni e il linguaggio C ne fornisce una quantità notevole. Di base, nel linguaggio, sono presenti tre tipi di operatori, comuni a tutti i linguaggi di programmazione:

  • Operatori Aritmetici: utilizzati per eseguire operazioni matematiche come l'addizione, la sottrazione, la moltiplicazione e la divisione.
  • Operatori Relazionali: utilizzati per confrontare valori e produrre risultati booleani (vero o falso).
  • Operatori Logici: utilizzati per combinare espressioni booleane e costruire condizioni logiche.

Ma il C non si limita a questi tre tipi. Introdurremo i vari tipi di operatori man mano nel corso di questa guida.

In questa lezione, partiremo dalle basi sulle espressioni e studieremo gli operatori aritmetici, i quali sono i più comuni e utilizzati in C. Vedremo come utilizzarli per eseguire operazioni matematiche e come combinare più operatori in un'unica espressione.

Espressioni e Operatori

Abbiamo visto, nel capitolo precedente, che un programma scritto in C ha uno schema riconducibile al seguente:

direttive

int main() {
    dichiarazioni
    istruzioni
}

Le istruzioni sono le azioni che il programma deve compiere. Esse possono essere di vario tipo. Possono indicare al programma di svolgere un compito come, ad esempio, aprire un file, leggere un dato da tastiera, scrivere un dato su schermo e così via. Ma possono anche essere delle espressioni.

Un'espressione è un'istruzione che restituisce un risultato. Quindi possiamo considerare un'espressione come una categoria di istruzioni.

In C, tranne poche eccezioni, tutte le istruzioni sono quasi sempre espressioni. Questo significa che quasi tutte le istruzioni restituiscono un valore. In gergo tecnico si dice che un'espressione viene valutata.

Definizione

Espressioni

In linguaggio C, un'Espressione è un tipo di istruzione che restituisce un valore.

Quando un'espressione viene eseguita si dice che viene valutata. La valutazione di un'espressione consiste nel calcolare il valore che essa restituisce.

Le espressioni più semplici sono quelle che contengono solo una variabile o una costante. Ad esempio, l'espressione a restituisce il valore della variabile a, mentre l'espressione 5 restituisce il valore costante 5:

/* Esempi di espressioni semplici */
a; // restituisce il valore della variabile a
5; // restituisce il valore costante 5
Definizione

Espressioni Semplici

In linguaggio C, un'Espressione Semplice è un tipo di espressione composta esclusivamente da una variabile o da una costante.

Valutare un'espressione semplice significa restituire il valore della variabile o della costante.

Per creare espressioni più complesse dobbiamo adoperare gli operatori. Gli operatori sono simboli speciali che indicano al compilatore di eseguire un'operazione specifica. Ad esempio, l'operatore + indica al compilatore di eseguire un'operazione di addizione tra due operandi.

/* Esempio di espressione complessa */
a + b; // restituisce la somma tra le variabili a e b

La cosa importante da capire è che gli operandi sono sempre valutati prima degli operatori. Quindi, nell'espressione a + b, le variabili a e b vengono valutate prima di eseguire l'operazione di addizione.

Inoltre, quando si combinano più espressioni che contengono operandi e operatori, è importante considerare la precedenza e l'associatività degli operatori. Questi due concetti determinano l'ordine in cui gli operatori vengono valutati. Studieremo questo concetto nei prossimi paragrafi.

Definizione

Operatori

In linguaggio C, un'Operatore è un simbolo speciale che applica un'operazione a uno o più operandi.

Un operando è a sua volta un'espressione che restituisce un valore.

In C, gli operatori vengono valutati in base alla loro precedenza e associatività.

In C, gli operatori possono essere anche classificati in base al numero di operandi che accettano:

Definizione

Classificazione degli Operatori in base al numero di operandi

  • Operatore Unario: accetta un solo operando. Ad esempio, l'operatore di negazione - cambia il segno di un numero;
  • Operatore Binario: accetta due operandi. Ad esempio, l'operatore di addizione + somma due numeri;
  • Operatore Ternario: accetta tre operandi.

Inoltre, gli operatori unari si dividono in due tipi:

Definizione

Classificazione degli Operatori Unari

  • Operatore Prefisso: l'operatore viene posto prima dell'operando. Ad esempio, l'operatore di negazione - viene posto prima del numero da negare;
  • Operatore Postfisso: l'operatore viene posto dopo l'operando.

Operatori Aritmetici

Iniziamo a studiare gli operatori del C partendo da quelli più semplici e comuni: gli operatori aritmetici.

Essi sono utilizzati per eseguire operazioni matematiche come l'addizione, la sottrazione, la moltiplicazione e la divisione, e rappresentano la base su cui si fonda la maggior parte dei linguaggi di programmazione. Del resto, i computer sono nati per eseguire operazioni matematiche!

Ecco una lista degli operatori aritmetici del linguaggio C:

Operatore Tipo Descrizione
+ Unario Prefisso Operatore di identità
- Unario Prefisso Operatore di negazione
+ Binario Operatore di addizione
- Binario Operatore di sottrazione
* Binario Operatore di moltiplicazione
/ Binario Operatore di divisione
% Binario Operatore di resto o modulo
Tabella 1: Operatori Aritmetici in C

Esaminiamo questi operatori nel dettaglio.

La prima cosa da notare è che esistono due versioni dell'operatore di somma, +, e dell'operatore di sottrazione, -. Questo perché, tali operatori possono essere usati sia in forma unaria che binaria.

  • L'operatore + in forma unaria prende il nome di operatore di identità. Infatti esso non applica nessuna operazione al proprio operando ma serve soltanto ad esplicitare il fatto che l'operando sia positivo. Serve, cioè, più per un fatto di leggibilità che altro.

    Le due espressioni che seguono sono, infatti, equivalenti:

    int x = 5;
    int y = +5;
    

    Questo operatore è poco utilizzato in pratica. Si pensi che nelle versioni originarie del C non era nemmeno presente.

  • L'operatore - in forma unaria prende il nome di operatore di negazione. Esso cambia il segno dell'operando. Ad esempio, l'espressione -5 restituisce il valore -5, ossia il valore opposto di 5.

    int x = 5;
    int y = -5;
    
  • Gli operatori + e - in forma binaria sono, invece, gli operatori di addizione e sottrazione e il loro funzionamento è ovvio.

    int x = 5 + 3; // x = 8
    int y = 5 - 3; // y = 2
    
  • Analogamente, gli operatori * e / sono gli operatori di moltiplicazione e divisione. Essi eseguono le operazioni di moltiplicazione e divisione tra due operandi.

    int x = 5 * 3; // x = 15
    int y = 5 / 3; // y = 1
    
  • Infine, l'operatore % è l'operatore di resto o modulo. Esso è quello meno ovvio. Il suo scopo è quello di restituire il resto della divisione tra due numeri interi. Ad esempio, l'espressione 5 % 3 restituisce il valore 2, poiché il resto della divisione tra 5 e 3 è 2.

    int x = 5 % 3; // x = 2
    

Gli operatori aritmetici possono essere usati sia su tipi interi, di tipo int, che su variabili di tipo double in virgola mobile.

Inoltre, è possibile usare come operandi anche variabili di tipo misto. Ad esempio è possibile sommare, sottrarre, moltiplicare e dividere un intero con un numero in virgola mobile. In tal caso, il risultato sarà sempre un numero in virgola mobile. Questo comportamento prende il nome di coercizione.

int x = 5;
double y = 3.5;
double z = x + y; // z = 8.5
Definizione

Coercizione degli operatori aritmetici

In linguaggio C, la coercizione è il processo di conversione automatica di un tipo di dato in un altro tipo di dato compatibile.

Gli operatori aritmetici in C eseguono la coercizione dei tipi di dati in modo da produrre un risultato coerente.

Se un operatore aritmetico viene applicato a operandi di tipi misto, intero e in virgola mobile, il risultato sarà sempre in virgola mobile.

La coercizione è un argomento molto importante che studieremo nel dettaglio nelle prossime lezioni.

L'operatore di resto o modulo, invece, può essere usato solo con operandi di tipo intero. Se si tenta di usarlo con operandi in virgola mobile, il compilatore restituirà un errore.

double x = 5.5;
double y = 3.5;
double z = x % y; // Errore!
Nota

La Coercizione non vale per l'operatore di modulo

La coercizione non vale per l'operatore di modulo %. Esso può essere usato solo con operandi di tipo intero.

Dettagli sull'operatore di Divisione e Modulo

Gli operatori di divisione, /, e di modulo, %, hanno alcune particolarità che è importante conoscere.

  • Quando usiamo l'operatore di divisione tra numeri interi potremmo ottenere risultati inaspettati. In particolare, quando la divisione tra due interi non è un numero esatto, il risultato viene troncato eliminando la parte decimale. Ad esempio, l'espressione 5/2 restituisce 2 e non 2.5.

    int x = 5 / 2; // x = 2
    

    Per ottenere un risultato in virgola mobile, è necessario che almeno uno dei due operandi sia di tipo double.

    double y = 5 / 2.0; // y = 2.5
    
  • Se il secondo operando di un'operazione di divisione o di modulo è 0, il risultato sarà indefinito. Questo comportamento è dovuto al fatto che la divisione per zero è un'operazione non definita in matematica.

    int x = 5 / 0; // Risultato indefinito!
    

    Il problema è che il comportamento del programma, in questo caso, dipende da vari fattori. Due su tutti sono l'architettura del processore e il sistema operativo. In generale, la divisione per zero provoca un errore di divisione per zero che interrompe l'esecuzione del programma e viene segnalata come errore.

  • Quando uno dei due operandi, o entrambi, è negativo, il risultato dell'operazione di divisione può essere diverso da quello che ci si aspetta.

    Nello standard C89 non si dice esplicitamente se il risultato della divisione tra due numeri interi debba essere arrotondato verso l'alto o verso il basso. Motivo per cui, il risultato di 5/-2 potrebbe essere -2 o -3 a seconda del compilatore.

    A partire dallo standard C99, invece, si è stabilito che il risultato della divisione tra due numeri interi debba essere arrotondato verso zero. Questo significa che il risultato di 5/-2 sarà sempre -2.

    int x = 5;
    int y = -2;
    
    /* C89: il risultato potrebbe essere -2 o -3 */
    int z = x / y;
    
    /* C99: il risultato è sempre -2 */
    int w = x / y;
    
  • Stessa cosa vale per l'operatore di modulo. Anche in questo caso, il risultato della divisione tra due numeri interi può essere diverso a seconda del compilatore.

    Se il compilatore rispecchia lo standard C89, il risultato di -5 % 2 potrebbe essere -1 o 1. Se invece rispecchia lo standard C99, il risultato sarà sempre -1, ossia rispetta il segno del primo operando.

    int x = -5;
    int y = 2;
    
    /* C89: il risultato potrebbe essere -1 o 1 */
    int z = x % y;
    
    /* C99: il risultato è sempre -1 */
    int w = x % y;
    

Precedenza e Associatività degli Operatori

Quando un'espressione contiene al proprio interno più di un solo operatore, il suo significato potrebbe essere ambiguo.

Si prenda, ad esempio, la seguente espressione:

int x = 5 + 3 * 2;

Cosa rappresenta questa espressione? Bisogna sommare prima 5 e 3 e poi moltiplicare il risultato per 2 oppure bisogna moltiplicare prima 3 per 2 e poi sommare 5?

In matematica si usa la convenzione che prende il nome di PEMDAS per risolvere questo tipo di ambiguità. PEMDAS è un acronimo che sta per:

  • Parentesi
  • Esponenziali
  • Moltiplicazione e Divisione
  • Addizione e Sottrazione

Secondo questa convenzione, gli operatori di moltiplicazione e divisione hanno la precedenza rispetto agli operatori di addizione e sottrazione. Quindi, l'espressione 5 + 3 * 2 deve essere interpretata come 5 + (3 * 2), ossia 5 + 6, che è uguale a 11.

Allo stesso modo il linguaggio C utilizza la stessa convenzione e applica delle regole di precedenza degli operatori per risolvere le ambiguità.

Tali regole stabiliscono l'ordine in cui gli operatori vengono valutati:

Priorità Operatore
Massima + e - unari
Intermedia *, /, %
Minima + e - binari
Tabella 2: Regole di Precedenza degli Operatori in C

Le regole di precedenza stabiliscono che gli operatori unari + e - abbiano la precedenza massima. Questo significa che vengono valutati prima di tutti gli altri operatori.

Gli operatori di moltiplicazione *, divisione / e modulo % hanno precedenza rispetto agli operatori di addizione + e sottrazione -. Questo significa che vengono valutati prima di questi ultimi.

Infine, gli operatori di addizione + e sottrazione - hanno la precedenza minima. Questo significa che vengono valutati per ultimi.

Di seguito alcune espressioni di esempio che mostrano come vengono valutati gli operatori in base alla loro precedenza:

Espressione Risultato
5 + 3 * 2 5 + (3 \cdot 2) = 5 + 6 = 11
5 * 3 + 2 (5 \cdot 3) + 2 = 15 + 2 = 17
5 + 3 / 2 5 + (3 / 2) = 5 + 1 = 6
5 / 3 + 2 (5 / 3) + 2 = 1 + 2 = 3
5 / -2 + 3 (5 / -2) + 3 = -2 + 3 = 1
Tabella 3: Esempi di Espressioni con Operatori Aritmetici

Quando degli operatori hanno la stessa precedenza, la associatività degli operatori determina l'ordine in cui vengono valutati. L'associatività può essere da sinistra a destra o da destra a sinistra.

Per gli operatori aritmetici in C l'associatività è riportata nella tabella seguente:

Operatore Associatività
+, - unari Destra
+, - binari Sinistra
*, /, % Sinistra
Tabella 4: Associatività degli Operatori Aritmetici in C

Quindi, a parte gli operatori unari, tutti gli operatori aritmetici in C sono associativi da sinistra a destra. Questo significa che, in caso di operatori con la stessa precedenza, vengono valutati da sinistra a destra.

Ad esempio, prendiamo l'espressione che segue:

10 * 4 / 2 * 3

In base alle regole di precedenza e associatività, l'espressione viene valutata come segue:

\left( \left( \left( 10 \cdot 4 \right) / 2 \right) \cdot 3 \right) = 60

In questo caso, l'operatore di moltiplicazione * e l'operatore di divisione / hanno la stessa precedenza, ma vengono valutati da sinistra a destra.

Viceversa, gli operatori unari + e - hanno associatività da destra a sinistra. Questo significa che, in caso di più operatori unari consecutivi, vengono valutati da destra a sinistra.

Prendiamo, ad esempio, l'espressione:

- + - 5

In base alle regole di precedenza e associatività, l'espressione viene valutata come segue:

- \left( + \left( -5 \right) \right) = 5

Parentesi

Le regole di precedenza e associatività sono fondamentali nel linguaggio C così come in tutti i linguaggi di programmazione.

Tuttavia, non è necessario ricordarle, in quanto in C è possibile utilizzare le parentesi per forzare l'ordine di valutazione degli operatori.

Le parentesi hanno la precedenza massima e vengono valutate prima di tutti gli altri operatori. Questo significa che qualsiasi cosa sia racchiusa tra parentesi viene valutata per prima.

La tabella di priorità degli operatori, quindi, si modifica in questo modo:

Priorità Operatore
Massima ()
Alta + e - unari
Intermedia *, /, %
Minima + e - binari
Tabella 5: Regole di Precedenza degli Operatori con Parentesi in C

Usando le parentesi possiamo invertire la precedenza degli operatori e forzare l'ordine di valutazione. Ad esempio, se vogliamo eseguire un'addizione prima di una moltiplicazione, possiamo scrivere:

int x = (5 + 3) * 2; // x = 16

In questo caso, l'operazione di addizione viene eseguita prima dell'operazione di moltiplicazione.

Le parentesi possono anche essere innestate per creare espressioni più complesse. In questo caso, le parentesi più interne vengono valutate per prime. Ad esempio, l'espressione ((5 + 3) * 2) + 1 viene valutata come 17.

Definizione

Parentesi

In linguaggio C, le Parentesi sono utilizzate per forzare l'ordine di valutazione degli operatori.

Le parentesi hanno la precedenza massima e vengono valutate prima di tutti gli altri operatori.

Le parentesi possono essere innestate e, in tal caso, le parentesi più interne vengono valutate per prime.

Consiglio

Utilizzare le parentesi anche quando non sono necessarie

In certe espressioni, anche se le parentesi non sono strettamente necessarie, è sempre meglio utilizzarle per aumentare la leggibilità del codice.

Ad esempio, prendiamo il codice che segue:

int x = 5 + 3 * 2;

Anche se sappiamo che l'operatore di moltiplicazione ha la precedenza rispetto all'operatore di addizione, è sempre meglio scrivere:

int x = 5 + (3 * 2);

Questo rende il codice più chiaro e leggibile.

In Sintesi

Questa lezione ha introdotto il concetto di espressioni e operatori nel linguaggio C.

  • Un'espressione è un particolare tipo di istruzione che restituisce un valore.
  • Un'espressione può essere composta da variabili, costanti e operatori.
  • Gli operatori sono simboli speciali che applicano un'operazione a uno o più operandi.
  • Il C fornisce una serie di operatori aritmetici per eseguire operazioni matematiche.
  • Gli operatori aritmetici hanno una precedenza e un'associatività che determinano l'ordine di valutazione.
  • Le parentesi possono essere utilizzate per forzare l'ordine di valutazione degli operatori.

Nella prossima lezione studieremo un altro tipo di operatori molto importanti in C: gli operatori di assegnamento.