La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Ambito delle variabili Esaminiamo più a fondo le variabili dichiarate in una funzione e le loro relazioni con quelle dichiarate in altre funzioni. Per.

Presentazioni simili


Presentazione sul tema: "Ambito delle variabili Esaminiamo più a fondo le variabili dichiarate in una funzione e le loro relazioni con quelle dichiarate in altre funzioni. Per."— Transcript della presentazione:

1 Ambito delle variabili Esaminiamo più a fondo le variabili dichiarate in una funzione e le loro relazioni con quelle dichiarate in altre funzioni. Per loro natura, le funzioni C sono costruite come moduli indipendenti. Dato che le variabili create in una funzione sono disponibili solo alla funzione stessa, si dice che esse sono locali alla funzione. Questo termine si riferisce alla portata o ambito di una variabile, definito come la sezione del programma in cui la variabile è valida o nota. Una variabile può avere un ambito locale o globale. Una variabile (con ambito) locale è una variabile le cui locazioni di memoria sono state riservate da unistruzione di dichiarazione presente nel corpo della funzione. Le variabili locali sono significative solo se usate in espressioni o istruzioni entro la funzione che le dichiara. Ciò significa che lo stesso nome di variabile può essere dichiarato e usato in più di una funzione: per ogni funzione che dichiara la variabile, viene creata una variabile distinta.

2 Tutte le variabili usate finora erano locali. Ciò è un risultato diretto dellavere situato le istruzioni di dichiarazione entro le funzioni e di averle usate come istruzioni di definizione che fanno riservare al computer la memoria per la variabile dichiarata. Ma le istruzioni di dichiarazione possono essere situate fuori dalle funzioni, e non devono necessariamente agire come definizioni che fanno riservare nuove aree di memoria per la variabile dichiarata. Una variabile (con ambito) globale è una variabile la cui area di memoria è stata creata da unistruzione di dichiarazione esterna a qualsiasi funzione. Perciò essa è detta anche esterna. Le variabili globali possono essere usate da tutte le funzioni di un programma che siano fisicamente situate dopo le loro dichiarazioni. Ciò è illustrato dal programma seguente, dove abbiamo usato volutamente lo stesso nome di variabile allinterno di entrambe le funzioni contenute nel programma.

3 int primonum; /* crea la variabile GLOBALE primonum */ void valfun(void); primonum = 10; secnum = 20; printf("\nDa main(): 1° numero = %d",primonum); printf("\nDa main(): 2° numero = %d\n",secnum); valfun(); printf("\nDa main() ancora: 1° numero = %d",primonum); printf("\nDa main() ancora: 2° numero = %d",secnum); } #include void main(void) { int secnum; /* crea una prima variabile LOCALE secnum */ return; } void valfun(void) { int secnum; /*crea una seconda variabile LOCALE secnum*/ secnum = 30; /*influenza solo questa variabile LOCALE*/ printf("\nDa valfun(): 1° numero = %d",primonum); printf("\nDa valfun(): 2° numero = %d\n",secnum); primonum = 40; /*cambia primonum per entrambe le funzioni*/

4 La variabile primonum è globale, perché la sua area di memoria è creata da una istruzione di dichiarazione situata al di fuori di una funzione. Dato che entrambe le funzioni main() e valfun() sono definite dopo la dichiarazione di primonum, entrambe possono usare tale variabile senza bisogno di unulteriore dichiarazione. Il programma contiene anche due variabili locali separate, entrambe di nome secnum. La memoria per la variabile secnum nominata in main() è creata dallistruzione di dichiarazione situata in main(). Una differente area di memoria per la variabile secnum in valfun() è creata dallistruzione di dichiarazione situata nella funzione valfun().

5 Ciascuna variabile di nome secnum è locale alla funzione in cui è creata la sua area di memoria, e può essere usata solo dallinterno della relativa funzione.

