INDICE C++ 1.01 - Alcuni semplici programmi in C++ - Ingresso e uscita di informazioni (1-2-3-4) - Istruzioni condizionali (5-6-7-8) - Il ciclo for (9-10) - Il ciclo while (11-12A-12B-12C) - Gli array (13-14-15-16-17) - Le funzioni (18-19-20-21-22-23-24-25-26) 1.02 - 1.03 - 1.04 - 1.05 -
1.01 - Alcuni semplici programmi in C++ 1. QUESTO PROGRAMMA MOLTIPLICA 7 PER UN NUMERO QUALSIASI FORNITO IN LETTURA ATTRAVERSO LA TASTIERA E SOMMA 10 AL RISULTATO DEL PRODOTTO #include <stdio.h> /* Questa riga indica che una o più istruzioni che compariranno in seguito, si possono trovare nel file stdio.h */ #include <stdlib.h> main() { int x,y; printf(" QUESTO PROGRAMMA MOLTIPLICA 7 PER UN NUMERO QUALSIASI"); printf("\n FORNITO IN LETTURA ATTRAVERSO LA TASTIERA E SOMMA 10 AL"); printf("\n RISULTATO DEL PRODOTTO."); printf("\n\n\n\n dammi un numero x a tua scelta"); printf(" e poi premi il tasto \n\n"); printf(" x="); scanf("%d",&x); /* scanf aspetta che io fornisca il valore della variabile x in formato intero (%d) */ y=7*x+10;printf("\n"); printf(" *******************"); printf("\n *ECCO IL RISULTATO* "); printf("\n *******************"); printf("\n\n\n"); printf(" ******************************"); printf("\n * *"); printf("\n * *"); printf("\n * 7x%d+10=%d *",x,y); /* Vado a capo e stampo la stringa "7x[valore x]+10=[valore y] */ printf("\n * *"); printf("\n * *"); printf("\n ******************************\n\n\n"); system ("PAUSE"); /* Questa istruzione serve a fissare lo schermo nella situazione attuale in modo che noi possiamo leggervi il risultato */ return 0; /* Questa istruzione serve a terminare il programma e a restituire il controllo al sistema operativo del computer */ }
2. QUESTO PROGRAMMA MOLTIPLICA DUE NUMERI QUALSIASI a ED x FORNITI IN LETTURA ATTRAVERSO LA TASTIERA E SOMMA AL RISULTATO DEL PRODOTTO UN TERZO NUMERO b FORNITO IN LETTURA ATTRAVERSO LA TASTIERA #include <stdio.h> #include <stdlib.h> main() { int a,b,x,y; printf("\n\n\n\n a="); scanf("%d",&a); printf("\n x="); scanf("%d",&x); printf("\n b="); scanf("%d",&b); y=a*x+b; printf("\n %dx%d+%d=%d\n\n\n\n",a,x,b,y); system ("PAUSE"); return 0; }
3. QUESTO PROGRAMMA SCAMBIA I VALORI DI DUE VARIABILI IN MODO SBAGLIATO #include <stdio.h> #include <stdlib.h> main() { int a,b; printf("\n valori di a e b prima dello scambio"); printf( "\n\n a="); scanf("%d",&a); printf( "\n b="); scanf("%d",&b); a=b;b=a; // ECCO DOVE STA LO SBAGLIO → Sotto la spiegazione printf("\n valori di a e b dopo lo scambio"); printf("\n\n a=%d b=%d\n\n",a,b); system ("PAUSE"); return 0; } Supponiamo di avere assegnato a=1 e b=2; lo scambio dovrebbe avvenire mediante le due istruzioni a=b;b=a; tuttavia la prima delle due, cioè a=b, ha per effetto che il valore di b (cioè 2) viene trascritto in a e quindi a questo punto sia a che b hanno il valore 2 e quando viene eseguita la seconda istruzione b=a in b viene trascritto il valore attuale di a cioè 2; il risultato finale sarà che comparirà sullo schermo la scritta a=2 b=2 che non è assolutamente ciò che avremmo voluto vedervi comparire. A nulla vale tentare di scambiare l’ordine delle due istruzioni, modificando la riga in: a=b;b=a;
4. QUESTO PROGRAMMA SCAMBIA I VALORI DI DUE VARIABILI IN MODO CORRETTO #include <stdio.h> #include <stdlib.h> main() { int a,b,appo; printf("\n valori di a e b prima dello scambio"); printf( "\n\n a="); scanf("%d",&a); printf( "\n b="); scanf("%d",&b); appo=a;a=b;b=appo; printf("\n valori di a e b dopo lo scambio"); printf("\n\n a=%d b=%d\n\n",a,b); system ("PAUSE"); return 0; }
Istruzioni condizionali 5. QUESTO PROGRAMMA DETERMINA IL MAGGIORE FRA DUE NUMERI a E b #include <stdio.h> #include <stdlib.h> main() { int a,b,max; printf("\n\n primo numero a="); scanf("%d",&a); // LEGGO LA VARIABILE a printf("\n secondo numero b="); scanf("%d",&b); // LEGGO LA VARIABILE b max=a; if (b>max) max=b; // VEDI SOTTO LA SPIEGAZIONE DI QUESTE ISTRUZIONI printf("\n\n il massimo fra %d e %d è: %d",a,b,max); // LA "è" NON VIENE STAMPATA POICHE' NON CODIFICATA system ("PAUSE"); return 0; } La strategia per decidere quale è più grande fra a e b è la seguente: inizialmente mettiamo in max il valore di a, dopo di che, se il valore di b è maggiore del valore di max (e quindi del valore di a) allora cambiamo il valore di max con il valore di b, altrimenti lasciamo le cose come stanno. Il risultato è che alla fine in max ci sarà sicuramente il più grande fra i valori di a e b.
6. QUESTO PROGRAMMA DETERMINA IL MAGGIORE FRA QUATTRO NUMERI a,b,c E d #include <stdio.h> #include <stdlib.h> main() { int a,b,c,d,max; printf("\n primo numero a="); scanf("%d",&a); printf("\n secondo numero b="); scanf("%d",&b); printf("\n terzo numero c="); scanf("%d",&c); printf("\n quarto numero d="); scanf("%d",&d); max=a; if (b>max) max=b;if (c>max) max=c; if (d>max) max=d; printf("\n il massimo fra %d,%d,%d e %d --> MAX: %d\n\n",a,b,c,d,max); system ("PAUSE"); return 0; }
Creaiamo ora programma che calcola il discriminante di un polinomio (L'informatica applicata a problemi di matematica)
In una equazione di secondo grado del tipo ax2+bx+c=0 con coefficienti reali a, b e c, possiede due radici reali distinte se il discriminante: D=b2-4ac è positivo, altrimenti possiede una unica radice reale se il discriminante è nullo e infine possiede due radici complesse coniugate se il discriminante è negativo. Ecco un semplice programma in cui sfruttiamo di nuovo il costrutto if per decidere se una data equazione di secondo grado possiede due radici reali distinte oppure no. 7. QUESTO PROGRAMMA LEGGE I COEFFICIENTI a,b,c DI UN POLINOMIO DI SECONDO GRADO, NE CALCOLA IL DISCRIMINANTE E, SE QUESTO E' POSITIVO, SEGNALA CHE ESISTONO DUE RADICI REALI DISTINTE #include <stdio.h> #include <stdlib.h> main() { float a,b,c,delta; // VARIABILI REALI printf( "\n primo coefficiente a="); scanf("%f",&a); // ESSENDO VARIABILi REALI USIAMO IL FORMATO %f printf( "\n secondo coefficiente b="); scanf("%f",&b); printf( "\n terzo coefficiente c="); scanf("%f",&c); printf("\n"); delta=b*b-4*a*c; if (delta>0) // CONDIZIONE 1 printf(" ci sono due radici reali distinte\n"); else if (delta==0) // CONDIZIONE 2, CON == POICHE' IL SIMBOLO =, IN C++ È UN OPERATORE DI ASSEGNAZIONE E NON UN SIMBOLO DI RELAZIONE printf(" c'è una unica radice reale\n"); else printf(" non ci sono radici reali\n"); system ("PAUSE"); return 0; }
Non è difficile capire che, se noi annidiamo due volte il costrutto if-else dentro se stesso, potremo distinguere fra 4 casi, se lo annidiamo tre volte potremo distinguere fra 5 casi, e così via. Tuttavia il costrutto if-else ha l'inconveniente seguente: ogni volta che viene distinto un caso si può eseguire solamente una istruzione, che, negli esempi precedenti, è una istruzione printf mediante la quale viene stampato sullo schermo un messaggio. Sarebbe più logico procedere al calcolo completo delle radici; in altre parole, sarebbe meglio che il nostro programma facesse qualcosa del genere: se D>0 allora vengono calcolate e stampate sullo schermo le due radici reali x1=(-b+√D)/2a e x2=(-b-√D)/2a se D=0 allora viene calcolata e stampata sullo schermo la unica radice reale x1=-b/2a se D<0 allora vengono calcolate e stampate sullo schermo le due radici complesse coniugate x1=-b/2a+i(√-D/2a) e x2=-b/2a-i(-√D/2a) Per ottenere tutto ciò, è sufficiente racchiudere un certo numero di istruzioni fra le parentesi graffe aperta e chiusa; infatti in C++ ogni gruppo di istruzioni racchiuso fra { e } viene considerato come una unica istruzione; quindi in generale il costrutto if-else si può usare nella forma più generale: if (condizione1) {gruppo di istruzioni 1}; else if (condizione2) {gruppo di istruzioni 2}; else {gruppo di istruzioni 3}; 8. QUESTO PROGRAMMA LEGGE I COEFFICIENTI a,b,c DI UN POLINOMIO DI SECONDO GRADO, NE CALCOLA LE RADICI E LE STAMPA, SIA NEL CASO DI RADICI REALI CHE NEL CASO DI RADICI COMPLESSE #include <stdio.h> #include <math.h> #include <stdlib.h> main() { float a,b,c,delta,x1,x2,x,alfa,beta,aa,s; printf( "\n primo coefficiente a="); scanf("%f",&a); printf( "\n secondo coefficiente b="); scanf("%f",&b); printf( "\n terzo coefficiente c="); scanf("%f",&c); delta=b*b-4*a*c; aa=2*a; if (delta>0) { x1=(-b+sqrt(delta))/aa; x2=(-b-sqrt(delta))/aa; printf("\n ci sono due radici reali distinte \n"); printf("\n prima radice =%f \n",x1); printf("\n seconda radice =%f \n",x2); } else if (delta==0) { x=-b/aa; printf("\n c'è una unica radice reale =%f \n",x); } else { alfa=-b/aa; s=sqrt(-delta); beta=s/aa; printf("\n ci sono due radici complesse \n"); printf("\n prima radice =%f+i%f \n",alfa,beta); printf("\n seconda radice =%f-i%f \n",alfa,beta); } system ("PAUSE"); return 0; }
Il ciclo for Supponiamo di voler scrivere un programma che legge un numero intero n e poi calcola e stampa il valore di n!; l’idea che useremo per ottenere n! è la seguente: prima di tutto introduciamo una variabile intera in cui andare a mettere il risultato e che chiameremo fatt e a cui assegneremo inizialmente il valore 1; dopo di che sarà sufficiente ripetere l’istruzione fatt=fatt*i; con la variabile intera i che assume successivamente tutti i valori da 1 ad n; in ultimo avremo in fatt il valore richiesto di n!, per cui basterà stampare fatt. Per scrivere un programma in C++ che effettui questa sequenza di operazioni, occorre introdurre delle nuove istruzioni, che sono dette istruzioni di ciclo. La più semplice istruzione di questo tipo è l’istruzione for che, nei casi più comuni, si presenta al modo seguente: for (istruzione1;condizione;istruzione2;) istruzione3; dove la istruzione1 (detta anche istruzione di inizializzazione) serve a fissare il valore iniziale della variabile intera con cui si compie il ciclo e nel caso del fattoriale dovrebbe quindi essere i=1; la condizione (detta anche condizione di iterazione) serve per decidere fino a quando il ciclo andrà ripetuto e nel caso del fattoriale dovrebbe quindi essere i<=n; la istruzione2 (detta anche istruzione di aggiornamento) serve ad aggiornare la variabile con cui si compie il ciclo e nel caso del fattoriale dovrebbe essere i=i+1; infine la istruzione3 è l’istruzione che deve essere ripetuta per tutto il ciclo e che nel caso del fattoriale sarà fatt=fatt*i. (Si osservi che anche in questo caso l’istruzione che deve essere ripetuta per tutto il ciclo, può essere sostituita da un gruppo di istruzioni a patto che vengano racchiuse fra le parentesi graffe). Ecco come si presenterà un programma che calcola il fattoriale di un intero assegnato col procedimento sopra descritto: 9. QUESTO PROGRAMMA CALCOLA E STAMPA IL FATTORIALE DI UN NUMERO n FORNITO IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int i,n,fatt; printf("\n Inserire il num. per il calcolo del suo fattoriale --> n="); scanf("%d",&n); fatt=1; for (i=1;i<=n;i++) // i++ EQUIVALE A i=i+1 fatt=fatt*i; printf("\n Il fattoriale di %d=%d\n\n",n,fatt); // NEL 1° %d METTO IL VALORE DI n, NEL 2° QUELLO DI fatt system ("PAUSE"); return 0; } A questo proposito possiamo osservare subito che n! può essere calcolato sia facendo il prodotto dei numeri interi da 1 ad n, sia facendo il prodotto dei numeri interi da n ad 1; scritto in C++ sarà: 10. QUESTO PROGRAMMA CALCOLA E STAMPA IL FATTORIALE DI UN NUMERO n FORNITO IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int i,n,fatt; printf("\n Inserire il num. per il calcolo del suo fattoriale --> n="); scanf("%d",&n); fatt=1; for(i=n;i>=1;i--) // i-- EQUIVALE A i=i-1 fatt=fatt*i; printf("\n Il fattoriale di %d=%d\n\n",n,fatt); // NEL 1° %d METTO IL VALORE DI n, NEL 2° QUELLO DI fatt system ("PAUSE"); return 0; } ATTENZIONE! se con il programma sopra mettiamo come fattoriale un numero n>=13 il programma sembra impazzire e fornisce valori assurdi (addirittura negativi talvolta). La ragione di questo strano comportamento è molto semplice: dal momento che la porzione di memoria riservata ad una variabile intera è “finita” (4 bytes), di conseguenza solo un numero finito di numeri interi vi sarà rappresentabile (per la precisione i numeri interi da -2147483648 fino a +2147483647). Poiché 13! vale 6227020800, esso eccede il limite di rappresentabilità degli interi; purtroppo il compilatore C++ non si accorge di ciò, infatti esso, quando raggiunge il valore limite +2147483647, prosegue semplicemente ricominciando dal numero -2147483648 (ecco perché si possono ottenere valori negativi!). Quando trattiamo con numeri interi, dobbiamo quindi sempre tenere conto di questa limitazione del compilatore C++ per evitare di trovarci in situazioni simili. Una soluzione che possiamo usare per calcolare il fattoriale di numeri interi più grandi di 12 è quella di definire la variabile fatt come float: Quindi nel programma sopra modificheremo le 2 righe di codice in: int i,n;float fatt; printf("=%f",fatt);
Il ciclo while In tutti i programmi che abbiamo scritto finora per calcolare il fattoriale di un numero intero, abbiamo sempre usato l’istruzione for per eseguire il ciclo di calcolo; tuttavia questa non è l’unica istruzione del C++ che può essere usata per eseguire un ciclo. Un’altra istruzione che si usa per eseguire cicli è l’istruzione while, che ha la seguente sintassi: while (condizione) istruzione; Se condizione è vera, allora viene eseguita istruzione e successivamente si ritorna a valutare “condizione”, se questa è ancora vera si esegue di nuovo istruzione e così via fino a che condizione risulta falsa, nel qual caso si salta istruzione e si prosegue il programma. Un altra istruzione imparentata con while e che si può usare per eseguire cicli è l’istruzione do-while, che ha la seguente sintassi: do istruzione while (condizione); La differenza rispetto al semplicewhile è che con il dowhile prima si esegue istruzione e poi si valuta condizione e se questa è vera si torna ad eseguire istruzione altrimenti si prosegue nel programma; di conseguenza, con il do-while istruzione viene eseguita comunque almeno una volta, mentre con il semplice while istruzione potrebbe non essere mai eseguita (se cioè fin dalla prima volta condizione risultasse falsa). Naturalmente, sia per quanto riguarda while che do-while si potrà sostituire “istruzione” con un gruppo di istruzioni racchiudendole entro le parentesi graffe, per cui la forma più generale dell’istruzione while sarà: while (condizione) {gruppo di istruzioni}; mentre la forma più generale dell’istruzione do-while sarà: do {gruppo di istruzioni} while (condizione); Ecco qui di seguito due programmi con cui viene calcolato il fattoriale di un numero intero utilizzando appunto while e do-while. 11. QUESTO PROGRAMMA CALCOLA E STAMPA IL FATTORIALE DI UN NUMERO n FORNITO IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int i,n;int fatt; printf("\n n="); scanf("%d",&n); fatt=1; i=1; while (i<=n) { fatt=fatt*i; i=i+1; } printf("\n fattoriale di %d=%d\n\n",n,fatt); system ("PAUSE"); return 0; } #include <stdio.h> #include <stdlib.h> main() { int i,n;int fatt; printf("\n n="); scanf("%d",&n); fatt=1;i=1; do { i++; fatt=fatt*i; }while (i<n); printf("\n fattoriale di %d=%d\n\n",n,fatt); system ("PAUSE"); return 0; } Si osservi che la “condizione” negli esempi sopra è diversa appunto perché nel primo caso si usa il while semplice e quindi bisogna richiedere la condizione i<=n, mentre nel secondo caso si usa il do-while e quindi bisogna richiedere la condizione i<n. Il trucco che abbiamo usato per calcolare il prodotto dei numeri interi da 1 ad n, può essere facilmente modificato per calcolare, anziché il prodotto di n numeri, la somma di n numeri; per esempio, immaginiamo di voler scrivere un programma che calcola la media di n numeri reali: prima di tutto il nostro programma dovrà leggere il valore di una variabile n di tipo int, successivamente definiremo una variabile di tipo float che chiameremo media e a cui assegneremo inizialmente il valore 0, a questo punto inizieremo un ciclo che consisterà nel ripetere per n volte le due seguenti operazioni: 1) leggi il valore di una variabile x di tipo float 2) esegui la somma media=media+x al termine del ciclo, nella variabile media sarà memorizzato il valore della somma degli n numeri reali letti; a questo punto sarà sufficiente aggiungere l’istruzione media=media/n e poi far stampare in uscita il valore di media. Ecco come si presenta un programma in C++ che calcola il valor medio di n numeri assegnati tramite la tastiera; vediamo un esempio: 12A. QUESTO PROGRAMMA CALCOLA E STAMPA IL VALOR MEDIO DI n NUMERI REALI FORNITI IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int n,i;float x; printf("\n quanti sono i numeri"); printf(" di cui devo calcolare la media?"); printf("\n n= "); scanf("%d",&n); float media=0; // DICHIARAZIONE DI UNA VARIABILE IN CENTRO AL CODICE (Non necessario dichiararle all'inizio) // L'ISTRUZIONE float media=0; EQUIVALE ALLE 2 ISTRUZIONI: float media;media=0; for(i=1;i<=n;i++) { printf("\n dammi un numero reale x="); scanf("%f",&x); media=media+x; } media=media/n; printf("\n ecco il valor medio: %f\n\n",media); system ("PAUSE"); return 0; } 12B. ESEMPIO SOPRA SCRITTO CON IL WHILE AL POSTO DEL FOR #include <stdio.h> #include <stdlib.h> main() { int n,i;float x; printf("\n quanti sono i numeri"); printf(" di cui devo calcolare la media?"); printf("\n n= "); scanf("%d",&n); float media=0; //for(i=1;i<=n;i++) i=1; while (i<=n) { printf("\n dammi un numero reale x ="); scanf("%f",&x); media=media+x; i++; } media=media/n; printf("\n ecco il valor medio: %f\n\n",media); system ("PAUSE"); return 0; } 12C. ESEMPIO SOPRA SCRITTO CON IL DO-WHILE AL POSTO DEL FOR #include <stdio.h> #include <stdlib.h> main() { int n,i;float x; printf("\n quanti sono i numeri"); printf(" di cui devo calcolare la media?"); printf("\n n= "); scanf("%d",&n); float media=0; //for(i=1;i<=n;i++) i=1; do { printf("\n dammi un numero reale x ="); scanf("%f",&x); media=media+x; i++; }while (i<=n); media=media/n; printf("\n ecco il valor medio: %f\n\n",media); system ("PAUSE"); return 0; }
Gli array In C++ un array ad una dimensione è semplicemente una lista di variabili che hanno tutte lo stesso nome e che vengono distinte una dall’altra mediante un indice intero; per esempio, se a è un array di tipo int, esso è un insieme (finito) di variabili di tipo int la prima delle quali viene identificata da a[0], la seconda da a[1], la terza da a[2], la n-esima da a[n-1]; quando usiamo un array in un programma C++, dobbiamo dichiararlo (esattamente come facciamo per le altre variabili), e nella dichiarazione dovrà essere specificato: 1. il tipo delle variabili che formano l’array 2. il nome comune a tutte le variabili che formano l’array 3. quante sono le variabili che formano l’array e quindi la dichiarazione: int a[10]; serve a definire un array formato da 10 variabili intere di tipo int che verranno identificate una per una da: a[0],a[1],a[2],...,a[9] e analogamente la dichiarazione float v[7]; serve a definire un array formato da 7 variabili reali di tipo float che verranno identificate una per una da: v[0],v[1],v[2],...,v[6] Come si vede quindi la prima variabile di un array ha sempre l’indice 0 e la n-esima variabile di un array avrà quindi sempre indice n-1; questo fatto è talvolta un po’ scomodo, ma come vedremo in seguito, presenta anche alcuni vantaggi non indifferenti. Tra gli oggetti matematici che più comunemente vengono rappresentati utilizzando un array, ci sono i vettori; sappiamo che, per esempio, un vettore dell’ordinario spazio euclideo (reale) ad n dimensioni può essere identificato con una n-pla di numeri reali e pertanto appare naturale rappresentare in C++ un vettore ad n dimensioni mediante un array che abbia (almeno) n elementi. Ecco per esempio un programma che calcola la media di n numeri assegnati e nel quale noi memorizziamo gli n numeri come le componenti di un vettore x: 13. QUESTO PROGRAMMA CALCOLA E STAMPA IL VALOR MEDIO DI n NUMERI REALI FORNITI IN LETTURA COME COMPONENTI DI UN VETTORE #include <stdio.h> #include <stdlib.h> main() { int n,i; float media; float x[20]; // dico il compilatore che l’array x è costituito da 20 variabili di tipo float printf("\n Quante componenti ha il vettore che devo leggere?"); printf("\n n= "); scanf("%d",&n); if (n>20) // istruzione che verifica che l'array non superi 20 { printf("\n\n Attenzione! Ci sono troppi numeri."); system(“PAUSE”); return 0; // se supero 20 interrompo l’esecuzione del programma } for(i=1;i<=n;i++) { printf("\n dammi la componente numero %d del vettore x ",i); scanf("%f",&x[i-1]); // i-1 poichè come detto gli array partono da 0 } media=0; for(i=0;i<n;i++) media=media+x[i]; media=media/n; printf("\n\n Ecco il valor medio: %f\n\n",media); system ("PAUSE"); return 0; } Facciamo alcune osservazioni sul programma dell’esempio 13 sopra: 1 la dichiarazione float x[20]; avverte il compilatore che l’array x è costituito da 20 variabili di tipo float, pertanto la dimensione n del vettore che leggeremo non potrà eccedere 20, altrimenti il nostro array non avrebbe lo spazio sufficiente per memorizzare tutte le componenti del vettore x. Ecco perché, subito dopo la lettura del valore di n abbiamo posto la istruzione if (n>20) return 0; il cui scopo è quello di interrompere l’esecuzione del programma nel caso appunto in cui sia n>20. Purtroppo il compilatore C++, nel caso in cui si cercasse di memorizzare in un array più numeri di quelli previsti dalla dimensione dell’array, non segnalerebbe alcun errore; pertanto il controllo mediante l’istruzione if (n>20) return 0; è indispensabile per evitare di trovarsi in situazioni imprevedibili. 2 nel ciclo for, l’indice i viene fatto variare da 1 ad n, ma per ogni valore di i viene letta la variabile x[i-1], ciò vuol dire che la i-esima componente del vettore che stiamo leggendo viene memorizzata nella variabile dell’array che ha nome x[i-1], e ciò dipende dal fatto che, come abbiamo detto prima, nell’array x gli indici partono dal valore 0. 3 il programma dell’esempio 13 calcola la media di n numeri dati, esattamente come il programma dell’esempio 12A, tuttavia c’è da sottolineare una importante differenza fra i due, che è la seguente: nel programma dell’esempio 12A i numeri che vengono forniti in lettura sono tutti memorizzati nella medesima variabile x, e quindi ogni volta che viene letto un nuovo numero, il precedente viene perduto, cioè alla fine della esecuzione del programma l’unico numero di cui rimanga memoria è l’ultimo numero letto; viceversa nel programma dell’esempio 13 vengono prima memorizzati nell’array x tutti gli n numeri forniti dall’utente attraverso la tastiera, e successivamente ne viene calcolata e stampata la media, pertanto alla fine del programma sono ancora accessibili tutti gli n numeri letti. (Si osservi anche che, per ragioni di chiarezza abbiamo utilizzato due cicli for, uno per la lettura dei dati e l’altro per calcolare la media, tuttavia avremmo potuto facilmente usare un unico ciclo for come nell’esempio 12A; lasciamo al lettore il compito di effettuare per esercizio la necessaria modifica). Nel programma dell’ esempio 13 l’esecuzione si interrompe nel caso in cui sia n>20; sarebbe meglio fare in modo invece che, se n>20, il programma rifiutasse il valore assegnato stampando sullo schermo un opportuno messaggio di errore, ma che poi continuasse l’esecuzione richiedendo un nuovo valore in lettura per n; ecco come possiamo ottenere tutto ciò attraverso l’uso del costrutto while: 14. QUESTO PROGRAMMA CALCOLA E STAMPA IL VALOR MEDIO DI n NUMERI REALI FORNITI IN LETTURA COME COMPONENTI DI UN VETTORE #include <stdio.h> #include <stdlib.h> main() { int n,i; float media; float x[20]; // dico il compilatore che l’array x è costituito da 20 variabili di tipo float printf("\n Quante componenti ha il vettore che devo leggere?"); printf("\n n= "); scanf("%d",&n); while (n>20) { printf("\n Attenzione!!\n le componenti del vettore sono troppe."); printf("\n Scegli di nuovo quante componenti deve avere il vettore che devo leggere"); printf("\n n= "); scanf("%d",&n); } for(i=1;i<=n;i++) { printf("\n Dammi la componente numero %d del vettore x ",i); scanf("%f",&x[i-1]); // i-1 poichè come detto gli array partono da 0 } media=0; for(i=0;i<n;i++) media=media+x[i]; media=media/n; printf("\n\n Ecco il valor medio: %f\n\n",media); system ("PAUSE"); return 0; } Scriviamo ora un programma che legge un vettore ad n componenti e ne calcola la componente massima: 15. QUESTO PROGRAMMA TROVA LA MASSIMA COMPONENTE DI UN VETTORE FORNITO IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int n,i; float max; float v[31]; printf("\n\n Quante componenti ha il vettore che devo leggere?"); printf("\n n= "); scanf("%d",&n); while (n>30) { printf("\n Attenzione!!\n le componenti del vettore sono troppe."); printf("\n Scegli di nuovo quante componenti deve avere il vettore che devo leggere"); printf("\n n= "); scanf("%d",&n); } for(i=1;i<=n;i++) { printf("\n Dammi la componente numero %d del vettore x ",i); scanf("%f",&v[i]); } max=v[1]; for(i=2;i<=n;i++) if (max<v[i]) max=v[i]; printf("\n\n Massima componente=%f\n\n",max); system ("PAUSE"); return 0; } Per quanto riguarda quest’ultimo programma, osserviamo che, nel memorizzare il vettore nell’array v, abbiamo scelto di memorizzare la componente i-esima del vettore nella variabile v[i] (anziché nella variabile v[i-1] come in precedenza); ciò comporta due conseguenze: 1 la variabile v[0] non viene utilizzata mai per memorizzarvi alcuna componente del vettore 2 anche se l’array v è costituito da 31 variabili di tipo float, noi possiamo leggere vettori di dimensione al massimo 30 (appunto per il fatto che non utilizziamo la prima variabile dell’array). Tutto ciò ci porta ad evidenziare un fatto importante riguardo ai vettori ed agli array, che è il seguente: un vettore ed un array sono due cose distinte Un vettore ad n componenti, cioè una n-pla di numeri, può venire memorizzato in un array, ma non necessariamente deve occuparlo tutto, anzi di solito non lo occupa affatto tutto ma ne occupa solo una parte. Chi scrive un programma deve quindi porre particolare attenzione a questo fatto, avendo sempre presente in quale porzione dell’array si trova un certo vettore. Nel programma dell'esempio 15 abbiamo visto come trovare la massima componente di un vettore assegnato; se analizziamo l’algoritmo usato per ottenere ciò, appare subito evidente che, dopo l’esecuzione del ciclo for(i=2;i<=n;i++) if (max<v[i]) max=v[i]; nella variabile max viene a trovarsi necessariamente il valore della massima componente del vettore assegnato, tuttavia non è possibile sapere quale fosse stato il valore dell’indice i della componente massima del vettore. Se noi volessimo scrivere un programma che, oltre a determinare e stampare la massima componente di un vettore, determina e stampa anche il valore dell’indice per cui viene conseguito il massimo, dovremmo introdurre anche una variabile imax (stavolta di tipo int) in cui andare a memorizzare il valore dell’indice i ogni volta che viene cambiato il valore di max. Ecco come potremmo modificare il precedente programma per ottenere questo risultato: 16. QUESTO PROGRAMMA TROVA LA MASSIMA COMPONENTE DI UN VETTORE FORNITO IN LETTURA #include <stdio.h> #include <stdlib.h> main() { int n,i,imax; float max; float v[31]; printf("\n\n Quante componenti ha il vettore che devo leggere?"); printf("\n n= "); scanf("%d",&n); while (n>30) { printf("\n Attenzione!!\n le componenti del vettore sono troppe."); printf("\n Scegli di nuovo quante componenti deve avere il vettore che devo leggere"); printf("\n n= "); scanf("%d",&n); } for(i=1;i<=n;i++) { printf("\n Dammi la componente numero %d del vettore x ",i); scanf("%f",&v[i]); } max=v[1]; imax=1; for(i=2;i<=n;i++) if (max<v[i]) { max=v[i]; imax=i; } printf("\n\n Massima componente=%f",max); printf("\n\n Indice della massima componente=%d\n\n",imax); system ("PAUSE"); return 0; } Così come esistono gli array ad una dimensione, esistono in C++ anche gli array a due dimensioni, che sono semplicemente array con due indici anziché uno solo; le osservazioni che abbiamo fatto a proposito degli array ad una dimensione si possono estendere, con qualche ovvio aggiustamento, agli array a due dimensioni, quindi per esempio la dichiarazione float a[10][12]; serve a definire un array formato da 120(=10x12) variabili reali di tipo float che verranno identificate una per una da a[0][0],a[0][1],a[0][2],...,a[0][11], a[1][0],a[1][1],a[1][2],...,a[1][11], ..................................... a[9][0],a[9][1],a[9][2],....,a[9][11] Un uso ovvio degli array a due dimensioni in problemi matematici, è quello di utilizzarli per memorizzarvi una matrice; ecco qui di seguito un esempio di programma che fa le seguenti cose: 1 legge il numero delle righe (num_rig) e il numero delle colonne (num_col) di una matrice che dovrà essere introdotta in lettura tramite la tastiera 2 legge uno per uno gli elementi di una matrice delle dimensioni assegnate al punto 1 3 calcola e stampa la somma degli elementi di ciascuna riga 17. ESEMPIO DI ARRAY BIDIMENSIONALE #include <stdio.h> #include <stdlib.h> main() { float a[20][20]; float b[20]; int num_rig,num_col,i,j; printf("Numero delle righe della matrice: m="); scanf("%d",&num_rig); printf("Numero delle colonne della matrice: n="); scanf("%d",&num_col); printf("Introduci gli elementi della matrice\n\n"); for (i=1;i<=num_rig;i++) { for (j=1;j<=num_col;j++) { printf("a[%d,%d]=",i,j); scanf("%f",&a[i][j]); } } for (i=1;i<=num_rig;i++) { b[i]=0; for (j=1;j<=num_col;j++) b[i]=b[i]+a[i][j]; } for (i=1;i<=num_rig;i++) printf("\n b(%d)=%f\n",i,b[i]); printf("\n\n"); system ("PAUSE"); return 0; } Si osservi che nell’esempio 17 abbiamo utilizzato sia un array a due dimensioni a (per memorizzare la matrice), sia un array ad una dimensione b (per memorizzare le somme degli elementi di ciascuna riga della matrice); inoltre, sia per effettuare la lettura della matrice elemento per elemento, sia per calcolare le somme degli elementi di ciascuna riga, abbiamo dovuto utilizzare un doppio ciclo for, nel senso che abbiamo dovuto inserire due volte un ciclo for dentro un altro ciclo for.
Le funzioni Cominciamo con l’esaminare il seguente programma C++: 18. QUESTO PROGRAMMA CALCOLA IL VALORE ASSOLUTO (O MODULO) DI UN NUMERO REALE ASSEGNATO #include <iostream> #include <stdlib.h> using namespace std; #include <math.h> main() { float x,y; cout<<"\n Dammi un numero reale x="; cin>>x; y=fabs(x); cout<<"\n\n modulo di "; cout<<x; cout<<" = "; system ("PAUSE"); return 0; } Rispetto ai programmi che abbiamo visto in precedenza, ci sono alcune novità che ora commenteremo: prima di tutto possiamo vedere che le istruzioni di ingresso e uscita dei dati non sono più scanf e printf; per far entrare ed uscire i dati vengono ora utilizzate cin>> e cout<<, che sembrano anche più semplici da usare rispetto a scanf e printf, infatti cin>> richiede semplicemente che gli venga accodato il nome della variabile che si vuole introdurre (da tastiera), senza bisogno di specificare con quale tipo di formato, come si faceva con scanf, e analogamente cout<< richiede che gli venga accodato il nome della variabile o la sequenza di caratteri (racchiusa fra le virgolette) che si vuole stampare sul video. Si osservi che anche per cout<< è possibile (come per printf) compattare diverse istruzioni successive di stampa; ecco come si presenterà il programma sipra compattando le istruzioni cout<<: cout<<"\n\n modulo di "<<x<<" = "<<y; Come si vede, non occorre ripetere cout<< ogni volta, ma è sufficiente accodare alla precedente variabile (o sequenza di caratteri) in uscita un nuovo simbolo << e la successiva variabile (o sequenza di caratteri) che si vuole mandare in uscita subito dopo. E' importante osservare che anche cin>> e cout<< (come scanf e printf) sono definiti in un apposito file della Libreria Standard, che è il file iostream.h (nel caso di scanf e printf era invece il file stdio.h); ecco perché in testa al programma abbiamo dovuto questa volta aggiungere la direttiva #include Un utile esercizio potrebbe essere quello di riscrivere tutti i programmi che abbiamo esaminato fino ad adesso usando cin>> e cout<< al posto di scanf e printf. Passiamo ora a commentare l’istruzione y=fabs(x); in cui viene utilizzata la funzione fabs, che è una funzione matematica che calcola il valore assoluto (o modulo) di un numero reale (per calcolare invece il modulo di un numero intero si usa la funzione abs); per calcolare questa particolare funzione, il compilatore deve consultare il file math.h (dove questa funzione è definita), e quindi abbiamo dovuto premettere al programma la direttiva #include In un precedente programma avevamo dovuto usare questa stessa direttiva per poter utilizzare la funzione matematica sqrt (che calcola la radice quadrata di un numero reale); è ben evidente quindi che nel file math.h si troveranno molte altre funzioni matematiche, per esempio potremo trovarvi le funzioni sin, cos e tan che calcolano rispettivamente il seno, il coseno e la tangente trigonometrici di un numero reale, oppure potremo trovarvi le funzioni log, log10 ed exp che calcolano rispettivamente il logaritmo naturale, il logaritmo in base 10 e l’esponenziale di un numero reale, e così via molte altre funzioni di uso comune in matematica. L’uso di tutte queste funzioni matematiche è abbastanza intuitivo e abbastanza semplice: per esempio, nel caso della istruzione y=fabs(x); verrà preso il valore attuale della variabile x (che è quello introdotto, tramite tastiera, dall’istruzione cin>>x;), ne verrà calcolato il valore assoluto (fabs(x)) e il numero così ottenuto verrà assegnato come valore alla variabile y. Ciò che non è invece affatto intuitivo è: che cosa il compilatore trova nel file math.h? E’ ovvio che il compilatore troverà in math.h le istruzioni per calcolare le varie funzioni matematiche, ma il punto che noi vorremmo chiarire è il seguente: come sono fatte queste istruzioni? Per rispondere a queste domande, mostreremo ora un programma equivalente a quello dell’esempio sopra, ma nel quale la funzione che calcola il modulo di un numero reale è definita direttamente da noi, senza dover ricorrere al file math.h. Ecco come potrebbe essere fatto un programma del genere: 19. QUESTO PROGRAMMA CALCOLA IL VALORE ASSOLUTO (O MODULO) DI UN NUMERO REALE ASSEGNATO #include <iostream> #include <stdlib.h> using namespace std; float modulo(float); main() { float x,y; cout<<"\n Dammi un numero reale x="; cin>>x; y=modulo(x); // RICHIAMO LA FUNZIONE MODULO cout<<"\n\n modulo di "<<x<<" = "<<y << "\n\n"; system ("PAUSE"); return 0; } float modulo(float a) { if (a>=0) return a; else return -a; } Prima di tutto osserviamo che la funzione che calcola il valore assoluto è stata chiamata modulo anziché fabs; dal momento che la funzione modulo viene definita esplicitamente da noi, possiamo darle il nome che vogliamo (con qualche ragionevole limitazione di cui parleremo in seguito). La definizione vera e propria della funzione modulo è costituita dalle ultime due righe, cioè: float modulo(float a) { if (a>=0) return a; else return -a; } La prima di queste righe (detta testata della funzione), cioè float modulo(float a) serve a specificare tre cose: 1 che tipo di risultato fornisce la funzione (float) 2 il nome della funzione (modulo) 3 il tipo e il nome dell’argomento (float a) Dopo la testata della funzione seguono due parentesi graffe {....} entro le quali si trova il corpo della funzione, cioè le istruzioni if (a>=0) return a; else return -a; che sono appunto le istruzioni che servono a specificare che cosa deve calcolare la funzione modulo. Il significato di queste due ultime istruzioni dovrebbe essere abbastanza chiaro: quando il valore di a è maggiore o uguale a zero, la funzione modulo restituisce al main il valore stesso di a (return a;), altrimenti se a è minore di zero restituisce il valore di a cambiato dei segno return -a;)(, il che corrisponde esattamente a calcolare il valore assoluto di a. Le istruzioni di ritorno (cioè return a; e return -a;) hanno lo scopo sia di restituire un valore al programma chiamante (cioè al main), sia di terminare l’esecuzione della funzione modulo e di ritornare all’esecuzione del programma chiamante (cioè al main). Tutto ciò sembra abbastanza ragionevole e logico, tuttavia qualcuno potrebbe porsi questa domanda: perché nel main la funzione modulo viene applicata alla variabile x, mentre nella funzione modulo compare la variabile a? La risposta è questa: la variabile a è una variabile locale nella funzione modulo, quando il programma principale (cioè il main) incontra la funzione modulo applicata all’argomento x, il valore che ha la variabile x nel main viene passato come valore iniziale alla variabile locale a della funzione modulo e vengono eseguite le istruzioni previste dalla funzione modulo fino a che questa restituisce (tramite return a oppure return -a) al main il valore di modulo(x) (da assegnare alla variabile y). In effetti la variabile locale a della funzione modulo esiste fisicamente solo durante l’esecuzione della funzione modulo e termina di esistere nel momento in cui si ritorna all’esecuzione del programma chiamante (cioè, in questo caso, il main). Si osservi anche che, dove prima compariva la direttiva #include (che è stata soppressa perché non serve più) ora compare l’istruzione float modulo(float); Quest’ultima è chiamata il prototipo della funzione modulo, e serve ad avvertire il compilatore che la funzione modulo è una funzione che sarà definita più avanti (nel nostro caso dopo il main {.......}). Si osservi che quando si dichiara un prototipo di funzione, non occorre specificare il nome dell’argomento della funzione, ma solo il tipo cui appartiene il risultato della funzione (nel nostro caso il float che precede modulo indica che il risultato della funzione modulo sarà un numero reale) e il tipo dell’argomento (il float che segue modulo e che si trova tra due parentesi indica che anche l’argomento della funzione sarà un numero reale). Riassumendo ciò che abbiamo imparato dall’esempio precedente, possiamo dire che, volendo definire una funzione da usare nel programma principale, un modo di procedere potrebbe essere il seguente (vedremo poi che ci sono altri modi possibili): anteporre al main il prototipo della funzione che vogliamo definire, che avrà la seguente struttura: tipo della funzione nome della funzione(tipo degli argomenti); posporre al main la testata della funzione che vogliamo definire, che avrà la seguente struttura tipo della funzione nome della funzione(tipo e nome degli argomenti) e subito di seguito il corpo della funzione che vogliamo definire racchiuso fra le parentesi graffe aperta e chiusa. Nel caso specifico dell’esempio precedente, la funzione modulo ha un unico argomento, tuttavia è ovvio che sarà possibile definire funzioni che hanno più di un argomento; ecco un esempio di programma in cui viene definita una funzione norma di due argomenti che calcola la radice quadrata della somma dei quadrati di due numeri dati: 20. QUESTO PROGRAMMA CALCOLA LA RADICE QUADRATA DELLA SOMMA DEI QUADRATI DI DUE NUMERI X ED Y ASSEGNATI #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; float norma(float,float); main() { float x,y,z; cout<<"\n Dammi un numero reale x="; cin>>x; cout<<"\n Dammi un numero reale y="; cin>>y; z=norma(x,y); // RICHIAMO LA FUNZIONE NORMA cout<<"\n\n La norma di "<<x<<" e " << y << " = " <<z << "\n\n"; system ("PAUSE"); return 0; } float norma(float a,float b) { float c=sqrt(a*a+b*b); return c; } Un’altra cosa che ovviamente è possibile fare è quella di definire e utilizzare più di una funzione all’interno di uno stesso programma; ecco, per esempio, come possiamo modificare il programma dell'esempio 20 definendo sia la funzione norma che la funzione quad (che serve a calcolare il quadrato di un numero) 21. QUESTO PROGRAMMA CALCOLA LA RADICE QUADRATA DELLA SOMMA DEI QUADRATI DI DUE NUMERI X ED Y ASSEGNATI #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; float norma(float,float); float quad(float); main() { float x,y,z; cout<<"\n Dammi un numero reale x="; cin>>x; cout<<"\n Dammi un numero reale y="; cin>>y; z=norma(x,y); // RICHIAMO LA FUNZIONE NORMA cout<<"\n\n La norma di "<<x<<" e " << y << " = " <<z << "\n\n"; system ("PAUSE"); return 0; } float norma(float a,float b) { float c; c=sqrt(quad(a)+quad(b)); // RICHIAMO LA FUNZIONE QUAD return c; } float quad(float a) { float b; b=a*a; return b; } In quest’ultimo programma è importante osservare che le variabili locali a e b nella funzione norma sono distinte dalle variabili locali a e b nella funzione quad (pur avendo lo stesso nome); infatti, quando per esempio la variabile a viene definita nelle funzione quad, essa è una variabile locale nella funzione quad e il compilatore la considera distinta dalla variabile a che compare nella funzione norma. (Se quest’ultima frase vi risulta ancora piuttosto oscura, non vi preoccupate, perché ritorneremo fra breve su questo concetto cercando di chiarirlo meglio con degli esempi.) Fino ad ora abbiamo considerato funzioni definite in C++ che sostanzialmente corrispondono al concetto di funzione come noi lo usiamo comunemente in matematica: si passano alla funzione i valori degli argomenti e in cambio la funzione fornisce un valore di ritorno. Tuttavia le funzioni in C++ sono qualcosa di più di questo (e di diverso); per esempio, è possibile definire funzioni che non restituiscono alcun valore. Ci possiamo chiedere: a cosa serve una funzione del C++ che non calcola alcun valore? Certo non può servire a calcolare, per esempio, nessuna funzione matematica, tuttavia essa potrebbe essere utilizzata per eseguire qualche altro compito. Qui di seguito riportiamo una variante dell'esempio 21 in cui viene usata la funzione stampa, il cui unico scopo è quello di stampare sul video il risultato finale del programma: 22. QUESTO PROGRAMMA CALCOLA LA RADICE QUADRATA DELLA SOMMA DEI QUADRATI DI DUE NUMERI X ED Y ASSEGNATI #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; float norma(float,float); float quad(float); void stampa(float,float,float); main() { float x,y,z; cout<<"\n Dammi un numero reale x="; cin>>x; cout<<"\n Dammi un numero reale y="; cin>>y; z=norma(x,y); // RICHIAMO LA FUNZIONE NORMA stampa(x,y,z); // RICHIAMO LA FUNZIONE STAMPA system ("PAUSE"); return 0; } float norma(float a,float b) { float c; c=sqrt(quad(a)+quad(b)); // RICHIAMO LA FUNZIONE QUAD return c; } float quad(float a) { float b; b=a*a; return b; } void stampa(float a,float b,float c) { cout << "\n\n La norma di " << a << " e " << b << " = " <<c << "\n\n"; return; } Osserviamo prima di tutto che, nel definire la funzione stampa, abbiamo dichiarato che il tipo della funzione è void; ciò significa che la funzione stampa non ritorna alcun valore e infatti nel corpo della funzione l’istruzione finale è semplicemente un return; senza alcun valore ad esso collegato. Vediamo ora come agisce la funzione stampa: quando nel main si arriva all’istruzione stampa(x,y,z); vengono passati alla funzione stampa i valori delle variabili x, y e z, che vengono assegnati come valori iniziali delle variabili locali a, b e c rispettivamente della funzione; a questo punto la funzione stampa esegue la sua istruzione cout << "\n\n La norma di " << a << " e " << b << " = <<c; con cui viene stampato sul video il risultato del programma e successivamente l’istruzione return; termina l’esecuzione della funzione stampa. Si osservi che, sulla base di quanto abbiamo già detto a proposito delle variabili locali, le variabili a, b e c della funzione sono fisicamente distinte dalle variabili x, y e z del main; ciò implica che, se le istruzioni nella funzione stampa modificassero i valori di a, b e c, queste modifiche non avrebbero alcun effetto sui valori delle variabili x, y e z del main. Per comprendere meglio questi concetti, esaminiamo il seguente programma: 23. QUESTO PROGRAMMA USA UNA FUNZIONE PER SCAMBIARE DUE NUMERI DATI; IL PASSAGGIO DEI PARAMETRI AVVIENE PER VALORE E LO SCAMBIO E' EFFETTUATO PERCIO' IN MODO SBAGLIATO #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; void scambia(int,int); main() { int x,y; cout << "\n Dammi un numero reale x="; cin >> x; cout << "\n Dammi un numero reale y="; cin >> y; cout << "\n\n Ecco i numeri prima dello scambio: "; cout << "\n\n x=" << x << " y=" << y << endl << endl; scambia(x,y); cout << "\n\n ecco i numeri dopo lo scambio: "; cout << "\n\n x=" << x << " y=" << y << endl << endl; system("PAUSE"); return 0; } void scambia(int a,int b) { int aux; aux=a;a=b;b=aux; return; } (N.B.: l’istruzione endl è una istruzione equivalente a "\n", cioè quindi è una istruzione che semplicemente fa andare a capo.) In quest’ultimo programma viene utilizzata la funzione scambia (di tipo void); quando nel main si arriva alla istruzione scambia(x,y); vengono passati alla funzione i valori delle variabili x e y che vengono assegnati come valori alle variabili locali a e b; a questo punto la funzione scambia effettua lo scambio dei valori di a e b e questo scambio viene effettuato correttamente, cioè i valori di a e di b si scambiano effettivamente, tuttavia tutto ciò non provoca alcun cambiamento sulle variabili x e y del main. Ed infatti, se compiliamo ed eseguiamo il programma, vediamo che i valori di x e y prima e dopo lo scambio sono gli stessi. Potremmo essere tentati di risolvere questo problema modificando il programma precedente al modo seguente: void scambia(int x,int y) { int aux; aux=x;x=y;y=aux; return; } Come si vede, la modifica è consistita nel dare lo stesso nome (x e y) sia alle variabili del main che a quelle della funzione scambia; tuttavia ciò non risolve affatto il problema, come è facile verificare compilando ed eseguendo anche quest’ultimo programma. La ragione di ciò sta nel fatto che, come abbiamo già avuto modo di dire, il fatto che le variabili x e y nel main e nella funzione scambia abbiano lo stesso nome, non significa affatto che esse (cioè le loro posizioni di memoria nel calcolatore) coincidano, e il compilatore le considera variabili distinte anche se hanno lo stesso nome; il risultato sarà pertanto che i valori delle variabili x e y nel main non si scambiano affatto, esattamente come accadeva nel programma originale. Tutti i nostri problemi nascono dal fatto che il passaggio dei valori delle variabili dal main alla funzione scambia avviene con la modalità detta passaggio per valore delle variabili, che è appunto il modo di passaggio che abbiamo descritto in precedenza. In C++ esiste un altro modo di passare i valori delle variabili dal main ad una funzione, che è il cosiddetto passaggio per indirizzo (o per riferimento) delle variabili; senza entrare troppo nei dettagli (che verranno chiariti meglio nel seguito), possiamo dire che, quando si passa una variabile per indirizzo dal main ad una funzione, la variabile del main e la corrispondente variabile nella funzione sono fisicamente identiche, cioè occupano la stessa posizione nella memoria del calcolatore; ciò implica che ogni modifica alla variabile apportata nella funzione ha effetto anche sulla variabile del main. Il passaggio per indirizzo di una variabile si indica semplicemente aggiungendo il simbolo & al tipo della variabile, sia nel prototipo della funzione che nella testata dellan funzione. Ecco quindi come possiamo modificare il programma dell'esempio 23 in modo che lo scambio avvenga in modo corretto: 24. QUESTO PROGRAMMA USA UNA FUNZIONE PER SCAMBIARE DUE NUMERI DATI; IL PASSAGGIO DEI PARAMETRI AVVIENE PER RIFERIMENTO E LO SCAMBIO E' EFFETTUATO PERCIO' IN MODO CORRETTO #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; void scambia(int&,int&); main() { int x,y; cout << "\n Dammi un numero reale x="; cin >> x; cout << "\n Dammi un numero reale y="; cin >> y; cout << "\n\n Ecco i numeri prima dello scambio: "; cout << "\n\n x=" << x << " y=" << y << endl << endl; scambia(x,y); cout << "\n\n ecco i numeri dopo lo scambio: "; cout << "\n\n x=" << x << " y=" << y << endl << endl; system("PAUSE"); return 0; } void scambia(int& a,int& b) { int aux; aux=a;a=b;b=aux; return; } Una utilizzazione pratica del passaggio per indirizzo, potrebbe essere quella di servirsene per costruire funzioni che leggono valori, così come avevamo costruito una funzione che stampa valori (la funzione stampa nell’ Esempio 22); ecco appunto come possiamo modificare in questo senso il programma dell’ Esempio 22: 25. QUESTO PROGRAMMA GESTISCE UNA FUNZONE CHE LEGGE VALORI #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; float norma(float,float); float quad(float); void stampa(float,float,float); void leggi(float&,float&); main() { float x,y,z; leggi(x,y); z=norma(x,y); stampa(x,y,z); system("PAUSE"); return 0; } float norma(float a,float b) { float c; c=sqrt(quad(a)+quad(b)); return c; } float quad(float a) { float b; b=a*a; return b; } void stampa(float a,float b,float c) { cout << "\n\nla norma di " << a << " e " << b << " è " << c << "\n\n"; return; } void leggi(float& a,float& b) { cout << "\ndammi un numero reale x=";cin >> a; cout << "\ndammi un numero reale y=";cin >> b; return; } Come si vede, in questo caso abbiamo introdotto una funzione leggi (di tipo void) in cui le due variabili sono passate per indirizzo; in questo modo i valori che vengono letti (cioè assegnati tramite tastiera) per le variabili locali a e b nella funzione leggi, passano automaticamente nelle variabili x e y del main. Per concludere questa breve introduzione alle funzioni del C++, possiamo aggiungere che è possibile anche definire funzioni che non hanno alcun argomento. Ecco un possibile uso di una funzione di questo tipo: 26. QUESTO PROGRAMMA GESTISCE UNA FUNZONE CHE LEGGE VALORI #include <iostream> #include <stdlib.h> #include <math.h> using namespace std; float norma(float,float); float quad(float); void stampa(float,float,float); void leggi(float&,float&); void presenta(); main() { float x,y,z; presenta(); system("PAUSE"); leggi(x,y); z=norma(x,y); stampa(x,y,z); system("PAUSE"); return 0; } float norma(float a,float b) { float c; c=sqrt(quad(a)+quad(b)); return c; } float quad(float a) { float b; b=a*a; return b; } void stampa(float a,float b,float c) { cout << "\n\n La norma di " << a << " e " << b << " = " << c << "\n\n"; return; } void leggi(float& a,float& b) { cout << "\n dammi un numero reale x=";cin >> a; cout << "\n dammi un numero reale y=";cin >> b; return; } void presenta() { cout << "\n QUESTO PROGRAMMA CALCOLA LA RADICE QUADRATA"; cout << "\n DELLA SOMMA DEI QUADRATI DI DUE NUMERI X ED Y"; cout << "\n ASSEGNATI. SE SEI PRONTO A FORNIRMI I VALORI"; cout << "\n DI X E Y PREMI UN TASTO QUALSIASI\n\n"; return; } Come si vede la funzione presenta (di tipo void, cioè che non restituisce alcun valore) ha esclusivamente lo scopo di mandare inizialmente sullo schermo video un messaggio che presenta all’utilizzatore lo scopo del programma e quello che ci si aspetta da lui; lo schermo si immobilizza poi in questa posizione per effetto della istruzione system(“PAUSE”); fino a che l’utilizzatore non preme un tasto qualsiasi in modo che il programma possa riprendere la sua esecuzione. A questo punto qualcuno potrebbe chiedersi: forse system(“PAUSE”) (che è definita nel file stdlib.h) è anch’essa una funzione che ha come argomento "PAUSE"? La risposta è: si, è proprio così. E infine, qualcuno potrebbe anche chiedersi: ma forse anche main() potrebbe essere una funzione senza alcun argomento (come presenta())? E la risposta è ancora: si, è proprio così; main() è la testata della funzione e il corpo della funzione è tutto ciò che è racchiuso fra le parentesi graffe aperta e chiusa che seguono. Qualcuno potrebbe obiettare: ma quale è il tipo della funzione main()? (Come sappiamo infatti, nella testata di una funzione dovrebbe comparire il nome della funzione preceduto dal suo tipo). Naturalmente il tipo della funzione main non può essere void, infatti la funzione main termina con l’istruzione return 0; che restituisce il valore 0. E infatti la funzione main è di tipo int, soltanto che il tipo può, in questo caso particolare, essere omesso; tuttavia, se provate a modificare tutti i programmi che abbiamo scritto fino ad adesso sostituendo main() con int main() vedrete che tutto funzionerà perfettamente allo stesso modo. Per concludere quindi, ogni programma C++ è costituito dalla funzione main() ed (eventualmente) da altre funzioni che vengono chiamate all’interno della funzione main() e l’esecuzione del programma ha sempre inizio con una chiamata della funzione main().
1.02 -
1.03 -
1.04 -