Unità 2

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, 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:



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 l’output 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.

Inizio


Il costrutto cast


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 l’input 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 l’output di mai inteso come carattere (cosi è infatti definita la variabile), laddove nella riga 6 si effettua l’output del suo codice numerico (è usato un casting sulla variabile).

Inizio


Array a una dimensione


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 all’array come ad un insieme di cassetti numerati: per poter accedere al contenuto di un cassetto deve essere specificato il numero ad esso associato (l’indice).

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. Nell’array esiste un nome che però stavolta identifica l’array come struttura (il nome farà riferimento all’insieme dei cassetti dell’esempio precedente: questo concetto sarà chiarito meglio più avanti). I singoli elementi dell’array verranno referenziati specificando la loro posizione relativa all’interno della struttura (l’indice): l’elemento a cui ci si riferisce dipende dal valore assunto dall’indice.


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 l’insieme degli elementi cioè 10 numeri interi, numero[0], numero[5] individuano il primo ed il sesto numero della struttura. Se si utilizza numero[i], l’elemento 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 nell’array 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 l’uso della variabile conta: con una sola istruzione di input ripetuta il programma acquisisce valori da assegnare ai vari elementi dell’array. 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 dell’array 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 dell’array. Più avanti si esaminerà un modo diverso di uscita anticipata da un ciclo a contatore.

Se la temperatura ricercata è presente nell’array 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.


L’algoritmo sviluppato si basa sull’ipotesi 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:


Inizio


Stringhe


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 l’input 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 l’input è 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:


  1. La stringa introdotta da input potrebbe essere più lunga dei 30 (nell’esempio 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 l’input previsto

  2. Il carattere di \n, introdotto per chiudere l’input, 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 l’input, 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’);


L’ultima 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:



Per tenere conto mnemonicamente di tali valori, basta pensare al confronto come ad una sottrazione: fra l’altro 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

Inizio


La scelta multipla: istruzione else-if



Può essere necessario, nel corso di un programma, variare l’elaborazione 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 nell’ordine in cui sono scritte: se è vera la prima espressione si esegue l’istruzione specificata e poi si passa all’istruzione 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 <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 dall’input la stringa (cioè l’espressione algebrica da elaborare).

Nella riga con etichetta 3 c’è l’inizio 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 dell’array 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 nell’apposito contatore. Se il carattere esaminato non è una parentesi, mancando la clausola else, non viene preso in considerazione.

Inizio


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 l’elaborazione. Valutata l’espressione specificata, se il valore non coincide con alcuno di quelli specificati, viene eseguita l’istruzione 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 l’istruzione da eseguire a quella specificata nella case che verifica il valore cercato, occorre inserire l’istruzione break che fa proseguire l’elaborazione dall’istruzione 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 l’espressione da valutare: espress[c] cioè il carattere estratto dall’espressione 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é l’elaborazione continua da quel punto in poi, sia che si tratti di parentesi aperta che di parentesi chiusa si arriva all’aggiornamento del rispettivo contatore.

Nelle righe con etichetta 3 si blocca l’esecuzione 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 dell’elaborazione con l’istruzione successiva. L’ultima 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.

Inizio


Array a più dimensioni


Un array a più dimensioni è un insieme di elementi dello stesso tipo, ognuno referenziabile da un insieme di indici: tanti quante sono le dimensioni dell’array 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 (l’indice i viene utilizzato per identificare la riga esaminata della tabella). L’indice 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. L’uso 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 l’indice j varia da 1 a 10) . Si passa poi alla riga successiva e così via.

Nella riga con etichetta 4 si assegna il valore all’elemento 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 l’elemento 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. L’output è 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.

Inizio


Array di stringhe


Una applicazione interessante delle matrici è l’array 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 l’utilizzo all’interno del programma delle funzioni per il trattamento delle stringhe.

Nella riga 2 viene dichiarato l’array 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 l’ottavo 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. L’array parcerc[20] servirà per contenere la parola da cercare.

Nella riga 3 si acquisiscono le parole da inserire nel vocabolario: la prima volta l’istruzione 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. L’uso 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 nell’altro, così come nell’esempio della tavola pitagorica, contenenti l’istruzione 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 dall’input 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à l’eventuale ritrovamento della stringa all’interno 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. L’equivalenza 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 l’uscita 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 l’istruzione 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.

 


TORNA INDIETRO