Guida di C (base)

Esempi

Eccoci dunque giunti all'ultimo capitolo di questa guida base di C, nel quale non ho intenzione di trattare nuovi argomenti, ma di mettere in pratica quanto detto fin'ora con qualche programmino di esempio.

Lascio a voi la scelta su come utilizzare gli esempi; semplicemente ragionandoci su, oppure come "sfide" da risolvere senza guardare la soluzione... In ogni caso, idee, commenti, soluzioni alternative e quant'altro sono come al solito benvenuti!

Nota tecnica: i commenti, accuratamente posizionati nel codice originale, sulle pagine risultano un po' sfalsati, come posizione. Per guardare l'originale basta utilizzare la funzione view plain dei box di codice.

In attesa degli indici automatici in ReCMS, elenco gli esempi contenuti in questo articolo:

  • Rettangoli
  • Calcolatrice
  • Tabelline
  • Numeri primi
  • Media dei voti
  • Crittografia

Rettangoli

Iniziamo con un semplice problema di geometria: un programma che, date la base e l'altezza, calcoli perimetro e area del rettangolo corrispondente.

Anzitutto formalizziamo il problema: dobbiamo ricevere dall'utente due numeri (interi, per semplicità ) corrispondenti alla base e all'altezza del rettangolo e, come output, stamparne a video l'area ed il perimetro, che calcoleremo con le semplici formule imparate alle scuole elementari.

Suddivideremo dunque il programma in tre parti: la lettura dei dati, la loro elaborazione, e la stampa del risultato.

Vista l'intuitiva semplicità  delle tre parti, passo subito al codice sorgente del programma:

#include <stdio.h>

main()
{
	int base,altezza,
            perimetro,area;

	/* Input, legge i dati */
	printf("Inserisci la base del rettangolo: ");
	scanf("%d",&base);                            /* Legge 'base' */
	printf("Inserisci l'altezza del rettangolo: ");
	scanf("%d",&altezza);                         /* Legge 'altezza' */

	/* Elaborazione, esegue i calcoli */
	perimetro = 2*(base + altezza);                   /* Calcola il perimetro */
	area = base*altezza;                              /* Calcola l'area */

	/* Output, stampa i risultati */
	printf("Il perimetro del rettangolo è: %d",perimetro);
	printf("L'area del rettangolo è: %d",area);
}

Osservazioni

Propongo un'unica osservazione per questo programma: ci sono troppe variabili! Il buon programmatore C mette l'efficienza al primo posto, e nota subito che, mentre base e altezza sono indispensabili per i calcoli, perimetro ed area sono utilizzate soltanto per mantenere i risultati fino al momento della stampa, senza più essere considerate. Possiamo dunque svolgere i calcoli direttamente al momento della stampa, modificando le printf() in questo modo:

	printf("Il perimetro del rettangolo è: %d",2*(base + altezza));
	printf("L'area del rettangolo è: %d",base*altezza);

Apportata questa modifica, possiamo eliminare dal programma tutte le tracce delle variabili perimetro e area, ovvero la dichiarazione (che si ridurrà  a int base,altezza;) e l'intera fase di elaborazione, che sarà  eliminata del tutto.

Calcolatrice

Scontenti della calcolatrice di sistema, riluttanti ad usare le soluzioni Google, programmiamoci la nostra piccola calcolatrice.

In particolare l'intento di questo esempio sarà  di fornire uno strumento per eseguire le quattro operazioni binarie: addizione, sottrazione, moltiplicazione e divisione, limitandoci per semplicità  ai soli numeri interi. L'input del programma dovrà  inoltre essere nella forma OperandoSegnoOperando, ad esempio 2+2, o 3*2

Notiamo che la parte dei calcoli qui è relativamente semplice: sarà  conveniente predisporre tre variabili, contenenti rispettivamente i due operandi (a, b) ed l'operatore (op); sulla base dell'operatore, poi, sceglieremo l'operazione da effettuare (si preannuncia un costrutto switch), ed il resto sarà  semplice aritmetica.