6 Quando si esegue il programma, si ottiene la seguente uscita: Così, quando secnum() è usata in main(), si accede alllarea di memoria riservata da main() per la sua variabile secnum ; valfun(), si accede allarea di memoria riservata da valfun() per la sua variabile secnum.

7 Dato che primonum è una variabile globale, il suo valore può essere usato e cambiato sia da main() sia da valfun(). Inizialmente entrambe le funzioni visualizzano il valore 10 che main() ha memorizzato in primonum. Prima di ritornare, valfun() cambia il valore di primonum in 40, che è il valore visualizzato quando la variabile primonum è visualizzata successivamente dallinterno di main(). Dato che ogni funzione conosce solo le sue proprie variabili locali, main() può inviare alla funzione printf() solo il valore della sua secnum, e valfun() può inviare a printf() solo il valore della sua secnum. Così, ogni qualvolta secnum è ottenuta da main() viene visualizzato il valore 20 valfun() viene visualizzato 30.

8 Il C non confonde le due variabili secnum peché in un dato momento può essere eseguita una sola funzione, e si accede solo allarea di memoria per la variabile creata dalla funzione correntemente in esecuzione. Se la funzione usa una variabile non locale a essa, il programma cerca il suo nome nelle aree di memoria globale.

9 Uso delle variabili globali. Anziché passare variabili a una funzione, sarebbe possibile rendere tutte le variabili globali. Ciò non va fatto, perché il rendere indiscriminatamente tutte le variabili globali distrugge istantaneamente tutte le precauzioni fornite dal C per rendere le funzioni indipendenti e isolate luna dallaltra, compresa la necessità di progettare accuratamente il tipo di argomenti necessari a una funzione, le variabili usate in essa e il valore restituito. Luso di sole variabili globali può essere controproducente, specialmente in programmi di grandi dimensioni che contengono molte funzioni create dallutente. Dato che tutte le variabili di una funzione devono essere dichiarate, se si creano funzioni che usano variabili globali è necessario scrivere le appropriate dichiarazioni globali allinizio di ogni programma che usi la funzione, dato che esse non si trovano più nella funzione stessa.

10 Ancora più lunga e noiosa è la ricerca di un errore in un lungo programma che usi variabili globali, dato che esse possono essere usate e modificate da qualsiasi funzione che segua la dichiarazione globale. Tuttavia, le variabili globali possono anche risultare molto utili: se più funzioni richiedono laccesso a un gruppo di tabelle, le variabili globali consentono alle funzioni di compiere cambiamenti a una stessa tabella senza la necessità di passaggi multipli di tabelle.

11 Classi di memorizzazione delle variabili Come abbiamo visto, lambito di una variabile si può considerare come lo spazio entro il programma dove la variabile è valida e può essere usata. Dato un programma, si può prendere una matita e tracciare un riquadro intorno alla sezione del programma che rappresenta lambito di validità della variabile. In aggiunta alla dimensione spaziale rappresentata dal suo ambito, una variabile possiede anche una dimensione temporale, che si riferisce alla quantità di tempo durante il quale le locazioni di memoria sono riservate per essa. Ad es., tutte le locazioni di memoria di variabili sono rilasciate al computer quando termina lesecuzione di un programma.

12 Se si usa uno di questi nomi di classi, esso deve essere situato in unistruzione di dichiarazione prima del tipo dati della variabile. Esempi di istruzioni di dichiarazione che comprendono una designazione di classe di memorizzazione sono indicati in tabella. Le quattro classi di memorizzazione disponibili sono: Tuttavia, mentre un programma è ancora in esecuzione, aree di memoria per variabili temporanee sono riservate e successivamente restituite di nuovo al computer. Dove e per quanto tempo le locazioni di memoria per le variabili siano mantenute, prima di essere rilasciate, può essere determinato dalla classe di memorizzazione di una variabile.

13 Per illustrare il significato della classe di memorizzazione di una variabile, consideriamo dapprima le variabili locali (quelle create entro una funzione) e poi quelle globali (create fuori da una funzione).

