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, booleano e indefinito. Le parole chiavi utilizzate per dichiarare variabili di questi tipi sono: char, int, float, double, bool e void. 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. La dimensione del dato contenibile nella variabile dipende dallo spazio riservato in memoria per ogni tipo di dichiarazione e dalla modalità di conservazione. Per poter conoscere la dimensione in byte, che il compilatore che si sta usando, utilizza per la conservazione in memoria di un dato di un certo tipo, si può usare la funzione sizeof():
#include <iostream.h> main(){ cout << "\nDimensione di un char " << sizeof(char); cout << "\nDimensione di un bool " << sizeof(bool); cout << "\nDimensione di un int " << sizeof(int); cout << "\nDimensione di un float " << sizeof(float); cout << "\nDimensione di un double " << sizeof(double); }
Il programma mostra a video la dimensione in byte di un dato del tipo specificato come parametro della sizeof.
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 qui 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 |
bool |
8 |
0 (falso) o 1 (vero) |
Indicatori del verificarsi di eventi |
int |
16 |
da -32.768 a +32.767 |
Contatori, piccoli numeri, controllo-cicli |
int |
32 |
da -2.147.483.648 a 2.147.483.647 |
Contatori, piccoli numeri, controllo-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:
I tipi signed (char, int, long) conservano in memoria dati utilizzando il metodo del complemento a 2
Il tipo float e il tipo double sono rappresentazioni floating-point: vengono comunemente indicati, rispettivamente, come numeri a singola precisione e a doppia precisione
Si noti che il tipo char viene utilizzato sia per conservare piccoli numeri che per conservare il set di caratteri, cioè dati apparentemente completamente diversi fra di loro dal punto di vista della elaborazione. La cosa è meno strana di quello che può sembrare a prima vista, basta riflettere sulla circostanza che, anche se si tratta di caratteri, la conservazione in memoria avviene associando ad ogni carattere una configurazione binaria e, quindi, un numero. Si può allora considerare la stessa configurazione come numero o come carattere corrispondente a quella configurazione binaria: la configurazione 0100.0001 (secondo per esempio la codifica ASCII) può essere il numero 65 come anche il carattere A. Tutto ciò può tornare utile per certi tipi di elaborazione sui caratteri.
La specificazione int, se accompagnata da un modificatore, può essere omessa. Si può quindi dichiarare una variabile indifferentemente long int o solo long
Le assegnazioni sul tipo bool si possono anche effettuare utilizzando le costanti simboliche true (al posto del valore 1) e false (che sostituisce il valore 0).
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 sarebbe indispensabile.
Nelle assegnazioni alle variabili di tipo char il carattere da assegnare deve essere racchiuso fra apici.
Per loutput dei dati di tipo float, il C++ prevede per default la visualizzazione di 6 cifre. Se si vuole visualizzare una quantità di cifre maggiori, occorre utilizzare la funzione di manipolazione precision del flusso cout. Di seguito si può vedere degli esempi di uso di tale funzione, intanto è bene precisare che questa riguarda il modo come un dato è visualizzato e non influisce sulla conservazione del dato in memoria.
... float prova; prova = 1234.56789; cout << prova; // visualizza 1234.57 cout.precision(8); cout << prova; // visualizza 1234.5679 cout.precision(3); // numero di cifre inferiore a quelle // della parte intera .. cout << prova; // .. visualizza la rappresentazione // normalizzata 1.23e+03 ...
Il modificatore ha effetto per le operazioni di output che lo seguono e finché non viene effettuata una ulteriore modifica.
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:
main(){ int uno, due; float tre; uno = 1; due = 2; tre = uno/due; cout << 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ì:
main(){ int uno, due; float tre; uno = 1; due = 2; tre = (float) uno/due; cout << 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).
Esaminiamo adesso un programma che trasforma un carattere minuscolo nella sua rappresentazione maiuscola. Il programma sfrutta, per ottenere il suo risultato, il fatto che una variabile di tipo char, in definitiva, conserva un codice numerico; qui viene inoltre usato il casting per mostrare tale codice. 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 <iostream.h> #define SCARTO 32 /*1*/ main(){ char min,mai; cout << \n Conversione minuscolo-maiuscolo; cout << \n Introduci un carattere minuscolo; cin >> min; /*2*/ if (min>=97 && min<=122) { /*3*/ mai = min-SCARTO; /*4*/ cout << \n Rappresentazione maiuscola << mai; /*5*/ cout << \n Codice ASCII << (int)mai; /*6*/ }else cout << \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.
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 (cosi è infatti definita la variabile), laddove nella riga 6 si effettua loutput del suo codice numerico (è usato un casting sulla variabile).
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++) cin >> 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 <iostream.h> main(){ int temp[8],temprif; /*1*/ int i,posiz; // Acquisizione delle temperature rilevate for (i=0;i<=7;i++) { cout << \nRilevazione temperatura n° << i << ; cin >> temp[i]; } cout << \nTemperatura da ricercare ; cin >> 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*/ cout << \nTemperatura non rilevata; else cout << \nTemperatura acquisita nella rilevazione << 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 rappresenti 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:
Nella 1 dichiarare un vettore per conservare le posizioni. Per esempio postemp[8]
Nella condizione di uscita del ciclo presente nella 3 occorre togliere il test su posiz. Qui infatti se si trova la temperatura cercata occorre esaminare tutto larray alla ricerca delle eventuali altre ricorrenze.
La riga 4 verrebbe modificata in: postemp[++posiz]=i; bisogna cioè conservare la posizione i nellarray postemp nella posizione successiva a quella indicata da posiz (si tenga presente che il valore iniziale è 1). Il contatore posiz va quindi prima incrementato e poi utilizzato come indice dellarray.
Naturalmente la struttura condizionale 5 viene modificata per permettere di mandare in output larray postemp. Si può utilizzare un ciclo a contatore simile a quello utilizzato per linput delle temperature: lindice i varierà fra 0 e posiz e il ciclo conterrà una printf per la stampa dellelemento dellarray postemp.
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:
il metodo get di cin
Per effettuare linput della stringa basta specificare, come oggetto della destinazione del canale di input cin, il nome della variabile:
char nome[30];
cin >> nome;
In questo caso però la stringa digitata in input non può contenere spazi. Il carattere spazio viene, infatti, interpretato come separatore e quindi linput è terminato. Per accettare una stringa in cui è presente il carattere spazio è necessario usare il metodo cin.get():
char nome[30];
cin.get(nome,30,\n);
In questo caso sono accettati un massimo di 29 caratteri (29 caratteri letti dal canale di input + 1 terminatore di stringa, cioè \0). In questo modo si possono limitare i caratteri acquisiti al numero previsto nella dichiarazione. Se ci sono un numero inferiore di caratteri, il parametro \n della cin.get garantisce la lettura di tutti i caratteri fino al carattere invio.
Occorre tenere presente che questo modo di comportarsi, può generare qualche effetto collaterale di cui bisogna tener conto:
La stringa introdotta da input potrebbe essere più lunga dei 30 (nellesempio esaminato) previsti. In tal caso resterebbero nello stream di input alcuni caratteri residui che andrebbero a finire nel prossimo input. Alla prossima istruzione di input il programma non si fermerebbe in attesa, trovandosi già nel canale linput previsto
Il carattere di \n, introdotto per chiudere linput, viene lasciato nel buffer di tastiera. Anche in questo caso il prossimo input sarebbe saltato
Per evitare gli inconvenienti prima citati basta, dopo aver effettuato linput, procedere allo svuotamento del canale. In tal modo il prossimo input può essere effettuato nella maniera attesa.
char nome[30];
cin.get(nome,30,\n);
while (cin.get() != \n);
Lultima istruzione effettua un ciclo di acquisizione di caratteri finché non è letto il carattere di terminazione di linea.
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.
strcat, strlen e strcmp
La 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:
Restituisce un valore positivo se vale s1>s2
Restituisce un valore negativo se vale s1<s2
Restituisce 0 se s1 ed s2 sono uguali
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:
Ci possono essere tante else if quante ne occorrono per lelaborazione richiesta. Per ogni elaborazione listruzione eseguita è solo quella soggetta alla condizione verificata
La clausola else finale, se presente (in quanto, come lequivalente nella if già vista in precedenza, può mancare) gestisce listruzione eseguita quando nessuna delle espressioni specificate è verificata.
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 <iostream.h> main(){ char espress[30]; /*1*/ int pargraf,parquad,partond; int c; cout << \nEspressione algebrica ; cin.get(espress,30,\n); /*2*/ while (cin.get() != \n); pargraf=parquad=partond=0; for(c=0;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++; } cout << \nGraffe = << pargraf; cout << \nQuadre = << parquad; cout << \nTonde = << 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. Se prima la stringa era 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 ciclo continuerà finché il carattere esaminato non sarà essere null.
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 <iostream.h> main(){ char espress[30]; int pargraf,parquad,partond; int c; cout << \nEspressione algebrica ; cin.get(espress,30,\n); while (cin.get() != \n); pargraf=parquad=partond=0; for(c=0;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; } } cout << \nGraffe = << pargraf; cout << \nQuadre = << parquad; cout << \nTonde = << 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 è 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 <iostream.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++) cout << tavpit[i][j] << ; /*5*/ cout << "\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.
Nella riga 6 si manda in output semplicemente un newline che consente di passare a capo quando si tratta di stampare su video una nuova riga della tabella. Loutput è infatti posizionato 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 <iostream.h> #include <string.h> /*1*/ main(){ char vocab[10][20], parcerc[20]; /*2*/ int i; bool trovata; // Acquisisce parole da inserire nel vocabolario for (i=0;i<=9;i++) { cout << "\nParola " << i; cin.get(vocab[i],20,\n); /*3*/ while (cin.get() != \n); } // Acquisisce la parola da cercare cout << "\n\nParola da cercare (Invio per finire) "; cin.get(parcerc,20,\n); while (cin.get() != \n); while (strcmp(parcerc,"")) { /*4*/ // Cerca la parola trovata=false; /*5*/ for (i=0;i<=9;i++) { if (!strcmp(parcerc,vocab[i])) { /*6*/ trovata=true; /*7*/ break; /*8*/ } } if (trovata) /*9*/ cout << "\nParola trovata"; else cout << "\nParola non trovata"; // Prossima parola da cercare cout << "\n\nParola da cercare (Invio per finire) "; cin.get(parcerc,20,\n); while (cin.get() != \n); } }
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à cin.get(vocab[0],..), quindi cin.get(vocab[1],..) e infine cin.get (vocab[9],..) in relazione ai diversi valori assunti dal contatore i. Luso di un solo indice dipende dal fatto che, con la cin.get, 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 cin >> 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 booleana trovata che viene inizializzata a false nella riga 5 registrerà leventuale ritrovamento della stringa allinterno del vocabolario. Una variabile usata in questo modo è 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 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.