Il problema grosso di questo esercizio è il riconoscimento dell'input, ma è un problema che possiamo risolvere con un sapiente uso di scanf(). Sappiamo infatti che dobbiamo leggere, in sequenza, un numero, un carattere, ed un altro numero, e sarà  semplicemente quello che chiederemo di fare a scanf().

A questo punto non resta che vedere il codice:

#include <stdio.h>

main()
{
	int a,b;
	char op;

	printf("Inserisci l'operazione da effettuare: ");
	scanf("%d%c%d",&a,&op,&b);    /* Legge, in ordine, un numero, un carattere, e un numero */

	printf("Risultato: ");
	switch (op)                               /* Confronta i valori possibili di 'op' */
	{
		case '+':                             /* Se 'op' è un '+'... */
		printf("%d",a+b);                     /* ...fa un'addizione */
		break;

		case '-':                             /* Se 'op' è un '-'... */
		printf("%d",a-b);                     /* ...fa una sottrazione */
		break;

		case '*':                             /* Se 'op' è un '*'... */
		printf("%d",a*b);                     /* ...fa una moltiplicazione */
		break;

		case '/':                             /* Se 'op' è un '/'... */
		printf("%d",a/b);                     /* ...fa una divisione */
		break;

		default:
		printf("ERR");
	}

	printf("\n");
}

Osservazioni

Senz'altro è da notare il particolare utilizzo di scanf(): l'abbiamo sempre vista leggere un solo valore alla volta, mentre qui si occupa di ben tre variabili assieme. Attenzione, però, al fatto che l'input dev'essere adeguatamente formattato: scanf(), così come l'abbiamo scritta, si aspetta di ricevere stringhe tipo 2+2, 15/3 e via dicendo... Vediamo però cosa succede in alcuni casi se "sbagliamo" a scrivere l'input:

  • [dario@localhost c]$ ./a.out 
    Inserisci l'operazione da effettuare: 2 + 2
    Risultato: ERR

    Quella che per un essere umano è una banale variazione nella scrittura, per il calcolatore rappresenta un ostacolo insormontabile: ho aggiunto degli spazi tra i numeri ed il segno '+'. Cerchiamo quindi di pensare come una scanf(), per capire dove si è bloccato il programma: abbiamo detto che bisogna leggere, in ordine, un numero, un carattere ed un numero. Ebbene, il primo numero si trova correttamente al primo posto: è il 2. Il secondo elemento però dev'essere un carattere, ed effettivamente dopo il 2 c'è un carattere: lo spazio. è quello il problema, terminata la scanf(), la variabile op contiene uno spazio, che non è previsto tra i casi di switch. Viene quindi eseguito, all'interno di switch, il codice di default, ed il programma stampa ERR. Suggerimento: per capire comportamenti simili a questo può essere utile stampare, come prova (tecnicamente si dice debug), il contenuto della variabile op, nel caso di default.

  • [dario@localhost c]$ ./a.out 
    Inserisci l'operazione da effettuare: 2+ 2
    Risultato: 4
  • Questa volta, inaspettatamente, il programma funziona alla perfezione. Il motivo è semplice: la scanf() è progettata per ignorare spazi, invii e tabulazioni prima dei numeri (solo davanti ai numeri!). Come prova si possono provare gli input 2+<RET>2 e 2+<TAB>2, funzioneranno senza problemi (ricordo che RET sta per return, il normale tasto invio, e TAB è il carattere di tabulazione, quello a sinistra, subito sopra il "lucchetto" del caps lock)

  • [dario@localhost c]$ ./a.out 
    Inserisci l'operazione da effettuare: 2+ciao
    Risultato: 32769

    Le cose si fanno misteriose: che significato ha quel numeraccio? La risposta è... Nessuno! Semplicemente scanf() legge e riconosce i primi due elementi (2 e +), ma non trova il terzo, perchè non c'è nessun numero dopo il '+'. Le prime due variabili (a e op) sono dunque a posto, e contengono rispettivamente 2 e +. La terza invece non viene toccata e resta nel suo stato iniziale. Sappiamo che, in C, una variabile non inizializzata può contenere qualsiasi valore (a seconda di quanto era "sporco" il pezzo di memoria che è andata ad occupare): questa ne è la conferma.