14 Classi di memorizzazione di variabili locali Le variabili locali possono essere membri solo delle classi di memorizzazione auto. Se nellistruzione di dichiarazione non sinserisce alcuna descrizione di classe, la variabile è assegnata automaticamente alla classe auto (da auto matico), che è pertanto la classe preimpostata del C. Perciò tutte le variabili locali finora usate, essendo stata omessa la designazione di classe, erano auto. Larea di memoria per le variabili locali automatiche è riservata o creata automaticamente ogni volta che viene chiamata una funzione che dichiari variabili automatiche. Fin tanto che la funzione non abbia restituito il controllo alla sua funzione chiamante, tutte le variabili automatiche locali alla funzione sono vive, cioè è disponibile per esse larea di memoria.

15 Quando la funzione chiamata restituisce il controllo alla sua funzione chiamante, le sue variabili automatche locali muoiono, cioè la memorizzazione per esse è restituita al computer. Il processo si ripete ogni volta che una funzione viene chiamata.

16 #include void main(void) { int cont; /* cont è variabile AUTO LOCALE */ void testauto(void); /* prototipo di funzione */ for(cont = 1; cont <= 3; ++cont) testauto(); } void testauto(void) { int num = 0; /*crea e pone=0 la variabile AUTO num*/ printf("\nLa variabile automatica num vale %d", num); ++num; return; } Come esempio, consideriamo il programma seguente, dove la funzione testauto() è chiamata tre volte dalla main().

17 Esso produce unuscita in apparenza inaspettata: Ciò perché, ogni volta che si chiama testauto(), la variabile automatica num è creata e inizializzata a 0. Quando la funzione ritorna il controllo a main(), la variabile num è distrutta insieme a qualsiasi valore memorizzato in essa. Così, leffetto di incrementare num in testauto(), prima dellistruzione return della funzione, si perde quando il controllo è restituito a main(). Il valore della variabile automatica num è 0

18 static. Luso di variabili automatiche è appropriato per la maggior parte delle applicazioni. Tuttavia vi sono casi in cui si vorrebbe che una funzione ricordi i valori tra le chiamate successive, e questo è lo scopo della classe di memorizzazione static. Una variabile locale dichiarata static fa sì che il programma mantenga la variabile e il suo ultimo valore anche quando la funzione che lha dichiarata non è più in esecuzione. Una variabile statica locale non viene creata e distrutta ogni volta che si chiama la funzione che la dichiara ma, una volta creata, rimane in essere per tutta la vita del programma. Perciò lultimo valore memorizzato nella variabile quando è terminata lesecuzione della funzione è disponibile per la funzione la prossima volta che essa sarà chiamata.

19 Essa è detta inizializzazione di run time, dato che linizializzazione ha luogo ogni volta che sincontra listruzione. Questo tipo dinizializzazione resetta il valore della variabile a zero ogni volta che la funzione è chiamata, distruggendone il valore. Invece listruzione di dichiarazione Dato che le variabili statiche locali mantengono i loro valori, esse non vengono inizializzate in unistruzione di dichiarazione nella stessa maniera delle variabili automatiche. Infatti, consideriamo listruzione di dichiarazione del programma precedente che crea e imposta a zero la variabile automatica num int num = 0; crea una variabile statica (sia locale sia globale) e vi inserisce un valore dinizializzazione una sola volta, quando il programma è compilato. static int num = 0;

20 Nelle successive chiamate della funzione, il valore nella variabile è mantenuto senza venire ulteriormente inizializzato. Perciò, se nel programma precedente modifichiamo come segue listruzione di dichiarazione della variabile num #include void main(void) { int cont; /* cont è variabile AUTO LOCALE */ void test(void); /* prototipo di funzione */ for(cont = 1; cont <= 3; ++cont) test(); } void test(void) { static int num = 0; /*num è variabile STATICA LOCALE*/ printf("\nLa variabile statica num vale ora %d", num); ++num; return; }

