Input/output (ed altro) con la libreria standard in C¶
Di seguito sono riportate le risposte (con esempi di codice sorgente) ad alcune delle domande più frequenti riguardanti gli esercizi d'esame. Le risposte sono organizzate secondo il seguente
Argomenti sulla linea di comando¶
Per argomenti sulla linea di comando si intendono tutte le parole
(stringhe massimali non contenenti spazio) che seguono il nome del comando
impartito all'interprete. Ad esempio, se avete compilato un programma in un
file di nome soluzione
e lo invocate tramite l'interprete come:
./soluzione uno 2 tr_e
gli argomenti saranno le tre parole: uno
, 2
e tr_e
.
La funzione main
che ha segnatura:
int main( int argc, char *argv[] )
può accedere a tali parole tramite l'array frastagliato argv
il cui
i
-esimo puntatore punta alla stringa corrispondente all'i
-esimo
argomento (l'argomento di posto 0 è il nome del comando); il numero di
elementi contenuti nell'array è dato da argc
(che quindi è pari ad uno più
del numero di argomenti).
Osservate che gli argomenti sono stringhe, qualora sia richiesto trattare
alcuni di essi come numeri sarà necessario usare una funzione di conversione,
come ad esempio atoi
, o atof
(per maggiori dettagli, si usi il comando
man, o si veda la sezione Parsing of Numbers del manuale on-line
della GNU C Library).
Si riporta, a titolo di esempio, un programma che, dati per argomenti alcuni numeri interi, ne stampa la somma:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int somma, i;
somma = 0;
for (i = 1; i < argc; i++) somma += atoi(argv[i]);
printf("%d\n", somma);
return 0;
}
Scarica il codice sorgente
di questo esempio.
Input/Output¶
Flussi standard¶
Ad ogni processo sono associati (tra l'altro) due flussi standard, uno di ingresso (o input), detto stdin, ed uno di uscita (o output), detto stdout.
Qualora non diversamente specificato, le funzioni di libreria per l'I/O fanno riferimento ad essi; quando si invoca un processo tramite l'interprete di comandi (in assenza di redirezione), tali flussi sono, per così dire, rispettivamente associati a "tastiera" e "monitor". Potete trovare una descrizione della redirezione nella Introduzione a GNU/Linux (citata tra il :ref :labprog_mat-label del corso di programmazione).
Un elemento di particolare importanza è l'osservazione che un flusso di
ingresso può terminare, ossia è possibile che il suo contenuto si
esaurisca e che quindi non sia più possibile leggere da esso. Tale condizione
è sovente segnalata (tra l'altro) dal valore di ritorno delle funzioni della
GNU C Library tramite il valore convenzionale EOF
(definito nel file di
intestazione stdio.h
).
Nel caso di un processo invocato tramite l'interprete di comandi, quando il flusso di ingresso è quello standard e non è stato rediretto, è possibile segnalare la fine del flusso, o la sua terminazione, premendo ^D (ossia il tasto "control" contemporaneamente al tasto "d") una volta, se si è all'inizio della riga, o due volte altrimenti.
Non formattato¶
Per I/O non formattato si intende l'elaborazione dell'ingresso ed uscita considerati come sequenze di caratteri (o linee).
Orientato al carattere¶
L'input orientato al carattere è adatto ai casi in cui si chieda di elaborare i dati un carattere alla volta (particolarmente nel caso in cui ogni carattere emesso dipenda dall'ultimo carattere letto).
Le due funzioni più rilevanti sono getchar
e putchar
, per maggiori
dettagli, si usi il comando man, o si vedano le sezioni Character
Input e Simple Output del manuale on-line della GNU C Library.
Si riporta, a titolo di esempio, un programma che trascrive il suo ingresso rendendo maiuscoli tutti i caratteri alfabetici:
#include <ctype.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
int ch;
while ((ch = getchar()) != EOF)
putchar(isalpha(ch) ? toupper(ch) : ch);
return 0;
}
Scarica il codice sorgente
di questo
esempio.
Orientato alla linea¶
L'input orientato alla linea è adatto ai casi in cui si chieda di elaborare i dati una linea per volta (particolarmente nel caso in cui ogni linea emessa dipenda dall'ultima linea letta).
Le due funzioni più rilevanti sono gets
e puts
; particolare attenzione
va posta alla dimensione del buffer in cui effettuare la lettura: non c'è
garanzia che la funzione gets
la rispetti, per questa ragione talvolta è
preferibile usare la funzione fgets
. Per maggiori dettagli, si usi il
comando man, o si vedano le sezioni Line Input e Simple
Output del manuale on-line della GNU C Library.
Si riporta, a titolo di esempio, un programma che legge le linee in ingresso e ne stampa la lunghezza media (assumendo che la linea più lunga in ingresso sia di 256 caratteri):
#include <stdio.h>
#include <string.h>
#define MAX_LEN 256
int main(int argc, char *argv[]) {
int n, tot;
char buffer[MAX_LEN + 1];
tot = n = 0;
while (fgets(buffer, MAX_LEN + 1, stdin) != NULL) {
tot += strlen(buffer) - 1;
n++;
}
printf("%f", (float)tot / n);
return 0;
}
Scarica il codice sorgente
di questo esempio.
Formattato¶
Per I/O formattato si intende l'elaborazione dell'ingresso ed uscita attraverso apposite specifiche (dette stringe di formato) che definiscono come il suo contenuto debba essere presentato, o interpretato.
Il caso più tipico è quello per cui si voglia passare dal valore di una variabile numerica alla sequenza di caratteri che corrispondono alla sua rappresentazione decimale, o viceversa.
Le due funzioni più rilevanti sono scanf
e printf
, per maggiori
dettagli, si usi il comando man, o si vedano le sezioni Formatted
Input e Formatted Output del manuale on-line della GNU C Library.
Si riporta, a titolo di esempio, un programma che legge una sequenza di numeri interi e ne stampa la somma:
#include <stdio.h>
int main(int argc, char *argv[]) {
int somma, x;
somma = 0;
while (scanf("%d", &x) != EOF) somma += x;
printf("%d\n", somma);
return 0;
}
Scarica il codice sorgente
di questo
esempio.
Array dinamici¶
Nello standard ANSI del C non sono previsti array dinamici. Risulta però
talvolta utile poter (ri)dimensionare un array nel corso dell'esecuzione del
programma. Per fare questo è necessario utilizzare le funzioni per la gestione
dinamica della memoria quali malloc
, free
e realloc
; per maggiori
dettagli, si usi il comando man, o si veda le sezione
Unconstrained Allocation del manuale on-line della GNU C Library.
Si riporta, a titolo di esempio, un programma che legge una sequenza di lunghezza non specificata di numeri interi terminata da 0 e la stampa in ordine inverso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <stdio.h>
#include <stdlib.h>
int main(void) {
int x, *v, n, i;
n = 1;
v = (int *)malloc(sizeof(int) * n);
i = 0;
for (;;) {
scanf("%d", &x);
if (x == 0) break;
if (i == n) {
n *= 2;
v = (int *)realloc(v, sizeof(int) * n);
}
v[i++] = x;
}
n = i;
v = (int *)realloc(v, sizeof(int) * n);
for (i = n - 1; i >= 0; i--)
printf("%d\n", v[i]);
free(v);
return 0;
}
|
Scarica il codice sorgente
di questo
esempio.
Il programma funziona come segue: puntatore v
viene fatto corrispondere ad
uno spazio di dimensione n
inizialmente pari a 1 (linee 8--9); quando,
durante la lettura, la dimensione n
viene raggiunta dal numero i
di
interi già letti, la dimensione allocata viene raddoppiata (linee 15--18); al
termine della lettura, la dimensione viene adattata all'esatto numero di
interi letti (linee 22--23) ed lo spazio allocato viene liberato (linea 28).
Stringhe¶
Scrivere un numero in una stringa¶
Talvolta può essere utile scrivere (i caratteri corrispondenti alla
rappresentazione decimale di un) numero in una stringa. Per farlo è
sufficiente usare la funzione sprintf
che si comporta similmente alla
printf
, ma invece che stampare il risultato della formattazione lo
memorizza nella locazione specificata; per maggiori dettagli, si usi il
comando man, o si veda le sezione Formatted Output del
manuale on-line della GNU C Library.
Si riporta, a titolo di esempio, un programma che stampa il numero ottenuto concatenando la prima cifra di ciascuna delle prime dieci potenze di due:
#include <stdio.h>
int main(void) {
int p, i;
char buffer[4];
p = 1;
for (i = 0; i < 10; i++) {
sprintf(buffer, "%d", p);
putchar(buffer[0]);
p *= 2;
}
putchar('\n');
return 0;
}
Scarica il codice sorgente
di questo esempio.
Creare una copia di una stringa¶
Ci sono casi in cui è necessario copiare una stringa (ad esempio letta dal
flusso di ingresso standard, oppure ottenuta tramite un letterale) per poterla
successivamente modificare senza influire sull'istanza originaria. Sebbene
tale compito possa essere portato a termine calcolando la lunghezza della
stringa con strlen
, allocando lo spazio necessario a contenere la copia
con malloc
e quindi copiando la stringa con strcpy
(tutte funzioni che
dovrebbero esservi note), c'è una soluzione molto più semplice: la funzione
strdup
; per maggiori dettagli, si usi il comando man, o si veda
le sezione Copying and Concatenation del manuale on-line della GNU C
Library.
Si riporta, a titolo di esempio, un programma che legge una sequenza di (al più 10) stringhe (di al più 256 caratteri ciascuna) e le usa per popolare un array frastagliato che poi stampa in ordine inverso:
#include <stdio.h>
#include <string.h>
#define MAX_LINES 10
#define MAX_LEN 256
int main(void) {
char *linea[MAX_LINES], buffer[MAX_LEN + 1];
int i;
i = 0;
while (scanf("%s", buffer) != EOF)
linea[i++] = strdup(buffer);
while (i-- > 0)
printf("%s\n", linea[i]);
return 0;
}
Scarica il codice sorgente
di questo esempio.
Ordinamenti¶
Se è necessario ordinare un array di dati (siano essi numeri, stringhe o dati
strutturati), si può usare la funzione qsort
che ha la seguente segnatura:
void qsort( void *array, size_t count, size_t size, comparison_fn_t compare )
dove il primo argomento è il puntatore all'array da ordinare, il secondo è il numero di elementi in esso contenuti, il terzo è la dimensione (in byte) di ciascun elemento del vettore ed, in fine, una funzione di comparazione.
Posto che l'array da ordinare abbia nome array
(e se ne vogliano ordinare
tutti gli elementi), i primi tre argomenti sono dati rispettivamente da
array
, sizeof( array ) / sizeof( array[ 0 ] )
e sizeof( array[ 0 ]
)
. La funzione di comparazione è una funzione che dati due puntatori ad
elementi dell'array restituisce un intero negativo, nullo, o positivo a
seconda che l'elemento puntato dal primo puntatore sia, rispettivamente, da
considerarsi minore, uguale, o maggiore a quello puntato dal secondo
puntatore. Poiché qsort
è del tutto generica, nella segnatura della
funzione di comparazione i puntatori non hanno tipo (ossia sono puntatori a
void
), questo richiede un po' di attenzione nella scrittura della funzione
di comparazione che, per dereferenziare tali puntatori ed accedere ai valori
puntati da essi, dovrà dapprima fare un cast opportuno.
Si riportano a titolo di esempio due programmi che ordinano, rispettivamente, un array di interi, ed un array (frastagliato) di stringhe. Il primo, è il seguente:
Il programma che ordina in modo crescente gli interi è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h>
#include <stdlib.h>
int compara(const void *px, const void *py) {
int x = *(int *)px, y = *(int *)py;
if (x < y) return -1;
else if (x > y) return 1;
return 0;
}
int main(void) {
int i, n;
int array[] = {6, 4, 5, 3, 1, 2};
n = sizeof(array) / sizeof(array[0]);
qsort(array, n, sizeof(array[0]), compara);
for (i = 0; i < n; i++)
printf("%d ", array[i]);
printf("\n");
return 0;
}
|
Scarica il codice sorgente
di questo esempio.
La funzione di comparazione (linee 4--11) effettua il cast dei puntatori (linea 6) e quindi usando gli usuali operatori relazionali tra interi (linee 8--10) determina l'ordine dei due elementi. Per ottenere l'ordine decrescente è sufficiente invertire 1 e -1 nella funzione di comparazione (linee 8 e 9).
Il programma per ordinare in modo crescente le stringhe è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int compara(const void *ps, const void *pt) {
char *s = *(char **)ps, *t = *(char **)pt;
return strcmp(s, t);
}
int main(void) {
int i, n;
char *array[] = {"ciao", "mamma", "bella"};
n = sizeof(array) / sizeof(array[0]);
qsort(array, n, sizeof(array[0]), compara);
for (i = 0; i < n; i++)
printf("%s ", array[i]);
printf("\n");
return 0;
}
|
Scarica il codice sorgente
di questo esempio.
In questo caso, la funzione di comparazione (linee 5--10) effettua il cast dei
puntatori (linea 7) e quindi delega il confronto alla funzione strcmp
(si
osservi che non sarebbe stato corretto usare quest'ultima in vece della
funzione di comparazione perché le segnature differiscono per i tipi dei
puntatori). Per ottenere l'ordine decrescente è sufficiente anteporre un
segno - alla chiamata di strcmp
(linea 9).
Per maggiori dettagli sull'argomento, si usi il comando man, o si veda le sezione Searching and Sorting del manuale on-line della GNU C Library.