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 nel’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 intestaione 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 <stdio.h>
#include <ctype.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 gesione 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
31
#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 dimensoine (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
26
27
#include <stdlib.h>
#include <stdio.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
25
26
#include <stdlib.h>
#include <stdio.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.