21 otteniamo la seguente uscita: Come si vede, la variabile statica num è ipostata a zero solo una volta. Quindi la funzione test() la incrementa subito prima di restituire il controllo a main(). Il valore che num ha quando si lascia la funzione test() è trattenuto e visualizzato quando la funzione è chiamata di nuovo. Il valore della variabile statica num è ora 0 Il valore della variabile statica num è ora 1 Il valore della variabile statica num è ora 2

22 A differenza delle variabili automatiche, che possono essere inizializzate o con costanti o con espressioni che usano sia costanti sia variabili inizializate in precedenza, le variabili statiche possono essere inizializzate solo usando costanti o espressioni di costanti quali Inoltre, a differenza delle variabili automatiche le variabili statiche sono impostate a zero quando non sia fatta una inizializzazione esplicita. Perciò, nel programma precedente, la specifica inizializzazione di num a zero non è necessaria.

23 register. Lultima classe di memorizzazione disponibile per le variabili locali è la classe register, che viene usata meno diffusamente delle classi auto o static. Le variabili register hanno la stessa durata temporale delle variabili auto, coè una variabile register locale è creata quando si entra nella funzione che la dichiara, e distrutta quando termina lesecuzione della funzione. Lunica differenza tra le variabili auto e register è il posto dove è situata la memoria per la variabile. La memoria per tutte le variabili (locali e globali), tranne le register è riservata nellarea di memoria del computer. Ma i computer hanno anche zone aggiuntive di memoria ad alta velocità situate direttamente nellunità centrale di elaborazione, che possono essere usate anche per la memorizzazione di variabili. Queste speciali aree di memoria ad alta velocità sono dette registri.

24 Dato che essi sono fisicamente situati nellunità centrale di elaborazione, si può accedere a essi più velocemente che alle normali aree di memoria situate nellunità di memoria del computer. Inoltre le istruzioni che si riferiscono ai registri richiedono tipicamente meno spazio di quelle che si riferiscono a locazioni di memoria, dato che vi sono meno registri accessibili che locazioni di memoria. Oltre a diminuire le dimensioni di un programma C compilato, luso di variabili register può anche aumentare la velocità di esecuzione di un programma, se il computer in uso supporta questo tipo dati. Se non lo supporta, oppure se le variabili register dichiarate eccedono la capacità in registri del computer, le variabili dichiarate nella classe di memorizzazione register sono convertite automaticamente nella classe di memorizazione auto.

25 Lunica restrizione nelluso della classe di memorizzazione register è che non si può usare lindirizzo di una variabile register tramite loperatore & in quanto i registri non hanno indirizzi di memoria standard.

26 Classi di memorizzazione di variabili globali Ricordiamo che le variabili esterne o globali sono create da istruzioni di dichiarazione esterne a una funzione, e non vengono create e distrutte con la chiamata a una funzione. Una volta creata, una variabile globale esiste finché termina lesecuzione del programma in cui è dichiarata. Perciò le variabili globali non possono essere dichiarate come membri di classi di memorizzazione auto o register, che sono create e distrutte mentre il programma è in esecuzione, mentre possono essere dichiarate come membri delle classi di memorizzazione static extern

27 Le classi static ed extern hanno effetto solo sullambito, e non sulla durata temporale delle variabili globali. Come le variabili locali static, tutte le variabili globali sono inizializzate a zero al momento della compilazione se non è presente una esplicita inizializzazione.

28 extern. Lo scopo della classe di memorizzazione extern è di estendere lambito di una variabile globale oltre i suoi confini naturali Per comprendere ciò, osserviamo che tutti i programmi visti finora erano sempre contenuti in un solo file. Ciò non è richiesto dal C. I programmi di grandi dimensioni consistono tipicamente in molte funzioni, memorizzate in più file. Uno schema di esempio è mostrato nella figura seguente, dove le tre funzioni main(), funz1(), funz2() sono memorizzate in un file, e le due funzioni funz3(), funz4() in un altro.

29

