Appunti su Programmazione e
linguaggio C a cura del prof. Nunzio Brugaletta |
Unità 2 |
tipi di dati e modificatori di tipo - il costrutto cast - array a una dimensione - stringhe - la scelta multipla: istruzione else if - la scelta multipla: istruzioni switch case e break - array a più dimensioni - array di stringhe
Unità 1 - Unità 2 - Unità 3 - Unità 4 - Unità 5 - Unità 6
Tipi di dati e modificatori di tipo
Negli esempi trattati fino a questo punto tutte le variabili sono state dichiarate di tipo int. È bene però chiarire che esistono anche altri tipi di dichiarazione delle variabili in modo che, le stesse, possano contenere valori di natura diversa. D'altra parte ciò è facilmente intuibile: basta pensare alle limitazioni dettate appunto dal tipo int. In realtà i vari linguaggi di programmazione permettono di definire le variabili in diversi modi; ciò in relazione al tipo di dato da contenere e alle esigenze specifiche del tipo di problemi cui si rivolge il linguaggio stesso.
Nel linguaggio C esistono cinque tipi di dati primari: carattere, intero, reale, doppia precisione e indefinito. Le parole chiavi utilizzate per dichiarare variabili di questi tipi sono: char, int, float, double e void. La dimensione del dato contenibile nella variabile dipende dallo spazio riservato in memoria per ogni tipo di dichiarazione e dalla modalità di conservazione.
Per quanto riguarda il tipo void (tipo indefinito), si tratterà di ciò in un secondo tempo. Per il momento l'attenzione sarà rivolta agli altri tipi.
I tipi di dati primari, eccetto il tipo void, ammettono dei modificatori di tipo. Tali modificatori (signed, unsigned, long, short) sono impiegati per adattare con maggiore precisione i tipi di dati fondamentali alle esigenze del programmatore.
Nella tabella successiva sono elencati le varie combinazioni più comunemente usate con un esempio di utilizzo (per i valori ammessi si fa quì riferimento alla implementazione del C nei PC):
Tipo | Dim. in bit | Valori ammessi | Utilizzo |
char | 8 | da -128 a 127 | Numeri piccoli e caratteri ASCII |
unsigned char | 8 | da 0 a 255 | Piccoli numeri e il set di caratteri del PC |
int | 16 | da -32.768 a +32.767 | Contatori, piccoli numeri, controllo-cicli |
unsigned int | 16 | da 0 a 65.535 | Numeri e cicli |
long int | 32 | da -2.147.483.648 a 2.147.483.647 | Grandi numeri |
float | 32 | da 3,4x10-38 a 3,4x1038 | Notazione Scientifica (7 cifre di precisione) |
double | 64 | da 1,7x10-308 a 1,7x10308 | Notazione Scientifica (15 cifre di precisione) |
È bene fare qualche osservazione sulla tabella:
Per ogni tipo è prevista dal C una stringa di controllo per l'output formattato della printf (per il tipo int, come si ricorderà, tale stringa è %d): per la conversione del tipo char la stringa %c, per quella di tipo float o double la stringa %f, %l sta per long e %u per unsigned, la stringa %e fa in modo che il valore della variabile mandata in output sia visualizzato con il formato esponenziale.
Nelle assegnazioni è spesso necessario specificare meglio il valore da assegnare:
..
long a;
double b;
char c;
..
a = 12L;
b = 15.0;
c = A;
Il valore 12 andrebbe pure in una variabile di tipo int, il suffisso L fa in modo che venga espanso in un long.
Nelle assegnazioni ai tipi in virgola mobile è necessario specificare sempre la parte decimale anche quando, come nel caso precedente, non sarabbe indispensabile.
Nelle assegnazioni alle variabili di tipo char il carattere da assegnare deve essere racchiuso fra apici.
Esaminiamo adesso un programma che trasforma un carattere minuscolo nella sua rappresentazione maiuscola. Le righe più significative sono commentate da etichette numeriche cui si farà riferimento poi per alcune osservazioni:
/*
Conversione di un carattere da minuscolo a maiuscolo
Vale per la codifica ASCII
*/
#include <stdio.h>
#define SCARTO 32 /*1*/
main(){
char min,mai;
printf("\n Conversione minuscolo-maiuscolo");
printf("\n Introduci un carattere minuscolo");
scanf("%c",&min); /*2*/
if (min>=97 && min<=122) { /*3*/
mai = min-SCARTO; /*4*/
printf("\n Rappresentazione maiuscola %c",mai); /*5*/
printf("\n Codice ASCII %d",mai); /*6*/
}else
printf("\n Carattere non convertibile");
}
Nella riga con etichetta 1 è definita la costante SCARTO. Il valore 32 dipende dal fatto che, nel codice ASCII, tale è la distanza fra le maiuscole e le minuscole (es. A ha codice 65, a ha codice 97).
Nella riga con etichetta 2 si effettua linput del carattere da elaborare. Si noti luso di %c per indicare che si tratta appunto di un carattere.
Nella riga con etichetta 3 si controlla, utilizzando la sua rappresentazione numerica, se il carattere immesso rientra nei limiti della codifica ASCII delle lettere minuscole. Le lettere minuscole, in ASCII, hanno codice compreso fra 97 (la lettera minuscola a) e 122 (la lettera minuscola z). Il confronto poteva anche essere fatto sulla rappresentazione alfanumerica:
if (min>=a && min<=z)
Nella riga con etichetta 4 si effettua in pratica la trasformazione in maiuscolo. Al codice numerico associato al carattere viene sottratto il valore 32. In questo caso è utilizzato il codice numerico del carattere. Si noti che in questo contesto ha senso assegnare ad un char il risultato di una sottrazione (operazione numerica).
Nella riga con etichetta 5 si effettua loutput di mai inteso come carattere (è usato %c), laddove nella riga 6 si effettua loutput del suo codice numerico (è usato %d).
Le direttive di conversione possono avere varianti che specificano l'ampiezza dei campi, il numero di cifre decimali e un indicatore per l'allineamento del margine sinistro.
Un intero posto fra il segno di percentuale e il carattere di conversione indica l'ampiezza minima del campo (vengono aggiunti spazi per assicurare che questa ampiezza sia garantita, se, invece degli spazi si voglio tanti zeri, basta aggiungere uno zero prima dello specificatore di ampiezza). La direttiva %05d, per esempio, usa zeri come caratteri riempitivi in modo da portare la lunghezza totale a cinque caratteri. Per specificare il numero di cifre decimali di un numero in virgola mobile, si scrive, dopo lo specificatore di ampiezza, un punto seguito dal numero di cifre che si vuole siano stampate. La direttiva %10.4f, per esempio, stampa un numero di non meno di 10 cifre con 4 cifre dopo la virgola. Si può inoltre fare in modo che la stampa sia allineata a sinistra (per default lo è a destra) ponendo un segno meno: %-10.2f.
A volte può interessare effettuare dei cambiamenti al volo per conservare i risultati di determinate operazioni in variabili di tipo diverso principalmente quando si va da un tipo meno capiente ad un tipo più capiente. In tali casi il linguaggio C mette a disposizione del programmatore un costrutto chiamato cast.
Es:
int uno, due;
float tre;
main() {
uno = 1;
due = 2;
tre = uno/due;
printf("%f",tre);
}
Tale programma nonostante le aspettative (dettate dal fatto che la variabile tre è dichiarata float) produrrà un risultato nullo. Infatti la divisione viene effettuata su due valori di tipo int, il risultato viene conservato temporaneamente in una variabile di tipo int e, solo alla fine, conservato in una variabile float. È evidente, a questo punto, che la variabile tre conterrà solo la parte intera (cioè 0).
Affinchè la divisione produca il risultato atteso, è necessario avvisare il C di convertire il risultato intermedio prima della conservazione definitiva nella variabile di destinazione.
Tutto ciò è possibile utilizzando il costrutto in questione che ha la seguente sintassi:
(nome-di-tipo) espressione
Utilizzando questo costrutto, il nostro programma, si scriverebbe così:
int uno, due;
float tre;
main() {
uno = 1;
due = 2;
tre = (float) uno/due;
printf("%f",tre);
}
In questo caso il programma fornisce il risultato atteso. Infatti il quoziente viene calcolato come float e quindi, dopo, assegnato alla variabile tre.
In definitiva il costrutto cast forza una espressione a essere di un tipo specifico (nel nostro caso una divisione intera viene forzata a fornire un risultato di tipo virgola mobile).
L' array (in italiano conosciuto anche come vettore), è la prima struttura di dati trattata in Informatica. È quella più comunemente usata ed è quella che sta a fondamento di quasi tutte le altre.
Un array è un insieme di variabili dello stesso tipo cui è possibile accedere tramite un nome comune e referenziare uno specifico elemento tramite un indice. Possiamo pensare allarray come ad un insieme di cassetti numerati: per poter accedere al contenuto di un cassetto deve essere specificato il numero ad esso associato (lindice).
Nelle variabili semplici per accedere al valore contenuto in esse è necessario specificare il nome ed, inoltre, una variabile con un valore diverso avrà un nome diverso. Nellarray esiste un nome che però stavolta identifica larray come struttura (il nome farà riferimento allinsieme dei cassetti dellesempio precedente: questo concetto sarà chiarito meglio più avanti). I singoli elementi dellarray verranno referenziati specificando la loro posizione relativa allinterno della struttura (lindice): lelemento a cui ci si riferisce dipende dal valore assunto dallindice.
Gli elementi dell'array vengono allocati in posizioni di memoria adiacenti e, per tale motivo, è necessario dichiarare un array prima del suo uso in modo che il compilatore possa riservare in memoria lo spazio necessario per conservarlo.
Il formato generale della dichiarazione di un array a una dimensione è:
tipo nome_variabile[dimensione];
dove tipo è il tipo base dell'array, ovvero il tipo di ogni elemento dell'array; e il numero di elementi dell'array è definito da dimensione.
Il seguente esempio dichiara un array chiamato numero contenente un massimo di 10 elementi interi:
int numero[10];
Poiché il C assegna gli indici agli elementi di un array a partire da 0, gli elementi dell'array di interi appena dichiarato vanno da numero[0] a numero[9]. Quindi, per quanto osservato precedentemente: numero è il nome della struttura, identifica linsieme degli elementi cioè 10 numeri interi, numero[0], numero[5] individuano il primo ed il sesto numero della struttura. Se si utilizza numero[i], lelemento a cui ci si riferisce dipende dal valore assunto dalla variabile i che deve essere di tipo int.
Essendo poco abituati a concepire un elemento in posizione zero potrebbe essere opportuno, almeno inizialmente, sistemare gli elementi nellarray a cominciare dalla posizione 1: si sprecherà memoria riservata ad una posizione non utilizzata, ma i programmi potrebbero risultare più leggibili.
È bene ricordare che, seguendo tale convenzione, un array dichiarato contenente un massimo teorico di 10 elementi, può conservare gli stessi dalla posizione 1 alla posizione 9 (di fatto non potrà contenere più di nove elementi).
Il programma di esempio carica il nostro array con numeri provenienti dall'input:
main() {
int numero[10], conta;
for (conta=1;conta<=9;conta++)
scanf("%d",&numero[conta]);
}
Si noti nel programma di esempio luso della variabile conta: con una sola istruzione di input ripetuta il programma acquisisce valori da assegnare ai vari elementi dellarray. Il primo input viene effettuato su numero[1] (conta la prima volta vale 1), il secondo viene effettuato su numero[2] (conta vale 2) e così via fino al nono.
Questo tipo di array vengono detti ad una dimensione perchè per individuare un elemento all'interno della struttura, basta specificare un solo indice.
Il linguaggio C non esegue una verifica sui limiti dell'array, pertanto esiste la possibilità di superarli. Se ciò si verifica, per esempio in un assegnamento, si corre il rischio di alterare un'altra variabile o un pezzo di codice del programma. In altri termini si può utilizzare un array di dimensione n oltre i suoi limiti senza che vengano segnalati errori di compilazione o di esecuzione, ma con il risultato che probabilmente sarà il programma a mostrare dei malfunzionamenti.
Come caso di applicazione di un array numerico consideriamo il seguente esempio: le temperature rilevate nel corso della giornata in una determinata località sono conservate in un vettore, si richiede di scrivere un programma che, acquisita una determinata temperatura, determini se nella località considerata si è avuta nel corso della giornata tale temperatura e, in caso positivo, in quale rilevazione è registrata tale temperatura.
In termini informatici si tratta di effettuare una scansione sequenziale di un vettore per verificare se tale vettore contiene un determinato valore e, in caso positivo, se ne vuole conoscere la posizione.
Nelle righe più interessanti è presente un commento numerico per le osservazioni successive.
/* Ricerca un elemento in vettore e restituisce la posizione */
#include <stdio.h>
main(){
int temp[8],temprif; /*1*/
int i,posiz;
/* Acquisizione delle temperature rilevate */
for (i=0;i<=7;i++) {
printf("\nRilevazione temperatura n° %d ",i);
scanf("%d",&temp[i]);
}
printf("\nTemperatura da ricercare ");
scanf("%d",&temprif);
/* Scansione del vettore delle temperature */
posiz = -1; /*2*/
for (i=0;i<=7 && posiz==-1;i++) { /*3*/
if (temprif==temp[i])
posiz = i; /*4*/
}
/* Risultati della ricerca */
if (posiz == -1) /*5*/
printf("\nTemperatura non rilevata");
else
printf("\nTemperatura acquisita nella rilevazione %d",posiz);
}
Nella riga 1 si dichiara un array di 8 interi per conservare le rilevazioni delle temperature e una variabile per conservare la temperatura da cercare.
Nella riga 2 si inizializza una variabile che conterrà la posizione nella quale si troverà la temperatura cercata. Il valore 1 dipende dal fatto che le posizioni ammesse vanno dal valore 0 in su e, quindi, per indicare un valore che non indichi una posizione valida occorrerà utilizzare per esempio un numero negativo.
Nella riga 3 inizia il ciclo di scansione dellarray alla ricerca della temperatura di riferimento. Il controllo di fine ciclo si basa su due eventi che devono verificarsi contemporaneamente: elementi non finiti (i<=7) e temperatura non trovata (posiz==-1). Se infatti la temperatura cercata viene ritrovata è inutile continuare ancora ad esaminare gli altri elementi dellarray. Più avanti si esaminerà un modo diverso di uscita anticipata da un ciclo a contatore.
Se la temperatura ricercata è presente nellarray allora, nella riga 4, se ne conserva la posizione nella varabile posiz.
La riga 5 seleziona il caso temperatura non trovata (volere di posiz non modificato) dalla rilevazione ritrovata.
Lalgoritmo sviluppato si basa sullipotesi che, se cè, la temperatura ricercata si presenta una sola volta. Per togliere tale limitazione è necessario predisporre un vettore per contenere le posizioni delle varie ricorrenze. Le modifiche da apportare al programma saranno:
In C uno degli utilizzi degli array ad una dimensione riguarda la conservazione in memoria di stringhe di caratteri: queste in C sono array di tipo carattere la cui fine è segnalata da un carattere null (carattere terminatore), indicato come '\0'.
Il carattere null è il primo codice ASCII corrispondente al valore binario 00000000 e non ha niente a che vedere con il carattere 0 che ha, in ASCII, codice binario 00110000.
char a[10];
dichiara un vettore costituito da un massimo di dieci caratteri e:
char frase[] = "Oggi c'è il sole";
dichiara l'array monodimensionale frase il cui numero di caratteri è determinato dalla quantità di caratteri presenti fra doppi apici più uno (il carattere null che chiude la stringa).
È importante notare la differenza tra le due assegnazioni:
char a = 'r';
char b[] = "r";
Nel primo caso viene assegnato il solo carattere r, nel secondo la stringa r\0. In definitiva: se si vuole fare riferimento ad un solo carattere, questo deve essere racchiuso fra apici singoli; se, invece, si vuole fare riferimento ad una stringa, occorre racchiuderla fra doppi apici.
Sono disponibili parecchie funzioni specifiche per il trattamento delle stringhe nonostante il linguaggio C non possieda un dato di tipo stringa:
gets
e putsLe funzioni gets e puts sono funzioni specializzate nell'input e output di una stringa. Il programma seguente riceve in input una stringa e, subito dopo, la rimanda in output. Notare che in queste istruzioni si fa riferimento alla stringa nel suo complesso e, quindi, viene utilizzato il nome:
#include <stdio.h>
main() {
char s[80]; /* dichiara la stringa */
gets(s); /* riceve l'input */
puts(s); /* fornisce l'output */
}
Altre funzioni per il trattamento delle stringhe sono disponibili, per usi particolari, a patto che si includa nel programma la linea: #include <string.h>.
strcpy
Questa funzione copia una stringa in un'altra. Può essere utilizzata, per esempio, per assegnare un valore a una stringa. La sua sintassi è:
strcpy(s1,s2);
Si può immaginare che tale funzione equivale, come effetto, all'assegnamento nelle variabili di altro tipo (in altri termini è come se si scrivesse: s1=s2 assegna, cioè, alla stringa s1 il valore contenuto in s2. In realtà, come si vedrà più avanti, tale istruzione è valida ma assume altro significato).
strcat
, strlen e strcmpLa funzione:
strcat(s1,s2);
concatena la stringa s2 alla fine della stringa s1:
strcpy(s1,"buon"); /* stringa specificata in s1 */
strcpy(s2,"giorno"); /* lo stesso per s2 */
strcat(s1,s2); /* s1 conterrà "buongiorno" */
La funzione:
strlen(s1);
restituisce un valore numerico che rappresenta la lunghezza della stringa s1.
La funzione:
strcmp(s1,s2);
può essere utilizzata per effettuare comparazioni sul contenuto di due stringhe. In particolare tale funzione:
Per tenere conto mnemonicamente di tali valori, basta pensare al confronto come ad una sottrazione: fra laltro ciò è non è molto distante da quello che avviene in effetti. Se da s1 si sottrae s2 si avrà un valore positivo nel caso s1>s2, negativo se s1<s2 e nullo se sono uguali
La scelta multipla: istruzione else-if
Può essere necessario, nel corso di un programma, variare lelaborazione in seguito a più condizioni. In questi casi il linguaggio C fornisce la struttura:
if(espressione)
istruzione
else if(espressione)
istruzione
..
else
istruzione
In questo caso le espressioni vengono valutate nellordine in cui sono scritte: se è vera la prima espressione si esegue listruzione specificata e poi si passa allistruzione successiva alla chiusura della struttura. Se la prima condizione non è vera si passa ad esaminare la seconda e così via. Si tenga presente che:
Al fine di mostrare un esempio di alcune delle istruzioni esposte ultimamente scriviamo un programma che, data una espressione algebrica, calcoli quante parentesi graffe, quadre e tonde sono contenute nella espressione stessa. Le righe più significative sono etichettate per permettere un commento successivo:
/*
Conta i vari tipi di parentesi contenute in una espressione algebrica
(non fa distinzione fra parentesi aperte e chiuse)
*/
#include <stdio.h>
main()
{
char espress[30]; /*1*/
int pargraf,parquad,partond;
int c;
printf("\nEspressione algebrica ");
gets(espress); /*2*/
pargraf=parquad=partond=0;
for(c=0;c<=29 && espress[c]!=\0;c++) { /*3*/
if(espress[c]=={ || espress[c]==}) /*4*/
pargraf++;
else if(espress[c]==[ || espress[c]==]) /*4*/
parquad++;
else if(espress[c]==( || espress[c]==)) /*4*/
partond++;
}
printf("\nGraffe = %d",pargraf);
printf("\nQuadre = %d",parquad);
printf("\nTonde = %d",partond);
}
Nella riga con etichetta 1 si dichiara un vettore di tipo char e di lunghezza massima di 30 caratteri, cioè una stringa di 30 caratteri.
Nella riga con etichetta 2 si acquisisce dallinput la stringa (cioè lespressione algebrica da elaborare).
Nella riga con etichetta 3 cè linizio di un ciclo a contatore che servirà per la scansione della stringa: i caratteri verranno esaminati uno per volta per verificare se sono parentesi. Nella gets la stringa è vista nel suo complesso, qui si esaminano i singoli elementi dellarray cioè i caratteri che compongono la stringa. Si noti il controllo di fine ciclo: il contatore del ciclo, che qui assume il compito di indice del vettore, non può superare 29 (larray ha 30 elementi al massimo) ed inoltre il carattere esaminato non può essere null (qualora lo fosse, la stringa sarebbe terminata). Le due condizioni devono essere verificate contemporaneamente: se una delle due non è verificata (superamento dei limiti fissati o fine stringa) lelaborazione passa allistruzione immediatamente dopo la chiusura del ciclo: la printf dopo la chiusura della parentesi (qui le parentesi sono state aggiunte per aumentare la leggibilità infatti, contenendo il ciclo una sola istruzione, non sarebbero indispensabili).
Nelle righe etichettate con 4 si esaminano i vari casi possibili (i tre tipi di parentesi). Il carattere esaminato può essere indifferentemente la parentesi aperta o quella chiusa e, a seconda del tipo, viene conteggiato nellapposito contatore. Se il carattere esaminato non è una parentesi, mancando la clausola else, non viene preso in considerazione.
La scelta multipla: istruzioni switch-case e break
Esiste nel linguaggio C un'altra istruzione per codificare la scelta multipla. È necessario fare attenzione però alle diversità di comportamento. Sintatticamente la struttura si presenta in questo modo:
switch(espressione)
case valore:
istruzione
case valore:
istruzione
..
default:
istruzione
In pratica in questo caso vengono valutati i diversi valori che può assumere una espressione. Nelle varie istruzioni case si elencano i valori che può assumere espressione e che interessano per lelaborazione. Valutata lespressione specificata, se il valore non coincide con alcuno di quelli specificati, viene eseguita listruzione compresa nella clausola default. Occorre tenere presente che, sostanzialmente, i vari case esistenti agiscono da etichette: se espressione assume un valore specificato in un case, vengono eseguite le istruzioni a partire da quel punto in poi. Il valore specificato in case, in definitiva, assume funzione di punto di ingresso nella struttura.
Se si vuole delimitare listruzione da eseguire a quella specificata nella case che verifica il valore cercato, occorre inserire listruzione break che fa proseguire lelaborazione dallistruzione successiva alla chiusura della struttura.
Per chiarire meglio il funzionamento della struttura riscriviamo il programma del conteggio delle parentesi di una espressione algebrica facendo uso della nuova struttura.
Conta i vari tipi di parentesi contenute in una espressione algebrica
(non fa distinzione fra parentesi aperte e chiuse)
*/
#include <stdio.h>
main()
{
char espress[30];
int pargraf,parquad,partond;
int c;
printf("\nEspressione algebrica ");
gets(espress);
pargraf=parquad=partond=0;
for(c=0;c<=29 && espress[c]!=\0;c++) {
switch(espress[c]) { /*1*/
case {:; case }: /*2*/
pargraf++;
break; /*3*/
case [:; case ]: /*2*/
parquad++;
break; /*3*/
case (:; case ): /*2*/
partond++;
break;
}
}
printf("\nGraffe = %d",pargraf);
printf("\nQuadre = %d",parquad);
printf("\nTonde = %d",partond);
}
Nella riga con etichetta 1 viene specificata lespressione da valutare: espress[c] cioè il carattere estratto dallespressione algebrica.
Nelle righe con etichetta 2 si esaminano i casi parentesi aperta o parentesi chiusa. I singoli valori sono seguiti dalla istruzione nulla (il solo carattere ;) e, poiché lelaborazione continua da quel punto in poi, sia che si tratti di parentesi aperta che di parentesi chiusa si arriva allaggiornamento del rispettivo contatore.
Nelle righe con etichetta 3 si blocca lesecuzione del programma altrimenti, per esempio, una parentesi graffa oltre che come graffa verrebbe conteggiata anche come quadra e tonda, una parentesi quadra verrebbe conteggiata anche come tonda. Si noti che, anche se sono presenti due istruzioni, non vengono utilizzate parentesi per delimitare il blocco: il funzionamento della switch-case prevede infatti la continuazione dellelaborazione con listruzione successiva. Lultima istruzione break è inserita solo per coerenza con gli altri casi. Inoltre se in seguito si dovesse aggiungere una istruzione default, il programma continuerebbe a dare risultati coerenti senza necessità di interventi se non nella parte da inserire.
Un array a più dimensioni è un insieme di elementi dello stesso tipo, ognuno referenziabile da un insieme di indici: tanti quante sono le dimensioni dellarray stesso. Se si ha un array bidimensionale, per esempio, occorrono due indici per identificare un elemento della struttura.
Il formato generale della dichiarazione di un array a più dimensioni è:
tipo nome_variabile[dimensione] [dimensione] [dimensione]..
Gli array bidimensionali (matrici), che sono quelli più comunemente utilizzati, possono essere pensati come un insieme di elementi posizionati in una tabella: il primo indice identifica la riga, il secondo la colonna. Un elemento viene identificato specificando la riga e la colonna in cui si trova.
Come esempio di uso di un array a due dimensioni scriviamo un programma che costruisce e poi visualizza la tavola pitagorica. Le righe più significative sono, al solito, corredate da etichette numeriche che serviranno per i commenti successivi:
/* Stampa su video la tavola pitagorica */
#include <stdio.h>
main(){
int tavpit[11][11]; /*1*/
int i,j;
/* Costruisce la tavola */
for (i=1;i<=10;i++){ /*2*/
for (j=1;j<=10;j++){ /*3*/
tavpit[i][j]=i*j; /*4*/
}
}
/* Manda in output la tavola */
for (i=1;i<=10;i++) {
for (j=1;j<=10;j++)
printf("%3d ",tavpit[i][j]); /*5*/
printf("\n"); /*6*/
}
}
Nella riga con etichetta 1 si dichiara una matrice di 11 righe ed 11 colonne.
Nella riga con etichetta 2 si inizia il ciclo per la scansione delle righe della tabella (lindice i viene utilizzato per identificare la riga esaminata della tabella). Lindice viene inizializzato ad 1 non avendo interesse a visualizzare la riga formata da tutti valori nulli.
Nella riga con etichetta 3 si inizia un nuovo ciclo, interno al primo, per la scansione delle colonne. Luso delle parentesi mette in evidenza il funzionamento di questo doppio ciclo: si esamina la prima riga (i=1) e quindi le varie colonne di quella riga (per i=1 lindice J varia da 1 a 10) . Si passa poi alla riga successiva e così via.
Nella riga con etichetta 4 si assegna il valore allelemento della tabella che sta nella riga i e nella colonna j: nel nostro caso sarà equivalente al prodotto fra i due indici.
Nella riga con etichetta 5 viene mandato in output lelemento generico. La stringa di formattazione delloutput consente lallineamento e lincolonnamento dei numeri della tabella.
La printf della riga 6 manda in output semplicemente un newline che consente di passare a capo quando si tratta di stampare su video una nuova riga della tabella. La printf è infatti posizionata alla fine del ciclo controllato da j (quello che stampa le colonne di una riga), quindi dopo la stampa di tutte le colonne di una riga, e prima della chiusura del ciclo controllato da i (quello che stampa le righe) quindi prima di passare alla prossima riga.
Una applicazione interessante delle matrici è larray di stringhe. La stringa è un array di caratteri e quindi potremmo dire che un array di stringhe è un array di array di caratteri. Per chiarire la gestione di questo tipo di array esaminiamo un programma che si occupa di cercare delle parole in un vocabolario e ci dica, per ognuna, se è contenuta nel vocabolario stesso
/* Cerca parole in un vocabolario */#include <stdio.h>
#include <string.h> /*1*/
main(){
char vocab[10][20], parcerc[20]; /*2*/
int i,trovata;
/* Acquisisce parole da inserire nel vocabolario */
for (i=0;i<=9;i++) {
printf("\nParola %d ",i);
gets(vocab[i]); /*3*/
}
/* Acquisisce la parola da cercare */
printf("\n\nParola da cercare (Invio per finire) ");
gets(parcerc);
while (strcmp(parcerc,"")) { /*4*/
/* Cerca la parola */
trovata=0; /*5*/
for (i=0;i<=9;i++) {
if (!strcmp(parcerc,vocab[i])) { /*6*/
trovata=1; /*7*/
break; /*8*/
}
}
if (trovata) /*9*/
printf("\nParola trovata");
else
printf("\nParola non trovata");
/* Prossima parola da cercare */
printf("\n\nParola da cercare (Invio per finire) ");
gets(parcerc);
}
}
La dichiarazione della riga 1 permette lutilizzo allinterno del programma delle funzioni per il trattamento delle stringhe.
Nella riga 2 viene dichiarato larray di caratteri vocab[10][20]. Il nostro vocabolario conterrà quindi un massimo di 10 parole ognuna composta di un massimo di 20 caratteri. La prima parola è contenuta in vocab[0] e con vocab[2][7], qualora dovesse interessare, si individuerebbe lottavo carattere della terza parola del vocabolario. Ogni riga della tabella conterrà una parola e in ogni colonna ci sono i caratteri che compongono le parole. Larray parcerc[20] servirà per contenere la parola da cercare.
Nella riga 3 si acquisiscono le parole da inserire nel vocabolario: la prima volta listruzione si leggerà gets(vocab[0]), quindi gets(vocab[1]) e infine gets(vocab[9]) in relazione ai diversi valori assunti dal contatore i. Luso di un solo indice dipende dal fatto che, con la gets, si acquisisce una stringa e quindi nel nostro caso una intera riga della matrice. Se avessimo voluto acquisire le stringhe un carattere per volta, si sarebbero dovuti gestire due cicli uno annidato nellaltro, così come nellesempio della tavola pitagorica, contenenti listruzione scanf("%c",&vocab[i][j]). Alla fine avremmo dovuto aggiungere il carattere null.
Nella riga 4 si gestisce la condizione di uscita dal ciclo. La stringa ricevuta dallinput viene comparata con la stringa vuota (due doppi apici consecutivi): se alla richiesta di input della stringa da cercare si risponde premendo solamente il tasto Invio, viene acquisita una stringa contenente solo il carattere terminatore null. Ciò rende falsa la condizione e quindi il ciclo ha termine (strcmp in questo caso restituisce 0).
La variabile trovata che viene azzerata nella riga 5 registrerà leventuale ritrovamento della stringa allinterno del vocabolario. Il valore assegnato non ha il senso di un valore numerico a tutti gli effetti ma il senso di un valore logico (0 = falso, un valore non nullo=vero). Una variabile usata in questo modo viene chiamata solitamente variabile logica, switch o flag.
Nella 6 si confronta la parola da cercare via via con le parole contenute nel vocabolario. Lequivalenza delle due stringhe, quella da cercare e la parola del vocabolario esaminata, rende vera la condizione. In questo caso, riga 7, si rende vera la variabile trovata assegnandole un valore non nullo e con la break della riga 8 si forza luscita dal ciclo poiché è inutile continuare a confrontare la parola cercata con il resto delle parole del vocabolario: la ricerca viene sospesa.
Nella riga 9 si esegue un test sul flag trovata: se è vero (cioè è stata eseguita listruzione della riga 7) la parola è stata trovata, se è falso (è rimasto inalterato il valore assegnato nella istruzione della riga 5) la ricerca ha avuto esito negativo.