Introduzione alla funzione printf in linguaggio C
La funzione printf in linguaggio C è una delle funzioni più utilizzate per la stampa di output sullo schermo. Fa parte della libreria standard di input/output (stdio.h) e consente di formattare e visualizzare vari tipi di dati in modo flessibile.
Per questo motivo, è fondamentale comprendere dall'inizio come utilizzare correttamente printf per sfruttarne appieno le potenzialità.
- La funzione
printfè una delle funzioni più comuni in linguaggio C per la stampa di output formattato sullo schermo. - La sintassi generale di
printfprevede una stringa di formato seguita da uno o più argomenti da stampare. - Gli specificatori di formato, che iniziano con il simbolo di percentuale (
%), indicano dove inserire i valori da stampare e come formattarli. - È importante che il numero di specificatori di formato corrisponda al numero di argomenti forniti, altrimenti si potrebbero verificare comportamenti imprevisti.
- Le sequenze di escape, come
\nper andare a capo, consentono di inserire caratteri speciali all'interno delle stringhe di formato.
Funzionamento di base della funzione printf
La funzione printf è progettata per mostrare il contenuto di una stringa, che prende il nome di stringa di formato, con dei valori che possono essere posizionati all'interno di essa in alcuni punti specifici.
Una stringa è una sequenza di caratteri racchiusa tra virgolette doppie ("); torneremo sulle stringhe nelle prossime lezioni. Per ora, ci basti sapere che una stringa di formato può contenere del testo normale e dei segnaposto (o specificatori di formato) che indicano dove inserire i valori da stampare.
Quando invochiamo printf (ossia quando chiamiamo la funzione), dobbiamo specificare almeno un argomento: la stringa di formato. Eventualmente, possiamo fornire ulteriori argomenti che corrispondono ai segnaposto presenti nella stringa di formato.
La sintassi generale della funzione printf è la seguente:
printf("stringa di formato", argomento1, argomento2, ...);
Gli argomenti successivi alla stringa di formato possono essere valori costanti, variabili o espressioni più complesse.
In generale non c'è un limite al numero di valori che può essere stampato con una singola chiamata a printf, purché il numero di segnaposto nella stringa di formato corrisponda al numero di argomenti forniti.
Come menzionato, la stringa di formato può contenere sia testo normale, ossia normali caratteri, sia i cosiddetti specificatori di formato. Uno specificatore di formato è una sequenza di caratteri che inizia con il simbolo di percentuale (%) e indica sia la posizione in cui inserire un valore, sia il tipo di dato che si desidera stampare.
In particolare, le informazioni che seguono il simbolo di percentuale specificano come un valore deve essere convertito dalla sua rappresentazione binaria interna a una rappresentazione testuale leggibile dall'utente.
Ad esempio, lo specificatore di formato %d indica che il valore da stampare è un intero decimale con segno, mentre %f indica che il valore è un numero in virgola mobile (floating-point).
I caratteri normali presenti nella stringa di formato vengono stampati così come sono, senza alcuna modifica. Viceversa, ogniqualvolta viene incontrato uno specificatore di formato, printf preleva il corrispondente argomento dalla lista degli argomenti forniti e lo converte in una rappresentazione testuale secondo le regole definite dallo specificatore.
Consideriamo un esempio:
int a, b;
double x, y;
a = 10;
b = 20;
x = 3.14;
y = 2.71;
printf("Valori interi: a = %d, b = %d\n", a, b);
printf("Valori in virgola mobile: x = %lf, y = %lf\n", x, y);
In questo esempio, la prima chiamata a printf stampa i valori delle variabili a e b utilizzando lo specificatore di formato %d per gli interi. La seconda chiamata stampa i valori delle variabili x e y utilizzando lo specificatore di formato %lf per i numeri in virgola mobile di tipo double.
Il risultato dell'esecuzione di questo codice sarà:
Valori interi: a = 10, b = 20
Valori in virgola mobile: x = 3.140000, y = 2.710000
Come si può notare, i caratteri normali nella stringa di formato sono stati stampati così come sono. Gli specificatori di formato %d e %lf sono stati sostituiti dai valori delle variabili corrispondenti, convertiti in una rappresentazione testuale appropriata.
Un'altra osservazione riguarda il fatto che, nel sostituire gli specificatori di formato con i valori corrispondenti, printf segue l'ordine in cui gli argomenti sono stati forniti. Nel nostro esempio, il primo %d corrisponde al primo argomento a, il secondo %d al secondo argomento b, e così via.
Ricapitolando:
Funzione printf
La funzione printf consente di stampare testo formattato sullo schermo. La sua sintassi generale è:
printf("stringa di formato", argomento1, argomento2, ...);
La stringa di formato può contenere testo normale e specificatori di formato, che iniziano con il simbolo di percentuale (%) e indicano dove inserire i valori da stampare e come formattarli.
Gli argomenti successivi alla stringa di formato corrispondono ai segnaposto presenti nella stringa e vengono convertiti in rappresentazioni testuali secondo le regole definite dagli specificatori di formato.
Nell'utilizzare la funzione printf, però, bisogna fare attenzione a un aspetto fondamentale: il numero di specificatori di formato presenti nella stringa deve corrispondere esattamente al numero di argomenti forniti dopo la stringa stessa.
Attenzione al numero di argomenti della funzione printf
In generale, secondo lo standard del linguaggio C, non è necessario che il compilatore controlli che il numero di specificatori di formato nella stringa corrisponda al numero di argomenti forniti.
Ad esempio, il seguente codice è errato:
/* ERRORE: numero di argomenti insufficiente */
printf("Valori: a = %d, b = %d\n", a);
In questo caso, la stringa di formato contiene due specificatori di formato (%d), ma viene fornito solo un argomento (a). Tuttavia, il compilatore potrebbe non segnalare alcun errore ma, anzi, compilare il programma senza problemi. Nonostante ciò, l'esecuzione del programma potrebbe portare a comportamenti imprevisti, come la stampa di valori errati o addirittura il crash del programma.
Lo stesso vale per il caso opposto, in cui vengono forniti più argomenti di quanti ne siano necessari:
/* ERRORE: numero di argomenti in eccesso */
printf("Valore: a = %d\n", a, b);
In questo caso, la stringa di formato contiene un solo specificatore di formato (%d), ma vengono forniti due argomenti (a e b). Anche in questo caso, il compilatore potrebbe non segnalare alcun errore, ma l'esecuzione del programma potrebbe portare a comportamenti imprevisti.
In generale, è responsabilità del programmatore assicurarsi che il numero di specificatori di formato corrisponda al numero di argomenti forniti. In caso contrario, si rischia di incorrere in errori difficili da individuare e correggere.
Un altro aspetto importante da considerare riguarda il tipo di dato associato a ciascun specificatore di formato. Ogni specificatore di formato è progettato per gestire un tipo di dato specifico, e fornire un argomento di tipo errato può portare a comportamenti imprevisti.
Attenzione al tipo di dato degli argomenti della funzione printf
Così come lo standard del linguaggio C non richiede al compilatore di controllare il numero di argomenti forniti a printf, non richiede nemmeno di verificare che il tipo di dato di ciascun argomento corrisponda al tipo previsto dallo specificatore di formato.
Se lo sviluppatore fornisce un argomento di tipo errato, la funzione printf potrebbe, nel migliore dei casi, stampare valori senza senso, oppure, nel peggiore dei casi, causare il crash del programma.
Consideriamo il seguente esempio:
int a = 10;
double b = 3.14;
// ERRORE: tipi di dato errati
printf("%lf %d \n", a, b);
In questo caso, il primo specificatore di formato %lf si aspetta un argomento di tipo double, ma viene fornito un argomento di tipo int (a). Allo stesso modo, il secondo specificatore di formato %d si aspetta un argomento di tipo int, ma viene fornito un argomento di tipo double (b).
Dal momento che la funzione printf segue pedissequamente le specifiche fornite nella stringa di formato, essa stamperà un double seguito da un int, ma i valori stampati non avranno alcun senso, poiché i tipi di dato non corrispondono.
Per evitare questi problemi, è fondamentale che lo sviluppatore si assicuri che il tipo di dato di ciascun argomento corrisponda al tipo previsto dallo specificatore di formato corrispondente.
Introduzione agli Specificatori di formato
Gli specificatori di formato forniscono una grande capacità di controllo sull'output generato dalla funzione printf. Ma, spesso, possono risultare criptici e complessi da leggere, specialmente per gli sviluppatori neofiti.
Infatti, a questo punto della guida è troppo prematuro fornire tutte i dettagli su di essi e sulle loro funzionalità avanzate. Rimandiamo questo argomento alle lezioni successive, dove verranno trattati in modo più approfondito.
Per il momento, ci concentreremo sui loro aspetti principali.
In generale, la forma più comune e semplice di uno specificatore di formato è la seguente:
%-m.pX
Questa sintassi è composta da alcune parti obbligatorie:
- Il simbolo di percentuale (
%), che indica l'inizio di uno specificatore di formato. Questo simbolo è obbligatorio e deve essere presente in ogni specificatore di formato. - Una combinazione di lettere,
X, anch'essa obbligatoria, che indica il tipo di dato da stampare. Ad esempio,dper gli interi decimali,lfper i numeri in virgola mobile di tipodouble,cper i caratteri, e così via.
E da alcune parti opzionali:
- Un segno meno (
-), che indica l'allineamento a sinistra del valore stampato. Se non è presente, il valore viene allineato a destra per default. - Un numero intero positivo,
m, che specifica la larghezza minima del campo in cui il valore verrà stampato. Se il valore è più corto di questa larghezza, verrà riempito con spazi vuoti. - Un punto (
.) seguito da un numero intero positivo,p, che specifica la precisione del valore stampato. Per i numeri in virgola mobile, questo indica il numero di cifre da stampare dopo il punto decimale. Sepnon è specificato anche il punto deve essere omesso.
Larghezza minima del campo
La larghezza minima del campo (m) specifica il numero minimo di caratteri che devono essere utilizzati per stampare il valore. Se il valore da stampare è più corto di questa larghezza, il valore verrà stampato allineandolo a destra e verranno aggiunti degli spazi vuoti a sinistra per raggiungere la larghezza minima.
Per esempio, supponiamo di voler stampare il numero intero 123 con una larghezza minima del campo di 5:
int num = 123;
printf("Numero: %5d\n", num);
L'output sarà:
Numero: 123
In questo caso, il numero 123 viene stampato con due spazi vuoti a sinistra per raggiungere la larghezza minima di 5 caratteri.
Viceversa, se il valore da stampare è più lungo della larghezza minima specificata, il valore verrà stampato per intero senza alcuna modifica.
Ad esempio, se volessimo stampare il numero intero 12345 con una larghezza minima del campo di 3:
int num = 12345;
printf("Numero: %3d\n", num);
L'output sarà:
Numero: 12345
Quindi, in questo caso, la larghezza minima del campo è stata ignorata perché il valore da stampare è più lungo di 3 caratteri. In questo modo, printf garantisce che il valore venga sempre stampato per intero, indipendentemente dalla larghezza minima specificata.
Alla larghezza minima del campo si può associare anche il segno meno (-), che indica l'allineamento a sinistra del valore stampato. Ad esempio, se volessimo stampare due numeri interi con una larghezza minima del campo di 5, ma allineati a sinistra:
int num1 = 123;
int num2 = 456;
printf("Numeri: %-5d %-5d\n", num1, num2);
L'output sarà:
Numeri: 123 456
Precisione
Gli specificatori di formato possono includere anche una parte opzionale che indica la precisione (.p).
Il significato del valore di precisione dipende dal tipo di dato che si sta stampando. Nel caso dei numeri in virgola mobile, la precisione indica il numero di cifre da stampare dopo il punto decimale.
Ad esempio, supponiamo di voler stampare il numero in virgola mobile 3.14159 con una precisione di 2 cifre decimali:
double num = 3.14159;
printf("Numero: %.2lf\n", num);
L'output sarà:
Numero: 3.14
In questo caso, il numero 3.14159 viene arrotondato a 3.14, poiché abbiamo specificato una precisione di 2 cifre decimali.
Da notare che nel caso dei numeri in virgola mobile, se la precisione non viene specificata, printf utilizza una precisione predefinita di 6 cifre decimali.
Inoltre, il valore viene arrotondato e non troncato. Ad esempio, se volessimo stampare il numero 2.6789 con una precisione di 3 cifre decimali:
double num = 2.6789;
printf("Numero: %.3lf\n", num);
L'output sarà:
Numero: 2.679
In questo caso, il numero 2.6789 viene arrotondato a 2.679, poiché la quarta cifra decimale (9) è maggiore o uguale a 5.
Gli Specificatori di Formato più Comuni
Come abbiamo visto, la forma più comune di uno specificatore di formato è %-m.pX, dove X indica il tipo di dato da stampare.
Vediamo quali sono gli specificatori di formato più comuni e i loro significati:
-
%d: Stampa un intero decimale con segno (tipoint).Quando si adopera questo specificatore, la precisione (
.p) indica il numero minimo di cifre da stampare. Se il numero ha meno cifre, verranno aggiunti degli zeri a sinistra.Nel caso in cui la precisione viene omessa, essa viene assunta per default pari a
1. Quindi, i seguenti specificatori sono equivalenti:%de%.1d. -
%le: Stampa un numero in virgola mobile in notazione scientifica (tipodouble).La precisione (
.p) indica il numero di cifre da stampare dopo il punto decimale. Nel caso in cui la precisione venga omessa, essa viene assunta per default pari a6. Viceversa, se la precisione è0, non verrà stampato alcun punto decimale.Ad esempio, volendo stampare il numero
12345.6789con una precisione di2cifre decimali in notazione scientifica:double num = 12345.6789; printf("Numero: %.2le\n", num);L'output sarà:
Numero: 1.23e+04 -
%lf: Stampa un numero in virgola mobile in notazione decimale (tipodouble).La precisione (
.p) indica il numero di cifre da stampare dopo il punto decimale. Nel caso in cui la precisione venga omessa, essa viene assunta per default pari a6. Viceversa, se la precisione è0, non verrà stampato alcun punto decimale.Ad esempio, volendo stampare il numero
3.14159con una precisione di3cifre decimali:double num = 3.14159; printf("Numero: %.3lf\n", num);L'output sarà:
Numero: 3.142 -
%lg: Stampa un numero in virgola mobile utilizzando la notazione più compatta tra decimale e scientifica (tipodouble).Con questo specificatore, la precisione (
.p) non indica più il numero di cifre decimali, ma il numero totale di cifre significative da stampare.Inoltre, a differenza dello specificatore
%lf, questo specificatore non aggiunge zeri alla fine del numero per raggiungere la precisione specificata.In più, se il numero non ha cifre decimali, non verrà stampato alcun punto decimale.
Questo specificatore è particolarmente utile quando si vogliono stampare numeri la cui dimensione non è nota a priori oppure quando quest'ultima varia notevolmente. Quando lo si utilizza per stampare numeri non molto grandi o non molto piccoli, si ottiene un output in formato decimale, come se avessimo usato
%lf. Viceversa, quando si stampano numeri molto grandi o molto piccoli, si ottiene un output in notazione scientifica, come se avessimo usato%le, salvando, in questo modo, spazio prezioso.
Oltre a questi specificatori di formato, ne esistono molti altri che consentono di stampare diversi tipi di dati, come caratteri, stringhe, numeri esadecimali, ottali, e così via. Questi verranno trattati in modo più approfondito nelle lezioni successive man mano che affronteremo argomenti più avanzati relativi alla funzione printf.
Esempio Completo
Proviamo, adesso, a mettere insieme quanto appreso finora con un esempio che stampa numeri sia interi che in virgola mobile, utilizzando vari specificatori di formato.
#include <stdio.h>
int main() {
int a = 42;
double b = 1024.56789;
printf("|%d|%5d|%-5d|%5.3d|\n", a, a, a, a);
printf("|%10.3lf|%10.3e|%-10.3lg|\n", b, b, b);
return 0;
}
Se proviamo a compilare ed eseguire questo programma, otterremo il seguente output:
|42| 42|42 | 042|
| 1024.568| 1.025e+03|1.02e+03 |
In questo esempio, abbiamo definito due variabili: a di tipo int e b di tipo double. Successivamente, abbiamo utilizzato la funzione printf per stampare i valori di queste variabili con diversi specificatori di formato.
Abbiamo usato il carattere | per delimitare visivamente i campi stampati, in modo da evidenziare l'effetto della larghezza minima del campo e dell'allineamento.
Adesso, vediamo nel dettaglio i singoli specificatori di formato utilizzati:
%d: Stampa il valore diacome un intero decimale con segno utilizzando il minimo spazio possibile.%5d: Stampa il valore diacon una larghezza minima del campo di5, allineato a destra. Poichéaha solo2cifre, verranno aggiunti3spazi vuoti a sinistra.%-5d: Stampa il valore diacon una larghezza minima del campo di5, allineato a sinistra. Poichéaha solo2cifre, verranno aggiunti3spazi vuoti a destra.%5.3d: Stampa il valore diacon una larghezza minima del campo di5e una precisione di3cifre. Poichéaha solo2cifre, verrà aggiunto uno zero a sinistra per raggiungere la precisione di3, e verranno aggiunti2spazi vuoti a sinistra per raggiungere la larghezza minima di5.%10.3lf: Stampa il valore dibcome un numero in virgola mobile con una larghezza minima del campo di10e una precisione di3cifre decimali. Poichébha7cifre prima del punto decimale e5cifre decimali, verranno aggiunti2spazi vuoti a sinistra per raggiungere la larghezza minima di10.%10.3e: Stampa il valore dibin notazione scientifica con una larghezza minima del campo di10e una precisione di3cifre decimali. Anche in questo caso, verranno aggiunti2spazi vuoti a sinistra per raggiungere la larghezza minima di10.%-10.3lg: Stampa il valore dibutilizzando la notazione più compatta tra decimale e scientifica, con una larghezza minima del campo di10e una precisione di3cifre significative, allineato a sinistra. Poichébviene stampato in notazione scientifica con4cifre significative, verranno aggiunti6spazi vuoti a destra per raggiungere la larghezza minima di10.
In questo modo, l'esempio dimostra come utilizzare la funzione printf con vari specificatori di formato per controllare l'aspetto dell'output stampato sullo schermo.
Introduzione alle Sequenze di Escape
Concludiamo questa introduzione alla funzione printf parlando delle sequenze di escape.
Spesso, nei programmi visti sinora, abbiamo inserito, al termine delle stringhe di formato, la sequenza di escape \n. Questa sequenza speciale indica a printf di andare a capo dopo aver stampato il contenuto della stringa.
Questa sequenza fa parte di un insieme più ampio di stringhe che prendono collettivamente il nome di sequenze di escape. Esse sono utilizzate per rappresentare caratteri speciali all'interno delle stringhe, che altrimenti non potrebbero essere inseriti direttamente dalla tastiera oppure avrebbero un significato diverso per il compilatore, come ad esempio le virgolette doppie (").
Esistono numerose sequenze di escape, che vedremo in dettaglio più avanti, ma per ora ci basti conoscere le più comuni:
\n\t\a\b
Quando la funzione printf incontra una sequenza di escape all'interno della stringa di formato, essa interpreta la sequenza come un'azione da svolgere, piuttosto che come una semplice serie di caratteri da stampare.
Per cui, quando incontra la sequenza \n, printf va a capo, spostando il cursore alla riga successiva. Allo stesso modo, la sequenza \t inserisce una tabulazione orizzontale, spostando il cursore alla posizione successiva di tabulazione. La sequenza \a produce un segnale acustico (un "beep") su alcuni terminali, mentre la sequenza \b sposta il cursore indietro di un carattere, cancellando il carattere precedente.
Il nome escape (che in inglese significa "fuga") deriva dal fatto che queste sequenze permettono di "fuggire" dal normale comportamento di stampa dei caratteri, consentendo di inserire caratteri speciali o di controllare il formato dell'output in modi che altrimenti non sarebbero possibili.
Una stringa di formato passata alla funzione printf può contenere più sequenze di escape, e queste possono essere combinate con testo normale e specificatori di formato per creare output complessi e ben formattati.
Ad esempio, consideriamo il seguente codice:
printf("Ciao,\nMondo!\n");
printf("Colonna1\tColonna2\tColonna3\n");
printf("Attenzione!\a\n");
L'output di questo codice sarà:
Ciao,
Mondo!
Colonna1 Colonna2 Colonna3
Attenzione!
In questo esempio, la prima chiamata a printf utilizza la sequenza di escape \n per andare a capo dopo "Ciao," e dopo "Mondo!". La seconda chiamata utilizza la sequenza di escape \t per inserire tabulazioni tra le colonne. La terza chiamata utilizza la sequenza di escape \a per produrre un segnale acustico prima di stampare "Attenzione!".
Esistono anche alcune sequenze di escape che consentono di stampare caratteri speciali all'interno delle stringhe, come ad esempio:
\\: Stampa una barra rovesciata (\).\": Stampa una virgoletta doppia (").
Infatti, poiché la barra rovesciata (\) è il carattere di escape, per stampare una barra rovesciata stessa è necessario utilizzare la sequenza \\. Allo stesso modo, per stampare una virgoletta doppia all'interno di una stringa racchiusa tra virgolette doppie, è necessario utilizzare la sequenza \".
Ad esempio, consideriamo il seguente codice:
printf("Percorso del file: C:\\Documenti\\File.txt\n");
printf("Citazione: \"La conoscenza è potere.\"\n");
L'output di questo codice sarà:
Percorso del file: C:\Documenti\File.txt
Citazione: "La conoscenza è potere."
In questo esempio, la prima chiamata a printf utilizza la sequenza di escape \\ per stampare le barre rovesciate nel percorso del file su un sistema Windows. La seconda chiamata utilizza la sequenza di escape \" per stampare le virgolette doppie intorno alla citazione.