30 Per i file illustrati in figura, le variabili globali prezzo, ricavo e coupon dichiarate in file1.c possono essere usate solo dalle funzioni main(), funz1() e funz2() in questo file. La singola variabile globale tasso, dichiarata in file2.c può essere usata solo dalle funzioni funz3() e funz4() in file2.c. Tuttavia, sebbene la variabile prezzo sia stata creata in file1.c, potremmo volerla usare in file2.c. Ciò è possibile se sinserisce in file2.c listruzione di dichiarazione extern int prezzo; come indicato in figura.

31

32 Dato che questa istruzione è allinizio di file2.c, essa estende a file2.c lambito della variabile prezzo, che così può essere usata in funz3() e funz4(). Così, se sinserisce in funz4() listruzione extern float ricavo; si estende a funz4() lambito della variabile globale ricavo, creata in file1.c. Analogamente, lambito della variabile globale tasso, creata in file2.c, è esteso a funz1() e funz2() se sinserisce prima di funz1() listruzione di dichiarazione extern double tasso; Si osservi che tasso non è disponibile in main(). Unistruzione di dichiarazione che contenga la parola extern è differente da qualsiasi altra istruzione di dichiarazione, in quanto

33 non causa la creazione di una nuova variabile riservandole nuova memoria Essa informa semplicemente il computer che la variabile già esiste e può ora essere usata. La memorizzazione effettiva per la variabile deve essere creata in qualche altra parte del programma usando una, e solo una, istruzione di dichiarazione globale, in cui non vi sia la parola extern. Linizializzazione di una variabile globale può essere fatta con la sua dichiarazione originale, mentre non è consentita allinterno di una dichiarazione extern, e causerebbe un errore di compilazione. Lesistenza della classe di memorizzazione extern è la ragione per cui abbiamo distinto con cura la creazione e la dichiarazione di una variabile. Infatti le istruzioni di dichiarazione che contengono la parola extern non creano nuove aree di memoria, ma estendono solo lambito di variabili globali esistenti.

34 static. Lultima classe globale, static, è usata per evitare lestensione di una variabile globale in un secondo file. Le variabili static globali sono dichiarate nella stessa maniera delle static locali, tranne per il fatto che listruzione di dichiarazione è situata fuori una funzione. Lambito di una variabile static globale non si può estendere oltre il file in cui è dichiarata. Ciò fornisce un grado di riservatezza per le variabili static globali. Dato che esse sono conosciute e possono essere usate solo nel file in cui sono dichiarate, altri file non possono accedervi o cambianre i valori. Le variabili static globali non possono essere successivamente estese a un secondo file con unistruzione di dichiarazione extern, dato che ciò causerebbe un errore di compilazione.

35 Anche i vettori, come le variabili scalari, possono essere dichiarati dentro o fuori una funzione. I vettori dichiarati dentro una funzione sono detti vettori locali, quelli dichiarati fuori sono detti vettori globali. Consideriamo, ad es., la seguente sezione di codice:

36

37 Come le variabili scalari, anche i vettori globali e i locali static sono creati una sola volta, in fase di compilazione, e conservano i loro valori finché termina lesecuzione di main(). I vettori auto sono creati e distrutti ogni volta che viene chiamata la funzione in cui sono locali. Perciò i vettori litri, dist e corse sono creati una sola volta, mentre km è creato e distrutto 10 volte. Vettori a due dimensioni. Come i vettori a una dimensione, anche quelli a due dimensioni possono essere dichiarati dentro o fuori una funzione. Quelli dichiarati allinterno di una funzione sono locali, quelli allesterno sono globali. Ad es., il seguente segmento di codice:

38 int bingo[2] [3]; main() { static double lotto[104] [6]; double totip[52] [6];. } dichiara: bingo come un vettore a due dimensioni globale di 2 righe e 3 colonne lotto come un vettore a due dimensioni statico locale di 104 righe e 6 colonne totip come un vettore automatico locale di 52 righe e 6 colonne.