Propongo in ultimo un esercizio: chi riesce a modificare la parte relativa alla divisione, in modo che stampi anche il resto? Riempite la pagina dei commenti!

Tabelline

Tutti noi ricordiamo con affetto il tempo in cui, alle scuole elementari, imparavamo a memoria le tabelline... Realizziamo ora un programma che sia in grado di declamare qualsiasi tabellina scelta dall'utente che lo "interroga".

Prima di iniziare, diciamo che per "tabellina" intendiamo la moltiplicazione della base (scelta dall'utente) per i primi 10 numeri naturali; ad esempio la tabellina del 2 sarà  2x1, 2x2, 2x3 ... 2x10.

A questo punto è chiaro che l'unico dato da chiedere all'utente è la base, che è sufficiente per declamare l'intera tabellina. Conclusa la parte di input utilizzeremo un ciclo per stampare a video le 10 moltiplicazioni che compongono la tabellina.

#include <stdio.h>

main()
{
	int i,base;		/* Dichiara il contatore 'i' e la base 'base' */

	/* Input */
	printf("Inserisci la base della tabellina: ");
	scanf("%d",&base);

	for(i=1; i<=10; i++) printf("%d x %d = %d\n",base,i,base*i);
}

Osservazioni

Non ci sono molte osservazioni da fare su questo codice, solo un po' di attenzione va dedicata alla printf() all'interno del ciclo, per capire come è strutturata la stringa di controllo; essa stampa, nell'ordine, un numero (%d), la stringa " x " (il carattere 'x', preceduto e seguito da uno spazio), un numero (%d), la stringa " = " ed un numero (%d). I tre numeri seguono lo stesso ordine degli argomenti di printf(): il primo è la base, il secondo è l'indice (sì, è proprio la stessa variabile che viene incrementata dal ciclo for) ed il terzo è il risultato, dato dalla semplice operazione 'base x indice'.

Così com'è il programma sembra perfetto, ma c'è una piccola cosa che si potrebbe migliorare: se osserviamo la colonna di segni '=' che il programma produce, notiamo che l'ultimo è disallineato rispetto agli altri, perchè il numero 10 è composto da due cifre, anzichè una. Per ottenere una struttura ordinata ed omogenea (che, tra le altre cose, favorisce la lettura da parte degli utenti) sarebbe meglio avere tutti gli '=' allineati sulla stessa colonna. Come fare? Una prima idea potrebbe essere quella di fermare il ciclo a 2x9, per poi fare una printf() apposta per il 10, avendo cura di allineare correttamente gli uguali. Ma questo non piace a chi, come noi, sa usare printf() in modo intelligente:

for(i=1; i<=10; i++) printf("%d x %2d = %d\n",base,i,base*i);

Modificando il secondo %d in %2d ho detto a printf() di stampare il secondo numero in uno spazio di almeno due cifre, risolvendo così il problema in modo elegante e degno di un programmatore C.

Numeri primi

Affrontiamo ora uno dei problemi attualmente irrisolti della matematica: stabilire se un numero sia primo o meno.

Per farlo adotteremo un algoritmo molto semplice: sappiamo che un numero si dice primo se è divisibile solo per 1 e per se stesso, quindi per risolvere il problema basterà  provare tutte le divisioni possibili, se almeno una ha resto zero significa che il numero non è primo, perchè esiste un suo divisore.

La ricerca avverrà  quindi con un metodo detto di "forza bruta", ovvero provando una alla volta tutte le possibilità .

Faccio notare che, per risparmiare un po' di conti, conviene fermarsi alla metà  del numero (linea 16, grazie a MaxCube128 per la segnalazione): nessun numero, infatti, può essere diviso esattamente per un valore maggiore della sua metà  (ad esempio, 14 non può essere diviso per un numero maggiore di 7, 100 non può essere diviso da un numero maggiore di 50, 1000 da 500 e così via).

#include <stdio.h>

main()
{
	/* Dichiarazione delle variabili */
	int n,tmp,primo;

	/* Input del numero da verificare */
	printf("Inserisci un numero: ");
	scanf("%d",&n);

	tmp = 2;    /* 'tmp' sarà  usata nel ciclo come divisore. *
                 * Il primo divisore ad essere provato è 2    */

	primo = 1;	               /* Partiamo con l'idea che il numero sia primo */
	while( tmp <= n/2 )
	{
		if ( n%tmp == 0 ) {    /* Se è divisibile per tmp             *
                                * (ovvero se il resto della divisione *
                                *  intera "n/tmp" è uguale a 0)       */
			primo = 0;         /* 'primo' diventa 0 */
			break;             /* esce dal ciclo */
		}

		tmp++;                 /* Incrementa 'tmp' */
	}

	/* Controlla il valore di 'primo': se è 1 allora il numero *
	 * è effettivamente primo, altrimenti non lo è             */
	if ( primo == 1 ) printf("%d è un numero primo!\n",n);
	else printf("%d non è un numero primo...\n",n);
}

Osservazioni

In questo caso è da notare il particolare tipo di ragionamento fatto per stabilire se il numero è primo o meno: poichè non esiste un metodo matematico per stabilire se un numero sia primo o meno, il programma parte assumendo che il numero sia primo (riga 15) e, uno alla volta, passa in rassegna tutti i possibili controesempi. Appena ne trova uno (riga 18) si ferma, perchè ha trovato una condizione sufficiente per dire che il numero non è primo. Se invece, dopo aver fatto tutte le prove possibili, non ne trova nessuna che contraddica la tesi originaria (ovvero che il numero è primo), allora non resta che concludere che il numero dato in input è effettivamente un numero primo.

Esercizio: chi riesce a modificare il programma in modo che stampi (se c'è) il divisore che ha fatto bloccare il ciclo? Chi riesce a stampare il massimo divisore del numero in input?

Media dei voti

Sia per godersi i risultati del proprio duro lavoro, sia per paura del debito, o sia per semplice curiosità, prima o poi arriva il momento di fare la media dei propri voti: vediamo come programmare il calcolatore in modo che faccia il lavoro al posto nostro.

Prima di pensare all'algoritmo vero e proprio, propongo una considerazione: per fare la media dei voti sarà  necessario usare i numeri con la virgola (a meno di non voler vedere tutti i 5,9 diventare 5, ma non penso sia il caso...). Per fare ciò basterà  utilizzare il tipo float, corrispondente alla specifica di stampa %f.

Passiamo ora a capire come deve comportarsi il programma, e per farlo ricordo la nota formula della media aritmetica tra n voti, corrispondente alla somma di tutti i voti, fratto il numero (n) dei voti). Ovviamente non possiamo sapere a priori quanti voti inserirà  l'utente, e qui si apre il problema: dove metto tutti gli n voti? Non posso mica dire al programma di dichiarare n variabili, se non so quanto vale n!

Fortunatamente la logica viene in nostro aiuto, ed in particolare possiamo capire che alla formula effettivamente non interessano i singoli voti, ma piuttosto la loro somma, e questa la possiamo ottenere anche senza memorizzare singolarmente ogni numero: è sufficiente usare una variabile apposta (che chiamerò somma), a cui addizionare i numeri man mano che l'utente li inserisce (si preannuncia quindi un ciclo per la lettura dei numeri) .

Altra cosa da tenere a mente è però il numero totale dei voti, ma questo non è difficile: utilizzeremo la variabile n, che incrementeremo ogni volta che l'utente inserisce un voto.

#include <stdio.h>

main()
{
	/* Dichiarazione delle variabili */
	float somma,voto;            /* somma: somma totale dei voti                     */
                                 /* voto:  variabile temporanea per i voti inseriti  */
	int n;                       /* n:     contatore per il numero dei voti inseriti */

	/* Ciclo di lettura dell'input         *
	 * termina quando l'utente immette '0' */

	n=0;
	do {
		printf("Inserisci un voto (0 per terminare): ");
		scanf("%f",&voto);              /* Legge un voto */
		if (voto != 0) {                /* Se non è stato inserito uno 0... */
			somma += voto;              /* ...aggiunge alla somma...               */
			n++;                        /* ...e incrementa il contatore.           */
		}
	} while ( voto != 0 );              /* Fintantochè l'utente inserisce numeri *
                                         * diversi da 0                                 */

	printf("\nLa tua media è: %1.2f \n",somma/n);    /* media = somma dei voti diviso numero *
                                                             * dei voti                             */
}

Osservazioni

Per questo programma mi vengono in mente diverse osservazioni:

  • Come prima cosa volevo far notare un comportamento particolare, che si ottiene inserendo i voti tutti di fila, anzichè aspettando il prompt (la frase che li chiede):

    [dario@localhost c]$ ./a.out 
    Inserisci un voto (0 per terminare): 10 10 8 10 10 7.5 0
    Inserisci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inseri
    sci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un
     voto (0 per terminare): Inserisci un voto (0 per terminare): 
    La tua media è: 9.25
    

    Ormai il meccanismo lo conosciamo: è sempre il solito buffer di lettura. scanf() legge i numeri dal buffer, se questo è vuoto attende che l'utente ci scriva qualcosa, altrimenti, se c'è qualcosa (come in questo caso, in cui c'è l'intera sfilza di voti), scanf() "mangia" il contenuto già  presente.

  • Vediamo ora un caso estremo: il caso in cui l'utente non immetta nessun voto. Cosa succederà ?

    [dario@localhost c]$ ./a.out 
    Inserisci un voto (0 per terminare): 0
    
    La tua media è: nan 
    

    Apparentemente il programma ci sta prendendo in giro, ma in realtà  è difficile che i progettisti delle librerie di I/O abbiano tutto questo senso dell'umorismo; infatti dietro la misteriosa sigla nan si cela un significato ben preciso: nan sta per Not A Number. In parole povere c'è stato un errore nei conti.

    Per capire l'errore bisogna ricostruire l'andamento del programma, con particolare attenzione al calcolo che ha generato l'errore, ovvero somma/n, linea 24. Questo conto coinvolge due variabili: somma ed n. Non essendo stato inserito alcun numero, la variabile somma non è mai stata toccata (il contenuto dell'if, linea 17, non viene mai eseguito), e può quindi contenere un valore qualsiasi. Questa cosa non ci piace, ma ancora peggio è il valore di n: non essendo mai stata incrementata vale il numero che le è stato inizialmente assegnato (linea 13), ovvero zero. Ecco il motivo per cui il risultato di somma/n non è un numero: no division by zero!

    Mi viene spontaneo proporre un esercizio: chi riesce a modificare il programma in modo che stampi un messaggio d'errore nel caso in cui non siano stati inseriti voti?

  • Ultima cosa: noi italiani siamo abituati, nei numeri decimali, a separare parte intera e parte decimale con la virgola. Gli statunitensi (che, fanno un po' da padroni nello scenario informatico) invece utilizzano il punto, e quindi anche il nostro programma si aspetta di ricevere numeri con il punto al posto della virgola.

    Come al solito il mio quesito è: che succede se inserisco un numero "con la virgola", nel senso più letterale possibile del termine? (e perchè succede?)

    Inserisci un voto (0 per terminare): 9,5
    Inserisci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inseri
    sci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un
     voto (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un voto 
    (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un voto (0 per
     terminare): Inserisci un voto (0 per terminare): Inserisci un voto (0 per termi
    nare): Inserisci un voto (0 per terminare): Inserisci un voto (0 per terminare):
     Inserisci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inser
    isci un voto (0 per terminare): Inserisci un voto (0 per terminare): Inserisci u
    n voto (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un voto
     (0 per terminare): Inserisci un voto (0 per terminare): Inserisci un voto (0 pe
    
    [...]
    
    r terminare): Inserisci un voto (0 per termina^C
    

    In informatica questo si chiama infinite loop (e si termina con CTRL+C). Il motivo per cui succede questa cosa è, come si può intuire, la virgola. scanf(), a cui è stata data la specifica %f (floating point, in italiano i numeri con la virgola), si aspetta di trovare un numero, intero (ad esempio 5), o decimale (ad esempio 9.5). Al primo giro trova il 9, ma si ferma lì, perchè non conosce il significato della virgola (naturalmente, se la virgola fosse stata un punto, scanf() avrebbe letto il numero per intero). Questo significa che, letto il 9, nel buffer rimane tutto il resto (,5). Al secondo giro scanf() si trova davanti una virgola, che non corrisponde a quello che cerca (%f) quindi... non fa niente. Nel buffer rimane quel ,5, e al terzo giro il comportamento sarà  identico al secondo. La situazione a questo punto è irrimediabile: il ciclo continuerà  così all'infinito.

    Esercizio (difficile): chi riesce a modificare il programma in modo da prevenire questo tipo di infinite loop (stampando un messaggio di errore nel caso in cui scanf non riconosca l'input)? Suggerimento: se ho una variabile m, di tipo intero, l'assegnamento m = scanf(...);, oltre a eseguire normalmente la printf() salva in m il numero di elementi riconosciuti da printf().

Crittografia

Uno dei problemi più interessanti, che da sempre l'umanità  pone a se stessa, è quello della crittografia, ovvero come scrivere messaggi in codice, che siano leggibili solo da mittente e destinatario, ma che risultino incomprensibili a qualsiasi altro "ficcanaso".

Il sistema di cifratura che utilizzeremo in questo esercizio è uno dei cifrari più antichi e famosi che esistano, e lo dobbiamo niente meno che a... Giulio Cesare!

Il concetto che sta alla base del cifrario di Cesare è piuttosto semplice: ogni lettera che compone il messaggio originale (che chiameremo testo in chiaro) viene spostata in avanti di tre posizioni (consideriamo l'alfabeto americano, che comprende le lettere j, k, w, x e y); in questo modo la a diventa d, la b diventa e, e così via fino a x, y e z, che diventano rispettivamente a, b e c (raggiunta l'ultima lettera si ricomincia a contare dall'inizio dell'alfabeto). Chiariamo definitivamente questo concetto con un esempio:

attaccare gli irriducibili galli
dwwdffduh jol luulgxflelol jdool

Per semplicità  supporremo che siano inserite solo lettere minuscole (quindi niente accenti, numeri, maiuscole ecc...), e che la frase in input termini con un punto.

Analizziamo ora il problema: dobbiamo leggere una sequenza di lettere, di volta in volta trasformando il carattere in chiaro nel corrispondente carattere cifrato. Ovviamente sarebbe scomodo chiedere all'utente di inserire la sua frase una lettera per volta, per questo possiamo usare le proprietà  del buffer, viste nel corso (→ Input da tastiera). In particolare chiederemo all'utente di inserire la frase tutta in una volta, per poi ripetere getchar(), che "mangerà " uno per volta i caratteri nel buffer, fino alla fine della stringa (abbiamo supposto che la stringa finisca con un punto.).

Ora viene la parte interessante: ogni carattere "mangiato" deve essere trasformato nel suo corrispondente cifrato. Ovvero bisogna spostare il carattere in avanti di tre posizioni (avendo cura di ricominciare da capo, se si supera la z).

Qui è necessario aprire una parentesi su come il calcolatore 'vede' i caratteri: sappiamo che nel computer tutti gli oggetti (finestre, icone, immagini, musica ecc..) sono in realtà  lunghe distese di numeri; Anche i caratteri che compongono le parole lo sono, ed esiste un particolare codice, detto codice ASCII, che associa ad ogni carattere il numero corrispondente. Questo in C possiamo sperimentarlo facilmente, ad esempio stampando un carattere come se fosse un numero (printf("%d",c);, dove c è una variabile di tipo char). L'istruzione avrà  l'effetto di stampare il codice ASCII del carattere contenuto in c. Una tabella del codice ASCII può essere consultata a questo indirizzo: http://www.asciitable.com/.

A questo punto è facile notare che, nel codice ASCII, tutte le lettere sono consecutive: basterà  incrementare (algebricamente) il carattere corrente di tre unità  per trasformarlo in quello cifrato (occhio però ai caratteri oltre la 'z'):

#include <stdio.h>

main()
{
	char c;

	printf("Inserisci la frase da criptare (termina con '.'):\n");

	while ( (c=getchar()) != '.' )      /* ATTENZIONE: contemporaneamente                  *
                                         *  - legge un carattere                           *
                                         *  - lo mette nella variabile 'c'                 *
                                         *  - se 'c' non è un punto entra nel ciclo *
                                         *    (altrimenti il ciclo termina)                */
	{
		if (c == ' ') { /* Non modifica gli spazi, ovvero, se è    */
                        /* stato letto uno spazio...               */
			putchar(c); /* ...stampa il carattere (lo spazio)...   */
			continue;   /* ...e ripete il ciclo da capo (ignorando *
                         * le istruzioni successive)               */
		}

		c += 3;                   /* Incrementa il carattere di tre posizioni */
		if (c > 'z') c -= 26;  /* Se ha superato la 'z', lo riporta        *
                                   * all'inizio dell'alfabeto.                */

		printf("%c",c);           /* Stampa il carattere criptato             */
	}

	printf("\n");
}

Osservazioni

  • Anzitutto un'osservazione definitiva sul concetto di char in C: se ancora non fosse chiaro, una variabile char non è niente più che un numero, ed in particolare è il codice ASCII del carattere in questione; il fatto che venga visualizzata come un carattere è dovuto alla specifica %c, che provvede ad interpretare correttamente il numero contenuto nella variabile.

    Per questo motivo posso usare tranquillamente gli operatori matematici (linee 22 e 23) sui caratteri: scrivere c > 'z' è esattamente come scrivere c > 122 (122 è il codice ASCII per il carattere 'z').

    Come ulteriore conferma, provate a compilare l'istruzione printf("%c",122);

  • Esercizio: il programma per criptare da solo è poco utile, chi riesce a scrivere un programma che ri-trasformi il testo criptato nell'originale in chiaro? Sarebbe carino anche poter scegliere la chiave con cui si cripta il testo... (per solutori più che abili)

  • Quest'ultimo punto è dedicato al mondo Unix (Linux, Mac, BSD ecc...), infatti non riguarda strettamente il C, ma più che altro il terminale.

    Risolto l'esercizio precedente, abbiamo sviluppato un piccolo (ma completo) sistema di crittografia: possiamo usarlo per scambiare messaggi con altre persone, a cui daremo il software creato.

    Risulterebbe comodo poter salvare i messaggi criptati in files di testo, in modo da poterli scambiare più facilmente. Farlo direttamente in C non è difficile, ma non è argomento del corso base. Altrettanto semplice è però usare il terminale per farlo:

    [dario@localhost crittografia]$ ./cripta > messaggio.txt
    attaccare gli irriducibili galli.
    [dario@localhost crittografia]$ ls
    cripta  cripta.c  decripta messaggio.txt 

    Utilizzando la parentesi concava (./cripta > messaggio.txt) ho operato quella che si chiama redirezione dell'output, ovvero ho detto alla shell (il terminale) di mettere in un file (messaggio.txt) tutto quello che normalmente sarebbe finito sul video.

    Controlliamo che il file contenga effettivamente il messaggio, stampandone a video il contenuto:

    [dario@localhost crittografia]$ cat messaggio.txt 
    dwwdffduh jol luulgxflelol jdool.

    Ok! Attenzione però, per fare questo ho dovuto eliminare dal programma la prima printf(), altrimenti nel file sarebbero finite anche le istruzioni. Inoltre ho dovuto fare in modo che il programma stampi l'ultimo punto (al prossimo passo vedremo perchè).

    Il nostro socio, che avrà  a disposizione il programma decripta e il file messaggio.txt, dovrà  eseguire un'operazione simile, per decrittare il messaggio:

    [dario@localhost crittografia]$ ./decripta < messaggio.txt
    attaccare gli irriducibili galli

    Perfetto! Questa volta la redirezione dev'essere operata verso il programma: il contenuto del file sarà  letto dal programma come se fossero i caratteri realmente battuti dall'utente. Per questo è stato necessario mantenere il punto: altrimenti decripta non sarebbe riuscito a capire dove finiva la frase.

Direi che per il momento è tutto, il resto lo lascio alla vostra fantasia :)

  1. Introduzione
  2. Preparare l'ambiente
  3. Il primo programma
  4. La funzione printf()
  5. Le variabili
  6. Input da tastiera
  7. Esecuzione condizionale: if
  8. Cicli
  9. Esempi
  10. Bibliografia