39 Chiamate per valore e per riferimento Nel normale corso delle operazioni, una funzione chiamata riceve valori dalla sua funzione chiamante, li memorizza nei suoi propri argomenti locali, manipola questi in modo opportuno ed eventualmente restituisce un singolo valore. Questo metodo di chiamare una funzione e passarle valori è detto chiamata per valore a una funzione. La chiamata per valore è una caratteristica vantaggiosa del C, che consente di scrivere le funzioni come entità indipendenti che possono usare qualsiasi nome di variabile senza preoccuparsi se anche altre funzioni stiano usando lo stesso nome. Nello scrivere una funzione, è opportuno considerare gli argomenti o come variabili inizializzate, o come variabili alle quali saranno assegnati valori quando la funzione sarà eseguita. In nessun momento, tuttavia, la funzione chiamata ha accesso diretto a qualsiasi variabile locale contenuta nella funzione chiamante.

40 Vi sono però casi in cui conviene dare a una funzione chiamata accesso alle variabili locali della funzione chiamante, in modo che la funzione chiamata possa usare e cambiare i loro valori senza la conoscenza della funzione chiamante, nella quale le variabili locali sono dichiarate. Per fare ciò è necessario che alla funzione chiamata sia passato lindirizzo della variabile. Una volta che la funzione chiamata abbia tale indirizzo, essa sa dove la variabile vive, e può accedere a essa usando lindirizzo e loperatore di indirezione *. Il passaggio di indirizzi è detto chiamata per riferimento a una funzione, dato che la funzione chiamata può riferire la, o accedere alla variabile usando lindirizzo passato. Vediamo le tecniche necessarie per passare indirizzi a una funzione e fare sì che essa li accetti e li usi.

41 Passaggio, memorizzazione e uso degli indirizzi. Per passare, memorizzare e usare gli indirizzi si usano: loperatore di indirizzo & i puntatori loperatore di indirezione *. Consideriamo, come esempio, la seguente funzione principale: printf("\nIl minore è %lf", primonum); printf("\nIl maggiore è %lf", secnum); } #include void main(void) { double primonum, secnum; void ordinum(double *, double *); /* prototipo */ printf("Scrivi due numeri: "); scanf("%lf %lf", &primonum, &secnum); ordinum(&primonum, &secnum); /* chiamata */

42 Essa passa gli indirizzi delle due variabili primonum e secnum alla funzione ordinum() (oltre che, come al solito, a scanf() ), la quale confronta i valori contenuti negli indirizzi passati ed eventualmente li scambia, in modo che il valore più piccolo vada nel primo indirizzo. Se invece degli indirizzi di primonum e secnum fossero passati a ordinum() i loro valori (come avveniva in un programma precedente), la funzione non potrebbe scambiare i valori nelle variabili, perché non avrebbe accesso a esse. Nella funzione precedente il prototipo della funzione ordinum() dichiara che essa non restituisce un valore e si aspetta come argomenti due puntatori a (indirizzi di) variabili in doppia precisione. Perciò la successiva chiamata a ordinum() richiede che le siano passati due indirizzi di numeri in doppia precisione. void ordinum(double *, double *);

43 Nella intestazione di ordinum() vanno dichiarati due argomenti che possano memorizzare gli indirizzi passati, quali, ad es., double *num1_indir; double *num2_indir; (in tal modo sia num1_indir, sia num2_indir puntano a variabili in doppia precisione). Quindi lintestazione sarà: Prima di scrivere il corpo di ordinum() che confronti (e scambi se necessario) i due valori, controlliamo che i valori cui si accede usando gli indirizzi in num1_indir e num2_indir siano corretti, scrivendo il programma seguente: ordinum(double *num1_indir, double *num_2indir)

44 Esso produce la seguente uscita: Il numero il cui indirizzo è in num1_indir è Il numero il cui indirizzo è in num2_indir è void ordinum(double *num1_indir, double *num2_indir) { printf("Il numero il cui indirizzo è in num1_indir è %lf", *num1_indir); printf("\nIl numero il cui indirizzo è in num2_indir è %lf", *num2_indir); } #include void main(void) { double primonum = 20.0, secnum = 5.0; void ordinum(double *, double *); /* prototipo */ } ordinum(&primonum, &secnum); /* chiamata */

45 Osservazione. Nelle due chiamate a printf() in ordinum() si usa loperatore di indirezione * per accedere ai valori memorizzati in primonum e secnum. ordinum() non ha conoscenza di questi nomi di variabile, ma ha lindirizzo di primonum memorizzato in num1_indir, e quello di secnum in num2_indir. Avendo verificato che ordinum() può accedere alle variabili locali primonum e secnum di main(), possiamo ora scrivere leffettiva funzione ordinum(). Essa confronta i valori nelle variabili primonum e secnum, controllando se sono nellordine desiderato, tramite listruzione: if (*num1_indir > *num2_indir) La funzione ordinum() è allora:

46 void ordinum (double *num1_indir, double *num2_indir) { double temp; if (*num1_indir > *num2_indir) { temp = *num1_indir; *num1_indir = *num2_indir; *num2_indir = temp; } return; } Si osservi che luso dei puntatori ha permesso di scambiare tra loro i due valori se essi non sono nellordine desiderato.

47 Confronto tra chiamata per valore e per riferimento. Per confrontare il diverso funzionamento della chiamata per valore e della chiamata per riferimento a una funzione, consideriamo i due programmi seguenti, che calcolano il cubo di un numero: il primo passando il valore della base (chiamata per valore), #include int main() { int numero = 5; printf("Il valore del numero è: %d\n", numero); printf("Il nuovo valore del numero è: %d\n", numero); } { return n*n*n; } int cuboVal(int); numero = cuboVal(numero); /*passa il valore di numero*/ int cuboVal(int n)

48 #include int main() { int numero = 5; printf("Il valore del numero è: %d\n", numero); printf("Il nuovo valore del numero è: %d\n", numero); } { *punt_n = *punt_n * *punt_n * *punt_n; } void cuboRif(int *); cuboRif(&numero); /* passa l'indirizzo di numero */ void cuboRif(int *punt_n) il secondo passando il suo indirizzo (chiamata per riferimento).

49 Il primo programma passa il valore della variabile numero alla funzione cuboVal utilizzando una chiamata per valore. La funzione cuboVal eleva al cubo il suo argomento e restituisce a main() il nuovo valore utilizzando unistruzione return. Il nuovo valore viene assegnato a numero nella funzione main(). Il secondo programma passa lindirizzo della variabile numero alla funzione cuboRif utilizzando una chiamata per riferimento. La funzione cuboRif riceve come parametro un puntatore a un int chiamato punt_n. La funzione risolve il riferimento del puntatore ed eleva al cubo il valore puntato da punt_n (cioè n ), quindi assegna il valore a *punt_n (che corrisponde al numero di main() ), modificando quindi il valore di numero in main(). I due programmi producono, ovviamente, la stessa uscita: Il valore del numero è: 5 Il nuovo valore del numero è: 125

50 Bolle per riferimento. Come altro esempio, modifichiamo il programma dellordinamento a bolle, in modo che chiami (per valore) una funzione bolle_rif, la quale chiami a sua volta (per riferimento) una funzione scambia. #include #define N 10 int main() { int a[N] = {22,5,67,98,45,32,101,99,73,10}; int i, passi; printf("Il vettore ordinato in ordine crescente è:\n"); for (i = 0; i < N; ++i) printf("%d ", a[i]); } int bolle_rif(int [], int); passi = bolle_rif(a, N);

51 passi++; } i++; } while (i < numel - 1 && passi != 0); return(i); } int bolle_rif(int *elem, int numel) { void scambia(int *, int *); int i, j, passi; i = 0; do { passi = 0; for (j=1; j < numel; j++) { if (elem[j] < elem[j-1]) { scambia(&elem[j], &elem[j-1]);

52 bolle_rif esegue lordinamento del vettore e chiama scambia per scambiare di posto gli elementi elem[j] ed elem[j-1]. Poiché il C applica lincapsulamento delle informazioni tra le funzioni, scambia non avrà accesso ai singoli elementi del vettore di bolle_rif. Dato che bolle_rif ha necessità che scambia abbia accesso agli elementi del vettore, in modo da poterli scambare di posto, bolle_rif passa a scambia ognuno di quegli elementi attraverso una chiamata per riferimento: in altri termini, passa in modo esplicito lindirizzo di ogni elemento del vettore. Come sappiamo, un vettore completo sarebbe passato automaticamente per riferimento, ma i suoi singoli elementi sono scalari, e sono passati normalmente per valore. void scambia(int *punt_elem1, int *punt_elem2) { int temp = *punt_elem1; *punt_elem1 = *punt_elem2; *punt_elem2 = temp; }

53 scambia(&elem[j], &elem[j-1]); che esegue una chiamata per riferimento. La funzione scambia riceve &elem[j] nel puntatore punt_elem1. Sebbene alla funzione scambia non sia consentito conoscere il nome elem[j] a causa dellincapsulamento delle informazioni, essa può comunque utilizzare *punt_elem1 come sinonimo per elem[j]. Di conseguenza, quando scambia fa riferimento a *punt_elem1, in realtà punta a elem[j] di bolle_rif. (Analogamente, quando fa riferimento a *punt_elem2, in realtà punta a elem[j-1] di bolle_rif ). Di conseguenza, nella chiamata a scambia, bolle_rif utilizza loperatore di indirizzo & con ognuno degli elementi del vettore, con listruzione seguente:

54 temp = elem[j]; elem[j] = elem[j-1]; elem[j-1] = temp; lo stesso effetto si ottiene inserendo in essa il seguente gruppo di istruzioni: int temp = *punt_elem1; *punt_elem1 = *punt_elem2; *punt_elem2 = temp; Per quanto alla funzione scambia non sia consentito un gruppo di istruzioni quali:

55 Osservazioni. Vediamo alcune caratteristiche della funzione bolle_rif degne di nota. 1. Nella sua intestazione per indicare che riceverà come argomento un vettore unidimensionale di interi, abbiamo dichiarato elem come int *elem anziché come int elem[] dato che queste due notazioni sono completamente intercambiabili. 2. Il prototipo della funzione scambia è stato inserito nel corpo di bolle_rif poiché questa è lunica funzione che la chiama. Lavere inserito il prototipo in bolle_rif limita le chiamate accettabili di scambia esclusivamente a quelle eseguite allinterno di bolle_rif. Le altre funzioni che tentassero di chiamare scambia non avrebbero acceso al giusto prototipo di funzione. Inserire i prototipi allinterno delle definizioni delle funzioni obbedisce al cosiddetto principio del minimo privilegio. int bolle_rif(int *elem, int numel)

56 3.La dimensione del vettore viene passata alla funzione bolle_rif in modo esplicito, il che offre due benefici principali: la riusabilità del software e una sua corretta progettazione. Dato che la funzione è stata definita in modo da ricevere la dimensione del vettore per mezzo di un argomento, essa potrà essere utilizzata da un qualsiasi programma che debba riordinare vettori unidimensionali di interi di qualsiasi dimensione. 4.Avremmo potuto memorizzare la dimensione del vettore in una variabile globale, che fosse accessibile allintero programma. Questo approccio sarebe stato ancora più efficiente, perché non sarebbe stato necessario creare una copia della dimensione durante la chiamata della funzione. Tuttavia, altri programmi che eventualmente richiedessero lordinamento di vettori di interi potrebbero non contenere la stessa variabile globale, e quindi non potrebbero usare la funzione.


Scaricare ppt "Ambito delle variabili Esaminiamo più a fondo le variabili dichiarate in una funzione e le loro relazioni con quelle dichiarate in altre funzioni. Per."

Presentazioni simili


Annunci Google